@tscircuit/schematic-trace-solver 0.0.4 → 0.0.5
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/cosmos.config.json +2 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +192 -28
- package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +3 -0
- package/lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts +4 -1
- package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +113 -26
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +123 -4
- package/lib/types/InputProblem.ts +1 -0
- package/package.json +1 -1
- package/site/examples/example01-basic.page.tsx +1 -0
- package/site/examples/example02.page.tsx +1 -0
- package/vite.config.ts +3 -0
package/cosmos.config.json
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -85,6 +85,7 @@ interface InputProblem {
|
|
|
85
85
|
directConnections: Array<InputDirectConnection>;
|
|
86
86
|
netConnections: Array<InputNetConnection>;
|
|
87
87
|
availableNetLabelOrientations: Record<NetId, FacingDirection[]>;
|
|
88
|
+
maxMspPairDistance?: number;
|
|
88
89
|
_chipObstacleSpatialIndex?: ChipObstacleSpatialIndex;
|
|
89
90
|
}
|
|
90
91
|
|
|
@@ -107,6 +108,7 @@ declare class MspConnectionPairSolver extends BaseSolver {
|
|
|
107
108
|
globalConnMap: ConnectivityMap;
|
|
108
109
|
queuedDcNetIds: string[];
|
|
109
110
|
chipMap: Record<string, InputChip>;
|
|
111
|
+
maxMspPairDistance: number;
|
|
110
112
|
pinMap: Record<string, InputPin & {
|
|
111
113
|
chipId: string;
|
|
112
114
|
}>;
|
|
@@ -339,7 +341,8 @@ declare class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
339
341
|
type OverlappingSameNetTraceGroup = {
|
|
340
342
|
globalConnNetId: string;
|
|
341
343
|
netId?: string;
|
|
342
|
-
overlappingTraces
|
|
344
|
+
overlappingTraces?: SolvedTracePath;
|
|
345
|
+
portOnlyPinId?: string;
|
|
343
346
|
};
|
|
344
347
|
interface NetLabelPlacement {
|
|
345
348
|
globalConnNetId: string;
|
package/dist/index.js
CHANGED
|
@@ -5274,8 +5274,9 @@ var getConnectivityMapsFromInputProblem = (inputProblem) => {
|
|
|
5274
5274
|
};
|
|
5275
5275
|
|
|
5276
5276
|
// lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts
|
|
5277
|
-
function getOrthogonalMinimumSpanningTree(pins) {
|
|
5277
|
+
function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
|
|
5278
5278
|
const n = pins.length;
|
|
5279
|
+
const maxDistance = opts?.maxDistance ?? Number.POSITIVE_INFINITY;
|
|
5279
5280
|
if (n <= 1) return [];
|
|
5280
5281
|
{
|
|
5281
5282
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5316,7 +5317,8 @@ function getOrthogonalMinimumSpanningTree(pins) {
|
|
|
5316
5317
|
}
|
|
5317
5318
|
for (let v = 0; v < n; v++) {
|
|
5318
5319
|
if (!inTree[v]) {
|
|
5319
|
-
const
|
|
5320
|
+
const d0 = manhattan(pins[u], pins[v]);
|
|
5321
|
+
const d = d0 > maxDistance ? Number.POSITIVE_INFINITY : d0;
|
|
5320
5322
|
if (d < bestDist[v] || d === bestDist[v] && pins[u].pinId < pins[parent[v]]?.pinId) {
|
|
5321
5323
|
bestDist[v] = d;
|
|
5322
5324
|
parent[v] = u;
|
|
@@ -5446,11 +5448,13 @@ var MspConnectionPairSolver = class extends BaseSolver {
|
|
|
5446
5448
|
globalConnMap;
|
|
5447
5449
|
queuedDcNetIds;
|
|
5448
5450
|
chipMap;
|
|
5451
|
+
maxMspPairDistance;
|
|
5449
5452
|
pinMap;
|
|
5450
5453
|
userNetIdByPinId;
|
|
5451
5454
|
constructor({ inputProblem }) {
|
|
5452
5455
|
super();
|
|
5453
5456
|
this.inputProblem = inputProblem;
|
|
5457
|
+
this.maxMspPairDistance = inputProblem.maxMspPairDistance ?? 1;
|
|
5454
5458
|
const { directConnMap, netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
|
|
5455
5459
|
this.dcConnMap = directConnMap;
|
|
5456
5460
|
this.globalConnMap = netConnMap;
|
|
@@ -5508,7 +5512,8 @@ var MspConnectionPairSolver = class extends BaseSolver {
|
|
|
5508
5512
|
return;
|
|
5509
5513
|
}
|
|
5510
5514
|
const msp = getOrthogonalMinimumSpanningTree(
|
|
5511
|
-
directlyConnectedPins.map((p) => this.pinMap[p])
|
|
5515
|
+
directlyConnectedPins.map((p) => this.pinMap[p]),
|
|
5516
|
+
{ maxDistance: this.maxMspPairDistance }
|
|
5512
5517
|
);
|
|
5513
5518
|
for (const [pin1, pin2] of msp) {
|
|
5514
5519
|
const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1);
|
|
@@ -7232,6 +7237,92 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
|
|
|
7232
7237
|
this.solved = true;
|
|
7233
7238
|
return;
|
|
7234
7239
|
}
|
|
7240
|
+
if (this.overlappingSameNetTraceGroup.portOnlyPinId) {
|
|
7241
|
+
const pinId = this.overlappingSameNetTraceGroup.portOnlyPinId;
|
|
7242
|
+
let pin = null;
|
|
7243
|
+
for (const chip of this.inputProblem.chips) {
|
|
7244
|
+
const p = chip.pins.find((pp) => pp.pinId === pinId);
|
|
7245
|
+
if (p) {
|
|
7246
|
+
pin = { x: p.x, y: p.y };
|
|
7247
|
+
break;
|
|
7248
|
+
}
|
|
7249
|
+
}
|
|
7250
|
+
if (!pin) {
|
|
7251
|
+
this.failed = true;
|
|
7252
|
+
this.error = `Port-only pin not found: ${pinId}`;
|
|
7253
|
+
return;
|
|
7254
|
+
}
|
|
7255
|
+
const orientations2 = this.availableOrientations.length > 0 ? this.availableOrientations : ["x+", "x-", "y+", "y-"];
|
|
7256
|
+
const anchor = { x: pin.x, y: pin.y };
|
|
7257
|
+
const outwardOf = (o) => o === "x+" ? { x: 1, y: 0 } : o === "x-" ? { x: -1, y: 0 } : o === "y+" ? { x: 0, y: 1 } : { x: 0, y: -1 };
|
|
7258
|
+
for (const orientation of orientations2) {
|
|
7259
|
+
const { width, height } = this.getDimsForOrientation(orientation);
|
|
7260
|
+
const baseCenter = this.getCenterFromAnchor(
|
|
7261
|
+
anchor,
|
|
7262
|
+
orientation,
|
|
7263
|
+
width,
|
|
7264
|
+
height
|
|
7265
|
+
);
|
|
7266
|
+
const outward = outwardOf(orientation);
|
|
7267
|
+
const offset = 1e-3;
|
|
7268
|
+
const center = {
|
|
7269
|
+
x: baseCenter.x + outward.x * offset,
|
|
7270
|
+
y: baseCenter.y + outward.y * offset
|
|
7271
|
+
};
|
|
7272
|
+
const bounds = this.getRectBounds(center, width, height);
|
|
7273
|
+
const chips = this.chipObstacleSpatialIndex.getChipsInBounds(bounds);
|
|
7274
|
+
if (chips.length > 0) {
|
|
7275
|
+
this.testedCandidates.push({
|
|
7276
|
+
center,
|
|
7277
|
+
width,
|
|
7278
|
+
height,
|
|
7279
|
+
bounds,
|
|
7280
|
+
anchor,
|
|
7281
|
+
orientation,
|
|
7282
|
+
status: "chip-collision",
|
|
7283
|
+
hostSegIndex: -1
|
|
7284
|
+
});
|
|
7285
|
+
continue;
|
|
7286
|
+
}
|
|
7287
|
+
if (this.rectIntersectsAnyTrace(bounds, "", -1)) {
|
|
7288
|
+
this.testedCandidates.push({
|
|
7289
|
+
center,
|
|
7290
|
+
width,
|
|
7291
|
+
height,
|
|
7292
|
+
bounds,
|
|
7293
|
+
anchor,
|
|
7294
|
+
orientation,
|
|
7295
|
+
status: "trace-collision",
|
|
7296
|
+
hostSegIndex: -1
|
|
7297
|
+
});
|
|
7298
|
+
continue;
|
|
7299
|
+
}
|
|
7300
|
+
this.testedCandidates.push({
|
|
7301
|
+
center,
|
|
7302
|
+
width,
|
|
7303
|
+
height,
|
|
7304
|
+
bounds,
|
|
7305
|
+
anchor,
|
|
7306
|
+
orientation,
|
|
7307
|
+
status: "ok",
|
|
7308
|
+
hostSegIndex: -1
|
|
7309
|
+
});
|
|
7310
|
+
this.netLabelPlacement = {
|
|
7311
|
+
globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
|
|
7312
|
+
dcConnNetId: void 0,
|
|
7313
|
+
orientation,
|
|
7314
|
+
anchorPoint: anchor,
|
|
7315
|
+
width,
|
|
7316
|
+
height,
|
|
7317
|
+
center
|
|
7318
|
+
};
|
|
7319
|
+
this.solved = true;
|
|
7320
|
+
return;
|
|
7321
|
+
}
|
|
7322
|
+
this.failed = true;
|
|
7323
|
+
this.error = "Could not place net label at port without collisions";
|
|
7324
|
+
return;
|
|
7325
|
+
}
|
|
7235
7326
|
const groupId = this.overlappingSameNetTraceGroup.globalConnNetId;
|
|
7236
7327
|
const chipsById = Object.fromEntries(
|
|
7237
7328
|
this.inputProblem.chips.map((c) => [c.chipId, c])
|
|
@@ -7266,8 +7357,13 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
|
|
|
7266
7357
|
(t) => t.pins[0].chipId === largestChipId || t.pins[1].chipId === largestChipId
|
|
7267
7358
|
);
|
|
7268
7359
|
let host = hostCandidates.length > 0 ? hostCandidates.reduce((a, b) => lengthOf(a) >= lengthOf(b) ? a : b) : this.overlappingSameNetTraceGroup.overlappingTraces;
|
|
7360
|
+
if (!host) {
|
|
7361
|
+
this.failed = true;
|
|
7362
|
+
this.error = "No host trace found for net label placement";
|
|
7363
|
+
return;
|
|
7364
|
+
}
|
|
7269
7365
|
let pts = host.tracePath.slice();
|
|
7270
|
-
if (largestChipId) {
|
|
7366
|
+
if (largestChipId && host) {
|
|
7271
7367
|
const largePin = host.pins[0].chipId === largestChipId ? host.pins[0] : host.pins[1];
|
|
7272
7368
|
const d0 = Math.abs(pts[0].x - largePin.x) + Math.abs(pts[0].y - largePin.y);
|
|
7273
7369
|
const dL = Math.abs(pts[pts.length - 1].x - largePin.x) + Math.abs(pts[pts.length - 1].y - largePin.y);
|
|
@@ -7406,7 +7502,7 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
|
|
|
7406
7502
|
const groupFill = getColorFromString(groupId, 0.5);
|
|
7407
7503
|
for (const trace of Object.values(this.inputTraceMap)) {
|
|
7408
7504
|
if (trace.globalConnNetId !== groupId) continue;
|
|
7409
|
-
const isHost = trace.mspPairId === host.mspPairId;
|
|
7505
|
+
const isHost = host ? trace.mspPairId === host.mspPairId : false;
|
|
7410
7506
|
graphics.lines.push({
|
|
7411
7507
|
points: trace.tracePath,
|
|
7412
7508
|
strokeColor: isHost ? groupStroke : groupFill,
|
|
@@ -7475,32 +7571,100 @@ var NetLabelPlacementSolver = class extends BaseSolver {
|
|
|
7475
7571
|
if (!byGlobal[key]) byGlobal[key] = [];
|
|
7476
7572
|
byGlobal[key].push(trace);
|
|
7477
7573
|
}
|
|
7574
|
+
const { netConnMap } = getConnectivityMapsFromInputProblem(
|
|
7575
|
+
this.inputProblem
|
|
7576
|
+
);
|
|
7577
|
+
const userNetIdByPinId = {};
|
|
7578
|
+
for (const dc of this.inputProblem.directConnections) {
|
|
7579
|
+
if (dc.netId) {
|
|
7580
|
+
const [a, b] = dc.pinIds;
|
|
7581
|
+
userNetIdByPinId[a] = dc.netId;
|
|
7582
|
+
userNetIdByPinId[b] = dc.netId;
|
|
7583
|
+
}
|
|
7584
|
+
}
|
|
7585
|
+
for (const nc of this.inputProblem.netConnections) {
|
|
7586
|
+
for (const pid of nc.pinIds) {
|
|
7587
|
+
userNetIdByPinId[pid] = nc.netId;
|
|
7588
|
+
}
|
|
7589
|
+
}
|
|
7478
7590
|
const groups = [];
|
|
7479
|
-
for (const
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7591
|
+
for (const globalConnNetId of Object.keys(netConnMap.netMap)) {
|
|
7592
|
+
const pinsInNet = netConnMap.getIdsConnectedToNet(
|
|
7593
|
+
globalConnNetId
|
|
7594
|
+
);
|
|
7595
|
+
const adj = {};
|
|
7596
|
+
for (const pid of pinsInNet) adj[pid] = /* @__PURE__ */ new Set();
|
|
7597
|
+
for (const t of byGlobal[globalConnNetId] ?? []) {
|
|
7598
|
+
const a = t.pins[0].pinId;
|
|
7599
|
+
const b = t.pins[1].pinId;
|
|
7600
|
+
if (adj[a] && adj[b]) {
|
|
7601
|
+
adj[a].add(b);
|
|
7602
|
+
adj[b].add(a);
|
|
7603
|
+
}
|
|
7604
|
+
}
|
|
7605
|
+
const visited = /* @__PURE__ */ new Set();
|
|
7606
|
+
for (const pid of pinsInNet) {
|
|
7607
|
+
if (visited.has(pid)) continue;
|
|
7608
|
+
const stack = [pid];
|
|
7609
|
+
const component = /* @__PURE__ */ new Set();
|
|
7610
|
+
visited.add(pid);
|
|
7611
|
+
while (stack.length > 0) {
|
|
7612
|
+
const u = stack.pop();
|
|
7613
|
+
component.add(u);
|
|
7614
|
+
for (const v of adj[u] ?? []) {
|
|
7615
|
+
if (!visited.has(v)) {
|
|
7616
|
+
visited.add(v);
|
|
7617
|
+
stack.push(v);
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7620
|
+
}
|
|
7621
|
+
const compTraces = (byGlobal[globalConnNetId] ?? []).filter(
|
|
7622
|
+
(t) => component.has(t.pins[0].pinId) && component.has(t.pins[1].pinId)
|
|
7623
|
+
);
|
|
7624
|
+
if (compTraces.length > 0) {
|
|
7625
|
+
const lengthOf = (path) => {
|
|
7626
|
+
let sum = 0;
|
|
7627
|
+
const pts = path.tracePath;
|
|
7628
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
7629
|
+
sum += Math.abs(pts[i + 1].x - pts[i].x) + Math.abs(pts[i + 1].y - pts[i].y);
|
|
7630
|
+
}
|
|
7631
|
+
return sum;
|
|
7632
|
+
};
|
|
7633
|
+
let rep = compTraces[0];
|
|
7634
|
+
let repLen = lengthOf(rep);
|
|
7635
|
+
for (let i = 1; i < compTraces.length; i++) {
|
|
7636
|
+
const len = lengthOf(compTraces[i]);
|
|
7637
|
+
if (len > repLen) {
|
|
7638
|
+
rep = compTraces[i];
|
|
7639
|
+
repLen = len;
|
|
7640
|
+
}
|
|
7641
|
+
}
|
|
7642
|
+
let userNetId = compTraces.find((t) => t.userNetId != null)?.userNetId;
|
|
7643
|
+
if (!userNetId) {
|
|
7644
|
+
for (const p of component) {
|
|
7645
|
+
if (userNetIdByPinId[p]) {
|
|
7646
|
+
userNetId = userNetIdByPinId[p];
|
|
7647
|
+
break;
|
|
7648
|
+
}
|
|
7649
|
+
}
|
|
7650
|
+
}
|
|
7651
|
+
groups.push({
|
|
7652
|
+
globalConnNetId,
|
|
7653
|
+
netId: userNetId,
|
|
7654
|
+
overlappingTraces: rep
|
|
7655
|
+
});
|
|
7656
|
+
} else {
|
|
7657
|
+
for (const p of component) {
|
|
7658
|
+
const userNetId = userNetIdByPinId[p];
|
|
7659
|
+
if (!userNetId) continue;
|
|
7660
|
+
groups.push({
|
|
7661
|
+
globalConnNetId,
|
|
7662
|
+
netId: userNetId,
|
|
7663
|
+
portOnlyPinId: p
|
|
7664
|
+
});
|
|
7665
|
+
}
|
|
7496
7666
|
}
|
|
7497
7667
|
}
|
|
7498
|
-
const userNetId = traces.find((t) => t.userNetId != null)?.userNetId;
|
|
7499
|
-
groups.push({
|
|
7500
|
-
globalConnNetId,
|
|
7501
|
-
netId: userNetId,
|
|
7502
|
-
overlappingTraces: rep
|
|
7503
|
-
});
|
|
7504
7668
|
}
|
|
7505
7669
|
return groups;
|
|
7506
7670
|
}
|
|
@@ -25,6 +25,7 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
25
25
|
globalConnMap: ConnectivityMap
|
|
26
26
|
queuedDcNetIds: string[]
|
|
27
27
|
chipMap: Record<string, InputChip>
|
|
28
|
+
maxMspPairDistance: number
|
|
28
29
|
|
|
29
30
|
pinMap: Record<string, InputPin & { chipId: string }>
|
|
30
31
|
userNetIdByPinId: Record<string, string | undefined>
|
|
@@ -33,6 +34,7 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
33
34
|
super()
|
|
34
35
|
|
|
35
36
|
this.inputProblem = inputProblem
|
|
37
|
+
this.maxMspPairDistance = inputProblem.maxMspPairDistance ?? 1
|
|
36
38
|
|
|
37
39
|
const { directConnMap, netConnMap } =
|
|
38
40
|
getConnectivityMapsFromInputProblem(inputProblem)
|
|
@@ -112,6 +114,7 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
112
114
|
|
|
113
115
|
const msp = getOrthogonalMinimumSpanningTree(
|
|
114
116
|
directlyConnectedPins.map((p) => this.pinMap[p]!),
|
|
117
|
+
{ maxDistance: this.maxMspPairDistance },
|
|
115
118
|
)
|
|
116
119
|
|
|
117
120
|
for (const [pin1, pin2] of msp) {
|
|
@@ -18,8 +18,10 @@ import type { InputPin, PinId } from "lib/types/InputProblem"
|
|
|
18
18
|
*/
|
|
19
19
|
export function getOrthogonalMinimumSpanningTree(
|
|
20
20
|
pins: InputPin[],
|
|
21
|
+
opts: { maxDistance?: number } = {},
|
|
21
22
|
): Array<[PinId, PinId]> {
|
|
22
23
|
const n = pins.length
|
|
24
|
+
const maxDistance = opts?.maxDistance ?? Number.POSITIVE_INFINITY
|
|
23
25
|
if (n <= 1) return []
|
|
24
26
|
|
|
25
27
|
// Quick validation (optional; remove if hot path)
|
|
@@ -79,7 +81,8 @@ export function getOrthogonalMinimumSpanningTree(
|
|
|
79
81
|
// Relax edges from u to all v not yet in the tree
|
|
80
82
|
for (let v = 0; v < n; v++) {
|
|
81
83
|
if (!inTree[v]) {
|
|
82
|
-
const
|
|
84
|
+
const d0 = manhattan(pins[u], pins[v])
|
|
85
|
+
const d = d0 > maxDistance ? Number.POSITIVE_INFINITY : d0
|
|
83
86
|
if (
|
|
84
87
|
d < bestDist[v] ||
|
|
85
88
|
(d === bestDist[v] && pins[u].pinId < pins[parent[v]]?.pinId)
|
|
@@ -8,6 +8,7 @@ import type { Point } from "@tscircuit/math-utils"
|
|
|
8
8
|
import type { GraphicsObject } from "graphics-debug"
|
|
9
9
|
import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
|
|
10
10
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
11
|
+
import { getConnectivityMapsFromInputProblem } from "../MspConnectionPairSolver/getConnectivityMapFromInputProblem"
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* A group of traces that have at least one overlapping segment and
|
|
@@ -16,7 +17,8 @@ import { getColorFromString } from "lib/utils/getColorFromString"
|
|
|
16
17
|
export type OverlappingSameNetTraceGroup = {
|
|
17
18
|
globalConnNetId: string
|
|
18
19
|
netId?: string
|
|
19
|
-
overlappingTraces
|
|
20
|
+
overlappingTraces?: SolvedTracePath
|
|
21
|
+
portOnlyPinId?: string
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export interface NetLabelPlacement {
|
|
@@ -76,7 +78,7 @@ export class NetLabelPlacementSolver extends BaseSolver {
|
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
computeOverlappingSameNetTraceGroups(): Array<OverlappingSameNetTraceGroup> {
|
|
79
|
-
// Group traces by their global connectivity net id.
|
|
81
|
+
// Group existing traces by their global connectivity net id.
|
|
80
82
|
const byGlobal: Record<string, Array<SolvedTracePath>> = {}
|
|
81
83
|
for (const trace of Object.values(this.inputTraceMap)) {
|
|
82
84
|
const key = trace.globalConnNetId
|
|
@@ -84,35 +86,120 @@ export class NetLabelPlacementSolver extends BaseSolver {
|
|
|
84
86
|
byGlobal[key].push(trace)
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
//
|
|
89
|
+
// Build global connectivity from input so we also consider pins with no traces
|
|
90
|
+
const { netConnMap } = getConnectivityMapsFromInputProblem(
|
|
91
|
+
this.inputProblem,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
// Map pins to user-provided netIds (if any)
|
|
95
|
+
const userNetIdByPinId: Record<string, string | undefined> = {}
|
|
96
|
+
for (const dc of this.inputProblem.directConnections) {
|
|
97
|
+
if (dc.netId) {
|
|
98
|
+
const [a, b] = dc.pinIds
|
|
99
|
+
userNetIdByPinId[a] = dc.netId
|
|
100
|
+
userNetIdByPinId[b] = dc.netId
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const nc of this.inputProblem.netConnections) {
|
|
104
|
+
for (const pid of nc.pinIds) {
|
|
105
|
+
userNetIdByPinId[pid] = nc.netId
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
88
109
|
const groups: Array<OverlappingSameNetTraceGroup> = []
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
110
|
+
|
|
111
|
+
// Consider every global connectivity net id
|
|
112
|
+
for (const globalConnNetId of Object.keys((netConnMap as any).netMap)) {
|
|
113
|
+
const pinsInNet = netConnMap.getIdsConnectedToNet(
|
|
114
|
+
globalConnNetId,
|
|
115
|
+
) as string[]
|
|
116
|
+
|
|
117
|
+
// Build adjacency from solved traces (edges)
|
|
118
|
+
const adj: Record<string, Set<string>> = {}
|
|
119
|
+
for (const pid of pinsInNet) adj[pid] = new Set()
|
|
120
|
+
for (const t of byGlobal[globalConnNetId] ?? []) {
|
|
121
|
+
const a = t.pins[0].pinId
|
|
122
|
+
const b = t.pins[1].pinId
|
|
123
|
+
if (adj[a] && adj[b]) {
|
|
124
|
+
adj[a].add(b)
|
|
125
|
+
adj[b].add(a)
|
|
98
126
|
}
|
|
99
|
-
return sum
|
|
100
127
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
|
|
129
|
+
// Find connected components based on trace edges
|
|
130
|
+
const visited = new Set<string>()
|
|
131
|
+
for (const pid of pinsInNet) {
|
|
132
|
+
if (visited.has(pid)) continue
|
|
133
|
+
const stack = [pid]
|
|
134
|
+
const component = new Set<string>()
|
|
135
|
+
visited.add(pid)
|
|
136
|
+
while (stack.length > 0) {
|
|
137
|
+
const u = stack.pop()!
|
|
138
|
+
component.add(u)
|
|
139
|
+
for (const v of adj[u] ?? []) {
|
|
140
|
+
if (!visited.has(v)) {
|
|
141
|
+
visited.add(v)
|
|
142
|
+
stack.push(v)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Collect traces fully inside this component
|
|
148
|
+
const compTraces = (byGlobal[globalConnNetId] ?? []).filter(
|
|
149
|
+
(t) =>
|
|
150
|
+
component.has(t.pins[0].pinId) && component.has(t.pins[1].pinId),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if (compTraces.length > 0) {
|
|
154
|
+
// Choose a representative trace (longest by L1 length)
|
|
155
|
+
const lengthOf = (path: SolvedTracePath) => {
|
|
156
|
+
let sum = 0
|
|
157
|
+
const pts = path.tracePath
|
|
158
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
159
|
+
sum +=
|
|
160
|
+
Math.abs(pts[i + 1]!.x - pts[i]!.x) +
|
|
161
|
+
Math.abs(pts[i + 1]!.y - pts[i]!.y)
|
|
162
|
+
}
|
|
163
|
+
return sum
|
|
164
|
+
}
|
|
165
|
+
let rep = compTraces[0]!
|
|
166
|
+
let repLen = lengthOf(rep)
|
|
167
|
+
for (let i = 1; i < compTraces.length; i++) {
|
|
168
|
+
const len = lengthOf(compTraces[i]!)
|
|
169
|
+
if (len > repLen) {
|
|
170
|
+
rep = compTraces[i]!
|
|
171
|
+
repLen = len
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let userNetId = compTraces.find((t) => t.userNetId != null)?.userNetId
|
|
176
|
+
if (!userNetId) {
|
|
177
|
+
for (const p of component) {
|
|
178
|
+
if (userNetIdByPinId[p]) {
|
|
179
|
+
userNetId = userNetIdByPinId[p]
|
|
180
|
+
break
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
groups.push({
|
|
186
|
+
globalConnNetId,
|
|
187
|
+
netId: userNetId,
|
|
188
|
+
overlappingTraces: rep,
|
|
189
|
+
})
|
|
190
|
+
} else {
|
|
191
|
+
// No traces in this component: place label at each pin that has a user net id
|
|
192
|
+
for (const p of component) {
|
|
193
|
+
const userNetId = userNetIdByPinId[p]
|
|
194
|
+
if (!userNetId) continue
|
|
195
|
+
groups.push({
|
|
196
|
+
globalConnNetId,
|
|
197
|
+
netId: userNetId,
|
|
198
|
+
portOnlyPinId: p,
|
|
199
|
+
})
|
|
200
|
+
}
|
|
108
201
|
}
|
|
109
202
|
}
|
|
110
|
-
const userNetId = traces.find((t) => t.userNetId != null)?.userNetId
|
|
111
|
-
groups.push({
|
|
112
|
-
globalConnNetId,
|
|
113
|
-
netId: userNetId,
|
|
114
|
-
overlappingTraces: rep,
|
|
115
|
-
})
|
|
116
203
|
}
|
|
117
204
|
|
|
118
205
|
return groups
|
|
@@ -172,6 +172,119 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
172
172
|
return
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
// Handle port-only island (no traces) by placing a label at the port
|
|
176
|
+
if (this.overlappingSameNetTraceGroup.portOnlyPinId) {
|
|
177
|
+
const pinId = this.overlappingSameNetTraceGroup.portOnlyPinId
|
|
178
|
+
// Find pin coordinates
|
|
179
|
+
let pin: { x: number; y: number } | null = null
|
|
180
|
+
for (const chip of this.inputProblem.chips) {
|
|
181
|
+
const p = chip.pins.find((pp) => pp.pinId === pinId)
|
|
182
|
+
if (p) {
|
|
183
|
+
pin = { x: p.x, y: p.y }
|
|
184
|
+
break
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (!pin) {
|
|
188
|
+
this.failed = true
|
|
189
|
+
this.error = `Port-only pin not found: ${pinId}`
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const orientations =
|
|
194
|
+
this.availableOrientations.length > 0
|
|
195
|
+
? this.availableOrientations
|
|
196
|
+
: (["x+", "x-", "y+", "y-"] as FacingDirection[])
|
|
197
|
+
|
|
198
|
+
const anchor = { x: pin.x, y: pin.y }
|
|
199
|
+
const outwardOf = (o: FacingDirection) =>
|
|
200
|
+
o === "x+"
|
|
201
|
+
? { x: 1, y: 0 }
|
|
202
|
+
: o === "x-"
|
|
203
|
+
? { x: -1, y: 0 }
|
|
204
|
+
: o === "y+"
|
|
205
|
+
? { x: 0, y: 1 }
|
|
206
|
+
: { x: 0, y: -1 }
|
|
207
|
+
|
|
208
|
+
for (const orientation of orientations) {
|
|
209
|
+
const { width, height } = this.getDimsForOrientation(orientation)
|
|
210
|
+
// Place label fully outside the chip: shift center slightly outward
|
|
211
|
+
const baseCenter = this.getCenterFromAnchor(
|
|
212
|
+
anchor,
|
|
213
|
+
orientation,
|
|
214
|
+
width,
|
|
215
|
+
height,
|
|
216
|
+
)
|
|
217
|
+
const outward = outwardOf(orientation)
|
|
218
|
+
const offset = 1e-3
|
|
219
|
+
const center = {
|
|
220
|
+
x: baseCenter.x + outward.x * offset,
|
|
221
|
+
y: baseCenter.y + outward.y * offset,
|
|
222
|
+
}
|
|
223
|
+
const bounds = this.getRectBounds(center, width, height)
|
|
224
|
+
|
|
225
|
+
// Chip collision check
|
|
226
|
+
const chips = this.chipObstacleSpatialIndex.getChipsInBounds(bounds)
|
|
227
|
+
if (chips.length > 0) {
|
|
228
|
+
this.testedCandidates.push({
|
|
229
|
+
center,
|
|
230
|
+
width,
|
|
231
|
+
height,
|
|
232
|
+
bounds,
|
|
233
|
+
anchor,
|
|
234
|
+
orientation,
|
|
235
|
+
status: "chip-collision",
|
|
236
|
+
hostSegIndex: -1,
|
|
237
|
+
})
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Trace collision check
|
|
242
|
+
if (
|
|
243
|
+
this.rectIntersectsAnyTrace(bounds, "" as MspConnectionPairId, -1)
|
|
244
|
+
) {
|
|
245
|
+
this.testedCandidates.push({
|
|
246
|
+
center,
|
|
247
|
+
width,
|
|
248
|
+
height,
|
|
249
|
+
bounds,
|
|
250
|
+
anchor,
|
|
251
|
+
orientation,
|
|
252
|
+
status: "trace-collision",
|
|
253
|
+
hostSegIndex: -1,
|
|
254
|
+
})
|
|
255
|
+
continue
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Found a valid placement
|
|
259
|
+
this.testedCandidates.push({
|
|
260
|
+
center,
|
|
261
|
+
width,
|
|
262
|
+
height,
|
|
263
|
+
bounds,
|
|
264
|
+
anchor,
|
|
265
|
+
orientation,
|
|
266
|
+
status: "ok",
|
|
267
|
+
hostSegIndex: -1,
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
this.netLabelPlacement = {
|
|
271
|
+
globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
|
|
272
|
+
dcConnNetId: undefined,
|
|
273
|
+
orientation,
|
|
274
|
+
anchorPoint: anchor,
|
|
275
|
+
width,
|
|
276
|
+
height,
|
|
277
|
+
center,
|
|
278
|
+
}
|
|
279
|
+
this.solved = true
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
this.failed = true
|
|
284
|
+
this.error = "Could not place net label at port without collisions"
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
|
|
175
288
|
// Prefer starting from the trace connected to the "largest" chip (most pins)
|
|
176
289
|
const groupId = this.overlappingSameNetTraceGroup.globalConnNetId
|
|
177
290
|
const chipsById: Record<string, InputChip> = Object.fromEntries(
|
|
@@ -218,9 +331,15 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
218
331
|
? hostCandidates.reduce((a, b) => (lengthOf(a) >= lengthOf(b) ? a : b))
|
|
219
332
|
: this.overlappingSameNetTraceGroup.overlappingTraces
|
|
220
333
|
|
|
334
|
+
if (!host) {
|
|
335
|
+
this.failed = true
|
|
336
|
+
this.error = "No host trace found for net label placement"
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
|
|
221
340
|
// Ensure we traverse the host path starting at the segment attached to the largest chip's pin
|
|
222
341
|
let pts = host.tracePath.slice()
|
|
223
|
-
if (largestChipId) {
|
|
342
|
+
if (largestChipId && host) {
|
|
224
343
|
const largePin =
|
|
225
344
|
host.pins[0].chipId === largestChipId ? host.pins[0] : host.pins[1]
|
|
226
345
|
const d0 =
|
|
@@ -312,7 +431,7 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
312
431
|
}
|
|
313
432
|
|
|
314
433
|
// Trace collision check (ignore the host segment)
|
|
315
|
-
if (this.rectIntersectsAnyTrace(bounds, host
|
|
434
|
+
if (this.rectIntersectsAnyTrace(bounds, host!.mspPairId, si)) {
|
|
316
435
|
this.testedCandidates.push({
|
|
317
436
|
center: testCenter,
|
|
318
437
|
width,
|
|
@@ -340,7 +459,7 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
340
459
|
|
|
341
460
|
this.netLabelPlacement = {
|
|
342
461
|
globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
|
|
343
|
-
dcConnNetId: host
|
|
462
|
+
dcConnNetId: host!.dcConnNetId,
|
|
344
463
|
orientation,
|
|
345
464
|
anchorPoint: anchor,
|
|
346
465
|
width,
|
|
@@ -411,7 +530,7 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
411
530
|
|
|
412
531
|
for (const trace of Object.values(this.inputTraceMap)) {
|
|
413
532
|
if (trace.globalConnNetId !== groupId) continue
|
|
414
|
-
const isHost = trace.mspPairId === host.mspPairId
|
|
533
|
+
const isHost = host ? trace.mspPairId === host.mspPairId : false
|
|
415
534
|
graphics.lines!.push({
|
|
416
535
|
points: trace.tracePath,
|
|
417
536
|
strokeColor: isHost ? groupStroke : groupFill,
|
package/package.json
CHANGED