@tscircuit/schematic-trace-solver 0.0.98 → 0.0.99
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 +1 -0
- package/dist/index.js +310 -220
- package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +19 -9
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +30 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/generateEndpointCollisionDetours.ts +84 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +18 -18
- package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +7 -7
- package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +5 -7
- package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +6 -12
- package/tests/examples/__snapshots__/example09.snap.svg +9 -13
- package/tests/examples/__snapshots__/example21.snap.svg +30 -32
- package/tests/examples/__snapshots__/example29.snap.svg +13 -19
- package/tests/repros/__snapshots__/repro129-host-custom-symbol-passives.snap.svg +60 -0
- package/tests/repros/assets/repro129-host-custom-symbol-passives.input.json +94 -0
- package/tests/repros/repro129-host-custom-symbol-passives.test.ts +16 -0
- package/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-direct-connection-distance.test.ts +46 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/generate-endpoint-collision-detours.test.ts +38 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { distance } from "@tscircuit/math-utils"
|
|
2
|
+
import type { ConnectivityMap } from "connectivity-map"
|
|
3
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
1
4
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
2
5
|
import type {
|
|
3
6
|
InputChip,
|
|
@@ -5,17 +8,17 @@ import type {
|
|
|
5
8
|
InputProblem,
|
|
6
9
|
PinId,
|
|
7
10
|
} from "lib/types/InputProblem"
|
|
8
|
-
import { ConnectivityMap } from "connectivity-map"
|
|
9
|
-
import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
|
|
10
|
-
import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
|
|
11
|
-
import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCenterLines"
|
|
12
|
-
import type { GraphicsObject } from "graphics-debug"
|
|
13
11
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
14
|
-
import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
|
|
15
12
|
import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections"
|
|
13
|
+
import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
|
|
14
|
+
import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCenterLines"
|
|
15
|
+
import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
|
|
16
|
+
import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
|
|
16
17
|
|
|
17
18
|
export type MspConnectionPairId = string
|
|
18
19
|
|
|
20
|
+
const getPinPairKey = (pinIds: [PinId, PinId]) => [...pinIds].sort().join("::")
|
|
21
|
+
|
|
19
22
|
export type MspConnectionPair = {
|
|
20
23
|
mspPairId: MspConnectionPairId
|
|
21
24
|
dcConnNetId: string
|
|
@@ -36,6 +39,7 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
36
39
|
|
|
37
40
|
pinMap: Record<string, InputPin & { chipId: string }>
|
|
38
41
|
userNetIdByPinId: Record<string, string | undefined>
|
|
42
|
+
directConnectionPinPairKeys: Set<string>
|
|
39
43
|
|
|
40
44
|
constructor({ inputProblem }: { inputProblem: InputProblem }) {
|
|
41
45
|
super()
|
|
@@ -62,7 +66,9 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
62
66
|
|
|
63
67
|
// Build a mapping from PinId to user-provided netId (if any)
|
|
64
68
|
this.userNetIdByPinId = {}
|
|
69
|
+
this.directConnectionPinPairKeys = new Set()
|
|
65
70
|
for (const dc of inputProblem.directConnections) {
|
|
71
|
+
this.directConnectionPinPairKeys.add(getPinPairKey(dc.pinIds))
|
|
66
72
|
if (dc.netId) {
|
|
67
73
|
const [a, b] = dc.pinIds
|
|
68
74
|
this.userNetIdByPinId[a] = dc.netId
|
|
@@ -105,9 +111,13 @@ export class MspConnectionPairSolver extends BaseSolver {
|
|
|
105
111
|
const [pin1, pin2] = directlyConnectedPins
|
|
106
112
|
const p1 = this.pinMap[pin1!]!
|
|
107
113
|
const p2 = this.pinMap[pin2!]!
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
const pinPairKey = getPinPairKey([pin1!, pin2!])
|
|
115
|
+
// Explicit source traces are classified by straight-line distance when
|
|
116
|
+
// their input is created; named nets retain the orthogonal route metric.
|
|
117
|
+
const pairDistance = this.directConnectionPinPairKeys.has(pinPairKey)
|
|
118
|
+
? distance(p1, p2)
|
|
119
|
+
: Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
|
|
120
|
+
if (pairDistance > this.maxMspPairDistance) {
|
|
111
121
|
// Too far apart; skip creating an MSP pair for this net
|
|
112
122
|
return
|
|
113
123
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
isVertical,
|
|
17
17
|
segmentOverlapsRectBoundary,
|
|
18
18
|
} from "./collisions"
|
|
19
|
+
import { generateEndpointCollisionDetours } from "./generateEndpointCollisionDetours"
|
|
19
20
|
import {
|
|
20
21
|
type Axis,
|
|
21
22
|
aabbFromPoints,
|
|
@@ -357,6 +358,35 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
357
358
|
const isFirstSegment = segIndex === 0
|
|
358
359
|
const isLastSegment = segIndex === path.length - 2
|
|
359
360
|
|
|
361
|
+
if (path.length === 3 && (isFirstSegment || isLastSegment)) {
|
|
362
|
+
const detours = generateEndpointCollisionDetours({
|
|
363
|
+
path,
|
|
364
|
+
collidingSegmentIndex: segIndex,
|
|
365
|
+
obstacle: rect,
|
|
366
|
+
})
|
|
367
|
+
.filter((detour) => {
|
|
368
|
+
const key = pathKey(detour)
|
|
369
|
+
if (this.visited.has(key)) return false
|
|
370
|
+
this.visited.add(key)
|
|
371
|
+
return true
|
|
372
|
+
})
|
|
373
|
+
.sort(
|
|
374
|
+
(a, b) =>
|
|
375
|
+
this.pathLength(a) - this.pathLength(b) ||
|
|
376
|
+
this.getPinBandPenalty(a) - this.getPinBandPenalty(b),
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
for (const detour of detours) {
|
|
380
|
+
const nextCollisionRects = new Set(collisionRects)
|
|
381
|
+
nextCollisionRects.add(rect)
|
|
382
|
+
this.queue.push({
|
|
383
|
+
path: detour,
|
|
384
|
+
collisionRects: nextCollisionRects,
|
|
385
|
+
})
|
|
386
|
+
}
|
|
387
|
+
return
|
|
388
|
+
}
|
|
389
|
+
|
|
360
390
|
if (isFirstSegment) {
|
|
361
391
|
// If first segment collides, move the second segment instead
|
|
362
392
|
if (path.length < 3) {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import { isHorizontal, isVertical } from "./collisions"
|
|
3
|
+
import { type Axis, midBetweenPointAndRect } from "./mid"
|
|
4
|
+
import type { ObstacleRect } from "./rect"
|
|
5
|
+
|
|
6
|
+
const getSegmentAxis = (start: Point, end: Point): Axis | null => {
|
|
7
|
+
if (isVertical(start, end)) return "x"
|
|
8
|
+
if (isHorizontal(start, end)) return "y"
|
|
9
|
+
return null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const hasOnlyNonzeroOrthogonalSegments = (path: Point[]) =>
|
|
13
|
+
path.every((point, index) => {
|
|
14
|
+
const nextPoint = path[index + 1]
|
|
15
|
+
if (!nextPoint) return true
|
|
16
|
+
if (!isHorizontal(point, nextPoint) && !isVertical(point, nextPoint)) {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
return Math.abs(point.x - nextPoint.x) + Math.abs(point.y - nextPoint.y) > 0
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const generateEndpointCollisionDetours = ({
|
|
23
|
+
path,
|
|
24
|
+
collidingSegmentIndex,
|
|
25
|
+
obstacle,
|
|
26
|
+
}: {
|
|
27
|
+
path: Point[]
|
|
28
|
+
collidingSegmentIndex: number
|
|
29
|
+
obstacle: ObstacleRect
|
|
30
|
+
}): Point[][] => {
|
|
31
|
+
if (path.length !== 3) return []
|
|
32
|
+
|
|
33
|
+
const lastSegmentIndex = path.length - 2
|
|
34
|
+
if (
|
|
35
|
+
collidingSegmentIndex !== 0 &&
|
|
36
|
+
collidingSegmentIndex !== lastSegmentIndex
|
|
37
|
+
) {
|
|
38
|
+
return []
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const shouldReverse = collidingSegmentIndex === lastSegmentIndex
|
|
42
|
+
const orderedPath = shouldReverse ? [...path].reverse() : path
|
|
43
|
+
const [start, corner, end] = orderedPath
|
|
44
|
+
const firstSegmentAxis = getSegmentAxis(start!, corner!)
|
|
45
|
+
const secondSegmentAxis = getSegmentAxis(corner!, end!)
|
|
46
|
+
if (!firstSegmentAxis || !secondSegmentAxis) return []
|
|
47
|
+
if (firstSegmentAxis === secondSegmentAxis) return []
|
|
48
|
+
|
|
49
|
+
const escapeCoordinates = [
|
|
50
|
+
...midBetweenPointAndRect(secondSegmentAxis, start!, obstacle),
|
|
51
|
+
...midBetweenPointAndRect(secondSegmentAxis, end!, obstacle),
|
|
52
|
+
]
|
|
53
|
+
const detourCoordinates = [
|
|
54
|
+
...midBetweenPointAndRect(firstSegmentAxis, start!, obstacle),
|
|
55
|
+
...midBetweenPointAndRect(firstSegmentAxis, end!, obstacle),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
const detours: Point[][] = []
|
|
59
|
+
for (const escapeCoordinate of [...new Set(escapeCoordinates)]) {
|
|
60
|
+
for (const detourCoordinate of [...new Set(detourCoordinates)]) {
|
|
61
|
+
const orderedDetour =
|
|
62
|
+
firstSegmentAxis === "y"
|
|
63
|
+
? [
|
|
64
|
+
start!,
|
|
65
|
+
{ x: escapeCoordinate, y: start!.y },
|
|
66
|
+
{ x: escapeCoordinate, y: detourCoordinate },
|
|
67
|
+
{ x: end!.x, y: detourCoordinate },
|
|
68
|
+
end!,
|
|
69
|
+
]
|
|
70
|
+
: [
|
|
71
|
+
start!,
|
|
72
|
+
{ x: start!.x, y: escapeCoordinate },
|
|
73
|
+
{ x: detourCoordinate, y: escapeCoordinate },
|
|
74
|
+
{ x: detourCoordinate, y: end!.y },
|
|
75
|
+
end!,
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
const detour = shouldReverse ? orderedDetour.reverse() : orderedDetour
|
|
79
|
+
if (hasOnlyNonzeroOrthogonalSegments(detour)) detours.push(detour)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return detours
|
|
84
|
+
}
|