@tscircuit/schematic-trace-solver 0.0.44 → 0.0.46

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 (39) hide show
  1. package/.github/workflows/bun-formatcheck.yml +1 -1
  2. package/.github/workflows/bun-pver-release.yml +1 -1
  3. package/.github/workflows/bun-test.yml +1 -1
  4. package/.github/workflows/bun-typecheck.yml +1 -1
  5. package/dist/index.d.ts +43 -8
  6. package/dist/index.js +1109 -185
  7. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +0 -1
  8. package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +83 -36
  9. package/lib/solvers/TraceCleanupSolver/hasCollisions.ts +16 -4
  10. package/lib/solvers/TraceCleanupSolver/is4PointRectangle.ts +17 -0
  11. package/lib/solvers/TraceCleanupSolver/isSegmentAnEndpointSegment.ts +36 -0
  12. package/lib/solvers/TraceCleanupSolver/mergeGraphicsObjects.ts +28 -0
  13. package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +18 -1
  14. package/lib/solvers/TraceCleanupSolver/recognizeStairStepPattern.ts +56 -0
  15. package/lib/solvers/TraceCleanupSolver/sub-solver/UntangleTraceSubsolver.ts +370 -0
  16. package/lib/solvers/TraceCleanupSolver/sub-solver/findAllLShapedTurns.ts +48 -0
  17. package/lib/solvers/TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles.ts +36 -0
  18. package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +107 -0
  19. package/lib/solvers/TraceCleanupSolver/sub-solver/generateRectangleCandidates.ts +55 -0
  20. package/lib/solvers/TraceCleanupSolver/sub-solver/getTraceObstacles.ts +25 -0
  21. package/lib/solvers/TraceCleanupSolver/sub-solver/isPathColliding.ts +58 -0
  22. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCandidates.ts +33 -0
  23. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCollision.ts +20 -0
  24. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeIntersectionPoints.ts +26 -0
  25. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeLSapes.ts +33 -0
  26. package/lib/solvers/TraceCleanupSolver/turnMinimization.ts +50 -56
  27. package/lib/solvers/TraceCleanupSolver/visualizeTightRectangle.ts +24 -0
  28. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +17 -3
  29. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts +26 -11
  30. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +95 -75
  31. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/filterLabelsAtTraceEdges.ts +74 -0
  32. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/groupLabelsByChipAndOrientation.ts +45 -0
  33. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/mergeLabelGroup.ts +71 -0
  34. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +150 -27
  35. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/isPointInsideLabel.ts +23 -0
  36. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/visualizeDecomposition.ts +54 -0
  37. package/package.json +1 -1
  38. package/tests/examples/__snapshots__/example29.snap.svg +6 -6
  39. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +8 -8
@@ -0,0 +1,74 @@
1
+ import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
2
+ import type { SolvedTracePath } from "../../../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
3
+ import type { Point } from "graphics-debug"
4
+ import { distance } from "@tscircuit/math-utils"
5
+
6
+ /**
7
+ * Filters a list of labels, returning only those that are physically located
8
+ * near the start or end point of any trace.
9
+ *
10
+ * This is hack so we do not need to know the position of ports
11
+ * and using only the trace geometry. we find out which labels are
12
+ * at/near port of chips and only keep those for merging.
13
+ */
14
+ export const filterLabelsAtTraceEdges = ({
15
+ labels,
16
+ traces,
17
+ distanceThreshold = 0.5, // Example threshold
18
+ }: {
19
+ labels: NetLabelPlacement[]
20
+ traces: SolvedTracePath[]
21
+ distanceThreshold?: number
22
+ }): NetLabelPlacement[] => {
23
+ // 1. Group traces by their net ID for efficient lookup
24
+ const tracesByNetId = new Map<string, SolvedTracePath[]>()
25
+ if (!traces) {
26
+ throw new Error("No traces provided to filterLabelsAtTraceEdges")
27
+ }
28
+ for (const trace of traces) {
29
+ if (!trace.globalConnNetId) continue
30
+ if (!tracesByNetId.has(trace.globalConnNetId)) {
31
+ tracesByNetId.set(trace.globalConnNetId, [])
32
+ }
33
+ tracesByNetId.get(trace.globalConnNetId)!.push(trace)
34
+ }
35
+
36
+ const filteredLabels: NetLabelPlacement[] = []
37
+
38
+ // 2. Iterate through labels and check proximity against only relevant traces
39
+ for (const label of labels) {
40
+ // Check if it's a port-only label (no associated MSP connection pairs)
41
+ if (label.mspConnectionPairIds.length === 0) {
42
+ filteredLabels.push(label)
43
+ continue // Skip trace-edge checks for port-only labels
44
+ }
45
+
46
+ const relevantTraces = tracesByNetId.get(label.globalConnNetId)
47
+ let isNearTraceEdge = false
48
+
49
+ if (!relevantTraces || relevantTraces.length === 0) {
50
+ continue
51
+ }
52
+
53
+ for (const trace of relevantTraces) {
54
+ if (trace.tracePath.length === 0) continue
55
+
56
+ const startPoint = trace.tracePath[0]
57
+ const endPoint = trace.tracePath[trace.tracePath.length - 1]
58
+
59
+ const startDist = distance(label.center, startPoint)
60
+ const endDist = distance(label.center, endPoint)
61
+
62
+ if (startDist <= distanceThreshold || endDist <= distanceThreshold) {
63
+ isNearTraceEdge = true
64
+ break // Found a nearby edge, no need to check other traces for this label
65
+ }
66
+ }
67
+
68
+ if (isNearTraceEdge) {
69
+ filteredLabels.push(label)
70
+ }
71
+ }
72
+
73
+ return filteredLabels
74
+ }
@@ -0,0 +1,45 @@
1
+ import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
2
+ import type { InputProblem } from "lib/types/InputProblem"
3
+
4
+ /**
5
+ * Groups NetLabelPlacement objects by their associated chip ID and orientation.
6
+ * Labels are grouped if they belong to the same chip and are positioned
7
+ * with the same orientation relative to that chip.
8
+ *
9
+ * @param labels An array of NetLabelPlacement objects to be grouped.
10
+ * @param chips An array of Chip objects from the InputProblem. (Currently not directly used for grouping logic, but part of the signature).
11
+ * @returns A record where keys are in the format "chipId-orientation" (e.g., "U1-left")
12
+ * and values are arrays of NetLabelPlacement objects belonging to that group.
13
+ */
14
+ export const groupLabelsByChipAndOrientation = ({
15
+ labels,
16
+ chips,
17
+ }: {
18
+ labels: NetLabelPlacement[]
19
+ chips: InputProblem["chips"]
20
+ }): Record<string, NetLabelPlacement[]> => {
21
+ const groupedLabels: Record<string, NetLabelPlacement[]> = {}
22
+
23
+ for (const label of labels) {
24
+ if (label.pinIds.length === 0) {
25
+ // Labels without pinIds cannot be associated with a chip and orientation for merging
26
+ continue
27
+ }
28
+
29
+ // Extract chipId from the first pinId (e.g., "U1.1" -> "U1")
30
+ const chipId = label.pinIds[0].split(".")[0]
31
+ if (!chipId) {
32
+ // Should not happen if pinIds are well-formed, but good to guard
33
+ continue
34
+ }
35
+
36
+ const key = `${chipId}-${label.orientation}`
37
+
38
+ if (!groupedLabels[key]) {
39
+ groupedLabels[key] = []
40
+ }
41
+ groupedLabels[key].push(label)
42
+ }
43
+
44
+ return groupedLabels
45
+ }
@@ -0,0 +1,71 @@
1
+ import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
2
+ import { getRectBounds } from "../../../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
3
+ import type { Point } from "graphics-debug" // Assuming Point is from graphics-debug or similar
4
+
5
+ /**
6
+ * Merges a group of NetLabelPlacement objects into a single, larger NetLabelPlacement.
7
+ * It calculates a new bounding box that encompasses all labels in the group,
8
+ * aggregates unique pin and MSP connection pair IDs, and creates a synthetic
9
+ * merged label.
10
+ *
11
+ * @param group An array of NetLabelPlacement objects to be merged. Assumes the group is not empty.
12
+ * @param groupKey A string key representing the group (e.g., "chip1-left"), used for generating the merged label's globalConnNetId.
13
+ * @returns An object containing the newly created merged NetLabelPlacement and a Set of the original globalConnNetIds that were merged.
14
+ * @throws Error if the input group is empty.
15
+ */
16
+ export const mergeLabelGroup = (
17
+ group: NetLabelPlacement[],
18
+ groupKey: string,
19
+ ): {
20
+ mergedLabel: NetLabelPlacement
21
+ originalNetIds: Set<string>
22
+ } => {
23
+ if (group.length === 0) {
24
+ throw new Error("Cannot merge an empty group of labels.")
25
+ }
26
+
27
+ let minX = Infinity
28
+ let minY = Infinity
29
+ let maxX = -Infinity
30
+ let maxY = -Infinity
31
+
32
+ const allPinIds = new Set<string>()
33
+ const allMspConnectionPairIds = new Set<string>()
34
+ const originalNetIds = new Set<string>()
35
+
36
+ for (const label of group) {
37
+ const bounds = getRectBounds(label.center, label.width, label.height)
38
+ minX = Math.min(minX, bounds.minX)
39
+ minY = Math.min(minY, bounds.minY)
40
+ maxX = Math.max(maxX, bounds.maxX)
41
+ maxY = Math.max(maxY, bounds.maxY)
42
+
43
+ label.pinIds.forEach((id) => allPinIds.add(id))
44
+ label.mspConnectionPairIds.forEach((id) => allMspConnectionPairIds.add(id))
45
+ originalNetIds.add(label.globalConnNetId)
46
+ }
47
+
48
+ const newWidth = maxX - minX
49
+ const newHeight = maxY - minY
50
+ const newCenter: Point = { x: minX + newWidth / 2, y: minY + newHeight / 2 }
51
+
52
+ // Use the first label as a template for properties that are consistent across the group
53
+ // like orientation.
54
+ const template = group[0]!
55
+ const syntheticId = `merged-group-${groupKey}`
56
+
57
+ const mergedLabel: NetLabelPlacement = {
58
+ ...template, // Copy common properties
59
+ globalConnNetId: syntheticId,
60
+ center: newCenter,
61
+ width: newWidth,
62
+ height: newHeight,
63
+ pinIds: Array.from(allPinIds),
64
+ mspConnectionPairIds: Array.from(allMspConnectionPairIds),
65
+ }
66
+
67
+ return {
68
+ mergedLabel,
69
+ originalNetIds,
70
+ }
71
+ }
@@ -6,14 +6,17 @@ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/
6
6
  import type { InputProblem } from "lib/types/InputProblem"
7
7
  import { detectTraceLabelOverlap } from "../../detectTraceLabelOverlap"
8
8
  import { SingleOverlapSolver } from "../SingleOverlapSolver/SingleOverlapSolver"
9
+ import { isPointInsideLabel } from "./isPointInsideLabel"
10
+ import { visualizeDecomposition } from "./visualizeDecomposition"
9
11
 
10
12
  type Overlap = ReturnType<typeof detectTraceLabelOverlap>[0]
11
13
 
12
14
  // Define a type for the input of the internal overlap solver to avoid conflicts
13
- interface OverlapCollectionSolverInput {
15
+ export interface OverlapCollectionSolverInput {
14
16
  inputProblem: InputProblem
15
17
  traces: SolvedTracePath[]
16
- netLabelPlacements: NetLabelPlacement[]
18
+ initialNetLabelPlacements: NetLabelPlacement[]
19
+ mergedNetLabelPlacements: NetLabelPlacement[]
17
20
  mergedLabelNetIdMap: Record<string, Set<string>>
18
21
  }
19
22
 
@@ -23,7 +26,8 @@ interface OverlapCollectionSolverInput {
23
26
  */
24
27
  export class OverlapAvoidanceStepSolver extends BaseSolver {
25
28
  inputProblem: InputProblem
26
- netLabelPlacements: NetLabelPlacement[]
29
+ initialNetLabelPlacements: NetLabelPlacement[]
30
+ mergedNetLabelPlacements: NetLabelPlacement[]
27
31
  mergedLabelNetIdMap: Record<string, Set<string>>
28
32
 
29
33
  allTraces: SolvedTracePath[]
@@ -36,15 +40,21 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
36
40
  private overlapQueue: Overlap[] = []
37
41
  private recentlyFailed: Set<string> = new Set()
38
42
 
43
+ private currentlyProcessingOverlap: Overlap | null = null
44
+ private decomposedChildLabels: NetLabelPlacement[] | null = null
45
+
39
46
  constructor(solverInput: OverlapCollectionSolverInput) {
40
47
  super()
41
48
  this.inputProblem = solverInput.inputProblem
42
- this.netLabelPlacements = solverInput.netLabelPlacements
49
+ this.initialNetLabelPlacements = solverInput.initialNetLabelPlacements
50
+ this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements
43
51
  this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap
44
52
  this.allTraces = [...solverInput.traces]
45
53
  }
46
54
 
47
55
  override _step() {
56
+ this.currentlyProcessingOverlap = null
57
+ this.decomposedChildLabels = null
48
58
  if (this.activeSubSolver) {
49
59
  this.activeSubSolver.step()
50
60
 
@@ -65,19 +75,14 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
65
75
  const overlapId = `${this.activeSubSolver.initialTrace.mspPairId}-${this.activeSubSolver.label.globalConnNetId}`
66
76
  this.recentlyFailed.add(overlapId)
67
77
  this.activeSubSolver = null
78
+ } else {
68
79
  }
69
80
  return
70
81
  }
71
82
 
72
- const overlaps = detectTraceLabelOverlap(
73
- this.allTraces,
74
- this.netLabelPlacements,
75
- ).filter((o) => {
76
- const originalNetIds = this.mergedLabelNetIdMap[o.label.globalConnNetId]
77
- if (originalNetIds) {
78
- return !originalNetIds.has(o.trace.globalConnNetId)
79
- }
80
- return o.trace.globalConnNetId !== o.label.globalConnNetId
83
+ const overlaps = detectTraceLabelOverlap({
84
+ traces: this.allTraces,
85
+ netLabels: this.mergedNetLabelPlacements,
81
86
  })
82
87
 
83
88
  if (overlaps.length === 0) {
@@ -85,6 +90,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
85
90
  return
86
91
  }
87
92
 
93
+ // Filter out overlaps that have recently failed to avoid infinite loops
88
94
  const nonFailedOverlaps = overlaps.filter((o) => {
89
95
  const overlapId = `${o.trace.mspPairId}-${o.label.globalConnNetId}`
90
96
  return !this.recentlyFailed.has(overlapId)
@@ -98,24 +104,104 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
98
104
  this.overlapQueue = nonFailedOverlaps
99
105
 
100
106
  const nextOverlap = this.overlapQueue.shift()
107
+ this.currentlyProcessingOverlap = nextOverlap ?? null
101
108
 
102
109
  if (nextOverlap) {
103
110
  const traceToFix = this.allTraces.find(
104
111
  (t) => t.mspPairId === nextOverlap.trace.mspPairId,
105
- )
106
- if (traceToFix) {
107
- const labelId = nextOverlap.label.globalConnNetId
108
- const detourCount = this.detourCountByLabel[labelId] || 0
109
- this.detourCountByLabel[labelId] = detourCount + 1
110
-
111
- this.activeSubSolver = new SingleOverlapSolver({
112
- trace: traceToFix,
113
- label: nextOverlap.label,
114
- problem: this.inputProblem,
115
- paddingBuffer: this.PADDING_BUFFER,
116
- detourCount,
117
- })
112
+ )!
113
+ const labelToAvoid = nextOverlap.label
114
+ const traceStartPoint = traceToFix.tracePath[0]
115
+
116
+ const originalNetIds =
117
+ this.mergedLabelNetIdMap[labelToAvoid.globalConnNetId]
118
+ const isSelfOverlap = originalNetIds?.has(traceToFix.globalConnNetId)
119
+
120
+ if (isSelfOverlap) {
121
+ const childLabels = this.initialNetLabelPlacements.filter((l) =>
122
+ originalNetIds.has(l.globalConnNetId),
123
+ )
124
+ this.decomposedChildLabels = childLabels
125
+ let actualOverlapLabel: NetLabelPlacement | null = null
126
+
127
+ for (const childLabel of childLabels) {
128
+ const overlapsWithChild = detectTraceLabelOverlap({
129
+ traces: [traceToFix],
130
+ netLabels: [childLabel],
131
+ })
132
+ if (overlapsWithChild.length > 0) {
133
+ actualOverlapLabel = childLabel
134
+ break
135
+ }
136
+ }
137
+
138
+ if (actualOverlapLabel) {
139
+ const labelId = actualOverlapLabel.globalConnNetId
140
+ const detourCount = this.detourCountByLabel[labelId] || 0
141
+ this.detourCountByLabel[labelId] = detourCount + 1
142
+ this.activeSubSolver = new SingleOverlapSolver({
143
+ trace: traceToFix,
144
+ label: actualOverlapLabel,
145
+ problem: this.inputProblem,
146
+ paddingBuffer: this.PADDING_BUFFER,
147
+ detourCount,
148
+ })
149
+ } else {
150
+ const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
151
+ this.recentlyFailed.add(overlapId)
152
+ }
153
+ return
154
+ }
155
+
156
+ if (
157
+ originalNetIds &&
158
+ isPointInsideLabel({ point: traceStartPoint, label: labelToAvoid })
159
+ ) {
160
+ const childLabels = this.initialNetLabelPlacements.filter((l) =>
161
+ originalNetIds.has(l.globalConnNetId),
162
+ )
163
+ this.decomposedChildLabels = childLabels
164
+ let actualOverlapLabel: NetLabelPlacement | null = null
165
+
166
+ for (const childLabel of childLabels) {
167
+ const overlapsWithChild = detectTraceLabelOverlap({
168
+ traces: [traceToFix],
169
+ netLabels: [childLabel],
170
+ })
171
+ if (overlapsWithChild.length > 0) {
172
+ actualOverlapLabel = childLabel
173
+ break
174
+ }
175
+ }
176
+
177
+ if (actualOverlapLabel) {
178
+ const labelId = actualOverlapLabel.globalConnNetId
179
+ const detourCount = this.detourCountByLabel[labelId] || 0
180
+ this.detourCountByLabel[labelId] = detourCount + 1
181
+ this.activeSubSolver = new SingleOverlapSolver({
182
+ trace: traceToFix,
183
+ label: actualOverlapLabel,
184
+ problem: this.inputProblem,
185
+ paddingBuffer: this.PADDING_BUFFER,
186
+ detourCount,
187
+ })
188
+ } else {
189
+ const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
190
+ this.recentlyFailed.add(overlapId)
191
+ }
192
+ return
118
193
  }
194
+
195
+ const labelId = labelToAvoid.globalConnNetId
196
+ const detourCount = this.detourCountByLabel[labelId] || 0
197
+ this.detourCountByLabel[labelId] = detourCount + 1
198
+ this.activeSubSolver = new SingleOverlapSolver({
199
+ trace: traceToFix,
200
+ label: labelToAvoid,
201
+ problem: this.inputProblem,
202
+ paddingBuffer: this.PADDING_BUFFER,
203
+ detourCount,
204
+ })
119
205
  }
120
206
  }
121
207
 
@@ -130,7 +216,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
130
216
  if (this.activeSubSolver) {
131
217
  return this.activeSubSolver.visualize()
132
218
  }
133
- // When idle, show all the traces
219
+
134
220
  const graphics = visualizeInputProblem(this.inputProblem)
135
221
  if (!graphics.lines) graphics.lines = []
136
222
  for (const trace of this.allTraces) {
@@ -139,6 +225,43 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
139
225
  strokeColor: "purple",
140
226
  })
141
227
  }
228
+
229
+ if (this.currentlyProcessingOverlap) {
230
+ const { trace, label } = this.currentlyProcessingOverlap
231
+
232
+ // Highlight the colliding trace
233
+ graphics.lines!.push({
234
+ points: trace.tracePath,
235
+ strokeColor: "red",
236
+ })
237
+
238
+ if (this.decomposedChildLabels) {
239
+ visualizeDecomposition({
240
+ decomposedChildLabels: this.decomposedChildLabels,
241
+ collidingTrace: trace,
242
+ mergedLabel: label,
243
+ graphics,
244
+ })
245
+ } else {
246
+ // Standard case: highlight the entire label
247
+ if (!graphics.rects) graphics.rects = []
248
+ graphics.rects.push({
249
+ center: label.center,
250
+ width: label.width,
251
+ height: label.height,
252
+ fill: "yellow",
253
+ })
254
+ if (!graphics.texts) graphics.texts = []
255
+ graphics.texts.push({
256
+ x: label.center.x,
257
+ y: label.center.y + label.height / 2 + 0.5,
258
+ text: `COLLISION: Trace ${trace.mspPairId} vs Label ${label.globalConnNetId}`,
259
+ fontSize: 0.3,
260
+ color: "red",
261
+ })
262
+ }
263
+ }
264
+
142
265
  return graphics
143
266
  }
144
267
  }
@@ -0,0 +1,23 @@
1
+ import type { Point } from "graphics-debug"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
4
+
5
+ /**
6
+ * Checks if a point is inside the bounding box of a net label.
7
+ */
8
+ export const isPointInsideLabel = ({
9
+ point,
10
+ label,
11
+ }: {
12
+ point: Point
13
+ label: NetLabelPlacement
14
+ }): boolean => {
15
+ const bounds = getRectBounds(label.center, label.width, label.height)
16
+
17
+ return (
18
+ point.x >= bounds.minX &&
19
+ point.x <= bounds.maxX &&
20
+ point.y >= bounds.minY &&
21
+ point.y <= bounds.maxY
22
+ )
23
+ }
@@ -0,0 +1,54 @@
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
+
5
+ interface VisualizeDecompositionParams {
6
+ decomposedChildLabels: NetLabelPlacement[]
7
+ collidingTrace: SolvedTracePath
8
+ mergedLabel: NetLabelPlacement
9
+ graphics: GraphicsObject
10
+ }
11
+
12
+ /**
13
+ * Visualizes the decomposition of a merged label during overlap avoidance.
14
+ * It draws individual child labels with distinct colors based on their relation
15
+ * to the colliding trace and adds a textual report to the graphics.
16
+ *
17
+ * @param {VisualizeDecompositionParams} params - The parameters for visualization.
18
+ * @param {NetLabelPlacement[]} params.decomposedChildLabels - The individual labels that make up the merged group.
19
+ * @param {SolvedTracePath} params.collidingTrace - The trace that is currently colliding with the merged label.
20
+ * @param {NetLabelPlacement} params.mergedLabel - The original merged label that is being decomposed.
21
+ * @param {GraphicsObject} params.graphics - The graphics object to which the visualization elements will be added.
22
+ * @returns {GraphicsObject} The modified graphics object with decomposition visualization elements.
23
+ */
24
+ export const visualizeDecomposition = (
25
+ params: VisualizeDecompositionParams,
26
+ ): GraphicsObject => {
27
+ const { decomposedChildLabels, collidingTrace, mergedLabel, graphics } =
28
+ params
29
+
30
+ if (!graphics.rects) graphics.rects = []
31
+ if (!graphics.texts) graphics.texts = []
32
+
33
+ for (const childLabel of decomposedChildLabels) {
34
+ const isOwnLabel =
35
+ childLabel.globalConnNetId === collidingTrace.globalConnNetId
36
+
37
+ graphics.rects.push({
38
+ center: childLabel.center,
39
+ width: childLabel.width,
40
+ height: childLabel.height,
41
+ fill: isOwnLabel ? "green" : "red", // Green for own label, red for others
42
+ })
43
+ }
44
+
45
+ graphics.texts.push({
46
+ x: mergedLabel.center.x,
47
+ y: mergedLabel.center.y + mergedLabel.height / 2 + 0.5,
48
+ text: `DECOMPOSITION: Trace ${collidingTrace.mspPairId} vs Merged Label ${mergedLabel.globalConnNetId}`,
49
+ fontSize: 0.3,
50
+ color: "blue",
51
+ })
52
+
53
+ return graphics
54
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.44",
4
+ "version": "0.0.46",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -543,7 +543,7 @@ x+" data-x="-8.4" data-y="-17" cx="198.31710258539454" cy="399.68032912258366" r
543
543
  <circle data-type="point" data-label="" data-x="3.5999999999999996" data-y="0.5000000000000013" cx="413.0516274346982" cy="86.52581371734914" r="3" fill="hsl(80, 100%, 50%, 0.9)" />
544
544
  </g>
545
545
  <g>
546
- <circle data-type="point" data-label="" data-x="-6.262500000000001" data-y="-12" cx="236.56668982417676" cy="310.2076104353738" r="3" fill="hsl(80, 100%, 50%, 0.9)" />
546
+ <circle data-type="point" data-label="" data-x="-6.112500000000001" data-y="-12" cx="239.25087138479304" cy="310.2076104353738" r="3" fill="hsl(80, 100%, 50%, 0.9)" />
547
547
  </g>
548
548
  <g>
549
549
  <circle data-type="point" data-label="" data-x="-4" data-y="-14.225" cx="277.0530950301392" cy="350.0229702511822" r="3" fill="hsl(80, 100%, 50%, 0.9)" />
@@ -849,7 +849,7 @@ x+" data-x="-8.4" data-y="-17" cx="198.31710258539454" cy="399.68032912258366" r
849
849
  <polyline data-points="-4.449999999999999,-10 -4,-10 -4,-10.45" data-type="line" data-label="" points="269.00055034829035,274.4185229604899 277.0530950301392,274.4185229604899 277.0530950301392,282.47106764233877" fill="none" stroke="purple" stroke-width="1" />
850
850
  </g>
851
851
  <g>
852
- <polyline data-points="-5.550000000000001,-10 -5.750000000000001,-10 -5.750000000000001,-10.1 -7.075,-10.1 -7.075,-5 -8.4,-5" data-type="line" data-label="" points="249.31655223710416,274.4185229604899 245.73764348961578,274.4185229604899 245.73764348961578,276.2079773342341 222.02737303750516,276.2079773342341 222.02737303750516,184.94580427328003 198.31710258539454,184.94580427328003" fill="none" stroke="purple" stroke-width="1" />
852
+ <polyline data-points="-5.550000000000001,-10 -5.750000000000001,-10 -5.750000000000001,-9 -8.200000000000001,-9 -8.200000000000001,-5 -8.4,-5" data-type="line" data-label="" points="249.31655223710416,274.4185229604899 245.73764348961578,274.4185229604899 245.73764348961578,256.52397922304795 201.89601133288292,256.52397922304795 201.89601133288292,184.94580427328003 198.31710258539454,184.94580427328003" fill="none" stroke="purple" stroke-width="1" />
853
853
  </g>
854
854
  <g>
855
855
  <polyline data-points="-4,-11.55 -4,-12 -3,-12 -3,-12.45" data-type="line" data-label="" points="277.0530950301392,302.1550657535249 277.0530950301392,310.2076104353738 294.9476387675812,310.2076104353738 294.9476387675812,318.2601551172227" fill="none" stroke="purple" stroke-width="1" />
@@ -858,7 +858,7 @@ x+" data-x="-8.4" data-y="-17" cx="198.31710258539454" cy="399.68032912258366" r
858
858
  <polyline data-points="-4.449999999999999,-12 -4,-12 -4,-11.55" data-type="line" data-label="" points="269.00055034829035,310.2076104353738 277.0530950301392,310.2076104353738 277.0530950301392,302.1550657535249" fill="none" stroke="purple" stroke-width="1" />
859
859
  </g>
860
860
  <g>
861
- <polyline data-points="-5.550000000000001,-12 -6.9750000000000005,-12 -6.9750000000000005,-9.2 -8.4,-9.2" data-type="line" data-label="" points="249.31655223710416,310.2076104353738 223.81682741124934,310.2076104353738 223.81682741124934,260.1028879705363 198.31710258539454,260.1028879705363" fill="none" stroke="purple" stroke-width="1" />
861
+ <polyline data-points="-5.550000000000001,-12 -6.675000000000001,-12 -6.675000000000001,-9.2 -8.4,-9.2" data-type="line" data-label="" points="249.31655223710416,310.2076104353738 229.18519053248195,310.2076104353738 229.18519053248195,260.1028879705363 198.31710258539454,260.1028879705363" fill="none" stroke="purple" stroke-width="1" />
862
862
  </g>
863
863
  <g>
864
864
  <polyline data-points="-4,-14.45 -4,-14 -3,-14 -3,-13.55" data-type="line" data-label="" points="277.0530950301392,354.0492425921067 277.0530950301392,345.9966979102578 294.9476387675812,345.9966979102578 294.9476387675812,337.94415322840894" fill="none" stroke="purple" stroke-width="1" />
@@ -876,7 +876,7 @@ x+" data-x="-8.4" data-y="-17" cx="198.31710258539454" cy="399.68032912258366" r
876
876
  <polyline data-points="-3,-16.45 -3,-16 -4,-16 -4,-15.55" data-type="line" data-label="" points="294.9476387675812,389.8383300669906 294.9476387675812,381.7857853851417 277.0530950301392,381.7857853851417 277.0530950301392,373.73324070329284" fill="none" stroke="purple" stroke-width="1" />
877
877
  </g>
878
878
  <g>
879
- <polyline data-points="-5.550000000000001,-16 -6.875000000000001,-16 -6.875000000000001,-9.9 -8.200000000000001,-9.9 -8.200000000000001,-10 -8.4,-10" data-type="line" data-label="" points="249.31655223710416,381.7857853851417 225.60628178499354,381.7857853851417 225.60628178499354,272.6290685867457 201.89601133288292,272.6290685867457 201.89601133288292,274.4185229604899 198.31710258539454,274.4185229604899" fill="none" stroke="purple" stroke-width="1" />
879
+ <polyline data-points="-5.550000000000001,-16 -6.875000000000001,-16 -6.875000000000001,-10 -8.4,-10" data-type="line" data-label="" points="249.31655223710416,381.7857853851417 225.60628178499354,381.7857853851417 225.60628178499354,274.4185229604899 198.31710258539454,274.4185229604899" fill="none" stroke="purple" stroke-width="1" />
880
880
  </g>
881
881
  <g>
882
882
  <polyline data-points="-4,-18.45 -4,-18 -3,-18 -3,-17.55" data-type="line" data-label="" points="277.0530950301392,425.6274175418746 277.0530950301392,417.5748728600257 294.9476387675812,417.5748728600257 294.9476387675812,409.52232817817685" fill="none" stroke="purple" stroke-width="1" />
@@ -1056,7 +1056,7 @@ x+" data-x="-8.4" data-y="-17" cx="198.31710258539454" cy="399.68032912258366" r
1056
1056
  <rect data-type="rect" data-label="" data-x="3.3739999999999997" data-y="0.7000000000000015" x="404.9811882091119" y="81.15745059611653" width="8.052544681848872" height="3.5789087474883985" fill="hsl(80, 100%, 50%, 0.35)" stroke="black" stroke-width="0.05588295598214286" />
1057
1057
  </g>
1058
1058
  <g>
1059
- <rect data-type="rect" data-label="" data-x="-5.750000000000001" data-y="-9.775" x="243.94818911587157" y="266.365978278641" width="3.5789087474883843" height="8.052544681848872" fill="hsl(80, 100%, 50%, 0.35)" stroke="black" stroke-width="0.05588295598214286" />
1059
+ <rect data-type="rect" data-label="" data-x="-5.750000000000001" data-y="-10.225" x="243.94818911587157" y="274.4185229604899" width="3.5789087474883843" height="8.052544681848872" fill="hsl(80, 100%, 50%, 0.35)" stroke="black" stroke-width="0.05588295598214286" />
1060
1060
  </g>
1061
1061
  <g>
1062
1062
  <rect data-type="rect" data-label="" data-x="-3.775" data-y="-11.775" x="277.0530950301392" y="304.3918837207052" width="8.052544681848929" height="3.5789087474884127" fill="hsl(80, 100%, 50%, 0.35)" stroke="black" stroke-width="0.05588295598214286" />
@@ -1065,7 +1065,7 @@ x+" data-x="-8.4" data-y="-17" cx="198.31710258539454" cy="399.68032912258366" r
1065
1065
  <rect data-type="rect" data-label="" data-x="3.3739999999999997" data-y="0.5000000000000013" x="404.9811882091119" y="84.73635934360493" width="8.052544681848872" height="3.5789087474883985" fill="hsl(80, 100%, 50%, 0.35)" stroke="black" stroke-width="0.05588295598214286" />
1066
1066
  </g>
1067
1067
  <g>
1068
- <rect data-type="rect" data-label="" data-x="-6.262500000000001" data-y="-11.775" x="234.77723545043256" y="302.1550657535249" width="3.5789087474883843" height="8.052544681848872" fill="hsl(80, 100%, 50%, 0.35)" stroke="black" stroke-width="0.05588295598214286" />
1068
+ <rect data-type="rect" data-label="" data-x="-6.112500000000001" data-y="-11.775" x="237.46141701104887" y="302.1550657535249" width="3.578908747488356" height="8.052544681848872" fill="hsl(80, 100%, 50%, 0.35)" stroke="black" stroke-width="0.05588295598214286" />
1069
1069
  </g>
1070
1070
  <g>
1071
1071
  <rect data-type="rect" data-label="" data-x="-3.775" data-y="-14.225" x="277.0530950301392" y="348.23351587743804" width="8.052544681848929" height="3.578908747488356" fill="hsl(80, 100%, 50%, 0.35)" stroke="black" stroke-width="0.05588295598214286" />
@@ -160,28 +160,28 @@ x-" data-x="-0.42" data-y="-0.1" cx="279.0224461302513" cy="382.68590086027035"
160
160
  <rect data-type="rect" data-label="schematic_component_6" data-x="0" data-y="0" x="277.802876074604" y="349.51359534666426" width="43.416693981043295" height="56.58805058203393" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.020499027410714285" />
161
161
  </g>
162
162
  <g>
163
- <rect data-type="rect" data-label="connectivity_net0" data-x="-4.78" data-y="2.7600000000000002" x="55.35329792453959" y="238.288806271632" width="21.95226100165104" height="9.75656044517828" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
163
+ <rect data-type="rect" data-label="connectivity_net2" data-x="6" data-y="0.776" x="587.3297561978845" y="328.976035609564" width="9.756560445178138" height="21.952261001651095" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
164
164
  </g>
165
165
  <g>
166
- <rect data-type="rect" data-label="connectivity_net3" data-x="3" data-y="-0.93" x="440.9813495202105" y="415.85820637387644" width="9.756560445178309" height="14.634840667767435" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
166
+ <rect data-type="rect" data-label="connectivity_net2" data-x="3.7199999999999998" data-y="3.225" x="476.10496712285226" y="209.50695295835615" width="9.756560445178252" height="21.952261001651095" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
167
167
  </g>
168
168
  <g>
169
- <rect data-type="rect" data-label="connectivity_net3" data-x="6" data-y="-0.7010000000000001" x="587.3297561978845" y="404.68694466414735" width="9.756560445178138" height="14.634840667767378" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
169
+ <rect data-type="rect" data-label="connectivity_net1" data-x="0.3" data-y="0.8059999999999999" x="309.2677835103039" y="327.51255154278726" width="9.756560445178309" height="21.952261001651095" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
170
170
  </g>
171
171
  <g>
172
- <rect data-type="rect" data-label="connectivity_net3" data-x="-4.995" data-y="-0.7958008" x="50.96284572420936" y="409.31159334140364" width="9.756560445178224" height="14.634840667767378" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
172
+ <rect data-type="rect" data-label="connectivity_net1" data-x="0.78" data-y="3.1950000000000003" x="332.68352857873174" y="210.97043702513287" width="9.756560445178252" height="21.952261001651124" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
173
173
  </g>
174
174
  <g>
175
- <rect data-type="rect" data-label="connectivity_net1" data-x="0.78" data-y="3.1950000000000003" x="332.68352857873174" y="210.97043702513287" width="9.756560445178252" height="21.952261001651124" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
175
+ <rect data-type="rect" data-label="connectivity_net3" data-x="6" data-y="-0.7010000000000001" x="587.3297561978845" y="404.68694466414735" width="9.756560445178138" height="14.634840667767378" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
176
176
  </g>
177
177
  <g>
178
- <rect data-type="rect" data-label="merged-group-M1-y+" data-x="-0.39186722499999993" data-y="0.4655" x="241.7653395028014" y="327.51255154278726" width="77.25900445268081" height="55.17334931748309" fill="hsl(296, 100%, 50%, 0.3)" stroke="hsl(296, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
178
+ <rect data-type="rect" data-label="connectivity_net3" data-x="3" data-y="-0.93" x="440.9813495202105" y="415.85820637387644" width="9.756560445178309" height="14.634840667767435" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
179
179
  </g>
180
180
  <g>
181
- <rect data-type="rect" data-label="connectivity_net2" data-x="3.7199999999999998" data-y="3.225" x="476.10496712285226" y="209.50695295835615" width="9.756560445178252" height="21.952261001651095" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
181
+ <rect data-type="rect" data-label="connectivity_net0" data-x="-4.78" data-y="2.7600000000000002" x="55.35329792453959" y="238.288806271632" width="21.95226100165104" height="9.75656044517828" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
182
182
  </g>
183
183
  <g>
184
- <rect data-type="rect" data-label="connectivity_net2" data-x="6" data-y="0.776" x="587.3297561978845" y="328.976035609564" width="9.756560445178138" height="21.952261001651095" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
184
+ <rect data-type="rect" data-label="connectivity_net4" data-x="-1.08373445" data-y="0.12500000000000014" x="241.7653395028014" y="360.73363985861926" width="9.75656044517828" height="21.952261001651095" fill="hsl(40, 100%, 50%, 0.3)" stroke="hsl(40, 100%, 50%, 1)" stroke-width="0.020499027410714285" />
185
185
  </g>
186
186
  <g id="crosshair" style="display: none">
187
187
  <line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5" />