@tscircuit/schematic-trace-solver 0.0.51 → 0.0.52

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