@tscircuit/schematic-trace-solver 0.0.51 → 0.0.53

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 (30) hide show
  1. package/dist/index.d.ts +65 -6
  2. package/dist/index.js +835 -80
  3. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +3 -39
  4. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +16 -0
  5. package/lib/solvers/SchematicTracePipelineSolver/colorAvailableNetOrientationLabels.ts +54 -0
  6. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver.ts +331 -0
  7. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/candidates.ts +387 -0
  8. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/geometry.ts +254 -0
  9. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/types.ts +47 -0
  10. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/visualize.ts +159 -0
  11. package/package.json +1 -1
  12. package/tests/assets/example30.json +4 -1
  13. package/tests/examples/__snapshots__/example01.snap.svg +6 -3
  14. package/tests/examples/__snapshots__/example02.snap.svg +8 -4
  15. package/tests/examples/__snapshots__/example04.snap.svg +4 -2
  16. package/tests/examples/__snapshots__/example07.snap.svg +10 -5
  17. package/tests/examples/__snapshots__/example12.snap.svg +8 -4
  18. package/tests/examples/__snapshots__/example13.snap.svg +16 -8
  19. package/tests/examples/__snapshots__/example14.snap.svg +16 -8
  20. package/tests/examples/__snapshots__/example15.snap.svg +18 -9
  21. package/tests/examples/__snapshots__/example16.snap.svg +8 -4
  22. package/tests/examples/__snapshots__/example17.snap.svg +9 -5
  23. package/tests/examples/__snapshots__/example18.snap.svg +10 -5
  24. package/tests/examples/__snapshots__/example20.snap.svg +6 -3
  25. package/tests/examples/__snapshots__/example21.snap.svg +17 -12
  26. package/tests/examples/__snapshots__/example22.snap.svg +8 -4
  27. package/tests/examples/__snapshots__/example25.snap.svg +18 -9
  28. package/tests/examples/__snapshots__/example30.snap.svg +74 -62
  29. package/tests/examples/__snapshots__/example31.snap.svg +2 -1
  30. package/tests/fixtures/matcher.ts +21 -0
@@ -0,0 +1,387 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import {
3
+ getCenterFromAnchor,
4
+ getDimsForOrientation,
5
+ } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
6
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
7
+ import type { InputProblem } from "lib/types/InputProblem"
8
+ import type { FacingDirection } from "lib/utils/dir"
9
+ import {
10
+ EPS,
11
+ getManhattanDistance,
12
+ getPointAtTraceDistance,
13
+ getTraceLength,
14
+ getTraceVertexDistances,
15
+ } from "./geometry"
16
+ import type { Bounds, LabelCandidate, TraceLocation } from "./types"
17
+
18
+ const CANDIDATE_STEP = 0.1
19
+
20
+ export const generateCandidatesAlongTrace = (params: {
21
+ inputProblem: InputProblem
22
+ label: NetLabelPlacement
23
+ traceLocation: TraceLocation
24
+ }) => {
25
+ const { inputProblem, label, traceLocation } = params
26
+ const traceLength = getTraceLength(traceLocation.trace)
27
+ const orientationConstraint = getOrientationConstraint(inputProblem, label)
28
+ const candidateDistances = getCandidateDistances(
29
+ traceLength,
30
+ getTraceVertexDistances(traceLocation.trace),
31
+ )
32
+ const netLabelWidth = getNetLabelWidth(inputProblem, label)
33
+ const candidates: LabelCandidate[] = []
34
+ const seenCandidateKeys = new Set<string>()
35
+
36
+ for (const pathDistance of candidateDistances) {
37
+ const point = getPointAtTraceDistance(traceLocation.trace, pathDistance)
38
+ const orientations = getOrientationsForPoint({
39
+ inputProblem,
40
+ label,
41
+ point,
42
+ orientationConstraint,
43
+ })
44
+
45
+ for (const orientation of orientations) {
46
+ if (isSamePlacement(label, point, orientation)) continue
47
+ const key = [point.x.toFixed(6), point.y.toFixed(6), orientation].join(
48
+ ":",
49
+ )
50
+ if (seenCandidateKeys.has(key)) continue
51
+ seenCandidateKeys.add(key)
52
+
53
+ candidates.push(
54
+ createCandidate({
55
+ point,
56
+ orientation,
57
+ netLabelWidth,
58
+ traceLocation,
59
+ pathDistance,
60
+ label,
61
+ }),
62
+ )
63
+ }
64
+ }
65
+
66
+ return candidates
67
+ }
68
+
69
+ const getCandidateDistances = (
70
+ traceLength: number,
71
+ vertexDistances: number[],
72
+ ) => {
73
+ const distances = new Set<number>()
74
+ const maxSteps = Math.ceil(traceLength / CANDIDATE_STEP)
75
+
76
+ for (let i = 0; i <= maxSteps; i++) {
77
+ const distance = Math.min(traceLength, i * CANDIDATE_STEP)
78
+ distances.add(roundDistance(distance))
79
+ }
80
+ for (const distance of vertexDistances) {
81
+ distances.add(roundDistance(distance))
82
+ }
83
+
84
+ return [...distances]
85
+ .filter((distance) => distance >= -EPS && distance <= traceLength + EPS)
86
+ .sort((a, b) => a - b)
87
+ }
88
+
89
+ const getOrientationsForPoint = (params: {
90
+ inputProblem: InputProblem
91
+ label: NetLabelPlacement
92
+ point: Point
93
+ orientationConstraint: FacingDirection[] | null
94
+ }) => {
95
+ const { inputProblem, label, point, orientationConstraint } = params
96
+
97
+ if (orientationConstraint) {
98
+ return orientationConstraint
99
+ }
100
+
101
+ return getUnconstrainedOrientations({
102
+ label,
103
+ inputProblem,
104
+ point,
105
+ })
106
+ }
107
+
108
+ const createCandidate = (params: {
109
+ point: Point
110
+ orientation: FacingDirection
111
+ netLabelWidth: number
112
+ traceLocation: TraceLocation
113
+ pathDistance: number
114
+ label: NetLabelPlacement
115
+ }): LabelCandidate => {
116
+ const {
117
+ point,
118
+ orientation,
119
+ netLabelWidth,
120
+ traceLocation,
121
+ pathDistance,
122
+ label,
123
+ } = params
124
+ const { width, height } = getDimsForOrientation({
125
+ orientation,
126
+ netLabelWidth,
127
+ })
128
+
129
+ return {
130
+ anchorPoint: point,
131
+ center: getCenterFromAnchor(point, orientation, width, height),
132
+ width,
133
+ height,
134
+ orientation,
135
+ traceId: traceLocation.trace.mspPairId,
136
+ pathDistance,
137
+ distanceFromOriginal: getManhattanDistance(point, label.anchorPoint),
138
+ status: "valid",
139
+ selected: false,
140
+ }
141
+ }
142
+
143
+ const getOrientationConstraint = (
144
+ inputProblem: InputProblem,
145
+ label: NetLabelPlacement,
146
+ ): FacingDirection[] | null => {
147
+ const availableOrientations = inputProblem.availableNetLabelOrientations ?? {}
148
+ for (const netId of getOrientationConstraintKeys(inputProblem, label)) {
149
+ if (Object.hasOwn(availableOrientations, netId)) {
150
+ return dedupeOrientations(
151
+ (availableOrientations[netId] ?? [])
152
+ .map(normalizeFacingDirection)
153
+ .filter(
154
+ (orientation): orientation is FacingDirection =>
155
+ orientation !== undefined,
156
+ ),
157
+ )
158
+ }
159
+ }
160
+
161
+ return null
162
+ }
163
+
164
+ const getOrientationConstraintKeys = (
165
+ inputProblem: InputProblem,
166
+ label: NetLabelPlacement,
167
+ ) =>
168
+ dedupeStrings([
169
+ label.netId,
170
+ label.globalConnNetId,
171
+ ...inputProblem.netConnections
172
+ .filter((connection) =>
173
+ label.pinIds.some((pinId) => connection.pinIds.includes(pinId)),
174
+ )
175
+ .map((connection) => connection.netId),
176
+ ])
177
+
178
+ const getUnconstrainedOrientations = (params: {
179
+ label: NetLabelPlacement
180
+ inputProblem: InputProblem
181
+ point: Point
182
+ }) => {
183
+ const { label, inputProblem, point } = params
184
+ return dedupeOrientations([
185
+ ...getInitialOrientations(label),
186
+ ...getOutwardOrientations({
187
+ inputProblem,
188
+ point,
189
+ }),
190
+ ...ALL_ORIENTATIONS,
191
+ ])
192
+ }
193
+
194
+ const getInitialOrientations = (label: NetLabelPlacement) => [
195
+ label.orientation,
196
+ getFlippedOrientation(label.orientation),
197
+ ]
198
+
199
+ const getOutwardOrientations = (params: {
200
+ inputProblem: InputProblem
201
+ point: Point
202
+ }) => {
203
+ const { inputProblem, point } = params
204
+ const chipBounds = getChipBounds(inputProblem)
205
+
206
+ return dedupeOrientations(
207
+ getOutwardOrientationOptions(point, chipBounds)
208
+ .sort((a, b) => b.outwardScore - a.outwardScore)
209
+ .map(({ orientation }) => orientation),
210
+ )
211
+ }
212
+
213
+ const getOutwardOrientationOptions = (point: Point, chipBounds: ChipBounds) => {
214
+ const options: OutwardOrientationOption[] = []
215
+
216
+ options.push(
217
+ getOutwardAxisOption({
218
+ value: point.y,
219
+ min: chipBounds.minY,
220
+ max: chipBounds.maxY,
221
+ center: chipBounds.centerY,
222
+ positiveOrientation: "y+",
223
+ negativeOrientation: "y-",
224
+ }),
225
+ )
226
+ options.push(
227
+ getOutwardAxisOption({
228
+ value: point.x,
229
+ min: chipBounds.minX,
230
+ max: chipBounds.maxX,
231
+ center: chipBounds.centerX,
232
+ positiveOrientation: "x+",
233
+ negativeOrientation: "x-",
234
+ }),
235
+ )
236
+
237
+ return options
238
+ }
239
+
240
+ type OutwardOrientationOption = {
241
+ orientation: FacingDirection
242
+ outwardScore: number
243
+ }
244
+
245
+ const getOutwardAxisOption = (params: {
246
+ value: number
247
+ min: number
248
+ max: number
249
+ center: number
250
+ positiveOrientation: FacingDirection
251
+ negativeOrientation: FacingDirection
252
+ }): OutwardOrientationOption => {
253
+ const { value, min, max, center, positiveOrientation, negativeOrientation } =
254
+ params
255
+
256
+ if (value > max + EPS) {
257
+ return {
258
+ orientation: positiveOrientation,
259
+ outwardScore: 1 + value - max,
260
+ }
261
+ }
262
+ if (value < min - EPS) {
263
+ return {
264
+ orientation: negativeOrientation,
265
+ outwardScore: 1 + min - value,
266
+ }
267
+ }
268
+
269
+ return {
270
+ orientation: value >= center ? positiveOrientation : negativeOrientation,
271
+ outwardScore: getNormalizedCenterDistance(value, center, max - min),
272
+ }
273
+ }
274
+
275
+ const getNormalizedCenterDistance = (
276
+ value: number,
277
+ center: number,
278
+ span: number,
279
+ ) => Math.abs(value - center) / Math.max(EPS, span / 2)
280
+
281
+ const ALL_ORIENTATIONS: FacingDirection[] = ["x+", "x-", "y+", "y-"]
282
+
283
+ const getFlippedOrientation = (
284
+ orientation: FacingDirection,
285
+ ): FacingDirection => {
286
+ switch (orientation) {
287
+ case "x+":
288
+ return "x-"
289
+ case "x-":
290
+ return "x+"
291
+ case "y+":
292
+ return "y-"
293
+ case "y-":
294
+ return "y+"
295
+ }
296
+ }
297
+
298
+ type ChipBounds = Bounds & {
299
+ centerX: number
300
+ centerY: number
301
+ }
302
+
303
+ const getChipBounds = (inputProblem: InputProblem): ChipBounds => {
304
+ if (inputProblem.chips.length === 0) {
305
+ return {
306
+ minX: 0,
307
+ minY: 0,
308
+ maxX: 0,
309
+ maxY: 0,
310
+ centerX: 0,
311
+ centerY: 0,
312
+ }
313
+ }
314
+
315
+ const bounds = inputProblem.chips.reduce<Bounds>(
316
+ (acc, chip) => ({
317
+ minX: Math.min(acc.minX, chip.center.x - chip.width / 2),
318
+ minY: Math.min(acc.minY, chip.center.y - chip.height / 2),
319
+ maxX: Math.max(acc.maxX, chip.center.x + chip.width / 2),
320
+ maxY: Math.max(acc.maxY, chip.center.y + chip.height / 2),
321
+ }),
322
+ {
323
+ minX: Number.POSITIVE_INFINITY,
324
+ minY: Number.POSITIVE_INFINITY,
325
+ maxX: Number.NEGATIVE_INFINITY,
326
+ maxY: Number.NEGATIVE_INFINITY,
327
+ },
328
+ )
329
+
330
+ return {
331
+ ...bounds,
332
+ centerX: (bounds.minX + bounds.maxX) / 2,
333
+ centerY: (bounds.minY + bounds.maxY) / 2,
334
+ }
335
+ }
336
+
337
+ const getNetLabelWidth = (
338
+ inputProblem: InputProblem,
339
+ label: NetLabelPlacement,
340
+ ) => {
341
+ const configuredWidth = inputProblem.netConnections.find(
342
+ (connection) => connection.netId === label.netId,
343
+ )?.netLabelWidth
344
+ if (configuredWidth !== undefined) return configuredWidth
345
+
346
+ return label.orientation === "y+" || label.orientation === "y-"
347
+ ? label.height
348
+ : label.width
349
+ }
350
+
351
+ const roundDistance = (distance: number) => Number(distance.toFixed(6))
352
+
353
+ const dedupeOrientations = (orientations: FacingDirection[]) => [
354
+ ...new Set(orientations),
355
+ ]
356
+
357
+ const normalizeFacingDirection = (
358
+ value: string,
359
+ ): FacingDirection | undefined => {
360
+ switch (value) {
361
+ case "x+":
362
+ case "+x":
363
+ return "x+"
364
+ case "x-":
365
+ case "-x":
366
+ return "x-"
367
+ case "y+":
368
+ case "+y":
369
+ return "y+"
370
+ case "y-":
371
+ case "-y":
372
+ return "y-"
373
+ }
374
+ }
375
+
376
+ const dedupeStrings = (values: Array<string | undefined>) => [
377
+ ...new Set(values.filter((value): value is string => value !== undefined)),
378
+ ]
379
+
380
+ const isSamePlacement = (
381
+ label: NetLabelPlacement,
382
+ point: Point,
383
+ orientation: FacingDirection,
384
+ ) =>
385
+ Math.abs(point.x - label.anchorPoint.x) <= EPS &&
386
+ Math.abs(point.y - label.anchorPoint.y) <= EPS &&
387
+ orientation === label.orientation
@@ -0,0 +1,254 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
3
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import type { FacingDirection } from "lib/utils/dir"
5
+ import type { Bounds, TraceLocation } from "./types"
6
+
7
+ export const EPS = 1e-6
8
+ export const TRACE_BOUNDARY_TOLERANCE = 1e-6
9
+
10
+ export const getLabelBounds = (label: {
11
+ center: Point
12
+ width: number
13
+ height: number
14
+ }) => getRectBounds(label.center, label.width, label.height)
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 rectsTouchOrOverlap = (a: Bounds, b: Bounds) =>
21
+ Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) >= -EPS &&
22
+ Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) >= -EPS
23
+
24
+ export const traceCrossesBoundsInterior = (
25
+ bounds: Bounds,
26
+ traces: SolvedTracePath[],
27
+ ) => {
28
+ for (const trace of traces) {
29
+ const points = trace.tracePath
30
+ for (let i = 0; i < points.length - 1; i++) {
31
+ if (segmentCrossesBoundsInterior(points[i]!, points[i + 1]!, bounds)) {
32
+ return true
33
+ }
34
+ }
35
+ }
36
+
37
+ return false
38
+ }
39
+
40
+ export const getTraceLocationsForPoint = (
41
+ point: Point,
42
+ traces: SolvedTracePath[],
43
+ ) => {
44
+ const locations: TraceLocation[] = []
45
+
46
+ for (const trace of traces) {
47
+ if (getUniquePinCount(trace.pinIds) < 2) continue
48
+
49
+ let pathDistance = 0
50
+ for (let i = 0; i < trace.tracePath.length - 1; i++) {
51
+ const start = trace.tracePath[i]!
52
+ const end = trace.tracePath[i + 1]!
53
+ const segmentLength = getManhattanDistance(start, end)
54
+
55
+ if (isPointOnSegment(point, start, end)) {
56
+ locations.push({
57
+ trace,
58
+ distance: pathDistance + getManhattanDistance(start, point),
59
+ })
60
+ }
61
+
62
+ pathDistance += segmentLength
63
+ }
64
+ }
65
+
66
+ return locations
67
+ }
68
+
69
+ export const getTraceLength = (trace: SolvedTracePath) => {
70
+ let length = 0
71
+ for (let i = 0; i < trace.tracePath.length - 1; i++) {
72
+ length += getManhattanDistance(trace.tracePath[i]!, trace.tracePath[i + 1]!)
73
+ }
74
+ return length
75
+ }
76
+
77
+ export const getPointAtTraceDistance = (
78
+ trace: SolvedTracePath,
79
+ distance: number,
80
+ ) => {
81
+ let pathDistance = 0
82
+
83
+ for (let i = 0; i < trace.tracePath.length - 1; i++) {
84
+ const start = trace.tracePath[i]!
85
+ const end = trace.tracePath[i + 1]!
86
+ const segmentLength = getManhattanDistance(start, end)
87
+ const nextDistance = pathDistance + segmentLength
88
+
89
+ if (distance <= nextDistance + EPS) {
90
+ const offset = Math.max(
91
+ 0,
92
+ Math.min(segmentLength, distance - pathDistance),
93
+ )
94
+ const direction = getSegmentDirection(start, end)
95
+ return {
96
+ x: start.x + direction.x * offset,
97
+ y: start.y + direction.y * offset,
98
+ }
99
+ }
100
+
101
+ pathDistance = nextDistance
102
+ }
103
+
104
+ return trace.tracePath[trace.tracePath.length - 1]!
105
+ }
106
+
107
+ export const getTraceVertexDistances = (trace: SolvedTracePath) => {
108
+ const distances: number[] = []
109
+ let pathDistance = 0
110
+
111
+ for (let i = 0; i < trace.tracePath.length; i++) {
112
+ distances.push(pathDistance)
113
+ const nextPoint = trace.tracePath[i + 1]
114
+ if (nextPoint) {
115
+ pathDistance += getManhattanDistance(trace.tracePath[i]!, nextPoint)
116
+ }
117
+ }
118
+
119
+ return distances
120
+ }
121
+
122
+ export const getTraceDistanceCompatibleOrientations = (
123
+ trace: SolvedTracePath,
124
+ distance: number,
125
+ ) => {
126
+ const orientations = new Set<FacingDirection>()
127
+ let pathDistance = 0
128
+
129
+ for (let i = 0; i < trace.tracePath.length - 1; i++) {
130
+ const start = trace.tracePath[i]!
131
+ const end = trace.tracePath[i + 1]!
132
+ const segmentLength = getManhattanDistance(start, end)
133
+ const nextDistance = pathDistance + segmentLength
134
+
135
+ if (distance >= pathDistance - EPS && distance <= nextDistance + EPS) {
136
+ addSegmentCompatibleOrientations(orientations, trace, i)
137
+ if (Math.abs(distance - pathDistance) <= EPS) {
138
+ addSegmentCompatibleOrientations(orientations, trace, i - 1)
139
+ }
140
+ if (Math.abs(distance - nextDistance) <= EPS) {
141
+ addSegmentCompatibleOrientations(orientations, trace, i + 1)
142
+ }
143
+ return orientations
144
+ }
145
+
146
+ pathDistance = nextDistance
147
+ }
148
+
149
+ return orientations
150
+ }
151
+
152
+ export const getManhattanDistance = (a: Point, b: Point) =>
153
+ Math.abs(a.x - b.x) + Math.abs(a.y - b.y)
154
+
155
+ const addSegmentCompatibleOrientations = (
156
+ orientations: Set<FacingDirection>,
157
+ trace: SolvedTracePath,
158
+ segmentIndex: number,
159
+ ) => {
160
+ const start = trace.tracePath[segmentIndex]
161
+ const end = trace.tracePath[segmentIndex + 1]
162
+ if (!start || !end) return
163
+
164
+ if (isHorizontal(start, end)) {
165
+ orientations.add("y+")
166
+ orientations.add("y-")
167
+ } else if (isVertical(start, end)) {
168
+ orientations.add("x+")
169
+ orientations.add("x-")
170
+ }
171
+ }
172
+
173
+ const segmentCrossesBoundsInterior = (
174
+ start: Point,
175
+ end: Point,
176
+ bounds: Bounds,
177
+ ) => {
178
+ const interior = {
179
+ minX: bounds.minX + TRACE_BOUNDARY_TOLERANCE,
180
+ minY: bounds.minY + TRACE_BOUNDARY_TOLERANCE,
181
+ maxX: bounds.maxX - TRACE_BOUNDARY_TOLERANCE,
182
+ maxY: bounds.maxY - TRACE_BOUNDARY_TOLERANCE,
183
+ }
184
+
185
+ if (interior.minX >= interior.maxX || interior.minY >= interior.maxY) {
186
+ return false
187
+ }
188
+
189
+ if (isVertical(start, end)) {
190
+ if (start.x <= interior.minX + EPS || start.x >= interior.maxX - EPS) {
191
+ return false
192
+ }
193
+ return rangesOverlap(
194
+ Math.min(start.y, end.y),
195
+ Math.max(start.y, end.y),
196
+ interior.minY,
197
+ interior.maxY,
198
+ )
199
+ }
200
+
201
+ if (isHorizontal(start, end)) {
202
+ if (start.y <= interior.minY + EPS || start.y >= interior.maxY - EPS) {
203
+ return false
204
+ }
205
+ return rangesOverlap(
206
+ Math.min(start.x, end.x),
207
+ Math.max(start.x, end.x),
208
+ interior.minX,
209
+ interior.maxX,
210
+ )
211
+ }
212
+
213
+ return false
214
+ }
215
+
216
+ const isPointOnSegment = (point: Point, start: Point, end: Point) => {
217
+ if (isHorizontal(start, end)) {
218
+ return (
219
+ Math.abs(point.y - start.y) <= EPS &&
220
+ point.x >= Math.min(start.x, end.x) - EPS &&
221
+ point.x <= Math.max(start.x, end.x) + EPS
222
+ )
223
+ }
224
+
225
+ if (isVertical(start, end)) {
226
+ return (
227
+ Math.abs(point.x - start.x) <= EPS &&
228
+ point.y >= Math.min(start.y, end.y) - EPS &&
229
+ point.y <= Math.max(start.y, end.y) + EPS
230
+ )
231
+ }
232
+
233
+ return false
234
+ }
235
+
236
+ const getSegmentDirection = (start: Point, end: Point) => ({
237
+ x: isVertical(start, end) ? 0 : Math.sign(end.x - start.x),
238
+ y: isHorizontal(start, end) ? 0 : Math.sign(end.y - start.y),
239
+ })
240
+
241
+ const isHorizontal = (start: Point, end: Point) =>
242
+ Math.abs(start.y - end.y) <= EPS
243
+
244
+ const isVertical = (start: Point, end: Point) =>
245
+ Math.abs(start.x - end.x) <= EPS
246
+
247
+ const rangesOverlap = (
248
+ minA: number,
249
+ maxA: number,
250
+ minB: number,
251
+ maxB: number,
252
+ ) => Math.min(maxA, maxB) - Math.max(minA, minB) > EPS
253
+
254
+ const getUniquePinCount = (pinIds: string[]) => new Set(pinIds).size
@@ -0,0 +1,47 @@
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 TraceAnchoredNetLabelOverlapSolverParams {
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 LabelOverlap = {
21
+ firstLabelIndex: number
22
+ secondLabelIndex: number
23
+ }
24
+
25
+ export type TraceLocation = {
26
+ trace: SolvedTracePath
27
+ distance: number
28
+ }
29
+
30
+ export type CandidateStatus =
31
+ | "valid"
32
+ | "chip-collision"
33
+ | "trace-collision"
34
+ | "netlabel-collision"
35
+
36
+ export type LabelCandidate = {
37
+ anchorPoint: Point
38
+ center: Point
39
+ width: number
40
+ height: number
41
+ orientation: FacingDirection
42
+ traceId: string
43
+ pathDistance: number
44
+ distanceFromOriginal: number
45
+ status: CandidateStatus
46
+ selected: boolean
47
+ }