@tscircuit/schematic-trace-solver 0.0.44 → 0.0.45

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 (25) hide show
  1. package/dist/index.d.ts +14 -3
  2. package/dist/index.js +754 -95
  3. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +0 -1
  4. package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +83 -36
  5. package/lib/solvers/TraceCleanupSolver/hasCollisions.ts +16 -4
  6. package/lib/solvers/TraceCleanupSolver/is4PointRectangle.ts +17 -0
  7. package/lib/solvers/TraceCleanupSolver/isSegmentAnEndpointSegment.ts +36 -0
  8. package/lib/solvers/TraceCleanupSolver/mergeGraphicsObjects.ts +28 -0
  9. package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +18 -1
  10. package/lib/solvers/TraceCleanupSolver/recognizeStairStepPattern.ts +56 -0
  11. package/lib/solvers/TraceCleanupSolver/sub-solver/UntangleTraceSubsolver.ts +370 -0
  12. package/lib/solvers/TraceCleanupSolver/sub-solver/findAllLShapedTurns.ts +48 -0
  13. package/lib/solvers/TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles.ts +36 -0
  14. package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +107 -0
  15. package/lib/solvers/TraceCleanupSolver/sub-solver/generateRectangleCandidates.ts +55 -0
  16. package/lib/solvers/TraceCleanupSolver/sub-solver/getTraceObstacles.ts +25 -0
  17. package/lib/solvers/TraceCleanupSolver/sub-solver/isPathColliding.ts +58 -0
  18. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCandidates.ts +33 -0
  19. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCollision.ts +20 -0
  20. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeIntersectionPoints.ts +26 -0
  21. package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeLSapes.ts +33 -0
  22. package/lib/solvers/TraceCleanupSolver/turnMinimization.ts +50 -56
  23. package/lib/solvers/TraceCleanupSolver/visualizeTightRectangle.ts +24 -0
  24. package/package.json +1 -1
  25. package/tests/examples/__snapshots__/example29.snap.svg +6 -6
@@ -0,0 +1,33 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import type { Point } from "@tscircuit/math-utils"
3
+
4
+ /**
5
+ * Visualizes a set of candidate paths and optional intersection points.
6
+ * It draws each candidate path as a line with a specified color and marks intersection points with green circles.
7
+ * This function is useful for debugging and understanding the rerouting process.
8
+ */
9
+ export const visualizeCandidates = (
10
+ candidates: Point[][],
11
+ color = "gray",
12
+ intersectionPoints: Point[] = [],
13
+ ): GraphicsObject => {
14
+ const graphics: GraphicsObject = { lines: [], circles: [] }
15
+
16
+ for (const candidate of candidates) {
17
+ graphics.lines!.push({
18
+ points: candidate,
19
+ strokeColor: color,
20
+ })
21
+ }
22
+
23
+ // Draw intersection points
24
+ for (const point of intersectionPoints) {
25
+ graphics.circles!.push({
26
+ center: point,
27
+ radius: 0.01, // Larger radius for intersection points
28
+ fill: "green",
29
+ })
30
+ }
31
+
32
+ return graphics
33
+ }
@@ -0,0 +1,20 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import type { CollisionInfo } from "./isPathColliding"
3
+
4
+ /**
5
+ * Visualizes a collision point if collision information is provided and a collision occurred.
6
+ * It draws a red circle at the collision point to highlight the location of the collision.
7
+ */
8
+ export const visualizeCollision = (
9
+ collisionInfo: CollisionInfo | null,
10
+ ): GraphicsObject => {
11
+ const collisionGraphics: GraphicsObject = { circles: [] }
12
+ if (collisionInfo?.isColliding && collisionInfo.collisionPoint) {
13
+ collisionGraphics.circles!.push({
14
+ center: collisionInfo.collisionPoint,
15
+ radius: 0.01,
16
+ fill: "red",
17
+ })
18
+ }
19
+ return collisionGraphics
20
+ }
@@ -0,0 +1,26 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { GraphicsObject } from "graphics-debug"
3
+
4
+ /**
5
+ * Visualizes a set of intersection points by drawing circles at their locations.
6
+ * This function is used to highlight where different trace segments or obstacles intersect.
7
+ */
8
+ export const visualizeIntersectionPoints = (
9
+ points: Point[],
10
+ color = "red",
11
+ ): GraphicsObject => {
12
+ const graphics: GraphicsObject = { circles: [] }
13
+
14
+ for (const point of points) {
15
+ graphics.circles!.push({
16
+ center: {
17
+ x: point.x,
18
+ y: point.y,
19
+ },
20
+ radius: 0.01,
21
+ fill: color,
22
+ })
23
+ }
24
+
25
+ return graphics
26
+ }
@@ -0,0 +1,33 @@
1
+ import type { LShape } from "./findAllLShapedTurns"
2
+ import type { GraphicsObject } from "graphics-debug"
3
+
4
+ /**
5
+ * Visualizes L-shaped turns by drawing a blue circle at the corner point (p2)
6
+ * and light blue lines connecting p1, p2, and p3.
7
+ * This function can visualize a single L-shape or an array of L-shapes.
8
+ */
9
+ export const visualizeLSapes = (lShapes: LShape[] | LShape): GraphicsObject => {
10
+ const graphics: GraphicsObject = { circles: [], lines: [] }
11
+
12
+ const lShapesArray = Array.isArray(lShapes) ? lShapes : [lShapes]
13
+
14
+ for (const lShape of lShapesArray) {
15
+ // Draw the center point as a blue ball
16
+ graphics.circles!.push({
17
+ center: {
18
+ x: lShape.p2.x,
19
+ y: lShape.p2.y,
20
+ },
21
+ radius: 0.01,
22
+ fill: "blue",
23
+ })
24
+
25
+ // Draw the two lines in a light blue color
26
+ graphics.lines!.push({
27
+ points: [lShape.p1, lShape.p2, lShape.p3],
28
+ strokeColor: "lightblue",
29
+ })
30
+ }
31
+
32
+ return graphics
33
+ }
@@ -4,75 +4,32 @@ import { countTurns } from "./countTurns"
4
4
  import { simplifyPath } from "./simplifyPath"
5
5
  import { tryConnectPoints } from "./tryConnectPoints"
6
6
  import { hasCollisionsWithLabels } from "./hasCollisionsWithLabels"
7
-
7
+ import { recognizeStairStepPattern } from "./recognizeStairStepPattern"
8
+ import { isSegmentAnEndpointSegment } from "./isSegmentAnEndpointSegment"
9
+
10
+ /**
11
+ * Minimizes the number of turns in a given path while avoiding collisions with obstacles and labels.
12
+ * This function employs an iterative approach, attempting to simplify the path in several ways:
13
+ * 1. **Stair-step pattern recognition**: It first looks for and attempts to simplify stair-step patterns by connecting the start and end points of the pattern with a simpler path, if no collisions are introduced.
14
+ * 2. **Point removal and reconnection**: If no stair-step optimization is possible, it tries to remove intermediate points and reconnect the remaining segments with a simpler path, prioritizing solutions that reduce turns or path length.
15
+ * 3. **Collinear segment merging**: Finally, it attempts to merge collinear segments to further simplify the path.
16
+ * The process continues until no further improvements can be made without introducing collisions.
17
+ */
8
18
  export const minimizeTurns = ({
9
19
  path,
10
20
  obstacles,
11
21
  labelBounds,
22
+ originalPath,
12
23
  }: {
13
24
  path: Point[]
14
25
  obstacles: any[]
15
26
  labelBounds: any[]
27
+ originalPath: Point[]
16
28
  }): Point[] => {
17
29
  if (path.length <= 2) {
18
30
  return path
19
31
  }
20
32
 
21
- const recognizeStairStepPattern = (
22
- pathToCheck: Point[],
23
- startIdx: number,
24
- ): number => {
25
- if (startIdx >= pathToCheck.length - 3) return -1
26
-
27
- let endIdx = startIdx
28
- let isStairStep = true
29
-
30
- for (
31
- let i = startIdx;
32
- i < pathToCheck.length - 2 && i < startIdx + 10;
33
- i++
34
- ) {
35
- if (i + 2 >= pathToCheck.length) break
36
-
37
- const p1 = pathToCheck[i]
38
- const p2 = pathToCheck[i + 1]
39
- const p3 = pathToCheck[i + 2]
40
-
41
- const seg1Vertical = p1.x === p2.x
42
- const seg2Vertical = p2.x === p3.x
43
-
44
- if (seg1Vertical === seg2Vertical) {
45
- break
46
- }
47
-
48
- const seg1Direction = seg1Vertical
49
- ? Math.sign(p2.y - p1.y)
50
- : Math.sign(p2.x - p1.x)
51
-
52
- if (i > startIdx) {
53
- const prevP = pathToCheck[i - 1]
54
- const prevSegVertical = prevP.x === p1.x
55
- const prevDirection = prevSegVertical
56
- ? Math.sign(p1.y - prevP.y)
57
- : Math.sign(p1.x - prevP.x)
58
-
59
- if (
60
- (seg1Vertical &&
61
- prevSegVertical &&
62
- seg1Direction !== prevDirection) ||
63
- (!seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection)
64
- ) {
65
- isStairStep = false
66
- break
67
- }
68
- }
69
-
70
- endIdx = i + 2
71
- }
72
-
73
- return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1
74
- }
75
-
76
33
  let optimizedPath = [...path]
77
34
  let currentTurns = countTurns(optimizedPath)
78
35
  let improved = true
@@ -85,6 +42,21 @@ export const minimizeTurns = ({
85
42
  const stairEndIdx = recognizeStairStepPattern(optimizedPath, startIdx)
86
43
 
87
44
  if (stairEndIdx > 0) {
45
+ if (
46
+ isSegmentAnEndpointSegment(
47
+ optimizedPath[startIdx],
48
+ optimizedPath[startIdx + 1],
49
+ originalPath,
50
+ ) ||
51
+ isSegmentAnEndpointSegment(
52
+ optimizedPath[stairEndIdx - 1],
53
+ optimizedPath[stairEndIdx],
54
+ originalPath,
55
+ )
56
+ ) {
57
+ continue
58
+ }
59
+
88
60
  const startPoint = optimizedPath[startIdx]
89
61
  const endPoint = optimizedPath[stairEndIdx]
90
62
 
@@ -129,6 +101,21 @@ export const minimizeTurns = ({
129
101
 
130
102
  if (endIdx >= optimizedPath.length) continue
131
103
 
104
+ if (
105
+ isSegmentAnEndpointSegment(
106
+ optimizedPath[startIdx],
107
+ optimizedPath[startIdx + 1],
108
+ originalPath,
109
+ ) ||
110
+ isSegmentAnEndpointSegment(
111
+ optimizedPath[endIdx - 1],
112
+ optimizedPath[endIdx],
113
+ originalPath,
114
+ )
115
+ ) {
116
+ continue
117
+ }
118
+
132
119
  const startPoint = optimizedPath[startIdx]
133
120
  const endPoint = optimizedPath[endIdx]
134
121
 
@@ -179,6 +166,13 @@ export const minimizeTurns = ({
179
166
  const p2 = optimizedPath[i + 1]
180
167
  const p3 = optimizedPath[i + 2]
181
168
 
169
+ if (
170
+ isSegmentAnEndpointSegment(p1, p2, originalPath) ||
171
+ isSegmentAnEndpointSegment(p2, p3, originalPath)
172
+ ) {
173
+ continue
174
+ }
175
+
182
176
  const allVertical = p1.x === p2.x && p2.x === p3.x
183
177
  const allHorizontal = p1.y === p2.y && p2.y === p3.y
184
178
 
@@ -0,0 +1,24 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import type { Rectangle } from "./sub-solver/generateRectangleCandidates"
3
+
4
+ /**
5
+ * Visualizes a given rectangle by drawing it as a green-stroked rectangle.
6
+ * This function is useful for highlighting specific rectangular areas in a graphical representation.
7
+ */
8
+ export const visualizeTightRectangle = (
9
+ rectangle: Rectangle,
10
+ ): GraphicsObject => {
11
+ const graphics: GraphicsObject = { rects: [] }
12
+
13
+ graphics.rects!.push({
14
+ center: {
15
+ x: rectangle.x + rectangle.width / 2,
16
+ y: rectangle.y + rectangle.height / 2,
17
+ },
18
+ width: rectangle.width,
19
+ height: rectangle.height,
20
+ stroke: "green",
21
+ })
22
+
23
+ return graphics
24
+ }
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.45",
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" />