@tscircuit/schematic-trace-solver 0.0.182 → 0.0.184
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 -0
- package/dist/index.js +542 -132
- package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +30 -12
- package/lib/solvers/SameNetJunctionAlignmentSolver/collapseRedundantSharedEndpointStubs.ts +267 -0
- package/lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles.ts +1 -1
- package/lib/solvers/SameNetJunctionAlignmentSolver/getSharedPin.ts +13 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +45 -2
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts +58 -10
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +166 -57
- package/lib/solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts +108 -3
- package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +62 -15
- package/lib/solvers/TraceCleanupSolver/turnMinimization.ts +5 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +13 -20
- package/tests/bug-reports/bug-report-20260707T020342Z/__snapshots__/bug-report-20260707T020342Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260707T020342Z/bug-report-20260707T020342Z.test.ts +10 -0
- package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260708T095725Z/__snapshots__/bug-report-20260708T095725Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260716T144856Z/__snapshots__/bug-report-20260716T144856Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260717T022934Z/__snapshots__/bug-report-20260717T022934Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260717T042845Z/__snapshots__/bug-report-20260717T042845Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260728T144234Z/bug-report-20260728T144234Z.test.ts +2 -0
- package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260730T061837Z/bug-report-20260730T061837Z.test.ts +1 -1
- package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260901T134241Z/__snapshots__/bug-report-20260901T134241Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260902T092425Z/__snapshots__/bug-report-20260902T092425Z.snap.svg +2 -2
- package/tests/examples/__snapshots__/example02.snap.svg +2 -2
- package/tests/examples/__snapshots__/example13.snap.svg +2 -2
- package/tests/examples/__snapshots__/example14.snap.svg +2 -2
- package/tests/examples/__snapshots__/example15.snap.svg +2 -2
- package/tests/examples/__snapshots__/example18.snap.svg +2 -2
- package/tests/examples/__snapshots__/example19.snap.svg +2 -2
- package/tests/examples/__snapshots__/example21.snap.svg +2 -2
- package/tests/examples/__snapshots__/example27.snap.svg +2 -2
- package/tests/examples/__snapshots__/example29.snap.svg +2 -2
- package/tests/examples/__snapshots__/example42.snap.svg +2 -2
- package/tests/examples/__snapshots__/example45.snap.svg +2 -2
- package/tests/examples/__snapshots__/example46.snap.svg +2 -2
- package/tests/examples/__snapshots__/example47.snap.svg +2 -2
- package/tests/examples/__snapshots__/example51.snap.svg +2 -2
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +2 -2
- package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +2 -2
- package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +13 -13
- package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +2 -2
- package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-battery-management-bq24073.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-bc107a-common-emitter-routing.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-bluetooth-controller-ground-decoupling-groups.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-board-8090-power-section.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-bq24074-battery-charger.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-bq40z60-current-sense.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-esp12f-section.snap.svg +508 -0
- package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-hdc2080-ground-label-alignment.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-hdc3020-ground-label-alignment.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-isolated-rs485-isow7841.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-mouse-switch-ground-unnecessary-jog.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-mspm0l1306-capacitor-symmetry.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-pga300-c6-trace-alignment.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +26 -26
- package/tests/repros/__snapshots__/repro-power-group-rail-labels.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-rectifier-trace-overlap.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-rf1-unnecessary-dogleg.snap.svg +5 -5
- package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-smartwatch-power-sheet.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-tida-01389-hbridge.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-tida00553-j4-cell-divider.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-tmp1075-ground-label-overlap.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-tps61222-trace-intersection.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-trellis-core-gnd-net-label-overlap.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-type-c-vbus-led-ground-label.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +2 -2
- package/tests/repros/assets/repro-esp12f-status-section.input.json +1394 -0
- package/tests/repros/repro-esp12f-section.test.ts +12 -0
- package/tests/solvers/SameNetJunctionAlignmentSolver/collapse-redundant-shared-endpoint-stubs.test.ts +101 -0
|
@@ -2,6 +2,7 @@ import type { Point } from "@tscircuit/math-utils"
|
|
|
2
2
|
import { getSegmentIntersection } from "@tscircuit/math-utils/line-intersections"
|
|
3
3
|
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
4
|
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
5
|
+
import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
|
|
5
6
|
import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
6
7
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
7
8
|
import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
isVertical,
|
|
21
22
|
nearlyEqual,
|
|
22
23
|
} from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
|
|
23
|
-
import type { InputPin, InputProblem } from "lib/types/InputProblem"
|
|
24
|
+
import type { InputPin, InputProblem, PinId } from "lib/types/InputProblem"
|
|
24
25
|
import { doesPathCoincideWithTraces } from "lib/utils/doesPathCoincideWithTraces"
|
|
25
26
|
import {
|
|
26
27
|
pathEntersAnyNetLabel,
|
|
@@ -31,6 +32,8 @@ import {
|
|
|
31
32
|
getPinFacingAxis,
|
|
32
33
|
isCoordinateOnPinFacingSide,
|
|
33
34
|
} from "./findNearestSharedPinExitRail"
|
|
35
|
+
import { collapseRedundantSharedEndpointStubs } from "./collapseRedundantSharedEndpointStubs"
|
|
36
|
+
import { getSharedPin } from "./getSharedPin"
|
|
34
37
|
|
|
35
38
|
interface AlignSameNetJunctionsInput {
|
|
36
39
|
inputProblem: InputProblem
|
|
@@ -57,23 +60,30 @@ const MAX_SHARED_PIN_RAIL_OFFSET = 0.05
|
|
|
57
60
|
const MAX_SHARED_ENDPOINT_RAIL_OFFSET = 0.8
|
|
58
61
|
const MIN_RETURN_STEM_LENGTH = 0.05
|
|
59
62
|
|
|
63
|
+
const getMultiPinNetPinIds = (inputProblem: InputProblem) => {
|
|
64
|
+
const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem)
|
|
65
|
+
const inputPinIds = new Set<PinId>(
|
|
66
|
+
inputProblem.chips.flatMap((chip) => chip.pins.map((pin) => pin.pinId)),
|
|
67
|
+
)
|
|
68
|
+
return new Set(
|
|
69
|
+
[...inputPinIds].filter((pinId) => {
|
|
70
|
+
const globalNetId = netConnMap.getNetConnectedToId(pinId)
|
|
71
|
+
if (!globalNetId) return false
|
|
72
|
+
return (
|
|
73
|
+
netConnMap
|
|
74
|
+
.getIdsConnectedToNet(globalNetId)
|
|
75
|
+
.filter((connectedId) => inputPinIds.has(connectedId)).length > 2
|
|
76
|
+
)
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
const getSegmentAxis = (start: Point, end: Point) => {
|
|
61
82
|
if (isHorizontal(start, end)) return "x" as const
|
|
62
83
|
if (isVertical(start, end)) return "y" as const
|
|
63
84
|
return null
|
|
64
85
|
}
|
|
65
86
|
|
|
66
|
-
export const getSharedPin = ({
|
|
67
|
-
donorTrace,
|
|
68
|
-
branchTrace,
|
|
69
|
-
}: {
|
|
70
|
-
donorTrace: SolvedTracePath
|
|
71
|
-
branchTrace: SolvedTracePath
|
|
72
|
-
}): (InputPin & { chipId: string }) | null => {
|
|
73
|
-
const branchPinIds = new Set(branchTrace.pins.map((pin) => pin.pinId))
|
|
74
|
-
return donorTrace.pins.find((pin) => branchPinIds.has(pin.pinId)) ?? null
|
|
75
|
-
}
|
|
76
|
-
|
|
77
87
|
const getOtherPin = ({
|
|
78
88
|
trace,
|
|
79
89
|
sharedPin,
|
|
@@ -1301,6 +1311,14 @@ export const alignSameNetJunctions = ({
|
|
|
1301
1311
|
}
|
|
1302
1312
|
}
|
|
1303
1313
|
|
|
1314
|
+
const collapsedSharedEndpointStubs = collapseRedundantSharedEndpointStubs({
|
|
1315
|
+
traces: outputTraces,
|
|
1316
|
+
netLabelPlacements: outputNetLabelPlacements,
|
|
1317
|
+
netLabelConnectorTraceIds,
|
|
1318
|
+
multiPinNetPinIds: getMultiPinNetPinIds(inputProblem),
|
|
1319
|
+
})
|
|
1320
|
+
outputTraces = collapsedSharedEndpointStubs
|
|
1321
|
+
|
|
1304
1322
|
return {
|
|
1305
1323
|
traces: outputTraces,
|
|
1306
1324
|
netLabelPlacements: outputNetLabelPlacements,
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { PinId } from "lib/types/InputProblem"
|
|
3
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
|
+
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
5
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
6
|
+
import {
|
|
7
|
+
nearlyEqual,
|
|
8
|
+
pointsEqual,
|
|
9
|
+
} from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
|
|
10
|
+
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
11
|
+
import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
|
|
12
|
+
import { getSharedPin } from "./getSharedPin"
|
|
13
|
+
|
|
14
|
+
const getSegmentAxis = (start: Point, end: Point) => {
|
|
15
|
+
if (nearlyEqual(start.x, end.x)) return "y" as const
|
|
16
|
+
if (nearlyEqual(start.y, end.y)) return "x" as const
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const getPathFromSharedPin = ({
|
|
21
|
+
trace,
|
|
22
|
+
sharedPinId,
|
|
23
|
+
}: {
|
|
24
|
+
trace: SolvedTracePath
|
|
25
|
+
sharedPinId: PinId
|
|
26
|
+
}) => {
|
|
27
|
+
const tracePath = simplifyPath(trace.tracePath)
|
|
28
|
+
if (trace.pins[0]?.pinId === sharedPinId) return tracePath
|
|
29
|
+
return tracePath.reverse()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const traceCanLoseSharedPrefix = ({
|
|
33
|
+
trace,
|
|
34
|
+
sharedPrefix,
|
|
35
|
+
netLabelPlacements,
|
|
36
|
+
}: {
|
|
37
|
+
trace: SolvedTracePath
|
|
38
|
+
sharedPrefix: Point[]
|
|
39
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
40
|
+
}) => {
|
|
41
|
+
return !netLabelPlacements.some(
|
|
42
|
+
(label) =>
|
|
43
|
+
label.mspConnectionPairIds.includes(trace.mspPairId) &&
|
|
44
|
+
tracePathContainsPoint(sharedPrefix, label.anchorPoint),
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const getTraceWithoutSharedPrefix = ({
|
|
49
|
+
trace,
|
|
50
|
+
pathFromSharedPin,
|
|
51
|
+
sharedPinId,
|
|
52
|
+
branchPoint,
|
|
53
|
+
}: {
|
|
54
|
+
trace: SolvedTracePath
|
|
55
|
+
pathFromSharedPin: Point[]
|
|
56
|
+
sharedPinId: PinId
|
|
57
|
+
branchPoint: Point
|
|
58
|
+
}) => {
|
|
59
|
+
const branchSegmentIndex = pathFromSharedPin.findIndex((point, index) => {
|
|
60
|
+
const nextPoint = pathFromSharedPin[index + 1]
|
|
61
|
+
if (!nextPoint) return false
|
|
62
|
+
return tracePathContainsPoint([point, nextPoint], branchPoint)
|
|
63
|
+
})
|
|
64
|
+
if (branchSegmentIndex < 0) return null
|
|
65
|
+
const pathFromJunction = simplifyPath([
|
|
66
|
+
branchPoint,
|
|
67
|
+
...pathFromSharedPin.slice(branchSegmentIndex + 1),
|
|
68
|
+
])
|
|
69
|
+
let tracePath = pathFromJunction
|
|
70
|
+
if (trace.pins[0]?.pinId !== sharedPinId) {
|
|
71
|
+
tracePath = pathFromJunction.reverse()
|
|
72
|
+
}
|
|
73
|
+
return { ...trace, tracePath }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const collapseRedundantSharedEndpointStubs = ({
|
|
77
|
+
traces,
|
|
78
|
+
netLabelPlacements,
|
|
79
|
+
netLabelConnectorTraceIds,
|
|
80
|
+
multiPinNetPinIds,
|
|
81
|
+
}: {
|
|
82
|
+
traces: SolvedTracePath[]
|
|
83
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
84
|
+
netLabelConnectorTraceIds: ReadonlySet<MspConnectionPairId>
|
|
85
|
+
multiPinNetPinIds: ReadonlySet<PinId>
|
|
86
|
+
}) => {
|
|
87
|
+
const outputTraces = [...traces]
|
|
88
|
+
|
|
89
|
+
for (let firstIndex = 0; firstIndex < outputTraces.length; firstIndex++) {
|
|
90
|
+
const firstTrace = outputTraces[firstIndex]!
|
|
91
|
+
if (netLabelConnectorTraceIds.has(firstTrace.mspPairId)) continue
|
|
92
|
+
for (
|
|
93
|
+
let secondIndex = firstIndex + 1;
|
|
94
|
+
secondIndex < outputTraces.length;
|
|
95
|
+
secondIndex++
|
|
96
|
+
) {
|
|
97
|
+
const secondTrace = outputTraces[secondIndex]!
|
|
98
|
+
if (
|
|
99
|
+
netLabelConnectorTraceIds.has(secondTrace.mspPairId) ||
|
|
100
|
+
firstTrace.globalConnNetId !== secondTrace.globalConnNetId
|
|
101
|
+
) {
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
const sharedPin = getSharedPin({
|
|
105
|
+
donorTrace: firstTrace,
|
|
106
|
+
branchTrace: secondTrace,
|
|
107
|
+
})
|
|
108
|
+
if (!sharedPin || !multiPinNetPinIds.has(sharedPin.pinId)) continue
|
|
109
|
+
const firstPath = getPathFromSharedPin({
|
|
110
|
+
trace: firstTrace,
|
|
111
|
+
sharedPinId: sharedPin.pinId,
|
|
112
|
+
})
|
|
113
|
+
const secondPath = getPathFromSharedPin({
|
|
114
|
+
trace: secondTrace,
|
|
115
|
+
sharedPinId: sharedPin.pinId,
|
|
116
|
+
})
|
|
117
|
+
if (firstPath.length < 2 || secondPath.length < 2) continue
|
|
118
|
+
|
|
119
|
+
const departureAxis = getSegmentAxis(firstPath[0]!, firstPath[1]!)
|
|
120
|
+
const secondDepartureAxis = getSegmentAxis(secondPath[0]!, secondPath[1]!)
|
|
121
|
+
if (!departureAxis || departureAxis !== secondDepartureAxis) continue
|
|
122
|
+
|
|
123
|
+
const firstDepartureDirection =
|
|
124
|
+
firstPath[1]![departureAxis] - firstPath[0]![departureAxis]
|
|
125
|
+
const secondDepartureDirection =
|
|
126
|
+
secondPath[1]![departureAxis] - secondPath[0]![departureAxis]
|
|
127
|
+
if (
|
|
128
|
+
!pointsEqual(firstPath[1]!, secondPath[1]!) &&
|
|
129
|
+
Math.sign(firstDepartureDirection) ===
|
|
130
|
+
Math.sign(secondDepartureDirection)
|
|
131
|
+
) {
|
|
132
|
+
let traceIndexToCollapse = secondIndex
|
|
133
|
+
let traceToCollapse = secondTrace
|
|
134
|
+
let pathToCollapse = secondPath
|
|
135
|
+
if (
|
|
136
|
+
Math.abs(firstDepartureDirection) < Math.abs(secondDepartureDirection)
|
|
137
|
+
) {
|
|
138
|
+
traceIndexToCollapse = firstIndex
|
|
139
|
+
traceToCollapse = firstTrace
|
|
140
|
+
pathToCollapse = firstPath
|
|
141
|
+
}
|
|
142
|
+
const branchPoint = pathToCollapse[1]!
|
|
143
|
+
if (
|
|
144
|
+
!traceCanLoseSharedPrefix({
|
|
145
|
+
trace: traceToCollapse,
|
|
146
|
+
sharedPrefix: [pathToCollapse[0]!, branchPoint],
|
|
147
|
+
netLabelPlacements,
|
|
148
|
+
})
|
|
149
|
+
) {
|
|
150
|
+
if (traceIndexToCollapse === firstIndex) {
|
|
151
|
+
traceIndexToCollapse = secondIndex
|
|
152
|
+
traceToCollapse = secondTrace
|
|
153
|
+
pathToCollapse = secondPath
|
|
154
|
+
} else {
|
|
155
|
+
traceIndexToCollapse = firstIndex
|
|
156
|
+
traceToCollapse = firstTrace
|
|
157
|
+
pathToCollapse = firstPath
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (
|
|
161
|
+
!traceCanLoseSharedPrefix({
|
|
162
|
+
trace: traceToCollapse,
|
|
163
|
+
sharedPrefix: [pathToCollapse[0]!, branchPoint],
|
|
164
|
+
netLabelPlacements,
|
|
165
|
+
})
|
|
166
|
+
) {
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
const collapsedTrace = getTraceWithoutSharedPrefix({
|
|
170
|
+
trace: traceToCollapse,
|
|
171
|
+
pathFromSharedPin: pathToCollapse,
|
|
172
|
+
sharedPinId: sharedPin.pinId,
|
|
173
|
+
branchPoint,
|
|
174
|
+
})
|
|
175
|
+
if (!collapsedTrace) continue
|
|
176
|
+
outputTraces[traceIndexToCollapse] = collapsedTrace
|
|
177
|
+
break
|
|
178
|
+
}
|
|
179
|
+
if (firstPath.length < 3 || secondPath.length < 3) continue
|
|
180
|
+
if (!pointsEqual(firstPath[1]!, secondPath[1]!)) continue
|
|
181
|
+
|
|
182
|
+
const railAxis = getSegmentAxis(firstPath[1]!, firstPath[2]!)
|
|
183
|
+
const secondRailAxis = getSegmentAxis(secondPath[1]!, secondPath[2]!)
|
|
184
|
+
if (
|
|
185
|
+
!railAxis ||
|
|
186
|
+
railAxis !== secondRailAxis ||
|
|
187
|
+
railAxis === departureAxis
|
|
188
|
+
) {
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
const firstRailDirection =
|
|
192
|
+
firstPath[2]![railAxis] - firstPath[1]![railAxis]
|
|
193
|
+
const secondRailDirection =
|
|
194
|
+
secondPath[2]![railAxis] - secondPath[1]![railAxis]
|
|
195
|
+
const railsHaveSameDirection =
|
|
196
|
+
Math.sign(firstRailDirection) === Math.sign(secondRailDirection)
|
|
197
|
+
const sharedStub = firstPath.slice(0, 2)
|
|
198
|
+
if (
|
|
199
|
+
!railsHaveSameDirection &&
|
|
200
|
+
!nearlyEqual(
|
|
201
|
+
Math.abs(firstRailDirection),
|
|
202
|
+
Math.abs(secondRailDirection),
|
|
203
|
+
) &&
|
|
204
|
+
netLabelPlacements.some(
|
|
205
|
+
(label) =>
|
|
206
|
+
label.globalConnNetId === firstTrace.globalConnNetId &&
|
|
207
|
+
tracePathContainsPoint(sharedStub, label.anchorPoint),
|
|
208
|
+
)
|
|
209
|
+
) {
|
|
210
|
+
continue
|
|
211
|
+
}
|
|
212
|
+
let traceIndexToCollapse = secondIndex
|
|
213
|
+
let traceToCollapse = secondTrace
|
|
214
|
+
let pathToCollapse = secondPath
|
|
215
|
+
let branchPoint = firstPath[1]!
|
|
216
|
+
if (
|
|
217
|
+
railsHaveSameDirection &&
|
|
218
|
+
Math.abs(firstRailDirection) < Math.abs(secondRailDirection)
|
|
219
|
+
) {
|
|
220
|
+
traceIndexToCollapse = firstIndex
|
|
221
|
+
traceToCollapse = firstTrace
|
|
222
|
+
pathToCollapse = firstPath
|
|
223
|
+
branchPoint = firstPath[2]!
|
|
224
|
+
} else if (railsHaveSameDirection) {
|
|
225
|
+
branchPoint = secondPath[2]!
|
|
226
|
+
}
|
|
227
|
+
const sharedPrefix = [pathToCollapse[0]!, pathToCollapse[1]!, branchPoint]
|
|
228
|
+
if (
|
|
229
|
+
!traceCanLoseSharedPrefix({
|
|
230
|
+
trace: traceToCollapse,
|
|
231
|
+
sharedPrefix,
|
|
232
|
+
netLabelPlacements,
|
|
233
|
+
})
|
|
234
|
+
) {
|
|
235
|
+
if (traceIndexToCollapse === firstIndex) {
|
|
236
|
+
traceIndexToCollapse = secondIndex
|
|
237
|
+
traceToCollapse = secondTrace
|
|
238
|
+
pathToCollapse = secondPath
|
|
239
|
+
} else {
|
|
240
|
+
traceIndexToCollapse = firstIndex
|
|
241
|
+
traceToCollapse = firstTrace
|
|
242
|
+
pathToCollapse = firstPath
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (
|
|
246
|
+
!traceCanLoseSharedPrefix({
|
|
247
|
+
trace: traceToCollapse,
|
|
248
|
+
sharedPrefix: [pathToCollapse[0]!, pathToCollapse[1]!, branchPoint],
|
|
249
|
+
netLabelPlacements,
|
|
250
|
+
})
|
|
251
|
+
) {
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
const collapsedTrace = getTraceWithoutSharedPrefix({
|
|
255
|
+
trace: traceToCollapse,
|
|
256
|
+
pathFromSharedPin: pathToCollapse,
|
|
257
|
+
sharedPinId: sharedPin.pinId,
|
|
258
|
+
branchPoint,
|
|
259
|
+
})
|
|
260
|
+
if (!collapsedTrace) continue
|
|
261
|
+
outputTraces[traceIndexToCollapse] = collapsedTrace
|
|
262
|
+
break
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return outputTraces
|
|
267
|
+
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
pointsEqual,
|
|
12
12
|
} from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
|
|
13
13
|
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
14
|
-
import { getSharedPin } from "./
|
|
14
|
+
import { getSharedPin } from "./getSharedPin"
|
|
15
15
|
import {
|
|
16
16
|
pathEntersAnyNetLabel,
|
|
17
17
|
pathIntersectsAnyNetLabel,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
2
|
+
import type { InputPin } from "lib/types/InputProblem"
|
|
3
|
+
|
|
4
|
+
export const getSharedPin = ({
|
|
5
|
+
donorTrace,
|
|
6
|
+
branchTrace,
|
|
7
|
+
}: {
|
|
8
|
+
donorTrace: SolvedTracePath
|
|
9
|
+
branchTrace: SolvedTracePath
|
|
10
|
+
}): (InputPin & { chipId: string }) | null => {
|
|
11
|
+
const branchPinIds = new Set(branchTrace.pins.map((pin) => pin.pinId))
|
|
12
|
+
return donorTrace.pins.find((pin) => branchPinIds.has(pin.pinId)) ?? null
|
|
13
|
+
}
|
|
@@ -11,6 +11,7 @@ import { SchematicTraceSingleLineSolver2 } from "./SchematicTraceSingleLineSolve
|
|
|
11
11
|
import type { Guideline } from "../GuidelinesSolver/GuidelinesSolver"
|
|
12
12
|
import { visualizeGuidelines } from "../GuidelinesSolver/visualizeGuidelines"
|
|
13
13
|
import type { Point } from "@tscircuit/math-utils"
|
|
14
|
+
import { getPinDirectionCandidates } from "./SchematicTraceSingleLineSolver/getPinDirection"
|
|
14
15
|
|
|
15
16
|
const shouldPreferExteriorDetours = ({
|
|
16
17
|
connectionPair,
|
|
@@ -62,6 +63,7 @@ export class SchematicTraceLinesSolver extends BaseSolver {
|
|
|
62
63
|
chipMap: Record<string, InputChip>
|
|
63
64
|
|
|
64
65
|
currentConnectionPair: MspConnectionPair | null = null
|
|
66
|
+
retryingWithoutNetLabelClearance = false
|
|
65
67
|
|
|
66
68
|
solvedTracePaths: Array<SolvedTracePath> = []
|
|
67
69
|
failedConnectionPairs: Array<MspConnectionPair & { error?: string }> = []
|
|
@@ -101,6 +103,7 @@ export class SchematicTraceLinesSolver extends BaseSolver {
|
|
|
101
103
|
if (this.activeSubSolver?.solved) {
|
|
102
104
|
this.solvedTracePaths.push({
|
|
103
105
|
...this.currentConnectionPair!,
|
|
106
|
+
pins: this.activeSubSolver.pins,
|
|
104
107
|
tracePath: this.activeSubSolver!.solvedTracePath!,
|
|
105
108
|
mspConnectionPairIds: [this.currentConnectionPair!.mspPairId],
|
|
106
109
|
pinIds: [
|
|
@@ -110,8 +113,33 @@ export class SchematicTraceLinesSolver extends BaseSolver {
|
|
|
110
113
|
})
|
|
111
114
|
this.activeSubSolver = null
|
|
112
115
|
this.currentConnectionPair = null
|
|
116
|
+
this.retryingWithoutNetLabelClearance = false
|
|
113
117
|
}
|
|
114
118
|
if (this.activeSubSolver?.failed) {
|
|
119
|
+
if (
|
|
120
|
+
this.currentConnectionPair &&
|
|
121
|
+
!this.retryingWithoutNetLabelClearance &&
|
|
122
|
+
this.activeSubSolver.hasAmbiguousPinDirections
|
|
123
|
+
) {
|
|
124
|
+
const connectionPair = this.currentConnectionPair
|
|
125
|
+
this.retryingWithoutNetLabelClearance = true
|
|
126
|
+
this.activeSubSolver = new SchematicTraceSingleLineSolver2({
|
|
127
|
+
inputProblem: this.inputProblem,
|
|
128
|
+
pins: connectionPair.pins.map((pin) => ({
|
|
129
|
+
...pin,
|
|
130
|
+
})) as MspConnectionPair["pins"],
|
|
131
|
+
connectionPair,
|
|
132
|
+
chipMap: this.chipMap,
|
|
133
|
+
preferExteriorDetours: shouldPreferExteriorDetours({
|
|
134
|
+
connectionPair,
|
|
135
|
+
allConnectionPairs: this.mspConnectionPairs,
|
|
136
|
+
inputProblem: this.inputProblem,
|
|
137
|
+
}),
|
|
138
|
+
reserveNetLabelClearance: false,
|
|
139
|
+
})
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
115
143
|
// Record the failure for this connection and continue to the next pair
|
|
116
144
|
if (this.currentConnectionPair) {
|
|
117
145
|
this.failedConnectionPairs.push({
|
|
@@ -121,6 +149,7 @@ export class SchematicTraceLinesSolver extends BaseSolver {
|
|
|
121
149
|
}
|
|
122
150
|
this.activeSubSolver = null
|
|
123
151
|
this.currentConnectionPair = null
|
|
152
|
+
this.retryingWithoutNetLabelClearance = false
|
|
124
153
|
// Do not fail the whole solver; proceed to schedule the next pair
|
|
125
154
|
}
|
|
126
155
|
|
|
@@ -137,8 +166,22 @@ export class SchematicTraceLinesSolver extends BaseSolver {
|
|
|
137
166
|
}
|
|
138
167
|
|
|
139
168
|
this.currentConnectionPair = connectionPair
|
|
140
|
-
|
|
141
|
-
|
|
169
|
+
this.retryingWithoutNetLabelClearance = false
|
|
170
|
+
|
|
171
|
+
// Corner pins can legitimately face either adjacent edge. Keep their
|
|
172
|
+
// inferred direction local to this route so solving one branch cannot
|
|
173
|
+
// constrain a later branch that shares the same corner pin. Pins with one
|
|
174
|
+
// geometric direction retain the established shared representation.
|
|
175
|
+
const hasAmbiguousCornerPin = connectionPair.pins.some((pin) => {
|
|
176
|
+
if (pin._facingDirection) return false
|
|
177
|
+
const chip = this.chipMap[pin.chipId]
|
|
178
|
+
return chip && getPinDirectionCandidates(pin, chip).length > 1
|
|
179
|
+
})
|
|
180
|
+
const pins = hasAmbiguousCornerPin
|
|
181
|
+
? (connectionPair.pins.map((pin) => ({
|
|
182
|
+
...pin,
|
|
183
|
+
})) as MspConnectionPair["pins"])
|
|
184
|
+
: connectionPair.pins
|
|
142
185
|
|
|
143
186
|
this.activeSubSolver = new SchematicTraceSingleLineSolver2({
|
|
144
187
|
inputProblem: this.inputProblem,
|
package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { InputChip, InputPin } from "lib/types/InputProblem"
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export type PinDirection = "x+" | "x-" | "y+" | "y-"
|
|
4
|
+
|
|
5
|
+
export const getPinDirectionCandidates = (
|
|
4
6
|
pin: InputPin,
|
|
5
7
|
chip: InputChip,
|
|
6
|
-
|
|
8
|
+
connectedPin?: InputPin,
|
|
9
|
+
): PinDirection[] => {
|
|
7
10
|
// Determine what edge the pin lies on
|
|
8
11
|
const { x, y } = pin
|
|
9
12
|
const { center, width, height } = chip
|
|
@@ -26,17 +29,62 @@ export const getPinDirection = (
|
|
|
26
29
|
xMinusDistance,
|
|
27
30
|
)
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
// Preserve the established single-direction result as the primary choice.
|
|
33
|
+
// Candidate routing may treat nearly equal edge distances as an ambiguous
|
|
34
|
+
// corner, but context-free callers (such as visualization) should not change
|
|
35
|
+
// direction solely because the candidate comparison uses a tolerance.
|
|
36
|
+
const primaryDirection: PinDirection =
|
|
37
|
+
minDistance === yPlusDistance
|
|
38
|
+
? "y+"
|
|
39
|
+
: minDistance === yMinusDistance
|
|
40
|
+
? "y-"
|
|
41
|
+
: minDistance === xPlusDistance
|
|
42
|
+
? "x+"
|
|
43
|
+
: "x-"
|
|
44
|
+
|
|
45
|
+
const matchingDirections = [
|
|
46
|
+
{ direction: "y+" as const, distance: yPlusDistance },
|
|
47
|
+
{ direction: "y-" as const, distance: yMinusDistance },
|
|
48
|
+
{ direction: "x+" as const, distance: xPlusDistance },
|
|
49
|
+
{ direction: "x-" as const, distance: xMinusDistance },
|
|
50
|
+
]
|
|
51
|
+
.filter(({ distance }) => Math.abs(distance - minDistance) <= 1e-9)
|
|
52
|
+
.map(({ direction }) => direction)
|
|
53
|
+
|
|
54
|
+
const closestDirections = [
|
|
55
|
+
primaryDirection,
|
|
56
|
+
...matchingDirections.filter((direction) => direction !== primaryDirection),
|
|
57
|
+
]
|
|
32
58
|
|
|
33
|
-
if (
|
|
34
|
-
return
|
|
59
|
+
if (!connectedPin || closestDirections.length <= 1) {
|
|
60
|
+
return closestDirections
|
|
35
61
|
}
|
|
36
62
|
|
|
37
|
-
|
|
38
|
-
|
|
63
|
+
const xDistance = connectedPin.x - pin.x
|
|
64
|
+
const yDistance = connectedPin.y - pin.y
|
|
65
|
+
const directionTowardConnectedPin: PinDirection =
|
|
66
|
+
Math.abs(xDistance) >= Math.abs(yDistance)
|
|
67
|
+
? xDistance >= 0
|
|
68
|
+
? "x+"
|
|
69
|
+
: "x-"
|
|
70
|
+
: yDistance >= 0
|
|
71
|
+
? "y+"
|
|
72
|
+
: "y-"
|
|
73
|
+
|
|
74
|
+
if (!closestDirections.includes(directionTowardConnectedPin)) {
|
|
75
|
+
return closestDirections
|
|
39
76
|
}
|
|
40
77
|
|
|
41
|
-
return
|
|
78
|
+
return [
|
|
79
|
+
directionTowardConnectedPin,
|
|
80
|
+
...closestDirections.filter(
|
|
81
|
+
(direction) => direction !== directionTowardConnectedPin,
|
|
82
|
+
),
|
|
83
|
+
]
|
|
42
84
|
}
|
|
85
|
+
|
|
86
|
+
export const getPinDirection = (
|
|
87
|
+
pin: InputPin,
|
|
88
|
+
chip: InputChip,
|
|
89
|
+
connectedPin?: InputPin,
|
|
90
|
+
): PinDirection => getPinDirectionCandidates(pin, chip, connectedPin)[0]!
|