@tscircuit/schematic-trace-solver 0.0.49 → 0.0.51

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.
Files changed (23) hide show
  1. package/dist/index.d.ts +133 -1
  2. package/dist/index.js +1084 -41
  3. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +682 -0
  4. package/lib/solvers/AvailableNetOrientationSolver/constants.ts +6 -0
  5. package/lib/solvers/AvailableNetOrientationSolver/geometry.ts +192 -0
  6. package/lib/solvers/AvailableNetOrientationSolver/traces.ts +43 -0
  7. package/lib/solvers/AvailableNetOrientationSolver/types.ts +44 -0
  8. package/lib/solvers/AvailableNetOrientationSolver/visualize.ts +123 -0
  9. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +30 -0
  10. package/lib/solvers/VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver.ts +268 -0
  11. package/lib/solvers/VccNetLabelCornerPlacementSolver/geometry.ts +59 -0
  12. package/lib/solvers/VccNetLabelCornerPlacementSolver/types.ts +37 -0
  13. package/lib/solvers/VccNetLabelCornerPlacementSolver/visualize.ts +114 -0
  14. package/package.json +1 -1
  15. package/tests/assets/example22.json +1 -1
  16. package/tests/examples/__snapshots__/example12.snap.svg +33 -30
  17. package/tests/examples/__snapshots__/example13.snap.svg +10 -4
  18. package/tests/examples/__snapshots__/example14.snap.svg +72 -66
  19. package/tests/examples/__snapshots__/example16.snap.svg +33 -27
  20. package/tests/examples/__snapshots__/example20.snap.svg +29 -23
  21. package/tests/examples/__snapshots__/example21.snap.svg +77 -65
  22. package/tests/examples/__snapshots__/example22.snap.svg +31 -25
  23. package/tests/examples/__snapshots__/example31.snap.svg +12 -12
@@ -0,0 +1,192 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
3
+ import type { InputProblem } from "lib/types/InputProblem"
4
+ import type { FacingDirection } from "lib/utils/dir"
5
+ import { EPS, TRACE_BOUNDARY_TOLERANCE } from "./constants"
6
+ import type { Bounds, ChipSide } from "./types"
7
+
8
+ export const isYOrientation = (
9
+ orientation: FacingDirection,
10
+ ): orientation is "y+" | "y-" => orientation === "y+" || orientation === "y-"
11
+
12
+ export const isXOrientation = (
13
+ orientation: FacingDirection,
14
+ ): orientation is "x+" | "x-" => orientation === "x+" || orientation === "x-"
15
+
16
+ export const rectsOverlap = (a: Bounds, b: Bounds) =>
17
+ Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) > EPS &&
18
+ Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) > EPS
19
+
20
+ export const rangesOverlap = (
21
+ minA: number,
22
+ maxA: number,
23
+ minB: number,
24
+ maxB: number,
25
+ ) => Math.min(maxA, maxB) - Math.max(minA, minB) > EPS
26
+
27
+ export const traceCrossesBoundsInterior = (
28
+ bounds: Bounds,
29
+ traceMap: Record<string, SolvedTracePath>,
30
+ ) => {
31
+ for (const trace of Object.values(traceMap)) {
32
+ const points = trace.tracePath
33
+ for (let i = 0; i < points.length - 1; i++) {
34
+ if (segmentCrossesBoundsInterior(points[i]!, points[i + 1]!, bounds)) {
35
+ return true
36
+ }
37
+ }
38
+ }
39
+
40
+ return false
41
+ }
42
+
43
+ const segmentCrossesBoundsInterior = (p1: Point, p2: Point, bounds: Bounds) => {
44
+ const interiorBounds = {
45
+ minX: bounds.minX + TRACE_BOUNDARY_TOLERANCE,
46
+ minY: bounds.minY + TRACE_BOUNDARY_TOLERANCE,
47
+ maxX: bounds.maxX - TRACE_BOUNDARY_TOLERANCE,
48
+ maxY: bounds.maxY - TRACE_BOUNDARY_TOLERANCE,
49
+ }
50
+
51
+ if (
52
+ interiorBounds.minX >= interiorBounds.maxX ||
53
+ interiorBounds.minY >= interiorBounds.maxY
54
+ ) {
55
+ return false
56
+ }
57
+
58
+ if (sameX(p1, p2)) {
59
+ if (
60
+ p1.x <= interiorBounds.minX + EPS ||
61
+ p1.x >= interiorBounds.maxX - EPS
62
+ ) {
63
+ return false
64
+ }
65
+ return rangesOverlap(
66
+ Math.min(p1.y, p2.y),
67
+ Math.max(p1.y, p2.y),
68
+ interiorBounds.minY,
69
+ interiorBounds.maxY,
70
+ )
71
+ }
72
+
73
+ if (sameY(p1, p2)) {
74
+ if (
75
+ p1.y <= interiorBounds.minY + EPS ||
76
+ p1.y >= interiorBounds.maxY - EPS
77
+ ) {
78
+ return false
79
+ }
80
+ return rangesOverlap(
81
+ Math.min(p1.x, p2.x),
82
+ Math.max(p1.x, p2.x),
83
+ interiorBounds.minX,
84
+ interiorBounds.maxX,
85
+ )
86
+ }
87
+
88
+ return false
89
+ }
90
+
91
+ export const tracePathCrossesAnyTrace = (
92
+ tracePath: Point[],
93
+ traceMap: Record<string, SolvedTracePath>,
94
+ ) => {
95
+ for (const trace of Object.values(traceMap)) {
96
+ const points = trace.tracePath
97
+ for (let i = 0; i < tracePath.length - 1; i++) {
98
+ for (let j = 0; j < points.length - 1; j++) {
99
+ if (
100
+ segmentsStrictlyCross(
101
+ tracePath[i]!,
102
+ tracePath[i + 1]!,
103
+ points[j]!,
104
+ points[j + 1]!,
105
+ )
106
+ ) {
107
+ return true
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ return false
114
+ }
115
+
116
+ const segmentsStrictlyCross = (a1: Point, a2: Point, b1: Point, b2: Point) => {
117
+ if (sameX(a1, a2) && sameY(b1, b2)) {
118
+ return (
119
+ a1.x > Math.min(b1.x, b2.x) + EPS &&
120
+ a1.x < Math.max(b1.x, b2.x) - EPS &&
121
+ b1.y > Math.min(a1.y, a2.y) + EPS &&
122
+ b1.y < Math.max(a1.y, a2.y) - EPS
123
+ )
124
+ }
125
+
126
+ if (sameY(a1, a2) && sameX(b1, b2)) {
127
+ return (
128
+ b1.x > Math.min(a1.x, a2.x) + EPS &&
129
+ b1.x < Math.max(a1.x, a2.x) - EPS &&
130
+ a1.y > Math.min(b1.y, b2.y) + EPS &&
131
+ a1.y < Math.max(b1.y, b2.y) - EPS
132
+ )
133
+ }
134
+
135
+ return false
136
+ }
137
+
138
+ export const getSideDistances = (point: Point, bounds: Bounds) =>
139
+ [
140
+ ["left", Math.abs(point.x - bounds.minX)] as const,
141
+ ["right", Math.abs(point.x - bounds.maxX)] as const,
142
+ ["bottom", Math.abs(point.y - bounds.minY)] as const,
143
+ ["top", Math.abs(point.y - bounds.maxY)] as const,
144
+ ] satisfies Array<readonly [ChipSide, number]>
145
+
146
+ export const getConnectorTracePath = (
147
+ source: Point,
148
+ target: Point,
149
+ orientation: FacingDirection,
150
+ ) =>
151
+ simplifyOrthogonalPath(
152
+ isYOrientation(orientation)
153
+ ? [source, { x: target.x, y: source.y }, target]
154
+ : [source, { x: source.x, y: target.y }, target],
155
+ )
156
+
157
+ const simplifyOrthogonalPath = (path: Point[]) => {
158
+ const deduped = path.filter(
159
+ (point, index) => index === 0 || !pointsEqual(point, path[index - 1]!),
160
+ )
161
+ if (deduped.length < 3) return deduped
162
+
163
+ const simplified: Point[] = [deduped[0]!]
164
+ for (let i = 1; i < deduped.length - 1; i++) {
165
+ const prev = simplified[simplified.length - 1]!
166
+ const point = deduped[i]!
167
+ const next = deduped[i + 1]!
168
+ if (
169
+ (sameX(prev, point) && sameX(point, next)) ||
170
+ (sameY(prev, point) && sameY(point, next))
171
+ ) {
172
+ continue
173
+ }
174
+ simplified.push(point)
175
+ }
176
+ simplified.push(deduped[deduped.length - 1]!)
177
+ return simplified
178
+ }
179
+
180
+ const pointsEqual = (a: Point, b: Point) => sameX(a, b) && sameY(a, b)
181
+
182
+ const sameX = (a: Point, b: Point) => Math.abs(a.x - b.x) <= EPS
183
+
184
+ const sameY = (a: Point, b: Point) => Math.abs(a.y - b.y) <= EPS
185
+
186
+ export const getMaxSearchDistance = (inputProblem: InputProblem) => {
187
+ const maxChipWidth = Math.max(
188
+ ...inputProblem.chips.map((chip) => chip.width),
189
+ 1,
190
+ )
191
+ return maxChipWidth * 3
192
+ }
@@ -0,0 +1,43 @@
1
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
2
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
3
+ import type { InputPin, InputProblem } from "lib/types/InputProblem"
4
+ import type { CandidateLabel } from "./types"
5
+
6
+ export const getPinMap = (inputProblem: InputProblem) => {
7
+ const pinMap: Record<string, InputPin & { chipId: string }> = {}
8
+ for (const chip of inputProblem.chips) {
9
+ for (const pin of chip.pins) {
10
+ pinMap[pin.pinId] = { ...pin, chipId: chip.chipId }
11
+ }
12
+ }
13
+ return pinMap
14
+ }
15
+
16
+ export const getTracePins = (
17
+ label: NetLabelPlacement,
18
+ pinMap: Record<string, InputPin & { chipId: string }>,
19
+ ): SolvedTracePath["pins"] => {
20
+ const pins = label.pinIds.flatMap((pinId) => {
21
+ const pin = pinMap[pinId]
22
+ return pin ? [pin] : []
23
+ })
24
+
25
+ if (pins.length >= 2) return [pins[0]!, pins[1]!]
26
+ if (pins.length === 1) return [pins[0]!, pins[0]!]
27
+
28
+ const syntheticPin = {
29
+ pinId: `${label.globalConnNetId}-netlabel-anchor`,
30
+ x: label.anchorPoint.x,
31
+ y: label.anchorPoint.y,
32
+ chipId: "available-net-orientation",
33
+ }
34
+ return [syntheticPin, syntheticPin]
35
+ }
36
+
37
+ export const toNetLabelPlacementPatch = (candidate: CandidateLabel) => ({
38
+ orientation: candidate.orientation,
39
+ anchorPoint: candidate.anchorPoint,
40
+ center: candidate.center,
41
+ width: candidate.width,
42
+ height: candidate.height,
43
+ })
@@ -0,0 +1,44 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import type { InputProblem } from "lib/types/InputProblem"
5
+ import type { FacingDirection } from "lib/utils/dir"
6
+
7
+ export interface AvailableNetOrientationSolverParams {
8
+ inputProblem: InputProblem
9
+ traces: SolvedTracePath[]
10
+ netLabelPlacements: NetLabelPlacement[]
11
+ }
12
+
13
+ export type Bounds = {
14
+ minX: number
15
+ minY: number
16
+ maxX: number
17
+ maxY: number
18
+ }
19
+
20
+ export type ChipSide = "left" | "right" | "top" | "bottom"
21
+
22
+ export type CandidateLabel = {
23
+ orientation: FacingDirection
24
+ anchorPoint: Point
25
+ center: Point
26
+ width: number
27
+ height: number
28
+ }
29
+
30
+ export type CandidateStatus =
31
+ | "valid"
32
+ | "chip-collision"
33
+ | "trace-collision"
34
+ | "netlabel-collision"
35
+
36
+ export type CandidatePhase = "rotate" | "shift"
37
+
38
+ export type EvaluatedCandidate = CandidateLabel & {
39
+ status: CandidateStatus
40
+ selected: boolean
41
+ phase: CandidatePhase
42
+ distance?: number
43
+ outwardDistance?: number
44
+ }
@@ -0,0 +1,123 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
5
+ import type { InputProblem } from "lib/types/InputProblem"
6
+ import { getColorFromString } from "lib/utils/getColorFromString"
7
+ import { CANDIDATE_REJECTED_COLOR, CANDIDATE_SELECTED_COLOR } from "./constants"
8
+ import type { EvaluatedCandidate } from "./types"
9
+
10
+ export const visualizeAvailableNetOrientationSolver = (params: {
11
+ inputProblem: InputProblem
12
+ traces: SolvedTracePath[]
13
+ outputNetLabelPlacements: NetLabelPlacement[]
14
+ currentLabel: NetLabelPlacement | null
15
+ currentCandidateResults: EvaluatedCandidate[]
16
+ solved: boolean
17
+ }): GraphicsObject => {
18
+ const graphics = visualizeInputProblem(params.inputProblem)
19
+ ensureGraphicsArrays(graphics)
20
+
21
+ drawTraces(graphics, params.traces)
22
+ drawNetLabels(graphics, params.outputNetLabelPlacements)
23
+ drawCurrentLabel(graphics, params.currentLabel, params.solved)
24
+ drawCurrentCandidates(graphics, params.currentCandidateResults, params.solved)
25
+
26
+ return graphics
27
+ }
28
+
29
+ const drawTraces = (graphics: GraphicsObject, traces: SolvedTracePath[]) => {
30
+ for (const trace of traces) {
31
+ graphics.lines!.push({
32
+ points: trace.tracePath,
33
+ strokeColor: "purple",
34
+ } as any)
35
+ }
36
+ }
37
+
38
+ const drawNetLabels = (
39
+ graphics: GraphicsObject,
40
+ labels: NetLabelPlacement[],
41
+ ) => {
42
+ for (const label of labels) {
43
+ graphics.rects!.push({
44
+ center: label.center,
45
+ width: label.width,
46
+ height: label.height,
47
+ fill: getColorFromString(label.globalConnNetId, 0.35),
48
+ strokeColor: getColorFromString(label.globalConnNetId, 0.9),
49
+ label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
50
+ } as any)
51
+ graphics.points!.push({
52
+ x: label.anchorPoint.x,
53
+ y: label.anchorPoint.y,
54
+ color: getColorFromString(label.globalConnNetId, 0.9),
55
+ label: `anchorPoint\norientation: ${label.orientation}`,
56
+ } as any)
57
+ }
58
+ }
59
+
60
+ const drawCurrentLabel = (
61
+ graphics: GraphicsObject,
62
+ currentLabel: NetLabelPlacement | null,
63
+ solved: boolean,
64
+ ) => {
65
+ if (!currentLabel || solved) return
66
+
67
+ graphics.rects!.push({
68
+ center: currentLabel.center,
69
+ width: currentLabel.width,
70
+ height: currentLabel.height,
71
+ fill: "rgba(255, 0, 0, 0.2)",
72
+ strokeColor: CANDIDATE_REJECTED_COLOR,
73
+ label: `available orientation target\n${currentLabel.netId ?? currentLabel.globalConnNetId}`,
74
+ } as any)
75
+ }
76
+
77
+ const drawCurrentCandidates = (
78
+ graphics: GraphicsObject,
79
+ candidates: EvaluatedCandidate[],
80
+ solved: boolean,
81
+ ) => {
82
+ if (solved) return
83
+
84
+ for (const candidate of candidates) {
85
+ const color = candidate.selected
86
+ ? CANDIDATE_SELECTED_COLOR
87
+ : CANDIDATE_REJECTED_COLOR
88
+ const distanceLabel =
89
+ candidate.distance === undefined
90
+ ? ""
91
+ : `\ndistance: ${candidate.distance.toFixed(3)}`
92
+ const outwardDistanceLabel =
93
+ candidate.outwardDistance === undefined || candidate.outwardDistance === 0
94
+ ? ""
95
+ : `\noutward distance: ${candidate.outwardDistance.toFixed(3)}`
96
+
97
+ graphics.rects!.push({
98
+ center: candidate.center,
99
+ width: candidate.width,
100
+ height: candidate.height,
101
+ fill: candidate.selected
102
+ ? "rgba(0, 0, 255, 0.2)"
103
+ : "rgba(255, 0, 0, 0.15)",
104
+ strokeColor: color,
105
+ strokeDash: candidate.selected ? undefined : "4 2",
106
+ label: `${candidate.selected ? "selected" : candidate.status} available orientation\nphase: ${candidate.phase}\norientation: ${candidate.orientation}${distanceLabel}${outwardDistanceLabel}`,
107
+ } as any)
108
+ graphics.points!.push({
109
+ x: candidate.anchorPoint.x,
110
+ y: candidate.anchorPoint.y,
111
+ color,
112
+ label: `candidate anchor\n${candidate.phase}\n${candidate.orientation}`,
113
+ } as any)
114
+ }
115
+ }
116
+
117
+ const ensureGraphicsArrays = (graphics: GraphicsObject) => {
118
+ if (!graphics.lines) graphics.lines = []
119
+ if (!graphics.points) graphics.points = []
120
+ if (!graphics.rects) graphics.rects = []
121
+ if (!graphics.circles) graphics.circles = []
122
+ if (!graphics.texts) graphics.texts = []
123
+ }
@@ -21,6 +21,8 @@ import { LongDistancePairSolver } from "../LongDistancePairSolver/LongDistancePa
21
21
  import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver"
22
22
  import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver"
23
23
  import { Example28Solver } from "../Example28Solver/Example28Solver"
24
+ import { AvailableNetOrientationSolver } from "../AvailableNetOrientationSolver/AvailableNetOrientationSolver"
25
+ import { VccNetLabelCornerPlacementSolver } from "../VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver"
24
26
 
25
27
  type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
26
28
  solverName: string
@@ -71,6 +73,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
71
73
  traceLabelOverlapAvoidanceSolver?: TraceLabelOverlapAvoidanceSolver
72
74
  traceCleanupSolver?: TraceCleanupSolver
73
75
  example28Solver?: Example28Solver
76
+ availableNetOrientationSolver?: AvailableNetOrientationSolver
77
+ vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver
74
78
 
75
79
  startTimeOfPhase: Record<string, number>
76
80
  endTimeOfPhase: Record<string, number>
@@ -240,6 +244,32 @@ export class SchematicTracePipelineSolver extends BaseSolver {
240
244
  },
241
245
  ]
242
246
  }),
247
+ definePipelineStep(
248
+ "availableNetOrientationSolver",
249
+ AvailableNetOrientationSolver,
250
+ (instance) => [
251
+ {
252
+ inputProblem: instance.inputProblem,
253
+ traces: instance.example28Solver!.outputTraces,
254
+ netLabelPlacements:
255
+ instance.example28Solver!.outputNetLabelPlacements,
256
+ },
257
+ ],
258
+ ),
259
+ definePipelineStep(
260
+ "vccNetLabelCornerPlacementSolver",
261
+ VccNetLabelCornerPlacementSolver,
262
+ (instance) => {
263
+ return [
264
+ {
265
+ inputProblem: instance.inputProblem,
266
+ traces: instance.availableNetOrientationSolver!.traces,
267
+ netLabelPlacements:
268
+ instance.availableNetOrientationSolver!.outputNetLabelPlacements,
269
+ },
270
+ ]
271
+ },
272
+ ),
243
273
  ]
244
274
 
245
275
  constructor(inputProblem: InputProblem) {