@tscircuit/schematic-trace-solver 0.0.7 → 0.0.9
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 -5
- package/dist/index.js +488 -344
- package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +29 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +199 -465
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts +89 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/anchors.ts +11 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts +45 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry.ts +48 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/host.ts +64 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts +177 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +24 -3
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts +38 -3
- package/package.json +1 -1
- package/site/SchematicTraceLinesSolver/SchematicTraceLinesSolver01.page.tsx +122 -0
- package/site/components/GenericSolverDebugger.tsx +16 -0
- package/site/examples/example01-basic.page.tsx +2 -2
- package/site/examples/example04-single-symbol.page.tsx +46 -0
- package/tests/solvers/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver_repro01.json +88 -0
- package/tests/solvers/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver_repro01.test.ts +16 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
3
|
+
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
4
|
+
import { chooseHostTraceForGroup } from "./host"
|
|
5
|
+
import type { SingleNetLabelPlacementSolver } from "./SingleNetLabelPlacementSolver"
|
|
6
|
+
|
|
7
|
+
export function visualizeSingleNetLabelPlacementSolver(
|
|
8
|
+
solver: SingleNetLabelPlacementSolver,
|
|
9
|
+
): GraphicsObject {
|
|
10
|
+
const graphics = visualizeInputProblem(solver.inputProblem)
|
|
11
|
+
|
|
12
|
+
// Visualize the entire trace group for this net id
|
|
13
|
+
const groupId = solver.overlappingSameNetTraceGroup.globalConnNetId
|
|
14
|
+
const host = chooseHostTraceForGroup({
|
|
15
|
+
inputProblem: solver.inputProblem,
|
|
16
|
+
inputTraceMap: solver.inputTraceMap,
|
|
17
|
+
globalConnNetId: groupId,
|
|
18
|
+
fallbackTrace: solver.overlappingSameNetTraceGroup.overlappingTraces,
|
|
19
|
+
})
|
|
20
|
+
const groupStroke = getColorFromString(groupId, 0.9)
|
|
21
|
+
const groupFill = getColorFromString(groupId, 0.5)
|
|
22
|
+
|
|
23
|
+
for (const trace of Object.values(solver.inputTraceMap)) {
|
|
24
|
+
if (trace.globalConnNetId !== groupId) continue
|
|
25
|
+
const isHost = host ? trace.mspPairId === host.mspPairId : false
|
|
26
|
+
graphics.lines!.push({
|
|
27
|
+
points: trace.tracePath,
|
|
28
|
+
strokeColor: isHost ? groupStroke : groupFill,
|
|
29
|
+
strokeWidth: isHost ? 0.006 : 0.003,
|
|
30
|
+
strokeDash: isHost ? undefined : "4 2",
|
|
31
|
+
} as any)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Visualize all tested candidate rectangles with reason coloring
|
|
35
|
+
for (const c of solver.testedCandidates) {
|
|
36
|
+
const fill =
|
|
37
|
+
c.status === "ok"
|
|
38
|
+
? "rgba(0, 180, 0, 0.25)"
|
|
39
|
+
: c.status === "chip-collision"
|
|
40
|
+
? "rgba(220, 0, 0, 0.25)"
|
|
41
|
+
: c.status === "trace-collision"
|
|
42
|
+
? "rgba(220, 140, 0, 0.25)"
|
|
43
|
+
: "rgba(120, 120, 120, 0.15)"
|
|
44
|
+
const stroke =
|
|
45
|
+
c.status === "ok"
|
|
46
|
+
? "green"
|
|
47
|
+
: c.status === "chip-collision"
|
|
48
|
+
? "red"
|
|
49
|
+
: c.status === "trace-collision"
|
|
50
|
+
? "orange"
|
|
51
|
+
: "gray"
|
|
52
|
+
|
|
53
|
+
graphics.rects!.push({
|
|
54
|
+
center: {
|
|
55
|
+
x: (c.bounds.minX + c.bounds.maxX) / 2,
|
|
56
|
+
y: (c.bounds.minY + c.bounds.maxY) / 2,
|
|
57
|
+
},
|
|
58
|
+
width: c.width,
|
|
59
|
+
height: c.height,
|
|
60
|
+
fill,
|
|
61
|
+
strokeColor: stroke,
|
|
62
|
+
} as any)
|
|
63
|
+
|
|
64
|
+
graphics.points!.push({
|
|
65
|
+
x: c.anchor.x,
|
|
66
|
+
y: c.anchor.y,
|
|
67
|
+
color: stroke,
|
|
68
|
+
} as any)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Visualize the final accepted label (if any)
|
|
72
|
+
if (solver.netLabelPlacement) {
|
|
73
|
+
const p = solver.netLabelPlacement
|
|
74
|
+
graphics.rects!.push({
|
|
75
|
+
center: p.center,
|
|
76
|
+
width: p.width,
|
|
77
|
+
height: p.height,
|
|
78
|
+
fill: "rgba(0, 128, 255, 0.35)",
|
|
79
|
+
strokeColor: "blue",
|
|
80
|
+
} as any)
|
|
81
|
+
graphics.points!.push({
|
|
82
|
+
x: p.anchorPoint.x,
|
|
83
|
+
y: p.anchorPoint.y,
|
|
84
|
+
color: "blue",
|
|
85
|
+
} as any)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return graphics
|
|
89
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
2
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
3
|
+
|
|
4
|
+
export function segmentIntersectsRect(
|
|
5
|
+
p1: { x: number; y: number },
|
|
6
|
+
p2: { x: number; y: number },
|
|
7
|
+
rect: { minX: number; minY: number; maxX: number; maxY: number },
|
|
8
|
+
EPS = 1e-9,
|
|
9
|
+
): boolean {
|
|
10
|
+
const isVert = Math.abs(p1.x - p2.x) < EPS
|
|
11
|
+
const isHorz = Math.abs(p1.y - p2.y) < EPS
|
|
12
|
+
if (!isVert && !isHorz) return false
|
|
13
|
+
|
|
14
|
+
if (isVert) {
|
|
15
|
+
const x = p1.x
|
|
16
|
+
if (x < rect.minX - EPS || x > rect.maxX + EPS) return false
|
|
17
|
+
const segMinY = Math.min(p1.y, p2.y)
|
|
18
|
+
const segMaxY = Math.max(p1.y, p2.y)
|
|
19
|
+
const overlap = Math.min(segMaxY, rect.maxY) - Math.max(segMinY, rect.minY)
|
|
20
|
+
return overlap > EPS
|
|
21
|
+
} else {
|
|
22
|
+
const y = p1.y
|
|
23
|
+
if (y < rect.minY - EPS || y > rect.maxY + EPS) return false
|
|
24
|
+
const segMinX = Math.min(p1.x, p2.x)
|
|
25
|
+
const segMaxX = Math.max(p1.x, p2.x)
|
|
26
|
+
const overlap = Math.min(segMaxX, rect.maxX) - Math.max(segMinX, rect.minX)
|
|
27
|
+
return overlap > EPS
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function rectIntersectsAnyTrace(
|
|
32
|
+
bounds: { minX: number; minY: number; maxX: number; maxY: number },
|
|
33
|
+
inputTraceMap: Record<MspConnectionPairId, SolvedTracePath>,
|
|
34
|
+
hostPathId?: MspConnectionPairId,
|
|
35
|
+
hostSegIndex?: number,
|
|
36
|
+
): boolean {
|
|
37
|
+
for (const [pairId, solved] of Object.entries(inputTraceMap)) {
|
|
38
|
+
const pts = solved.tracePath
|
|
39
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
40
|
+
if (pairId === hostPathId && i === hostSegIndex) continue
|
|
41
|
+
if (segmentIntersectsRect(pts[i]!, pts[i + 1]!, bounds)) return true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
2
|
+
|
|
3
|
+
export const NET_LABEL_HORIZONTAL_WIDTH = 0.4
|
|
4
|
+
export const NET_LABEL_HORIZONTAL_HEIGHT = 0.2
|
|
5
|
+
|
|
6
|
+
export function getDimsForOrientation(orientation: FacingDirection) {
|
|
7
|
+
if (orientation === "y+" || orientation === "y-") {
|
|
8
|
+
return {
|
|
9
|
+
width: NET_LABEL_HORIZONTAL_HEIGHT,
|
|
10
|
+
height: NET_LABEL_HORIZONTAL_WIDTH,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
width: NET_LABEL_HORIZONTAL_WIDTH,
|
|
15
|
+
height: NET_LABEL_HORIZONTAL_HEIGHT,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getCenterFromAnchor(
|
|
20
|
+
anchor: { x: number; y: number },
|
|
21
|
+
orientation: FacingDirection,
|
|
22
|
+
width: number,
|
|
23
|
+
height: number,
|
|
24
|
+
) {
|
|
25
|
+
switch (orientation) {
|
|
26
|
+
case "x+":
|
|
27
|
+
return { x: anchor.x + width / 2, y: anchor.y }
|
|
28
|
+
case "x-":
|
|
29
|
+
return { x: anchor.x - width / 2, y: anchor.y }
|
|
30
|
+
case "y+":
|
|
31
|
+
return { x: anchor.x, y: anchor.y + height / 2 }
|
|
32
|
+
case "y-":
|
|
33
|
+
return { x: anchor.x, y: anchor.y - height / 2 }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getRectBounds(
|
|
38
|
+
center: { x: number; y: number },
|
|
39
|
+
w: number,
|
|
40
|
+
h: number,
|
|
41
|
+
) {
|
|
42
|
+
return {
|
|
43
|
+
minX: center.x - w / 2,
|
|
44
|
+
minY: center.y - h / 2,
|
|
45
|
+
maxX: center.x + w / 2,
|
|
46
|
+
maxY: center.y + h / 2,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { InputChip, InputProblem } from "lib/types/InputProblem"
|
|
2
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
3
|
+
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
4
|
+
|
|
5
|
+
export function lengthOfTrace(path: SolvedTracePath): number {
|
|
6
|
+
let sum = 0
|
|
7
|
+
const pts = path.tracePath
|
|
8
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
9
|
+
sum +=
|
|
10
|
+
Math.abs(pts[i + 1]!.x - pts[i]!.x) + Math.abs(pts[i + 1]!.y - pts[i]!.y)
|
|
11
|
+
}
|
|
12
|
+
return sum
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function chooseHostTraceForGroup(params: {
|
|
16
|
+
inputProblem: InputProblem
|
|
17
|
+
inputTraceMap: Record<MspConnectionPairId, SolvedTracePath>
|
|
18
|
+
globalConnNetId: string
|
|
19
|
+
fallbackTrace?: SolvedTracePath
|
|
20
|
+
}): SolvedTracePath | undefined {
|
|
21
|
+
const { inputProblem, inputTraceMap, globalConnNetId, fallbackTrace } = params
|
|
22
|
+
|
|
23
|
+
const chipsById: Record<string, InputChip> = Object.fromEntries(
|
|
24
|
+
inputProblem.chips.map((c) => [c.chipId, c]),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const groupTraces = Object.values(inputTraceMap).filter(
|
|
28
|
+
(t) => t.globalConnNetId === globalConnNetId,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const chipIdsInGroup = new Set<string>()
|
|
32
|
+
for (const t of groupTraces) {
|
|
33
|
+
chipIdsInGroup.add(t.pins[0].chipId)
|
|
34
|
+
chipIdsInGroup.add(t.pins[1].chipId)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let largestChipId: string | null = null
|
|
38
|
+
let largestPinCount = -1
|
|
39
|
+
for (const id of chipIdsInGroup) {
|
|
40
|
+
const chip = chipsById[id]
|
|
41
|
+
const count = chip?.pins?.length ?? 0
|
|
42
|
+
if (count > largestPinCount) {
|
|
43
|
+
largestPinCount = count
|
|
44
|
+
largestChipId = id
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const hostCandidates =
|
|
49
|
+
largestChipId == null
|
|
50
|
+
? []
|
|
51
|
+
: groupTraces.filter(
|
|
52
|
+
(t) =>
|
|
53
|
+
t.pins[0].chipId === largestChipId ||
|
|
54
|
+
t.pins[1].chipId === largestChipId,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if (hostCandidates.length > 0) {
|
|
58
|
+
return hostCandidates.reduce((a, b) =>
|
|
59
|
+
lengthOfTrace(a) >= lengthOfTrace(b) ? a : b,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return fallbackTrace
|
|
64
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
2
|
+
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
3
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
5
|
+
import { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
|
|
6
|
+
import type {
|
|
7
|
+
NetLabelPlacement,
|
|
8
|
+
OverlappingSameNetTraceGroup,
|
|
9
|
+
} from "../NetLabelPlacementSolver"
|
|
10
|
+
import {
|
|
11
|
+
getDimsForOrientation,
|
|
12
|
+
getCenterFromAnchor,
|
|
13
|
+
getRectBounds,
|
|
14
|
+
} from "./geometry"
|
|
15
|
+
import { rectIntersectsAnyTrace } from "./collisions"
|
|
16
|
+
|
|
17
|
+
export function solveNetLabelPlacementForPortOnlyPin(params: {
|
|
18
|
+
inputProblem: InputProblem
|
|
19
|
+
inputTraceMap: Record<MspConnectionPairId, SolvedTracePath>
|
|
20
|
+
chipObstacleSpatialIndex: ChipObstacleSpatialIndex
|
|
21
|
+
overlappingSameNetTraceGroup: OverlappingSameNetTraceGroup
|
|
22
|
+
availableOrientations: FacingDirection[]
|
|
23
|
+
}): {
|
|
24
|
+
placement: NetLabelPlacement | null
|
|
25
|
+
testedCandidates: Array<{
|
|
26
|
+
center: { x: number; y: number }
|
|
27
|
+
width: number
|
|
28
|
+
height: number
|
|
29
|
+
bounds: { minX: number; minY: number; maxX: number; maxY: number }
|
|
30
|
+
anchor: { x: number; y: number }
|
|
31
|
+
orientation: FacingDirection
|
|
32
|
+
status: "ok" | "chip-collision" | "trace-collision" | "parallel-to-segment"
|
|
33
|
+
hostSegIndex: number
|
|
34
|
+
}>
|
|
35
|
+
error?: string
|
|
36
|
+
} {
|
|
37
|
+
const {
|
|
38
|
+
inputProblem,
|
|
39
|
+
inputTraceMap,
|
|
40
|
+
chipObstacleSpatialIndex,
|
|
41
|
+
overlappingSameNetTraceGroup,
|
|
42
|
+
availableOrientations,
|
|
43
|
+
} = params
|
|
44
|
+
|
|
45
|
+
const pinId = overlappingSameNetTraceGroup.portOnlyPinId
|
|
46
|
+
if (!pinId) {
|
|
47
|
+
return {
|
|
48
|
+
placement: null,
|
|
49
|
+
testedCandidates: [],
|
|
50
|
+
error: "No portOnlyPinId provided",
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Find pin coordinates
|
|
55
|
+
let pin: { x: number; y: number } | null = null
|
|
56
|
+
for (const chip of inputProblem.chips) {
|
|
57
|
+
const p = chip.pins.find((pp) => pp.pinId === pinId)
|
|
58
|
+
if (p) {
|
|
59
|
+
pin = { x: p.x, y: p.y }
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!pin) {
|
|
64
|
+
return {
|
|
65
|
+
placement: null,
|
|
66
|
+
testedCandidates: [],
|
|
67
|
+
error: `Port-only pin not found: ${pinId}`,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const orientations =
|
|
72
|
+
availableOrientations.length > 0
|
|
73
|
+
? availableOrientations
|
|
74
|
+
: (["x+", "x-", "y+", "y-"] as FacingDirection[])
|
|
75
|
+
|
|
76
|
+
const anchor = { x: pin.x, y: pin.y }
|
|
77
|
+
const outwardOf = (o: FacingDirection) =>
|
|
78
|
+
o === "x+"
|
|
79
|
+
? { x: 1, y: 0 }
|
|
80
|
+
: o === "x-"
|
|
81
|
+
? { x: -1, y: 0 }
|
|
82
|
+
: o === "y+"
|
|
83
|
+
? { x: 0, y: 1 }
|
|
84
|
+
: { x: 0, y: -1 }
|
|
85
|
+
|
|
86
|
+
const testedCandidates: Array<{
|
|
87
|
+
center: { x: number; y: number }
|
|
88
|
+
width: number
|
|
89
|
+
height: number
|
|
90
|
+
bounds: { minX: number; minY: number; maxX: number; maxY: number }
|
|
91
|
+
anchor: { x: number; y: number }
|
|
92
|
+
orientation: FacingDirection
|
|
93
|
+
status: "ok" | "chip-collision" | "trace-collision" | "parallel-to-segment"
|
|
94
|
+
hostSegIndex: number
|
|
95
|
+
}> = []
|
|
96
|
+
|
|
97
|
+
for (const orientation of orientations) {
|
|
98
|
+
const { width, height } = getDimsForOrientation(orientation)
|
|
99
|
+
// Place label fully outside the chip: shift center slightly outward
|
|
100
|
+
const baseCenter = getCenterFromAnchor(anchor, orientation, width, height)
|
|
101
|
+
const outward = outwardOf(orientation)
|
|
102
|
+
const offset = 1e-3
|
|
103
|
+
const center = {
|
|
104
|
+
x: baseCenter.x + outward.x * offset,
|
|
105
|
+
y: baseCenter.y + outward.y * offset,
|
|
106
|
+
}
|
|
107
|
+
const bounds = getRectBounds(center, width, height)
|
|
108
|
+
|
|
109
|
+
// Chip collision check
|
|
110
|
+
const chips = chipObstacleSpatialIndex.getChipsInBounds(bounds)
|
|
111
|
+
if (chips.length > 0) {
|
|
112
|
+
testedCandidates.push({
|
|
113
|
+
center,
|
|
114
|
+
width,
|
|
115
|
+
height,
|
|
116
|
+
bounds,
|
|
117
|
+
anchor,
|
|
118
|
+
orientation,
|
|
119
|
+
status: "chip-collision",
|
|
120
|
+
hostSegIndex: -1,
|
|
121
|
+
})
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Trace collision check
|
|
126
|
+
if (
|
|
127
|
+
rectIntersectsAnyTrace(
|
|
128
|
+
bounds,
|
|
129
|
+
inputTraceMap,
|
|
130
|
+
"" as MspConnectionPairId,
|
|
131
|
+
-1,
|
|
132
|
+
)
|
|
133
|
+
) {
|
|
134
|
+
testedCandidates.push({
|
|
135
|
+
center,
|
|
136
|
+
width,
|
|
137
|
+
height,
|
|
138
|
+
bounds,
|
|
139
|
+
anchor,
|
|
140
|
+
orientation,
|
|
141
|
+
status: "trace-collision",
|
|
142
|
+
hostSegIndex: -1,
|
|
143
|
+
})
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Found a valid placement
|
|
148
|
+
testedCandidates.push({
|
|
149
|
+
center,
|
|
150
|
+
width,
|
|
151
|
+
height,
|
|
152
|
+
bounds,
|
|
153
|
+
anchor,
|
|
154
|
+
orientation,
|
|
155
|
+
status: "ok",
|
|
156
|
+
hostSegIndex: -1,
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const placement: NetLabelPlacement = {
|
|
160
|
+
globalConnNetId: overlappingSameNetTraceGroup.globalConnNetId,
|
|
161
|
+
dcConnNetId: undefined,
|
|
162
|
+
orientation,
|
|
163
|
+
anchorPoint: anchor,
|
|
164
|
+
width,
|
|
165
|
+
height,
|
|
166
|
+
center,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { placement, testedCandidates }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
placement: null,
|
|
174
|
+
testedCandidates,
|
|
175
|
+
error: "Could not place net label at port without collisions",
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -30,6 +30,7 @@ export class SchematicTraceLinesSolver extends BaseSolver {
|
|
|
30
30
|
currentConnectionPair: MspConnectionPair | null = null
|
|
31
31
|
|
|
32
32
|
solvedTracePaths: Array<SolvedTracePath> = []
|
|
33
|
+
failedConnectionPairs: Array<MspConnectionPair & { error?: string }> = []
|
|
33
34
|
|
|
34
35
|
declare activeSubSolver: SchematicTraceSingleLineSolver | null
|
|
35
36
|
|
|
@@ -75,9 +76,16 @@ export class SchematicTraceLinesSolver extends BaseSolver {
|
|
|
75
76
|
this.currentConnectionPair = null
|
|
76
77
|
}
|
|
77
78
|
if (this.activeSubSolver?.failed) {
|
|
78
|
-
this
|
|
79
|
-
this.
|
|
80
|
-
|
|
79
|
+
// Record the failure for this connection and continue to the next pair
|
|
80
|
+
if (this.currentConnectionPair) {
|
|
81
|
+
this.failedConnectionPairs.push({
|
|
82
|
+
...this.currentConnectionPair,
|
|
83
|
+
error: this.activeSubSolver.error || undefined,
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
this.activeSubSolver = null
|
|
87
|
+
this.currentConnectionPair = null
|
|
88
|
+
// Do not fail the whole solver; proceed to schedule the next pair
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
if (this.activeSubSolver) {
|
|
@@ -122,6 +130,19 @@ export class SchematicTraceLinesSolver extends BaseSolver {
|
|
|
122
130
|
})
|
|
123
131
|
}
|
|
124
132
|
|
|
133
|
+
// Indicate failed connection pairs with dashed red lines between their pins
|
|
134
|
+
for (const pair of this.failedConnectionPairs) {
|
|
135
|
+
graphics.lines!.push({
|
|
136
|
+
points: [
|
|
137
|
+
{ x: pair.pins[0].x, y: pair.pins[0].y },
|
|
138
|
+
{ x: pair.pins[1].x, y: pair.pins[1].y },
|
|
139
|
+
],
|
|
140
|
+
strokeColor: "red",
|
|
141
|
+
strokeDash: "4 2",
|
|
142
|
+
strokeWidth: 0.004,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
125
146
|
return graphics
|
|
126
147
|
}
|
|
127
148
|
}
|
|
@@ -103,12 +103,47 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
|
|
|
103
103
|
|
|
104
104
|
const nextCandidatePath = this.queuedCandidatePaths.shift()!
|
|
105
105
|
|
|
106
|
-
const obstacleOps = {
|
|
107
|
-
excludeChipIds: [this.pins[0].chipId, this.pins[1].chipId],
|
|
108
|
-
}
|
|
109
106
|
for (let i = 0; i < nextCandidatePath.length - 1; i++) {
|
|
110
107
|
const start = nextCandidatePath[i]
|
|
111
108
|
const end = nextCandidatePath[i + 1]
|
|
109
|
+
|
|
110
|
+
// Determine which chips to exclude for this specific segment
|
|
111
|
+
let excludeChipIds: string[] = []
|
|
112
|
+
|
|
113
|
+
// Always exclude chips that contain the start or end points of this segment
|
|
114
|
+
// if those points are actually pin locations
|
|
115
|
+
const isStartPin = this.pins.some(
|
|
116
|
+
(pin) =>
|
|
117
|
+
Math.abs(pin.x - start.x) < 1e-10 &&
|
|
118
|
+
Math.abs(pin.y - start.y) < 1e-10,
|
|
119
|
+
)
|
|
120
|
+
const isEndPin = this.pins.some(
|
|
121
|
+
(pin) =>
|
|
122
|
+
Math.abs(pin.x - end.x) < 1e-10 && Math.abs(pin.y - end.y) < 1e-10,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if (isStartPin) {
|
|
126
|
+
const startPin = this.pins.find(
|
|
127
|
+
(pin) =>
|
|
128
|
+
Math.abs(pin.x - start.x) < 1e-10 &&
|
|
129
|
+
Math.abs(pin.y - start.y) < 1e-10,
|
|
130
|
+
)
|
|
131
|
+
if (startPin && !excludeChipIds.includes(startPin.chipId)) {
|
|
132
|
+
excludeChipIds.push(startPin.chipId)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (isEndPin) {
|
|
137
|
+
const endPin = this.pins.find(
|
|
138
|
+
(pin) =>
|
|
139
|
+
Math.abs(pin.x - end.x) < 1e-10 && Math.abs(pin.y - end.y) < 1e-10,
|
|
140
|
+
)
|
|
141
|
+
if (endPin && !excludeChipIds.includes(endPin.chipId)) {
|
|
142
|
+
excludeChipIds.push(endPin.chipId)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const obstacleOps = { excludeChipIds }
|
|
112
147
|
const intersects =
|
|
113
148
|
this.chipObstacleSpatialIndex.doesOrthogonalLineIntersectChip(
|
|
114
149
|
[start, end],
|
package/package.json
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useMemo } from "react"
|
|
2
|
+
import { GenericSolverDebugger } from "../components/GenericSolverDebugger"
|
|
3
|
+
import { SchematicTraceLinesSolver } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
|
+
|
|
5
|
+
const input = {
|
|
6
|
+
mspConnectionPairs: [
|
|
7
|
+
{
|
|
8
|
+
mspPairId: "Q1.1-Q1.2",
|
|
9
|
+
dcConnNetId: "connectivity_net0",
|
|
10
|
+
globalConnNetId: "connectivity_net0",
|
|
11
|
+
userNetId: "V3_3",
|
|
12
|
+
pins: [
|
|
13
|
+
{
|
|
14
|
+
pinId: "Q1.1",
|
|
15
|
+
x: 0.30397715550000004,
|
|
16
|
+
y: 0.5800832909999993,
|
|
17
|
+
_facingDirection: "y+",
|
|
18
|
+
chipId: "schematic_component_0",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
pinId: "Q1.2",
|
|
22
|
+
x: 0.31067575550000137,
|
|
23
|
+
y: -0.5800832909999993,
|
|
24
|
+
_facingDirection: "y-",
|
|
25
|
+
chipId: "schematic_component_0",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
dcConnMap: {
|
|
31
|
+
netMap: {
|
|
32
|
+
connectivity_net0: ["Q1.1", "Q1.2"],
|
|
33
|
+
},
|
|
34
|
+
idToNetMap: {},
|
|
35
|
+
},
|
|
36
|
+
globalConnMap: {
|
|
37
|
+
netMap: {
|
|
38
|
+
connectivity_net0: ["Q1.1", "Q1.2"],
|
|
39
|
+
},
|
|
40
|
+
idToNetMap: {
|
|
41
|
+
"Q1.1": "connectivity_net0",
|
|
42
|
+
"Q1.2": "connectivity_net0",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
inputProblem: {
|
|
46
|
+
chips: [
|
|
47
|
+
{
|
|
48
|
+
chipId: "schematic_component_0",
|
|
49
|
+
center: {
|
|
50
|
+
x: 0,
|
|
51
|
+
y: 0,
|
|
52
|
+
},
|
|
53
|
+
width: 0.8935117710000002,
|
|
54
|
+
height: 1.1601665819999987,
|
|
55
|
+
pins: [
|
|
56
|
+
{
|
|
57
|
+
pinId: "Q1.1",
|
|
58
|
+
x: 0.30397715550000004,
|
|
59
|
+
y: 0.5800832909999993,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
pinId: "Q1.2",
|
|
63
|
+
x: 0.31067575550000137,
|
|
64
|
+
y: -0.5800832909999993,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
pinId: "Q1.3",
|
|
68
|
+
x: -0.4467558855000001,
|
|
69
|
+
y: -0.10250625000000019,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
directConnections: [],
|
|
75
|
+
netConnections: [
|
|
76
|
+
{
|
|
77
|
+
netId: "V3_3",
|
|
78
|
+
pinIds: ["Q1.1", "Q1.2"],
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
availableNetLabelOrientations: {
|
|
82
|
+
V3_3: ["y+"],
|
|
83
|
+
},
|
|
84
|
+
maxMspPairDistance: 2,
|
|
85
|
+
},
|
|
86
|
+
guidelines: [],
|
|
87
|
+
chipMap: {
|
|
88
|
+
schematic_component_0: {
|
|
89
|
+
chipId: "schematic_component_0",
|
|
90
|
+
center: {
|
|
91
|
+
x: 0,
|
|
92
|
+
y: 0,
|
|
93
|
+
},
|
|
94
|
+
width: 0.8935117710000002,
|
|
95
|
+
height: 1.1601665819999987,
|
|
96
|
+
pins: [
|
|
97
|
+
{
|
|
98
|
+
pinId: "Q1.1",
|
|
99
|
+
x: 0.30397715550000004,
|
|
100
|
+
y: 0.5800832909999993,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
pinId: "Q1.2",
|
|
104
|
+
x: 0.31067575550000137,
|
|
105
|
+
y: -0.5800832909999993,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
pinId: "Q1.3",
|
|
109
|
+
x: -0.4467558855000001,
|
|
110
|
+
y: -0.10250625000000019,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default () => {
|
|
118
|
+
const solver = useMemo(() => {
|
|
119
|
+
return new SchematicTraceLinesSolver(input as any)
|
|
120
|
+
}, [])
|
|
121
|
+
return <GenericSolverDebugger solver={solver} />
|
|
122
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
2
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
3
|
+
import { useMemo, useReducer } from "react"
|
|
4
|
+
import { InteractiveGraphics } from "graphics-debug/react"
|
|
5
|
+
import { SolverToolbar } from "./SolverToolbar"
|
|
6
|
+
|
|
7
|
+
export const GenericSolverDebugger = ({ solver }: { solver: BaseSolver }) => {
|
|
8
|
+
const [, incRenderCount] = useReducer((x) => x + 1, 0)
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div>
|
|
12
|
+
<SolverToolbar triggerRender={() => incRenderCount()} solver={solver} />
|
|
13
|
+
<InteractiveGraphics graphics={solver.visualize()} />
|
|
14
|
+
</div>
|
|
15
|
+
)
|
|
16
|
+
}
|