@tscircuit/schematic-trace-solver 0.0.95 → 0.0.97
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 +7 -6
- package/dist/index.js +271 -221
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +55 -14
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +31 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +11 -7
- package/lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts +47 -40
- package/lib/types/InputProblem.ts +1 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260706T213649Z/bug-report-20260706T213649Z.test.ts +3 -1
- package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +1 -1
- package/tests/bug-reports/bug-report-20260707T092615Z/bug-report-20260707T092615Z.test.ts +3 -1
- package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +178 -0
- package/tests/repros/__snapshots__/repro-rp2040-zero-crystal-fallback-netlabels.snap.svg +54 -0
- package/tests/repros/assets/repro-rp2040-gamepad-trace-alignment.input.json +237 -0
- package/tests/repros/assets/repro-rp2040-zero-crystal-fallback-netlabels.input.json +121 -0
- package/tests/repros/repro-rp2040-gamepad-trace-alignment.test.ts +14 -0
- package/tests/repros/repro-rp2040-zero-crystal-fallback-netlabels.test.ts +16 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/segment-intersects-rect-interior.test.ts +15 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-boundary.test.ts +18 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/segment-overlaps-rect-boundary-interior.test.ts +18 -0
|
@@ -1,24 +1,29 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import { calculateElbow } from "calculate-elbow"
|
|
1
3
|
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
-
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
3
4
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
4
5
|
import type { MspConnectionPair } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
6
|
+
import { getDimsForOrientation } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
7
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
8
|
+
import type { InputChip, InputProblem } from "lib/types/InputProblem"
|
|
9
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
10
|
+
import type { RectPadding } from "lib/utils/textBoxBounds"
|
|
8
11
|
import { getPinDirection } from "../SchematicTraceSingleLineSolver/getPinDirection"
|
|
9
|
-
import {
|
|
10
|
-
import { findFirstCollision, isHorizontal, isVertical } from "./collisions"
|
|
12
|
+
import { calculateDirectShortPath } from "./calculateDirectShortPath"
|
|
11
13
|
import {
|
|
14
|
+
findFirstCollision,
|
|
15
|
+
isHorizontal,
|
|
16
|
+
isVertical,
|
|
17
|
+
segmentOverlapsRectBoundary,
|
|
18
|
+
} from "./collisions"
|
|
19
|
+
import {
|
|
20
|
+
type Axis,
|
|
12
21
|
aabbFromPoints,
|
|
13
22
|
candidateMidsFromSet,
|
|
14
23
|
midBetweenPointAndRect,
|
|
15
|
-
type Axis,
|
|
16
24
|
} from "./mid"
|
|
17
25
|
import { pathKey, shiftSegmentOrth } from "./pathOps"
|
|
18
|
-
import type
|
|
19
|
-
import { getDimsForOrientation } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
20
|
-
import type { RectPadding } from "lib/utils/textBoxBounds"
|
|
21
|
-
import { calculateDirectShortPath } from "./calculateDirectShortPath"
|
|
26
|
+
import { getObstacleRects, type ObstacleRect } from "./rect"
|
|
22
27
|
|
|
23
28
|
type PathKey = string
|
|
24
29
|
|
|
@@ -268,9 +273,42 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
268
273
|
const collision = findFirstCollision(path, this.obstacles, {
|
|
269
274
|
excludeRectsForSegment: (segIndex) => {
|
|
270
275
|
const lastSegIndex = path.length - 2
|
|
276
|
+
const excludedRects = new Set<ObstacleRect>()
|
|
277
|
+
|
|
271
278
|
if (segIndex === 0 || segIndex === lastSegIndex) {
|
|
272
|
-
|
|
279
|
+
for (const textObstacle of this.textObstacles) {
|
|
280
|
+
excludedRects.add(textObstacle)
|
|
281
|
+
}
|
|
273
282
|
}
|
|
283
|
+
|
|
284
|
+
const segmentStart = path[segIndex]!
|
|
285
|
+
const segmentEnd = path[segIndex + 1]!
|
|
286
|
+
const endpointEpsilon = 1e-9
|
|
287
|
+
const segmentTouchesPin = (pin: MspConnectionPair["pins"][number]) =>
|
|
288
|
+
[segmentStart, segmentEnd].some(
|
|
289
|
+
(point) =>
|
|
290
|
+
Math.abs(point.x - pin.x) <= endpointEpsilon &&
|
|
291
|
+
Math.abs(point.y - pin.y) <= endpointEpsilon,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
for (const pin of this.pins) {
|
|
295
|
+
if (!segmentTouchesPin(pin)) continue
|
|
296
|
+
const endpointChipObstacle = this.obstacles.find(
|
|
297
|
+
(obstacle) =>
|
|
298
|
+
obstacle.kind === "chip" && obstacle.chipId === pin.chipId,
|
|
299
|
+
)
|
|
300
|
+
if (
|
|
301
|
+
endpointChipObstacle &&
|
|
302
|
+
segmentOverlapsRectBoundary(
|
|
303
|
+
segmentStart,
|
|
304
|
+
segmentEnd,
|
|
305
|
+
endpointChipObstacle,
|
|
306
|
+
)
|
|
307
|
+
) {
|
|
308
|
+
excludedRects.add(endpointChipObstacle)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
274
312
|
const adjacentEndpointSegIndex =
|
|
275
313
|
segIndex === 1
|
|
276
314
|
? 2
|
|
@@ -286,9 +324,12 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
286
324
|
path[adjacentEndpointSegIndex + 1]!,
|
|
287
325
|
)
|
|
288
326
|
) {
|
|
289
|
-
|
|
327
|
+
for (const textObstacle of this.endpointTextObstacles) {
|
|
328
|
+
excludedRects.add(textObstacle)
|
|
329
|
+
}
|
|
290
330
|
}
|
|
291
|
-
|
|
331
|
+
|
|
332
|
+
return excludedRects
|
|
292
333
|
},
|
|
293
334
|
})
|
|
294
335
|
|
|
@@ -35,6 +35,37 @@ export const segmentIntersectsRect = <TRect extends RectBounds>(
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export const segmentOverlapsRectBoundary = <TRect extends RectBounds>(
|
|
39
|
+
a: Point,
|
|
40
|
+
b: Point,
|
|
41
|
+
r: TRect,
|
|
42
|
+
eps = EPS,
|
|
43
|
+
): boolean => {
|
|
44
|
+
if (isVertical(a, b, eps)) {
|
|
45
|
+
const onVerticalBoundary =
|
|
46
|
+
Math.abs(a.x - r.minX) <= eps || Math.abs(a.x - r.maxX) <= eps
|
|
47
|
+
if (!onVerticalBoundary) return false
|
|
48
|
+
|
|
49
|
+
const overlap =
|
|
50
|
+
Math.min(Math.max(a.y, b.y), r.maxY) -
|
|
51
|
+
Math.max(Math.min(a.y, b.y), r.minY)
|
|
52
|
+
return overlap > eps
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (isHorizontal(a, b, eps)) {
|
|
56
|
+
const onHorizontalBoundary =
|
|
57
|
+
Math.abs(a.y - r.minY) <= eps || Math.abs(a.y - r.maxY) <= eps
|
|
58
|
+
if (!onHorizontalBoundary) return false
|
|
59
|
+
|
|
60
|
+
const overlap =
|
|
61
|
+
Math.min(Math.max(a.x, b.x), r.maxX) -
|
|
62
|
+
Math.max(Math.min(a.x, b.x), r.minX)
|
|
63
|
+
return overlap > eps
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
|
|
38
69
|
export const findFirstCollision = <TRect extends RectBounds>(
|
|
39
70
|
pts: Point[],
|
|
40
71
|
rects: TRect[],
|
|
@@ -61,9 +61,8 @@ function definePipelineStep<
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
allowLongDistanceTraces?: boolean
|
|
64
|
+
interface Options {
|
|
65
|
+
hideRatsNet?: boolean
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
export class SchematicTracePipelineSolver extends BaseSolver {
|
|
@@ -89,6 +88,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
89
88
|
firstIterationOfPhase: Record<string, number>
|
|
90
89
|
|
|
91
90
|
inputProblem: InputProblem
|
|
91
|
+
hideRatsNet: boolean
|
|
92
92
|
|
|
93
93
|
pipelineDef = [
|
|
94
94
|
definePipelineStep(
|
|
@@ -318,8 +318,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
318
318
|
),
|
|
319
319
|
]
|
|
320
320
|
|
|
321
|
-
constructor(inputProblem: InputProblem) {
|
|
321
|
+
constructor(inputProblem: InputProblem, opts?: Options) {
|
|
322
322
|
super()
|
|
323
|
+
this.hideRatsNet = opts?.hideRatsNet ?? false
|
|
323
324
|
this.inputProblem = this.cloneAndCorrectInputProblem(inputProblem)
|
|
324
325
|
this.MAX_ITERATIONS = 1e6
|
|
325
326
|
this.startTimeOfPhase = {}
|
|
@@ -330,8 +331,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
330
331
|
|
|
331
332
|
override getConstructorParams(): ConstructorParameters<
|
|
332
333
|
typeof SchematicTracePipelineSolver
|
|
333
|
-
>
|
|
334
|
-
return this.inputProblem
|
|
334
|
+
> {
|
|
335
|
+
return [this.inputProblem, { hideRatsNet: this.hideRatsNet }]
|
|
335
336
|
}
|
|
336
337
|
|
|
337
338
|
currentPipelineStepIndex = 0
|
|
@@ -340,6 +341,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
340
341
|
const cloned: InputProblem = structuredClone({
|
|
341
342
|
...original,
|
|
342
343
|
_chipObstacleSpatialIndex: undefined,
|
|
344
|
+
_hideRatsNet: this.hideRatsNet,
|
|
343
345
|
})
|
|
344
346
|
|
|
345
347
|
// First, expand chips so existing pin coordinates sit on or within their edges without shrinking.
|
|
@@ -399,7 +401,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
399
401
|
return this.activeSubSolver.visualize()
|
|
400
402
|
|
|
401
403
|
const visualizations = [
|
|
402
|
-
visualizeInputProblem(this.inputProblem
|
|
404
|
+
visualizeInputProblem(this.inputProblem, {
|
|
405
|
+
hideRatsNet: this.hideRatsNet,
|
|
406
|
+
}),
|
|
403
407
|
...(this.pipelineDef
|
|
404
408
|
.map((p) => (this as any)[p.solverName]?.visualize())
|
|
405
409
|
.filter(Boolean)
|
|
@@ -9,9 +9,14 @@ export const visualizeInputProblem = (
|
|
|
9
9
|
opts: {
|
|
10
10
|
chipAlpha?: number
|
|
11
11
|
connectionAlpha?: number
|
|
12
|
+
hideRatsNet?: boolean
|
|
12
13
|
} = {},
|
|
13
14
|
): GraphicsObject => {
|
|
14
|
-
const {
|
|
15
|
+
const {
|
|
16
|
+
connectionAlpha = 0.8,
|
|
17
|
+
chipAlpha = 0.8,
|
|
18
|
+
hideRatsNet = inputProblem._hideRatsNet ?? false,
|
|
19
|
+
} = opts
|
|
15
20
|
const graphics: Pick<
|
|
16
21
|
Required<GraphicsObject>,
|
|
17
22
|
"lines" | "points" | "rects"
|
|
@@ -58,48 +63,50 @@ export const visualizeInputProblem = (
|
|
|
58
63
|
} as any)
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
if (!hideRatsNet) {
|
|
67
|
+
for (const directConn of inputProblem.directConnections) {
|
|
68
|
+
const [pinId1, pinId2] = directConn.pinIds
|
|
69
|
+
const pin1 = pinIdMap.get(pinId1)!
|
|
70
|
+
const pin2 = pinIdMap.get(pinId2)!
|
|
71
|
+
if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
graphics.lines.push({
|
|
75
|
+
points: [
|
|
76
|
+
{
|
|
77
|
+
x: pin1.x,
|
|
78
|
+
y: pin1.y,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
x: pin2.x,
|
|
82
|
+
y: pin2.y,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
strokeColor: getColorFromString(
|
|
86
|
+
directConn.netId ?? `${pinId1}-${pinId2}`,
|
|
87
|
+
connectionAlpha,
|
|
88
|
+
),
|
|
89
|
+
})
|
|
67
90
|
}
|
|
68
|
-
graphics.lines.push({
|
|
69
|
-
points: [
|
|
70
|
-
{
|
|
71
|
-
x: pin1.x,
|
|
72
|
-
y: pin1.y,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
x: pin2.x,
|
|
76
|
-
y: pin2.y,
|
|
77
|
-
},
|
|
78
|
-
],
|
|
79
|
-
strokeColor: getColorFromString(
|
|
80
|
-
directConn.netId ?? `${pinId1}-${pinId2}`,
|
|
81
|
-
connectionAlpha,
|
|
82
|
-
),
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
91
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
for (const netConn of inputProblem.netConnections) {
|
|
93
|
+
const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId)!)
|
|
94
|
+
for (let i = 0; i < pins.length - 1; i++) {
|
|
95
|
+
for (let j = i + 1; j < pins.length; j++) {
|
|
96
|
+
const pin1 = pins[i]!
|
|
97
|
+
const pin2 = pins[j]!
|
|
98
|
+
if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
graphics.lines.push({
|
|
102
|
+
points: [
|
|
103
|
+
{ x: pin1.x, y: pin1.y },
|
|
104
|
+
{ x: pin2.x, y: pin2.y },
|
|
105
|
+
],
|
|
106
|
+
strokeColor: getColorFromString(netConn.netId, connectionAlpha),
|
|
107
|
+
strokeDash: "4 2",
|
|
108
|
+
})
|
|
94
109
|
}
|
|
95
|
-
graphics.lines.push({
|
|
96
|
-
points: [
|
|
97
|
-
{ x: pin1.x, y: pin1.y },
|
|
98
|
-
{ x: pin2.x, y: pin2.y },
|
|
99
|
-
],
|
|
100
|
-
strokeColor: getColorFromString(netConn.netId, connectionAlpha),
|
|
101
|
-
strokeDash: "4 2",
|
|
102
|
-
})
|
|
103
110
|
}
|
|
104
111
|
}
|
|
105
112
|
}
|