@tscircuit/schematic-trace-solver 0.0.44 → 0.0.46
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/.github/workflows/bun-formatcheck.yml +1 -1
- package/.github/workflows/bun-pver-release.yml +1 -1
- package/.github/workflows/bun-test.yml +1 -1
- package/.github/workflows/bun-typecheck.yml +1 -1
- package/dist/index.d.ts +43 -8
- package/dist/index.js +1109 -185
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +0 -1
- package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +83 -36
- package/lib/solvers/TraceCleanupSolver/hasCollisions.ts +16 -4
- package/lib/solvers/TraceCleanupSolver/is4PointRectangle.ts +17 -0
- package/lib/solvers/TraceCleanupSolver/isSegmentAnEndpointSegment.ts +36 -0
- package/lib/solvers/TraceCleanupSolver/mergeGraphicsObjects.ts +28 -0
- package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +18 -1
- package/lib/solvers/TraceCleanupSolver/recognizeStairStepPattern.ts +56 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/UntangleTraceSubsolver.ts +370 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/findAllLShapedTurns.ts +48 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles.ts +36 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +107 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/generateRectangleCandidates.ts +55 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/getTraceObstacles.ts +25 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/isPathColliding.ts +58 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCandidates.ts +33 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCollision.ts +20 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeIntersectionPoints.ts +26 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeLSapes.ts +33 -0
- package/lib/solvers/TraceCleanupSolver/turnMinimization.ts +50 -56
- package/lib/solvers/TraceCleanupSolver/visualizeTightRectangle.ts +24 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +17 -3
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts +26 -11
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +95 -75
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/filterLabelsAtTraceEdges.ts +74 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/groupLabelsByChipAndOrientation.ts +45 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/mergeLabelGroup.ts +71 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +150 -27
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/isPointInsideLabel.ts +23 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/visualizeDecomposition.ts +54 -0
- package/package.json +1 -1
- package/tests/examples/__snapshots__/example29.snap.svg +6 -6
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +8 -8
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Visualizes a set of candidate paths and optional intersection points.
|
|
6
|
+
* It draws each candidate path as a line with a specified color and marks intersection points with green circles.
|
|
7
|
+
* This function is useful for debugging and understanding the rerouting process.
|
|
8
|
+
*/
|
|
9
|
+
export const visualizeCandidates = (
|
|
10
|
+
candidates: Point[][],
|
|
11
|
+
color = "gray",
|
|
12
|
+
intersectionPoints: Point[] = [],
|
|
13
|
+
): GraphicsObject => {
|
|
14
|
+
const graphics: GraphicsObject = { lines: [], circles: [] }
|
|
15
|
+
|
|
16
|
+
for (const candidate of candidates) {
|
|
17
|
+
graphics.lines!.push({
|
|
18
|
+
points: candidate,
|
|
19
|
+
strokeColor: color,
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Draw intersection points
|
|
24
|
+
for (const point of intersectionPoints) {
|
|
25
|
+
graphics.circles!.push({
|
|
26
|
+
center: point,
|
|
27
|
+
radius: 0.01, // Larger radius for intersection points
|
|
28
|
+
fill: "green",
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return graphics
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import type { CollisionInfo } from "./isPathColliding"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Visualizes a collision point if collision information is provided and a collision occurred.
|
|
6
|
+
* It draws a red circle at the collision point to highlight the location of the collision.
|
|
7
|
+
*/
|
|
8
|
+
export const visualizeCollision = (
|
|
9
|
+
collisionInfo: CollisionInfo | null,
|
|
10
|
+
): GraphicsObject => {
|
|
11
|
+
const collisionGraphics: GraphicsObject = { circles: [] }
|
|
12
|
+
if (collisionInfo?.isColliding && collisionInfo.collisionPoint) {
|
|
13
|
+
collisionGraphics.circles!.push({
|
|
14
|
+
center: collisionInfo.collisionPoint,
|
|
15
|
+
radius: 0.01,
|
|
16
|
+
fill: "red",
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
return collisionGraphics
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Visualizes a set of intersection points by drawing circles at their locations.
|
|
6
|
+
* This function is used to highlight where different trace segments or obstacles intersect.
|
|
7
|
+
*/
|
|
8
|
+
export const visualizeIntersectionPoints = (
|
|
9
|
+
points: Point[],
|
|
10
|
+
color = "red",
|
|
11
|
+
): GraphicsObject => {
|
|
12
|
+
const graphics: GraphicsObject = { circles: [] }
|
|
13
|
+
|
|
14
|
+
for (const point of points) {
|
|
15
|
+
graphics.circles!.push({
|
|
16
|
+
center: {
|
|
17
|
+
x: point.x,
|
|
18
|
+
y: point.y,
|
|
19
|
+
},
|
|
20
|
+
radius: 0.01,
|
|
21
|
+
fill: color,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return graphics
|
|
26
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { LShape } from "./findAllLShapedTurns"
|
|
2
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Visualizes L-shaped turns by drawing a blue circle at the corner point (p2)
|
|
6
|
+
* and light blue lines connecting p1, p2, and p3.
|
|
7
|
+
* This function can visualize a single L-shape or an array of L-shapes.
|
|
8
|
+
*/
|
|
9
|
+
export const visualizeLSapes = (lShapes: LShape[] | LShape): GraphicsObject => {
|
|
10
|
+
const graphics: GraphicsObject = { circles: [], lines: [] }
|
|
11
|
+
|
|
12
|
+
const lShapesArray = Array.isArray(lShapes) ? lShapes : [lShapes]
|
|
13
|
+
|
|
14
|
+
for (const lShape of lShapesArray) {
|
|
15
|
+
// Draw the center point as a blue ball
|
|
16
|
+
graphics.circles!.push({
|
|
17
|
+
center: {
|
|
18
|
+
x: lShape.p2.x,
|
|
19
|
+
y: lShape.p2.y,
|
|
20
|
+
},
|
|
21
|
+
radius: 0.01,
|
|
22
|
+
fill: "blue",
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// Draw the two lines in a light blue color
|
|
26
|
+
graphics.lines!.push({
|
|
27
|
+
points: [lShape.p1, lShape.p2, lShape.p3],
|
|
28
|
+
strokeColor: "lightblue",
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return graphics
|
|
33
|
+
}
|
|
@@ -4,75 +4,32 @@ import { countTurns } from "./countTurns"
|
|
|
4
4
|
import { simplifyPath } from "./simplifyPath"
|
|
5
5
|
import { tryConnectPoints } from "./tryConnectPoints"
|
|
6
6
|
import { hasCollisionsWithLabels } from "./hasCollisionsWithLabels"
|
|
7
|
-
|
|
7
|
+
import { recognizeStairStepPattern } from "./recognizeStairStepPattern"
|
|
8
|
+
import { isSegmentAnEndpointSegment } from "./isSegmentAnEndpointSegment"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Minimizes the number of turns in a given path while avoiding collisions with obstacles and labels.
|
|
12
|
+
* This function employs an iterative approach, attempting to simplify the path in several ways:
|
|
13
|
+
* 1. **Stair-step pattern recognition**: It first looks for and attempts to simplify stair-step patterns by connecting the start and end points of the pattern with a simpler path, if no collisions are introduced.
|
|
14
|
+
* 2. **Point removal and reconnection**: If no stair-step optimization is possible, it tries to remove intermediate points and reconnect the remaining segments with a simpler path, prioritizing solutions that reduce turns or path length.
|
|
15
|
+
* 3. **Collinear segment merging**: Finally, it attempts to merge collinear segments to further simplify the path.
|
|
16
|
+
* The process continues until no further improvements can be made without introducing collisions.
|
|
17
|
+
*/
|
|
8
18
|
export const minimizeTurns = ({
|
|
9
19
|
path,
|
|
10
20
|
obstacles,
|
|
11
21
|
labelBounds,
|
|
22
|
+
originalPath,
|
|
12
23
|
}: {
|
|
13
24
|
path: Point[]
|
|
14
25
|
obstacles: any[]
|
|
15
26
|
labelBounds: any[]
|
|
27
|
+
originalPath: Point[]
|
|
16
28
|
}): Point[] => {
|
|
17
29
|
if (path.length <= 2) {
|
|
18
30
|
return path
|
|
19
31
|
}
|
|
20
32
|
|
|
21
|
-
const recognizeStairStepPattern = (
|
|
22
|
-
pathToCheck: Point[],
|
|
23
|
-
startIdx: number,
|
|
24
|
-
): number => {
|
|
25
|
-
if (startIdx >= pathToCheck.length - 3) return -1
|
|
26
|
-
|
|
27
|
-
let endIdx = startIdx
|
|
28
|
-
let isStairStep = true
|
|
29
|
-
|
|
30
|
-
for (
|
|
31
|
-
let i = startIdx;
|
|
32
|
-
i < pathToCheck.length - 2 && i < startIdx + 10;
|
|
33
|
-
i++
|
|
34
|
-
) {
|
|
35
|
-
if (i + 2 >= pathToCheck.length) break
|
|
36
|
-
|
|
37
|
-
const p1 = pathToCheck[i]
|
|
38
|
-
const p2 = pathToCheck[i + 1]
|
|
39
|
-
const p3 = pathToCheck[i + 2]
|
|
40
|
-
|
|
41
|
-
const seg1Vertical = p1.x === p2.x
|
|
42
|
-
const seg2Vertical = p2.x === p3.x
|
|
43
|
-
|
|
44
|
-
if (seg1Vertical === seg2Vertical) {
|
|
45
|
-
break
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const seg1Direction = seg1Vertical
|
|
49
|
-
? Math.sign(p2.y - p1.y)
|
|
50
|
-
: Math.sign(p2.x - p1.x)
|
|
51
|
-
|
|
52
|
-
if (i > startIdx) {
|
|
53
|
-
const prevP = pathToCheck[i - 1]
|
|
54
|
-
const prevSegVertical = prevP.x === p1.x
|
|
55
|
-
const prevDirection = prevSegVertical
|
|
56
|
-
? Math.sign(p1.y - prevP.y)
|
|
57
|
-
: Math.sign(p1.x - prevP.x)
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
(seg1Vertical &&
|
|
61
|
-
prevSegVertical &&
|
|
62
|
-
seg1Direction !== prevDirection) ||
|
|
63
|
-
(!seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection)
|
|
64
|
-
) {
|
|
65
|
-
isStairStep = false
|
|
66
|
-
break
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
endIdx = i + 2
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1
|
|
74
|
-
}
|
|
75
|
-
|
|
76
33
|
let optimizedPath = [...path]
|
|
77
34
|
let currentTurns = countTurns(optimizedPath)
|
|
78
35
|
let improved = true
|
|
@@ -85,6 +42,21 @@ export const minimizeTurns = ({
|
|
|
85
42
|
const stairEndIdx = recognizeStairStepPattern(optimizedPath, startIdx)
|
|
86
43
|
|
|
87
44
|
if (stairEndIdx > 0) {
|
|
45
|
+
if (
|
|
46
|
+
isSegmentAnEndpointSegment(
|
|
47
|
+
optimizedPath[startIdx],
|
|
48
|
+
optimizedPath[startIdx + 1],
|
|
49
|
+
originalPath,
|
|
50
|
+
) ||
|
|
51
|
+
isSegmentAnEndpointSegment(
|
|
52
|
+
optimizedPath[stairEndIdx - 1],
|
|
53
|
+
optimizedPath[stairEndIdx],
|
|
54
|
+
originalPath,
|
|
55
|
+
)
|
|
56
|
+
) {
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
88
60
|
const startPoint = optimizedPath[startIdx]
|
|
89
61
|
const endPoint = optimizedPath[stairEndIdx]
|
|
90
62
|
|
|
@@ -129,6 +101,21 @@ export const minimizeTurns = ({
|
|
|
129
101
|
|
|
130
102
|
if (endIdx >= optimizedPath.length) continue
|
|
131
103
|
|
|
104
|
+
if (
|
|
105
|
+
isSegmentAnEndpointSegment(
|
|
106
|
+
optimizedPath[startIdx],
|
|
107
|
+
optimizedPath[startIdx + 1],
|
|
108
|
+
originalPath,
|
|
109
|
+
) ||
|
|
110
|
+
isSegmentAnEndpointSegment(
|
|
111
|
+
optimizedPath[endIdx - 1],
|
|
112
|
+
optimizedPath[endIdx],
|
|
113
|
+
originalPath,
|
|
114
|
+
)
|
|
115
|
+
) {
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
|
|
132
119
|
const startPoint = optimizedPath[startIdx]
|
|
133
120
|
const endPoint = optimizedPath[endIdx]
|
|
134
121
|
|
|
@@ -179,6 +166,13 @@ export const minimizeTurns = ({
|
|
|
179
166
|
const p2 = optimizedPath[i + 1]
|
|
180
167
|
const p3 = optimizedPath[i + 2]
|
|
181
168
|
|
|
169
|
+
if (
|
|
170
|
+
isSegmentAnEndpointSegment(p1, p2, originalPath) ||
|
|
171
|
+
isSegmentAnEndpointSegment(p2, p3, originalPath)
|
|
172
|
+
) {
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
|
|
182
176
|
const allVertical = p1.x === p2.x && p2.x === p3.x
|
|
183
177
|
const allHorizontal = p1.y === p2.y && p2.y === p3.y
|
|
184
178
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import type { Rectangle } from "./sub-solver/generateRectangleCandidates"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Visualizes a given rectangle by drawing it as a green-stroked rectangle.
|
|
6
|
+
* This function is useful for highlighting specific rectangular areas in a graphical representation.
|
|
7
|
+
*/
|
|
8
|
+
export const visualizeTightRectangle = (
|
|
9
|
+
rectangle: Rectangle,
|
|
10
|
+
): GraphicsObject => {
|
|
11
|
+
const graphics: GraphicsObject = { rects: [] }
|
|
12
|
+
|
|
13
|
+
graphics.rects!.push({
|
|
14
|
+
center: {
|
|
15
|
+
x: rectangle.x + rectangle.width / 2,
|
|
16
|
+
y: rectangle.y + rectangle.height / 2,
|
|
17
|
+
},
|
|
18
|
+
width: rectangle.width,
|
|
19
|
+
height: rectangle.height,
|
|
20
|
+
stroke: "green",
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return graphics
|
|
24
|
+
}
|
|
@@ -15,8 +15,21 @@ interface TraceLabelOverlapAvoidanceSolverInput {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* A pipeline solver responsible for resolving overlaps between schematic traces and net labels.
|
|
19
|
+
*
|
|
20
|
+
* This solver orchestrates a sequence of sub-solvers to achieve its goal:
|
|
21
|
+
* 1. **MergedNetLabelObstacleSolver**: This solver first merges labels that are
|
|
22
|
+
* close to each other to form larger "obstacle" groups. This simplifies the
|
|
23
|
+
* problem by reducing the number of individual obstacles the traces need to avoid.
|
|
24
|
+
* 2. **OverlapAvoidanceStepSolver**: This solver then takes the output of the merging
|
|
25
|
+
* step and iteratively attempts to reroute traces to avoid the merged label obstacles.
|
|
26
|
+
* It handles one overlap at a time, making it a step-by-step process.
|
|
27
|
+
*
|
|
28
|
+
* The final output is a set of modified traces that have been rerouted to avoid
|
|
29
|
+
* labels, and the set of merged labels that were used as obstacles.
|
|
30
|
+
*
|
|
31
|
+
* @param {TraceLabelOverlapAvoidanceSolverInput} solverInput - The input for the solver,
|
|
32
|
+
* containing the initial traces, label placements, and the input problem definition.
|
|
20
33
|
*/
|
|
21
34
|
export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
22
35
|
inputProblem: InputProblem
|
|
@@ -65,7 +78,8 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
|
65
78
|
this.overlapAvoidanceSolver = new OverlapAvoidanceStepSolver({
|
|
66
79
|
inputProblem: this.inputProblem,
|
|
67
80
|
traces: this.traces,
|
|
68
|
-
netLabelPlacements
|
|
81
|
+
initialNetLabelPlacements: this.netLabelPlacements, // The original, unfiltered list
|
|
82
|
+
mergedNetLabelPlacements:
|
|
69
83
|
this.labelMergingSolver!.getOutput().netLabelPlacements,
|
|
70
84
|
mergedLabelNetIdMap:
|
|
71
85
|
this.labelMergingSolver!.getOutput().mergedLabelNetIdMap,
|
|
@@ -8,28 +8,43 @@ export interface TraceLabelOverlap {
|
|
|
8
8
|
label: NetLabelPlacement
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
)
|
|
11
|
+
/**
|
|
12
|
+
* Detects overlaps between a set of traces and a set of net labels.
|
|
13
|
+
* It identifies instances where a trace segment intersects with a label's bounding box.
|
|
14
|
+
* Self-attachments (where a trace and label belong to the same net) are explicitly ignored
|
|
15
|
+
* as they are not considered true overlaps for avoidance purposes.
|
|
16
|
+
*
|
|
17
|
+
* @param traces - An array of SolvedTracePath objects to check for overlaps.
|
|
18
|
+
* @param netLabels - An array of NetLabelPlacement objects representing the labels.
|
|
19
|
+
* @returns An array of TraceLabelOverlap objects, each indicating an overlap
|
|
20
|
+
* between a trace and a label.
|
|
21
|
+
*/
|
|
22
|
+
export const detectTraceLabelOverlap = ({
|
|
23
|
+
traces,
|
|
24
|
+
netLabels = [],
|
|
25
|
+
}: {
|
|
26
|
+
traces: SolvedTracePath[]
|
|
27
|
+
netLabels?: NetLabelPlacement[]
|
|
28
|
+
}): TraceLabelOverlap[] => {
|
|
15
29
|
const overlaps: TraceLabelOverlap[] = []
|
|
16
30
|
|
|
17
31
|
for (const trace of traces) {
|
|
18
32
|
for (const label of netLabels) {
|
|
19
33
|
const labelBounds = getRectBounds(label.center, label.width, label.height)
|
|
20
34
|
|
|
21
|
-
for (let
|
|
22
|
-
const p1 = trace.tracePath[
|
|
23
|
-
const p2 = trace.tracePath[
|
|
35
|
+
for (let j = 0; j < trace.tracePath.length - 1; j++) {
|
|
36
|
+
const p1 = trace.tracePath[j]
|
|
37
|
+
const p2 = trace.tracePath[j + 1]
|
|
24
38
|
|
|
25
39
|
if (segmentIntersectsRect(p1, p2, labelBounds)) {
|
|
26
|
-
//
|
|
27
|
-
//
|
|
40
|
+
// If the trace and label belong to the same net, it's a legitimate connection, not an overlap to avoid.
|
|
41
|
+
// This check is now redundant with the new logic in OverlapAvoidanceStepSolver,
|
|
42
|
+
// but kept here for now as per instruction not to remove anything unnecessary.
|
|
28
43
|
if (trace.globalConnNetId === label.globalConnNetId) {
|
|
29
|
-
break //
|
|
44
|
+
break // Break from the inner-most loop (segment loop)
|
|
30
45
|
}
|
|
31
46
|
overlaps.push({ trace, label })
|
|
32
|
-
break //
|
|
47
|
+
break // Break from the inner-most loop (segment loop)
|
|
33
48
|
}
|
|
34
49
|
}
|
|
35
50
|
}
|
|
@@ -6,6 +6,9 @@ import type { GraphicsObject, Line } from "graphics-debug"
|
|
|
6
6
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
7
7
|
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
8
8
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
9
|
+
import { groupLabelsByChipAndOrientation } from "./groupLabelsByChipAndOrientation"
|
|
10
|
+
import { mergeLabelGroup } from "./mergeLabelGroup"
|
|
11
|
+
import { filterLabelsAtTraceEdges } from "./filterLabelsAtTraceEdges"
|
|
9
12
|
|
|
10
13
|
interface LabelMergingSolverInput {
|
|
11
14
|
netLabelPlacements: NetLabelPlacement[]
|
|
@@ -18,9 +21,15 @@ interface LabelMergingSolverOutput {
|
|
|
18
21
|
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
type PipelineStep =
|
|
25
|
+
| "filtering_labels"
|
|
26
|
+
| "grouping_labels"
|
|
27
|
+
| "merging_groups"
|
|
28
|
+
| "finalizing"
|
|
29
|
+
|
|
21
30
|
/**
|
|
22
31
|
* Merges multiple net labels into a single, larger label if they are on the
|
|
23
|
-
* same side of the same chip
|
|
32
|
+
* same side of the same chip and physically adjacent.
|
|
24
33
|
*/
|
|
25
34
|
export class MergedNetLabelObstacleSolver extends BaseSolver {
|
|
26
35
|
private input: LabelMergingSolverInput
|
|
@@ -28,15 +37,21 @@ export class MergedNetLabelObstacleSolver extends BaseSolver {
|
|
|
28
37
|
private inputProblem: InputProblem
|
|
29
38
|
private traces: SolvedTracePath[]
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
// State for the new pipeline
|
|
41
|
+
private pipelineStep: PipelineStep = "filtering_labels"
|
|
42
|
+
private filteredLabels: NetLabelPlacement[] = []
|
|
43
|
+
private labelGroups: Record<string, NetLabelPlacement[]> = {}
|
|
44
|
+
private groupKeysToProcess: string[] = []
|
|
45
|
+
private finalPlacements: NetLabelPlacement[] = []
|
|
46
|
+
private mergedLabelNetIdMap: Record<string, Set<string>> = {}
|
|
47
|
+
private activeMergingGroupKey: string | null = null
|
|
33
48
|
|
|
49
|
+
constructor(solverInput: LabelMergingSolverInput) {
|
|
34
50
|
super()
|
|
35
51
|
this.input = solverInput
|
|
36
52
|
this.inputProblem = solverInput.inputProblem
|
|
37
53
|
this.traces = solverInput.traces
|
|
38
54
|
|
|
39
|
-
// Initialize output to a default state to allow visualization before the first step
|
|
40
55
|
this.output = {
|
|
41
56
|
netLabelPlacements: solverInput.netLabelPlacements,
|
|
42
57
|
mergedLabelNetIdMap: {},
|
|
@@ -44,75 +59,67 @@ export class MergedNetLabelObstacleSolver extends BaseSolver {
|
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
override _step() {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.solved = true
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const labelGroups: Record<string, NetLabelPlacement[]> = {}
|
|
60
|
-
|
|
61
|
-
for (const p of originalLabels) {
|
|
62
|
-
if (p.pinIds.length === 0) continue
|
|
63
|
-
const chipId = p.pinIds[0].split(".")[0]
|
|
64
|
-
if (!chipId) continue
|
|
65
|
-
const key = `${chipId}-${p.orientation}`
|
|
66
|
-
if (!(key in labelGroups)) {
|
|
67
|
-
labelGroups[key] = []
|
|
68
|
-
}
|
|
69
|
-
labelGroups[key]!.push(p)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const finalPlacements: NetLabelPlacement[] = []
|
|
73
|
-
for (const [key, group] of Object.entries(labelGroups)) {
|
|
74
|
-
if (group.length <= 1) {
|
|
75
|
-
finalPlacements.push(...group)
|
|
76
|
-
continue
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let minX = Infinity
|
|
80
|
-
let minY = Infinity
|
|
81
|
-
let maxX = -Infinity
|
|
82
|
-
let maxY = -Infinity
|
|
83
|
-
for (const p of group) {
|
|
84
|
-
const bounds = getRectBounds(p.center, p.width, p.height)
|
|
85
|
-
minX = Math.min(minX, bounds.minX)
|
|
86
|
-
minY = Math.min(minY, bounds.minY)
|
|
87
|
-
maxX = Math.max(maxX, bounds.maxX)
|
|
88
|
-
maxY = Math.max(maxY, bounds.maxY)
|
|
89
|
-
}
|
|
62
|
+
switch (this.pipelineStep) {
|
|
63
|
+
case "filtering_labels":
|
|
64
|
+
this.filteredLabels = filterLabelsAtTraceEdges({
|
|
65
|
+
labels: this.input.netLabelPlacements,
|
|
66
|
+
traces: this.traces,
|
|
67
|
+
})
|
|
68
|
+
this.pipelineStep = "grouping_labels"
|
|
69
|
+
break
|
|
90
70
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
...new Set(group.flatMap((p) => p.mspConnectionPairIds)),
|
|
107
|
-
],
|
|
108
|
-
})
|
|
109
|
-
}
|
|
71
|
+
case "grouping_labels":
|
|
72
|
+
this.labelGroups = groupLabelsByChipAndOrientation({
|
|
73
|
+
labels: this.filteredLabels,
|
|
74
|
+
chips: this.inputProblem.chips,
|
|
75
|
+
})
|
|
76
|
+
this.groupKeysToProcess = Object.keys(this.labelGroups)
|
|
77
|
+
this.pipelineStep = "merging_groups"
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
case "merging_groups":
|
|
81
|
+
if (this.groupKeysToProcess.length === 0) {
|
|
82
|
+
this.pipelineStep = "finalizing"
|
|
83
|
+
this.activeMergingGroupKey = null
|
|
84
|
+
break
|
|
85
|
+
}
|
|
110
86
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
87
|
+
const groupKey = this.groupKeysToProcess.pop()!
|
|
88
|
+
this.activeMergingGroupKey = groupKey
|
|
89
|
+
const group = this.labelGroups[groupKey]!
|
|
90
|
+
|
|
91
|
+
if (group.length > 1) {
|
|
92
|
+
const { mergedLabel, originalNetIds } = mergeLabelGroup(
|
|
93
|
+
group,
|
|
94
|
+
groupKey,
|
|
95
|
+
)
|
|
96
|
+
this.finalPlacements.push(mergedLabel)
|
|
97
|
+
this.mergedLabelNetIdMap[mergedLabel.globalConnNetId] = originalNetIds
|
|
98
|
+
} else {
|
|
99
|
+
this.finalPlacements.push(...group)
|
|
100
|
+
}
|
|
101
|
+
break
|
|
102
|
+
|
|
103
|
+
case "finalizing":
|
|
104
|
+
// Any labels that were filtered out and not part of any group should be added back
|
|
105
|
+
const processedOriginalIds = new Set(
|
|
106
|
+
this.finalPlacements.flatMap((p) =>
|
|
107
|
+
this.mergedLabelNetIdMap[p.globalConnNetId]
|
|
108
|
+
? [...this.mergedLabelNetIdMap[p.globalConnNetId]!]
|
|
109
|
+
: [p.globalConnNetId],
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
const unprocessedLabels = this.input.netLabelPlacements.filter(
|
|
113
|
+
(l) => !processedOriginalIds.has(l.globalConnNetId),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
this.output = {
|
|
117
|
+
netLabelPlacements: [...this.finalPlacements, ...unprocessedLabels],
|
|
118
|
+
mergedLabelNetIdMap: this.mergedLabelNetIdMap,
|
|
119
|
+
}
|
|
120
|
+
this.solved = true
|
|
121
|
+
break
|
|
114
122
|
}
|
|
115
|
-
this.solved = true
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
getOutput(): LabelMergingSolverOutput {
|
|
@@ -143,17 +150,33 @@ export class MergedNetLabelObstacleSolver extends BaseSolver {
|
|
|
143
150
|
graphics.lines!.push(line)
|
|
144
151
|
}
|
|
145
152
|
|
|
153
|
+
// Highlight the active sub-group being merged
|
|
154
|
+
if (
|
|
155
|
+
this.activeMergingGroupKey &&
|
|
156
|
+
this.labelGroups[this.activeMergingGroupKey]
|
|
157
|
+
) {
|
|
158
|
+
const activeGroup = this.labelGroups[this.activeMergingGroupKey]!
|
|
159
|
+
for (const label of activeGroup) {
|
|
160
|
+
graphics.rects.push({
|
|
161
|
+
center: label.center,
|
|
162
|
+
width: label.width,
|
|
163
|
+
height: label.height,
|
|
164
|
+
fill: "rgba(255, 165, 0, 0.5)", // Orange highlight
|
|
165
|
+
stroke: "orange",
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
146
170
|
for (const finalLabel of this.output.netLabelPlacements) {
|
|
147
171
|
const isMerged = finalLabel.globalConnNetId.startsWith("merged-group-")
|
|
148
172
|
const color = getColorFromString(finalLabel.globalConnNetId)
|
|
149
173
|
|
|
150
174
|
if (isMerged) {
|
|
151
|
-
// Draw the new merged label
|
|
152
175
|
graphics.rects.push({
|
|
153
176
|
center: finalLabel.center,
|
|
154
177
|
width: finalLabel.width,
|
|
155
178
|
height: finalLabel.height,
|
|
156
|
-
fill: color.replace(/, 1\)/, ", 0.2)"),
|
|
179
|
+
fill: color.replace(/, 1\)/, ", 0.2)"),
|
|
157
180
|
stroke: color,
|
|
158
181
|
label: finalLabel.globalConnNetId,
|
|
159
182
|
})
|
|
@@ -164,7 +187,6 @@ export class MergedNetLabelObstacleSolver extends BaseSolver {
|
|
|
164
187
|
for (const originalNetId of originalNetIds) {
|
|
165
188
|
const originalLabel = originalLabelsById.get(originalNetId)
|
|
166
189
|
if (originalLabel) {
|
|
167
|
-
// Draw the original label as a dashed box
|
|
168
190
|
const bounds = getRectBounds(
|
|
169
191
|
originalLabel.center,
|
|
170
192
|
originalLabel.width,
|
|
@@ -179,7 +201,6 @@ export class MergedNetLabelObstacleSolver extends BaseSolver {
|
|
|
179
201
|
strokeColor: color,
|
|
180
202
|
strokeDash: "4 4",
|
|
181
203
|
})
|
|
182
|
-
// Draw line from original to new center
|
|
183
204
|
graphics.lines.push({
|
|
184
205
|
points: [originalLabel.center, finalLabel.center],
|
|
185
206
|
strokeColor: color,
|
|
@@ -189,7 +210,6 @@ export class MergedNetLabelObstacleSolver extends BaseSolver {
|
|
|
189
210
|
}
|
|
190
211
|
}
|
|
191
212
|
} else {
|
|
192
|
-
// Draw un-merged labels
|
|
193
213
|
graphics.rects.push({
|
|
194
214
|
center: finalLabel.center,
|
|
195
215
|
width: finalLabel.width,
|