@tscircuit/schematic-trace-solver 0.0.28 → 0.0.30
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.js +127 -34
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +7 -8
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts +2 -3
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts +10 -3
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts +41 -14
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants.ts +129 -6
- package/package.json +1 -1
- package/site/examples/example08.page.tsx +1 -1
- package/tests/examples/__snapshots__/example01.snap.svg +29 -32
- package/tests/examples/__snapshots__/example02.snap.svg +5 -5
- package/tests/examples/__snapshots__/example04.snap.svg +12 -12
- package/tests/examples/__snapshots__/example05.snap.svg +35 -35
- package/tests/examples/__snapshots__/example06.snap.svg +14 -14
- package/tests/examples/__snapshots__/example08.snap.svg +23 -29
- package/tests/examples/__snapshots__/example11.snap.svg +33 -39
- package/tests/examples/__snapshots__/example12.snap.svg +29 -32
- package/tests/examples/__snapshots__/example13.snap.svg +84 -87
- package/tests/examples/__snapshots__/example15.snap.svg +57 -57
- package/tests/examples/example14.test.tsx +0 -1
- package/tests/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver01.test.ts +1 -2
package/dist/index.js
CHANGED
|
@@ -469,6 +469,68 @@ var dir = (facingDirection) => {
|
|
|
469
469
|
// lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants.ts
|
|
470
470
|
var EPS = 1e-6;
|
|
471
471
|
var MIN_LEN = 1e-6;
|
|
472
|
+
var checkIfUTurnNeeded = (elbow) => {
|
|
473
|
+
if (elbow.length !== 4) return false;
|
|
474
|
+
const [start, p1, p2, end] = elbow;
|
|
475
|
+
const startOrient = orientationOf(start, p1);
|
|
476
|
+
const startFacing = startOrient === "horizontal" ? p1.x > start.x ? "x+" : "x-" : p1.y > start.y ? "y+" : "y-";
|
|
477
|
+
const endOrient = orientationOf(p2, end);
|
|
478
|
+
const endFacing = endOrient === "horizontal" ? end.x > p2.x ? "x+" : "x-" : end.y > p2.y ? "y+" : "y-";
|
|
479
|
+
if (startFacing === "x+" && end.x < p1.x - EPS) return true;
|
|
480
|
+
if (startFacing === "x-" && end.x > p1.x + EPS) return true;
|
|
481
|
+
if (startFacing === "y+" && end.y < p1.y - EPS) return true;
|
|
482
|
+
if (startFacing === "y-" && end.y > p1.y + EPS) return true;
|
|
483
|
+
if (endFacing === "x-" && p2.x > end.x + EPS && startFacing === "x+")
|
|
484
|
+
return true;
|
|
485
|
+
if (endFacing === "x+" && p2.x < end.x - EPS && startFacing === "x-")
|
|
486
|
+
return true;
|
|
487
|
+
if (endFacing === "y-" && p2.y > end.y + EPS && startFacing === "y+")
|
|
488
|
+
return true;
|
|
489
|
+
if (endFacing === "y+" && p2.y < end.y - EPS && startFacing === "y-")
|
|
490
|
+
return true;
|
|
491
|
+
return false;
|
|
492
|
+
};
|
|
493
|
+
var expandToUTurn = (elbow) => {
|
|
494
|
+
if (elbow.length !== 4) return elbow;
|
|
495
|
+
const [start, p1, p2, end] = elbow;
|
|
496
|
+
const overshoot = Math.max(
|
|
497
|
+
Math.abs(start.x - p1.x),
|
|
498
|
+
Math.abs(start.y - p1.y),
|
|
499
|
+
0.2
|
|
500
|
+
// minimum overshoot
|
|
501
|
+
);
|
|
502
|
+
const startOrient = orientationOf(start, p1);
|
|
503
|
+
const expanded = [{ ...start }];
|
|
504
|
+
if (startOrient === "horizontal") {
|
|
505
|
+
const startDir = p1.x > start.x ? 1 : -1;
|
|
506
|
+
expanded.push({ x: start.x + startDir * overshoot, y: start.y });
|
|
507
|
+
const verticalSpace = Math.max(
|
|
508
|
+
overshoot * 2,
|
|
509
|
+
Math.abs(end.y - start.y) + overshoot
|
|
510
|
+
);
|
|
511
|
+
const midY = end.y > start.y ? start.y - verticalSpace : start.y + verticalSpace;
|
|
512
|
+
expanded.push({ x: start.x + startDir * overshoot, y: midY });
|
|
513
|
+
const endOrient = orientationOf(p2, end);
|
|
514
|
+
const endApproachX = endOrient === "horizontal" ? end.x > p2.x ? end.x - overshoot : end.x + overshoot : end.x;
|
|
515
|
+
expanded.push({ x: endApproachX, y: midY });
|
|
516
|
+
expanded.push({ x: endApproachX, y: end.y });
|
|
517
|
+
} else {
|
|
518
|
+
const startDir = p1.y > start.y ? 1 : -1;
|
|
519
|
+
expanded.push({ x: start.x, y: start.y + startDir * overshoot });
|
|
520
|
+
const horizontalSpace = Math.max(
|
|
521
|
+
overshoot * 2,
|
|
522
|
+
Math.abs(end.x - start.x) + overshoot
|
|
523
|
+
);
|
|
524
|
+
const midX = end.x > start.x ? start.x - horizontalSpace : start.x + horizontalSpace;
|
|
525
|
+
expanded.push({ x: midX, y: start.y + startDir * overshoot });
|
|
526
|
+
const endOrient = orientationOf(p2, end);
|
|
527
|
+
const endApproachY = endOrient === "vertical" ? end.y > p2.y ? end.y - overshoot : end.y + overshoot : end.y;
|
|
528
|
+
expanded.push({ x: midX, y: endApproachY });
|
|
529
|
+
expanded.push({ x: end.x, y: endApproachY });
|
|
530
|
+
}
|
|
531
|
+
expanded.push({ ...end });
|
|
532
|
+
return expanded;
|
|
533
|
+
};
|
|
472
534
|
var isAxisAligned = (a, b) => Math.abs(a.x - b.x) < EPS || Math.abs(a.y - b.y) < EPS;
|
|
473
535
|
var orientationOf = (a, b) => {
|
|
474
536
|
const dx = Math.abs(a.x - b.x);
|
|
@@ -550,15 +612,20 @@ var generateElbowVariants = ({
|
|
|
550
612
|
const elbow = preprocessElbow(baseElbow);
|
|
551
613
|
assertOrthogonalPolyline(elbow);
|
|
552
614
|
const nPts = elbow.length;
|
|
553
|
-
|
|
554
|
-
if (nSegs < 5) {
|
|
615
|
+
if (nPts < 4) {
|
|
555
616
|
return {
|
|
556
617
|
elbowVariants: [elbow.map((p) => ({ ...p }))],
|
|
557
618
|
movableSegments: []
|
|
558
619
|
};
|
|
620
|
+
} else if (nPts === 4) {
|
|
621
|
+
const needsUTurn = checkIfUTurnNeeded(elbow);
|
|
622
|
+
if (needsUTurn) {
|
|
623
|
+
const expandedElbow = expandToUTurn(elbow);
|
|
624
|
+
return generateElbowVariants({ baseElbow: expandedElbow, guidelines });
|
|
625
|
+
}
|
|
559
626
|
}
|
|
560
|
-
const firstMovableIndex =
|
|
561
|
-
const lastMovableIndex = nPts -
|
|
627
|
+
const firstMovableIndex = 1;
|
|
628
|
+
const lastMovableIndex = nPts - 3;
|
|
562
629
|
const movableSegments = [];
|
|
563
630
|
const movableIdx = [];
|
|
564
631
|
const axes = [];
|
|
@@ -1427,10 +1494,11 @@ function rectIntersectsAnyTrace(bounds, inputTraceMap, hostPathId, hostSegIndex)
|
|
|
1427
1494
|
const pts = solved.tracePath;
|
|
1428
1495
|
for (let i = 0; i < pts.length - 1; i++) {
|
|
1429
1496
|
if (pairId === hostPathId && i === hostSegIndex) continue;
|
|
1430
|
-
if (segmentIntersectsRect(pts[i], pts[i + 1], bounds))
|
|
1497
|
+
if (segmentIntersectsRect(pts[i], pts[i + 1], bounds))
|
|
1498
|
+
return { hasIntersection: true, mspPairId: pairId, segIndex: i };
|
|
1431
1499
|
}
|
|
1432
1500
|
}
|
|
1433
|
-
return false;
|
|
1501
|
+
return { hasIntersection: false };
|
|
1434
1502
|
}
|
|
1435
1503
|
|
|
1436
1504
|
// lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/host.ts
|
|
@@ -1515,14 +1583,16 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
|
|
|
1515
1583
|
};
|
|
1516
1584
|
}
|
|
1517
1585
|
let pin = null;
|
|
1586
|
+
let pinFacingDirection = null;
|
|
1518
1587
|
for (const chip of inputProblem.chips) {
|
|
1519
1588
|
const p = chip.pins.find((pp) => pp.pinId === pinId);
|
|
1520
1589
|
if (p) {
|
|
1521
1590
|
pin = { x: p.x, y: p.y };
|
|
1591
|
+
pinFacingDirection = p._facingDirection || getPinDirection(p, chip);
|
|
1522
1592
|
break;
|
|
1523
1593
|
}
|
|
1524
1594
|
}
|
|
1525
|
-
if (!pin) {
|
|
1595
|
+
if (!pin || !pinFacingDirection) {
|
|
1526
1596
|
return {
|
|
1527
1597
|
placement: null,
|
|
1528
1598
|
testedCandidates: [],
|
|
@@ -1534,21 +1604,21 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
|
|
|
1534
1604
|
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 };
|
|
1535
1605
|
const testedCandidates = [];
|
|
1536
1606
|
for (const orientation of orientations) {
|
|
1537
|
-
const { width, height } = getDimsForOrientation(orientation);
|
|
1538
|
-
const
|
|
1539
|
-
const
|
|
1540
|
-
const
|
|
1607
|
+
const { width: width2, height: height2 } = getDimsForOrientation(orientation);
|
|
1608
|
+
const baseCenter2 = getCenterFromAnchor(anchor, orientation, width2, height2);
|
|
1609
|
+
const outward2 = outwardOf(orientation);
|
|
1610
|
+
const offset2 = 1e-3;
|
|
1541
1611
|
const center = {
|
|
1542
|
-
x:
|
|
1543
|
-
y:
|
|
1612
|
+
x: baseCenter2.x + outward2.x * offset2,
|
|
1613
|
+
y: baseCenter2.y + outward2.y * offset2
|
|
1544
1614
|
};
|
|
1545
|
-
const bounds = getRectBounds(center,
|
|
1615
|
+
const bounds = getRectBounds(center, width2, height2);
|
|
1546
1616
|
const chips = chipObstacleSpatialIndex.getChipsInBounds(bounds);
|
|
1547
1617
|
if (chips.length > 0) {
|
|
1548
1618
|
testedCandidates.push({
|
|
1549
1619
|
center,
|
|
1550
|
-
width,
|
|
1551
|
-
height,
|
|
1620
|
+
width: width2,
|
|
1621
|
+
height: height2,
|
|
1552
1622
|
bounds,
|
|
1553
1623
|
anchor,
|
|
1554
1624
|
orientation,
|
|
@@ -1557,16 +1627,17 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
|
|
|
1557
1627
|
});
|
|
1558
1628
|
continue;
|
|
1559
1629
|
}
|
|
1560
|
-
|
|
1630
|
+
const traceIntersectionResult = rectIntersectsAnyTrace(
|
|
1561
1631
|
bounds,
|
|
1562
1632
|
inputTraceMap,
|
|
1563
1633
|
"",
|
|
1564
1634
|
-1
|
|
1565
|
-
)
|
|
1635
|
+
);
|
|
1636
|
+
if (traceIntersectionResult.hasIntersection) {
|
|
1566
1637
|
testedCandidates.push({
|
|
1567
1638
|
center,
|
|
1568
|
-
width,
|
|
1569
|
-
height,
|
|
1639
|
+
width: width2,
|
|
1640
|
+
height: height2,
|
|
1570
1641
|
bounds,
|
|
1571
1642
|
anchor,
|
|
1572
1643
|
orientation,
|
|
@@ -1577,8 +1648,8 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
|
|
|
1577
1648
|
}
|
|
1578
1649
|
testedCandidates.push({
|
|
1579
1650
|
center,
|
|
1580
|
-
width,
|
|
1581
|
-
height,
|
|
1651
|
+
width: width2,
|
|
1652
|
+
height: height2,
|
|
1582
1653
|
bounds,
|
|
1583
1654
|
anchor,
|
|
1584
1655
|
orientation,
|
|
@@ -1593,17 +1664,39 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
|
|
|
1593
1664
|
pinIds: [pinId],
|
|
1594
1665
|
orientation,
|
|
1595
1666
|
anchorPoint: anchor,
|
|
1596
|
-
width,
|
|
1597
|
-
height,
|
|
1667
|
+
width: width2,
|
|
1668
|
+
height: height2,
|
|
1598
1669
|
center
|
|
1599
1670
|
};
|
|
1600
1671
|
return { placement, testedCandidates };
|
|
1601
1672
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1673
|
+
const fallbackOrientation = pinFacingDirection;
|
|
1674
|
+
const { width, height } = getDimsForOrientation(fallbackOrientation);
|
|
1675
|
+
const baseCenter = getCenterFromAnchor(
|
|
1676
|
+
anchor,
|
|
1677
|
+
fallbackOrientation,
|
|
1678
|
+
width,
|
|
1679
|
+
height
|
|
1680
|
+
);
|
|
1681
|
+
const outward = outwardOf(fallbackOrientation);
|
|
1682
|
+
const offset = 1e-3;
|
|
1683
|
+
const fallbackCenter = {
|
|
1684
|
+
x: baseCenter.x + outward.x * offset,
|
|
1685
|
+
y: baseCenter.y + outward.y * offset
|
|
1686
|
+
};
|
|
1687
|
+
const fallbackPlacement = {
|
|
1688
|
+
globalConnNetId: overlappingSameNetTraceGroup.globalConnNetId,
|
|
1689
|
+
dcConnNetId: void 0,
|
|
1690
|
+
netId: overlappingSameNetTraceGroup.netId,
|
|
1691
|
+
mspConnectionPairIds: [],
|
|
1692
|
+
pinIds: [pinId],
|
|
1693
|
+
orientation: fallbackOrientation,
|
|
1694
|
+
anchorPoint: anchor,
|
|
1695
|
+
width,
|
|
1696
|
+
height,
|
|
1697
|
+
center: fallbackCenter
|
|
1606
1698
|
};
|
|
1699
|
+
return { placement: fallbackPlacement, testedCandidates };
|
|
1607
1700
|
}
|
|
1608
1701
|
|
|
1609
1702
|
// lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts
|
|
@@ -1619,12 +1712,11 @@ function visualizeSingleNetLabelPlacementSolver(solver) {
|
|
|
1619
1712
|
const groupStroke = getColorFromString(groupId, 0.9);
|
|
1620
1713
|
const groupFill = getColorFromString(groupId, 0.5);
|
|
1621
1714
|
for (const trace of Object.values(solver.inputTraceMap)) {
|
|
1622
|
-
if (trace.globalConnNetId !== groupId) continue;
|
|
1623
1715
|
const isHost = host ? trace.mspPairId === host.mspPairId : false;
|
|
1624
1716
|
graphics.lines.push({
|
|
1625
|
-
points: trace.tracePath
|
|
1626
|
-
strokeColor: isHost ? groupStroke : groupFill,
|
|
1627
|
-
strokeDash: isHost ?
|
|
1717
|
+
points: trace.tracePath
|
|
1718
|
+
// strokeColor: isHost ? groupStroke : groupFill,
|
|
1719
|
+
// strokeDash: isHost ? undefined : "4 2",
|
|
1628
1720
|
});
|
|
1629
1721
|
}
|
|
1630
1722
|
for (const c of solver.testedCandidates) {
|
|
@@ -1796,12 +1888,13 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
|
|
|
1796
1888
|
});
|
|
1797
1889
|
continue;
|
|
1798
1890
|
}
|
|
1799
|
-
|
|
1891
|
+
const traceIntersectionResult = rectIntersectsAnyTrace(
|
|
1800
1892
|
bounds,
|
|
1801
1893
|
this.inputTraceMap,
|
|
1802
1894
|
curr.mspPairId,
|
|
1803
1895
|
si
|
|
1804
|
-
)
|
|
1896
|
+
);
|
|
1897
|
+
if (traceIntersectionResult.hasIntersection) {
|
|
1805
1898
|
this.testedCandidates.push({
|
|
1806
1899
|
center: testCenter,
|
|
1807
1900
|
width,
|
|
@@ -261,14 +261,13 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
|
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
// Trace collision check (ignore the host segment)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
) {
|
|
264
|
+
const traceIntersectionResult = rectIntersectsAnyTrace(
|
|
265
|
+
bounds,
|
|
266
|
+
this.inputTraceMap,
|
|
267
|
+
curr.mspPairId,
|
|
268
|
+
si,
|
|
269
|
+
)
|
|
270
|
+
if (traceIntersectionResult.hasIntersection) {
|
|
272
271
|
this.testedCandidates.push({
|
|
273
272
|
center: testCenter,
|
|
274
273
|
width,
|
|
@@ -21,12 +21,11 @@ export function visualizeSingleNetLabelPlacementSolver(
|
|
|
21
21
|
const groupFill = getColorFromString(groupId, 0.5)
|
|
22
22
|
|
|
23
23
|
for (const trace of Object.values(solver.inputTraceMap)) {
|
|
24
|
-
if (trace.globalConnNetId !== groupId) continue
|
|
25
24
|
const isHost = host ? trace.mspPairId === host.mspPairId : false
|
|
26
25
|
graphics.lines!.push({
|
|
27
26
|
points: trace.tracePath,
|
|
28
|
-
strokeColor: isHost ? groupStroke : groupFill,
|
|
29
|
-
strokeDash: isHost ? undefined : "4 2",
|
|
27
|
+
// strokeColor: isHost ? groupStroke : groupFill,
|
|
28
|
+
// strokeDash: isHost ? undefined : "4 2",
|
|
30
29
|
} as any)
|
|
31
30
|
}
|
|
32
31
|
|
|
@@ -33,13 +33,20 @@ export function rectIntersectsAnyTrace(
|
|
|
33
33
|
inputTraceMap: Record<MspConnectionPairId, SolvedTracePath>,
|
|
34
34
|
hostPathId?: MspConnectionPairId,
|
|
35
35
|
hostSegIndex?: number,
|
|
36
|
-
):
|
|
36
|
+
):
|
|
37
|
+
| {
|
|
38
|
+
hasIntersection: true
|
|
39
|
+
mspPairId: MspConnectionPairId
|
|
40
|
+
segIndex: number
|
|
41
|
+
}
|
|
42
|
+
| { hasIntersection: false } {
|
|
37
43
|
for (const [pairId, solved] of Object.entries(inputTraceMap)) {
|
|
38
44
|
const pts = solved.tracePath
|
|
39
45
|
for (let i = 0; i < pts.length - 1; i++) {
|
|
40
46
|
if (pairId === hostPathId && i === hostSegIndex) continue
|
|
41
|
-
if (segmentIntersectsRect(pts[i]!, pts[i + 1]!, bounds))
|
|
47
|
+
if (segmentIntersectsRect(pts[i]!, pts[i + 1]!, bounds))
|
|
48
|
+
return { hasIntersection: true, mspPairId: pairId, segIndex: i }
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
|
-
return false
|
|
51
|
+
return { hasIntersection: false }
|
|
45
52
|
}
|
package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/Ms
|
|
|
3
3
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
4
|
import type { FacingDirection } from "lib/utils/dir"
|
|
5
5
|
import { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
|
|
6
|
+
import { getPinDirection } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
|
|
6
7
|
import type {
|
|
7
8
|
NetLabelPlacement,
|
|
8
9
|
OverlappingSameNetTraceGroup,
|
|
@@ -51,16 +52,18 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
// Find pin coordinates
|
|
55
|
+
// Find pin coordinates and facing direction
|
|
55
56
|
let pin: { x: number; y: number } | null = null
|
|
57
|
+
let pinFacingDirection: FacingDirection | null = null
|
|
56
58
|
for (const chip of inputProblem.chips) {
|
|
57
59
|
const p = chip.pins.find((pp) => pp.pinId === pinId)
|
|
58
60
|
if (p) {
|
|
59
61
|
pin = { x: p.x, y: p.y }
|
|
62
|
+
pinFacingDirection = p._facingDirection || getPinDirection(p, chip)
|
|
60
63
|
break
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
|
-
if (!pin) {
|
|
66
|
+
if (!pin || !pinFacingDirection) {
|
|
64
67
|
return {
|
|
65
68
|
placement: null,
|
|
66
69
|
testedCandidates: [],
|
|
@@ -123,14 +126,13 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
|
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
// Trace collision check
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
) {
|
|
129
|
+
const traceIntersectionResult = rectIntersectsAnyTrace(
|
|
130
|
+
bounds,
|
|
131
|
+
inputTraceMap,
|
|
132
|
+
"" as MspConnectionPairId,
|
|
133
|
+
-1,
|
|
134
|
+
)
|
|
135
|
+
if (traceIntersectionResult.hasIntersection) {
|
|
134
136
|
testedCandidates.push({
|
|
135
137
|
center,
|
|
136
138
|
width,
|
|
@@ -172,9 +174,34 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
|
|
|
172
174
|
return { placement, testedCandidates }
|
|
173
175
|
}
|
|
174
176
|
|
|
175
|
-
return
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
// If no valid placements found, return placement using pin's facing direction
|
|
178
|
+
const fallbackOrientation = pinFacingDirection
|
|
179
|
+
const { width, height } = getDimsForOrientation(fallbackOrientation)
|
|
180
|
+
const baseCenter = getCenterFromAnchor(
|
|
181
|
+
anchor,
|
|
182
|
+
fallbackOrientation,
|
|
183
|
+
width,
|
|
184
|
+
height,
|
|
185
|
+
)
|
|
186
|
+
const outward = outwardOf(fallbackOrientation)
|
|
187
|
+
const offset = 1e-3
|
|
188
|
+
const fallbackCenter = {
|
|
189
|
+
x: baseCenter.x + outward.x * offset,
|
|
190
|
+
y: baseCenter.y + outward.y * offset,
|
|
179
191
|
}
|
|
192
|
+
|
|
193
|
+
const fallbackPlacement: NetLabelPlacement = {
|
|
194
|
+
globalConnNetId: overlappingSameNetTraceGroup.globalConnNetId,
|
|
195
|
+
dcConnNetId: undefined,
|
|
196
|
+
netId: overlappingSameNetTraceGroup.netId,
|
|
197
|
+
mspConnectionPairIds: [],
|
|
198
|
+
pinIds: [pinId],
|
|
199
|
+
orientation: fallbackOrientation,
|
|
200
|
+
anchorPoint: anchor,
|
|
201
|
+
width,
|
|
202
|
+
height,
|
|
203
|
+
center: fallbackCenter,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return { placement: fallbackPlacement, testedCandidates }
|
|
180
207
|
}
|
|
@@ -14,6 +14,123 @@ export interface MovableSegment {
|
|
|
14
14
|
const EPS = 1e-6 // numeric tolerance for equality
|
|
15
15
|
const MIN_LEN = 1e-6 // forbid near-zero length segments (adjacent collapses)
|
|
16
16
|
|
|
17
|
+
const checkIfUTurnNeeded = (elbow: Point[]): boolean => {
|
|
18
|
+
if (elbow.length !== 4) return false
|
|
19
|
+
|
|
20
|
+
const [start, p1, p2, end] = elbow
|
|
21
|
+
|
|
22
|
+
// Determine facing directions from the path segments
|
|
23
|
+
const startOrient = orientationOf(start, p1)
|
|
24
|
+
const startFacing: FacingDirection =
|
|
25
|
+
startOrient === "horizontal"
|
|
26
|
+
? p1.x > start.x
|
|
27
|
+
? "x+"
|
|
28
|
+
: "x-"
|
|
29
|
+
: p1.y > start.y
|
|
30
|
+
? "y+"
|
|
31
|
+
: "y-"
|
|
32
|
+
|
|
33
|
+
const endOrient = orientationOf(p2, end)
|
|
34
|
+
const endFacing: FacingDirection =
|
|
35
|
+
endOrient === "horizontal"
|
|
36
|
+
? end.x > p2.x
|
|
37
|
+
? "x+"
|
|
38
|
+
: "x-"
|
|
39
|
+
: end.y > p2.y
|
|
40
|
+
? "y+"
|
|
41
|
+
: "y-"
|
|
42
|
+
|
|
43
|
+
// Check if path overshoots and needs to turn back
|
|
44
|
+
if (startFacing === "x+" && end.x < p1.x - EPS) return true
|
|
45
|
+
if (startFacing === "x-" && end.x > p1.x + EPS) return true
|
|
46
|
+
if (startFacing === "y+" && end.y < p1.y - EPS) return true
|
|
47
|
+
if (startFacing === "y-" && end.y > p1.y + EPS) return true
|
|
48
|
+
|
|
49
|
+
// Check if path segments conflict (p2 is past the end in the facing direction)
|
|
50
|
+
if (endFacing === "x-" && p2.x > end.x + EPS && startFacing === "x+")
|
|
51
|
+
return true
|
|
52
|
+
if (endFacing === "x+" && p2.x < end.x - EPS && startFacing === "x-")
|
|
53
|
+
return true
|
|
54
|
+
if (endFacing === "y-" && p2.y > end.y + EPS && startFacing === "y+")
|
|
55
|
+
return true
|
|
56
|
+
if (endFacing === "y+" && p2.y < end.y - EPS && startFacing === "y-")
|
|
57
|
+
return true
|
|
58
|
+
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const expandToUTurn = (elbow: Point[]): Point[] => {
|
|
63
|
+
if (elbow.length !== 4) return elbow
|
|
64
|
+
|
|
65
|
+
const [start, p1, p2, end] = elbow
|
|
66
|
+
|
|
67
|
+
// Calculate overshoot distance from existing segments
|
|
68
|
+
const overshoot = Math.max(
|
|
69
|
+
Math.abs(start.x - p1.x),
|
|
70
|
+
Math.abs(start.y - p1.y),
|
|
71
|
+
0.2, // minimum overshoot
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const startOrient = orientationOf(start, p1)
|
|
75
|
+
const expanded: Point[] = [{ ...start }]
|
|
76
|
+
|
|
77
|
+
if (startOrient === "horizontal") {
|
|
78
|
+
const startDir = p1.x > start.x ? 1 : -1
|
|
79
|
+
expanded.push({ x: start.x + startDir * overshoot, y: start.y })
|
|
80
|
+
|
|
81
|
+
// Move away vertically to create space for U-turn
|
|
82
|
+
const verticalSpace = Math.max(
|
|
83
|
+
overshoot * 2,
|
|
84
|
+
Math.abs(end.y - start.y) + overshoot,
|
|
85
|
+
)
|
|
86
|
+
const midY =
|
|
87
|
+
end.y > start.y ? start.y - verticalSpace : start.y + verticalSpace
|
|
88
|
+
|
|
89
|
+
expanded.push({ x: start.x + startDir * overshoot, y: midY })
|
|
90
|
+
|
|
91
|
+
// Approach end from its required direction
|
|
92
|
+
const endOrient = orientationOf(p2, end)
|
|
93
|
+
const endApproachX =
|
|
94
|
+
endOrient === "horizontal"
|
|
95
|
+
? end.x > p2.x
|
|
96
|
+
? end.x - overshoot
|
|
97
|
+
: end.x + overshoot
|
|
98
|
+
: end.x
|
|
99
|
+
|
|
100
|
+
expanded.push({ x: endApproachX, y: midY })
|
|
101
|
+
expanded.push({ x: endApproachX, y: end.y })
|
|
102
|
+
} else {
|
|
103
|
+
// Vertical start
|
|
104
|
+
const startDir = p1.y > start.y ? 1 : -1
|
|
105
|
+
expanded.push({ x: start.x, y: start.y + startDir * overshoot })
|
|
106
|
+
|
|
107
|
+
// Move away horizontally to create space for U-turn
|
|
108
|
+
const horizontalSpace = Math.max(
|
|
109
|
+
overshoot * 2,
|
|
110
|
+
Math.abs(end.x - start.x) + overshoot,
|
|
111
|
+
)
|
|
112
|
+
const midX =
|
|
113
|
+
end.x > start.x ? start.x - horizontalSpace : start.x + horizontalSpace
|
|
114
|
+
|
|
115
|
+
expanded.push({ x: midX, y: start.y + startDir * overshoot })
|
|
116
|
+
|
|
117
|
+
// Approach end from its required direction
|
|
118
|
+
const endOrient = orientationOf(p2, end)
|
|
119
|
+
const endApproachY =
|
|
120
|
+
endOrient === "vertical"
|
|
121
|
+
? end.y > p2.y
|
|
122
|
+
? end.y - overshoot
|
|
123
|
+
: end.y + overshoot
|
|
124
|
+
: end.y
|
|
125
|
+
|
|
126
|
+
expanded.push({ x: midX, y: endApproachY })
|
|
127
|
+
expanded.push({ x: end.x, y: endApproachY })
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
expanded.push({ ...end })
|
|
131
|
+
return expanded
|
|
132
|
+
}
|
|
133
|
+
|
|
17
134
|
/** True if segment [a,b] is axis-aligned (horizontal or vertical). */
|
|
18
135
|
const isAxisAligned = (a: Point, b: Point): boolean =>
|
|
19
136
|
Math.abs(a.x - b.x) < EPS || Math.abs(a.y - b.y) < EPS
|
|
@@ -155,21 +272,27 @@ export const generateElbowVariants = ({
|
|
|
155
272
|
assertOrthogonalPolyline(elbow)
|
|
156
273
|
|
|
157
274
|
const nPts = elbow.length
|
|
158
|
-
const nSegs = nPts - 1
|
|
159
275
|
|
|
160
276
|
// No interior segments to move if path is too short.
|
|
161
|
-
if (
|
|
277
|
+
if (nPts < 4) {
|
|
162
278
|
return {
|
|
163
279
|
elbowVariants: [elbow.map((p) => ({ ...p }))],
|
|
164
280
|
movableSegments: [],
|
|
165
281
|
}
|
|
282
|
+
} else if (nPts === 4) {
|
|
283
|
+
const needsUTurn = checkIfUTurnNeeded(elbow)
|
|
284
|
+
if (needsUTurn) {
|
|
285
|
+
const expandedElbow = expandToUTurn(elbow)
|
|
286
|
+
// Recursively call with the expanded path
|
|
287
|
+
return generateElbowVariants({ baseElbow: expandedElbow, guidelines })
|
|
288
|
+
}
|
|
166
289
|
}
|
|
167
290
|
|
|
168
291
|
// 2) Choose which segments are allowed to move.
|
|
169
|
-
// We avoid
|
|
170
|
-
// movable indices i in [
|
|
171
|
-
const firstMovableIndex =
|
|
172
|
-
const lastMovableIndex = nPts -
|
|
292
|
+
// We avoid moving the very first and last segments of the:
|
|
293
|
+
// movable indices i in [1 .. (nPts - 3)] inclusive (segment i connects P[i] -> P[i+1]).
|
|
294
|
+
const firstMovableIndex = 1
|
|
295
|
+
const lastMovableIndex = nPts - 3
|
|
173
296
|
|
|
174
297
|
const movableSegments: MovableSegment[] = []
|
|
175
298
|
const movableIdx: number[] = []
|
package/package.json
CHANGED