@tscircuit/schematic-trace-solver 0.0.68 → 0.0.69

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.
package/dist/index.js CHANGED
@@ -2799,19 +2799,32 @@ var detectTraceLabelOverlap = ({
2799
2799
  };
2800
2800
 
2801
2801
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts
2802
- var findTraceViolationZone = (path, labelBounds) => {
2803
- const isPointInside = (p) => p.x > labelBounds.minX && p.x < labelBounds.maxX && p.y > labelBounds.minY && p.y < labelBounds.maxY;
2804
- let firstInsideIndex = -1;
2805
- let lastInsideIndex = -1;
2806
- for (let i = 0; i < path.length; i++) {
2807
- if (isPointInside(path[i])) {
2808
- if (firstInsideIndex === -1) {
2809
- firstInsideIndex = i;
2810
- }
2811
- lastInsideIndex = i;
2802
+ var findTraceViolationZone = ({
2803
+ path,
2804
+ labelBounds,
2805
+ paddedBounds = labelBounds
2806
+ }) => {
2807
+ let firstCollidingSeg = -1;
2808
+ let lastCollidingSeg = -1;
2809
+ for (let i = 0; i < path.length - 1; i++) {
2810
+ if (segmentIntersectsRect2(path[i], path[i + 1], labelBounds)) {
2811
+ if (firstCollidingSeg === -1) firstCollidingSeg = i;
2812
+ lastCollidingSeg = i;
2812
2813
  }
2813
2814
  }
2814
- return { firstInsideIndex, lastInsideIndex };
2815
+ if (firstCollidingSeg === -1) {
2816
+ return { firstInsideIndex: -1, lastInsideIndex: -1 };
2817
+ }
2818
+ const isInsidePadded = (p) => p.x > paddedBounds.minX && p.x < paddedBounds.maxX && p.y > paddedBounds.minY && p.y < paddedBounds.maxY;
2819
+ let entryIndex = firstCollidingSeg;
2820
+ while (entryIndex > 0 && isInsidePadded(path[entryIndex])) {
2821
+ entryIndex--;
2822
+ }
2823
+ let exitIndex = lastCollidingSeg + 1;
2824
+ while (exitIndex < path.length - 1 && isInsidePadded(path[exitIndex])) {
2825
+ exitIndex++;
2826
+ }
2827
+ return { firstInsideIndex: entryIndex + 1, lastInsideIndex: exitIndex - 1 };
2815
2828
  };
2816
2829
 
2817
2830
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts
@@ -2917,20 +2930,21 @@ var generateFourPointDetourCandidates = ({
2917
2930
  }
2918
2931
  exitPoint = initialTrace.tracePath[exitIndex];
2919
2932
  } else {
2920
- let collidingSegIndex = -1;
2933
+ let firstCollidingSeg = -1;
2934
+ let lastCollidingSeg = -1;
2921
2935
  for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
2922
2936
  if (segmentIntersectsRect(
2923
2937
  initialTrace.tracePath[i],
2924
2938
  initialTrace.tracePath[i + 1],
2925
- { ...paddedLabelBounds, chipId: "temp-obstacle" }
2939
+ { ...labelBounds, chipId: "temp-obstacle" }
2926
2940
  )) {
2927
- collidingSegIndex = i;
2928
- break;
2941
+ if (firstCollidingSeg === -1) firstCollidingSeg = i;
2942
+ lastCollidingSeg = i;
2929
2943
  }
2930
2944
  }
2931
- if (collidingSegIndex === -1) return [];
2932
- entryIndex = collidingSegIndex;
2933
- exitIndex = collidingSegIndex + 1;
2945
+ if (firstCollidingSeg === -1) return [];
2946
+ entryIndex = firstCollidingSeg;
2947
+ exitIndex = lastCollidingSeg + 1;
2934
2948
  entryPoint = initialTrace.tracePath[entryIndex];
2935
2949
  exitPoint = initialTrace.tracePath[exitIndex];
2936
2950
  }
@@ -3039,10 +3053,18 @@ var generateRerouteCandidates = ({
3039
3053
  paddingBuffer,
3040
3054
  detourCount
3041
3055
  });
3042
- const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone(
3043
- initialTrace.tracePath,
3044
- labelBounds
3045
- );
3056
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
3057
+ const paddedLabelBounds = {
3058
+ minX: labelBounds.minX - effectivePadding,
3059
+ maxX: labelBounds.maxX + effectivePadding,
3060
+ minY: labelBounds.minY - effectivePadding,
3061
+ maxY: labelBounds.maxY + effectivePadding
3062
+ };
3063
+ const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone({
3064
+ path: initialTrace.tracePath,
3065
+ labelBounds,
3066
+ paddedBounds: paddedLabelBounds
3067
+ });
3046
3068
  const snipReconnectCandidates = generateSnipAndReconnectCandidates({
3047
3069
  initialTrace,
3048
3070
  firstInsideIndex,
@@ -3131,7 +3153,11 @@ var SingleOverlapSolver = class extends BaseSolver {
3131
3153
  this._tried++;
3132
3154
  const nextCandidatePath = this.queuedCandidatePaths.shift();
3133
3155
  const simplifiedPath = simplifyPath(nextCandidatePath);
3134
- if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles) && !doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)) {
3156
+ const stillOverlapsLabel = detectTraceLabelOverlap({
3157
+ traces: [{ ...this.initialTrace, tracePath: simplifiedPath }],
3158
+ netLabels: [this.label]
3159
+ }).length > 0;
3160
+ if (!stillOverlapsLabel && !isPathCollidingWithObstacles(simplifiedPath, this.obstacles) && !doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)) {
3135
3161
  this.solvedTracePath = simplifiedPath;
3136
3162
  this.solved = true;
3137
3163
  }
@@ -55,10 +55,19 @@ export const generateRerouteCandidates = ({
55
55
  detourCount,
56
56
  })
57
57
 
58
- const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone(
59
- initialTrace.tracePath,
58
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer
59
+ const paddedLabelBounds = {
60
+ minX: labelBounds.minX - effectivePadding,
61
+ maxX: labelBounds.maxX + effectivePadding,
62
+ minY: labelBounds.minY - effectivePadding,
63
+ maxY: labelBounds.maxY + effectivePadding,
64
+ }
65
+
66
+ const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone({
67
+ path: initialTrace.tracePath,
60
68
  labelBounds,
61
- )
69
+ paddedBounds: paddedLabelBounds,
70
+ })
62
71
 
63
72
  const snipReconnectCandidates = generateSnipAndReconnectCandidates({
64
73
  initialTrace,
@@ -9,6 +9,7 @@ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/Schemati
9
9
  import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
10
10
  import { generateRerouteCandidates } from "../../rerouteCollidingTrace"
11
11
  import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
12
+ import { detectTraceLabelOverlap } from "../../detectTraceLabelOverlap"
12
13
 
13
14
  interface SingleOverlapSolverInput {
14
15
  trace: SolvedTracePath
@@ -141,7 +142,17 @@ export class SingleOverlapSolver extends BaseSolver {
141
142
  const nextCandidatePath = this.queuedCandidatePaths.shift()!
142
143
  const simplifiedPath = simplifyPath(nextCandidatePath)
143
144
 
145
+ // A candidate is only valid if it actually clears the label it is meant to
146
+ // avoid. Without this check a "detour" that still grazes the label (e.g. one
147
+ // built around the wrong segment) would be accepted as solved.
148
+ const stillOverlapsLabel =
149
+ detectTraceLabelOverlap({
150
+ traces: [{ ...this.initialTrace, tracePath: simplifiedPath }],
151
+ netLabels: [this.label],
152
+ }).length > 0
153
+
144
154
  if (
155
+ !stillOverlapsLabel &&
145
156
  !isPathCollidingWithObstacles(simplifiedPath, this.obstacles) &&
146
157
  !doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)
147
158
  ) {
@@ -71,25 +71,30 @@ export const generateFourPointDetourCandidates = ({
71
71
  }
72
72
  exitPoint = initialTrace.tracePath[exitIndex]
73
73
  } else {
74
- // STRATEGY 1: Single Label - find the first intersecting segment
75
- let collidingSegIndex = -1
74
+ // STRATEGY 1: Single Label - reroute around the contiguous span of segments
75
+ // that actually overlap the label. Detection uses the *unpadded* bounds so
76
+ // a short pin stub that merely pokes into the padding zone is not mistaken
77
+ // for the real crossing (which would leave the offending segment in place
78
+ // and create a useless loop).
79
+ let firstCollidingSeg = -1
80
+ let lastCollidingSeg = -1
76
81
  for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
77
82
  if (
78
83
  segmentIntersectsRect(
79
84
  initialTrace.tracePath[i],
80
85
  initialTrace.tracePath[i + 1],
81
- { ...paddedLabelBounds, chipId: "temp-obstacle" },
86
+ { ...labelBounds, chipId: "temp-obstacle" },
82
87
  )
83
88
  ) {
84
- collidingSegIndex = i
85
- break
89
+ if (firstCollidingSeg === -1) firstCollidingSeg = i
90
+ lastCollidingSeg = i
86
91
  }
87
92
  }
88
93
 
89
- if (collidingSegIndex === -1) return []
94
+ if (firstCollidingSeg === -1) return []
90
95
 
91
- entryIndex = collidingSegIndex
92
- exitIndex = collidingSegIndex + 1
96
+ entryIndex = firstCollidingSeg
97
+ exitIndex = lastCollidingSeg + 1
93
98
  entryPoint = initialTrace.tracePath[entryIndex]
94
99
  exitPoint = initialTrace.tracePath[exitIndex]
95
100
  }
@@ -1,25 +1,59 @@
1
- import type { Point } from "@tscircuit/math-utils"
1
+ import type { Point, Bounds } from "@tscircuit/math-utils"
2
+ import { segmentIntersectsRect } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
2
3
 
3
- export const findTraceViolationZone = (
4
- path: Point[],
5
- labelBounds: { minX: number; maxX: number; minY: number; maxY: number },
6
- ) => {
7
- const isPointInside = (p: Point) =>
8
- p.x > labelBounds.minX &&
9
- p.x < labelBounds.maxX &&
10
- p.y > labelBounds.minY &&
11
- p.y < labelBounds.maxY
4
+ /**
5
+ * Locates the portion of a trace that must be rerouted to clear a label.
6
+ *
7
+ * Detection is segment-based against the (unpadded) label bounds so it also
8
+ * catches traces that cross the label edge-to-edge, or run *along* one of its
9
+ * edges, without ever placing a vertex strictly inside it (the previous
10
+ * vertex-only test missed both cases).
11
+ *
12
+ * The returned range describes the vertices to remove: the reroute reconnects
13
+ * `path[firstInsideIndex - 1]` to `path[lastInsideIndex + 1]`. Those two anchor
14
+ * vertices are pushed outward until they sit outside the padded label so the
15
+ * detour has room to pivot without immediately re-entering it.
16
+ */
17
+ export const findTraceViolationZone = ({
18
+ path,
19
+ labelBounds,
20
+ paddedBounds = labelBounds,
21
+ }: {
22
+ path: Point[]
23
+ labelBounds: Bounds
24
+ paddedBounds?: Bounds
25
+ }) => {
26
+ let firstCollidingSeg = -1
27
+ let lastCollidingSeg = -1
28
+ for (let i = 0; i < path.length - 1; i++) {
29
+ if (segmentIntersectsRect(path[i]!, path[i + 1]!, labelBounds)) {
30
+ if (firstCollidingSeg === -1) firstCollidingSeg = i
31
+ lastCollidingSeg = i
32
+ }
33
+ }
12
34
 
13
- let firstInsideIndex = -1
14
- let lastInsideIndex = -1
35
+ if (firstCollidingSeg === -1) {
36
+ return { firstInsideIndex: -1, lastInsideIndex: -1 }
37
+ }
15
38
 
16
- for (let i = 0; i < path.length; i++) {
17
- if (isPointInside(path[i])) {
18
- if (firstInsideIndex === -1) {
19
- firstInsideIndex = i
20
- }
21
- lastInsideIndex = i
22
- }
39
+ const isInsidePadded = (p: Point) =>
40
+ p.x > paddedBounds.minX &&
41
+ p.x < paddedBounds.maxX &&
42
+ p.y > paddedBounds.minY &&
43
+ p.y < paddedBounds.maxY
44
+
45
+ // Anchor before the crossing: walk back to the first vertex clear of the
46
+ // padded label.
47
+ let entryIndex = firstCollidingSeg
48
+ while (entryIndex > 0 && isInsidePadded(path[entryIndex]!)) {
49
+ entryIndex--
50
+ }
51
+
52
+ // Anchor after the crossing.
53
+ let exitIndex = lastCollidingSeg + 1
54
+ while (exitIndex < path.length - 1 && isInsidePadded(path[exitIndex]!)) {
55
+ exitIndex++
23
56
  }
24
- return { firstInsideIndex, lastInsideIndex }
57
+
58
+ return { firstInsideIndex: entryIndex + 1, lastInsideIndex: exitIndex - 1 }
25
59
  }
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.68",
4
+ "version": "0.0.69",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",