@tscircuit/schematic-trace-solver 0.0.86 → 0.0.88

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 (117) hide show
  1. package/.agents/skills/schematic-trace-visualization/SKILL.md +115 -0
  2. package/.github/scripts/import-json-bug-report.ts +15 -1
  3. package/.github/workflows/json-bug-report.yml +3 -2
  4. package/AGENTS.md +22 -0
  5. package/dist/index.js +86 -10
  6. package/lib/solvers/Example28Solver/Example28Solver.ts +46 -10
  7. package/lib/solvers/Example28Solver/countCrossingsWithOtherTraces.ts +19 -0
  8. package/lib/solvers/Example28Solver/scoreSolutionTracePath.ts +26 -0
  9. package/lib/testing/PipelineStageDebugRunner.ts +344 -0
  10. package/package.json +3 -2
  11. package/scripts/debug-pipeline-stages.ts +146 -0
  12. package/site/bug-reports/bug-report-20260706T220324Z.page.tsx +4 -0
  13. package/site/bug-reports/bug-report-20260707T020342Z.page.tsx +4 -0
  14. package/site/bug-reports/bug-report-20260707T092615Z.page.tsx +4 -0
  15. package/site/bug-reports/bug-report-20260707T134549Z.page.tsx +4 -0
  16. package/site/bug-reports/bug-report-20260707T134722Z.page.tsx +4 -0
  17. package/site/bug-reports/bug-report-20260707T140410Z.page.tsx +4 -0
  18. package/site/bug-reports/bug-report-20260707T141025Z.page.tsx +4 -0
  19. package/site/bug-reports/bug-report-20260707T141421Z.page.tsx +4 -0
  20. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +357 -3448
  21. package/tests/bug-reports/bug-report-20260707T020342Z/__snapshots__/bug-report-20260707T020342Z.snap.svg +64 -0
  22. package/tests/bug-reports/bug-report-20260707T020342Z/bug-report-20260707T020342Z.json +233 -0
  23. package/tests/bug-reports/bug-report-20260707T020342Z/bug-report-20260707T020342Z.test.ts +12 -0
  24. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +2098 -0
  25. package/tests/bug-reports/bug-report-20260707T092615Z/bug-report-20260707T092615Z.json +1392 -0
  26. package/tests/bug-reports/bug-report-20260707T092615Z/bug-report-20260707T092615Z.test.ts +12 -0
  27. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +857 -0
  28. package/tests/bug-reports/bug-report-20260707T134549Z/bug-report-20260707T134549Z.json +652 -0
  29. package/tests/bug-reports/bug-report-20260707T134549Z/bug-report-20260707T134549Z.test.ts +12 -0
  30. package/tests/bug-reports/bug-report-20260707T134722Z/__snapshots__/bug-report-20260707T134722Z.snap.svg +275 -0
  31. package/tests/bug-reports/bug-report-20260707T134722Z/bug-report-20260707T134722Z.json +265 -0
  32. package/tests/bug-reports/bug-report-20260707T134722Z/bug-report-20260707T134722Z.test.ts +12 -0
  33. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +950 -0
  34. package/tests/bug-reports/bug-report-20260707T140410Z/bug-report-20260707T140410Z.json +610 -0
  35. package/tests/bug-reports/bug-report-20260707T140410Z/bug-report-20260707T140410Z.test.ts +12 -0
  36. package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +218 -0
  37. package/tests/bug-reports/bug-report-20260707T141025Z/bug-report-20260707T141025Z.json +185 -0
  38. package/tests/bug-reports/bug-report-20260707T141025Z/bug-report-20260707T141025Z.test.ts +12 -0
  39. package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +613 -0
  40. package/tests/bug-reports/bug-report-20260707T141421Z/bug-report-20260707T141421Z.json +499 -0
  41. package/tests/bug-reports/bug-report-20260707T141421Z/bug-report-20260707T141421Z.test.ts +12 -0
  42. package/tests/examples/__snapshots__/example01.snap.svg +59 -162
  43. package/tests/examples/__snapshots__/example02.snap.svg +64 -273
  44. package/tests/examples/__snapshots__/example03.snap.svg +85 -290
  45. package/tests/examples/__snapshots__/example04.snap.svg +50 -94
  46. package/tests/examples/__snapshots__/example05.snap.svg +73 -191
  47. package/tests/examples/__snapshots__/example06.snap.svg +49 -95
  48. package/tests/examples/__snapshots__/example07.snap.svg +65 -218
  49. package/tests/examples/__snapshots__/example08.snap.svg +61 -143
  50. package/tests/examples/__snapshots__/example09.snap.svg +123 -493
  51. package/tests/examples/__snapshots__/example10.snap.svg +57 -136
  52. package/tests/examples/__snapshots__/example11.snap.svg +64 -173
  53. package/tests/examples/__snapshots__/example12.snap.svg +58 -159
  54. package/tests/examples/__snapshots__/example13.snap.svg +78 -348
  55. package/tests/examples/__snapshots__/example14.snap.svg +77 -293
  56. package/tests/examples/__snapshots__/example15.snap.svg +140 -984
  57. package/tests/examples/__snapshots__/example16.snap.svg +62 -162
  58. package/tests/examples/__snapshots__/example17.snap.svg +67 -198
  59. package/tests/examples/__snapshots__/example18.snap.svg +69 -249
  60. package/tests/examples/__snapshots__/example19.snap.svg +63 -202
  61. package/tests/examples/__snapshots__/example20.snap.svg +60 -148
  62. package/tests/examples/__snapshots__/example21.snap.svg +73 -298
  63. package/tests/examples/__snapshots__/example22.snap.svg +62 -156
  64. package/tests/examples/__snapshots__/example23.snap.svg +66 -172
  65. package/tests/examples/__snapshots__/example24.snap.svg +64 -164
  66. package/tests/examples/__snapshots__/example25.snap.svg +76 -299
  67. package/tests/examples/__snapshots__/example26.snap.svg +64 -164
  68. package/tests/examples/__snapshots__/example27.snap.svg +83 -300
  69. package/tests/examples/__snapshots__/example28.snap.svg +54 -115
  70. package/tests/examples/__snapshots__/example29.snap.svg +257 -1299
  71. package/tests/examples/__snapshots__/example30.snap.svg +91 -293
  72. package/tests/examples/__snapshots__/example31.snap.svg +47 -88
  73. package/tests/examples/__snapshots__/example32.snap.svg +107 -405
  74. package/tests/examples/__snapshots__/example33.snap.svg +71 -180
  75. package/tests/examples/__snapshots__/example34.snap.svg +55 -116
  76. package/tests/examples/__snapshots__/example35.snap.svg +69 -176
  77. package/tests/examples/__snapshots__/example36.snap.svg +65 -159
  78. package/tests/examples/__snapshots__/example37.snap.svg +75 -223
  79. package/tests/examples/__snapshots__/example38.snap.svg +46 -76
  80. package/tests/examples/__snapshots__/example39.snap.svg +51 -106
  81. package/tests/examples/__snapshots__/example40.snap.svg +83 -270
  82. package/tests/examples/__snapshots__/example41.snap.svg +54 -118
  83. package/tests/examples/__snapshots__/example42.snap.svg +67 -194
  84. package/tests/examples/__snapshots__/example43.snap.svg +62 -168
  85. package/tests/examples/__snapshots__/example44.snap.svg +86 -369
  86. package/tests/examples/__snapshots__/example45.snap.svg +60 -169
  87. package/tests/examples/__snapshots__/example46.snap.svg +80 -342
  88. package/tests/examples/__snapshots__/example47.snap.svg +82 -323
  89. package/tests/examples/__snapshots__/example48.snap.svg +64 -167
  90. package/tests/examples/__snapshots__/example49.snap.svg +120 -565
  91. package/tests/examples/__snapshots__/example50.snap.svg +71 -195
  92. package/tests/fixtures/getLastStepGraphicsObject.ts +38 -0
  93. package/tests/fixtures/getLastStepSvg.ts +9 -0
  94. package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +67 -218
  95. package/tests/repros/__snapshots__/manufacturePartNumber-text-box.snap.svg +47 -87
  96. package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +73 -272
  97. package/tests/repros/__snapshots__/repro-atmega328p-fault-pullup.snap.svg +284 -0
  98. package/tests/repros/__snapshots__/repro-atmega328p-missing-gnd-netlabel.snap.svg +470 -0
  99. package/tests/repros/__snapshots__/repro-bq24074-battery-charger.snap.svg +94 -395
  100. package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +88 -467
  101. package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +109 -569
  102. package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +95 -459
  103. package/tests/repros/__snapshots__/repro51-overlap-junction-crossing.snap.svg +64 -176
  104. package/tests/repros/__snapshots__/trace-overlap-box-resistor.snap.svg +51 -112
  105. package/tests/repros/assets/repro-atmega328p-fault-pullup.input.json +239 -0
  106. package/tests/repros/assets/repro-atmega328p-missing-gnd-netlabel.input.json +432 -0
  107. package/tests/repros/repro-atmega328p-fault-pullup.test.ts +11 -0
  108. package/tests/repros/repro-atmega328p-missing-gnd-netlabel.test.ts +16 -0
  109. package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.snap.svg +59 -159
  110. package/tests/solvers/TraceCleanupSolver/__snapshots__/TraceCleanupSolver.snap.svg +54 -118
  111. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +58 -245
  112. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +73 -222
  113. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +65 -204
  114. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +65 -183
  115. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +54 -139
  116. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +61 -215
  117. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +54 -124
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: schematic-trace-visualization
3
+ description: Work with schematic trace solver visualization in this repo. Use when changing solver `visualize()` or `preview()` output, adding debug graphics, creating SVG snapshots, inspecting pipeline stages, or generating graphics-debug artifacts for AI/headless review.
4
+ ---
5
+
6
+ # Schematic Trace Visualization
7
+
8
+ Use `GraphicsObject` from `graphics-debug` for solver debug views, SVG snapshots, and headless artifacts.
9
+
10
+ ## Graphics Object Types
11
+
12
+ Import visualization types from `graphics-debug`:
13
+
14
+ ```ts
15
+ import type {
16
+ GraphicsObject,
17
+ Point,
18
+ Line,
19
+ Rect,
20
+ Circle,
21
+ Text,
22
+ } from "graphics-debug"
23
+ ```
24
+
25
+ Do not redefine these shapes in repo docs, tests, or utilities.
26
+
27
+ ## Image Functions
28
+
29
+ This repo uses `graphics-debug` for SVG and PNG rendering:
30
+
31
+ ```ts
32
+ import {
33
+ getPngBufferFromGraphicsObject,
34
+ getSvgFromGraphicsObject,
35
+ } from "graphics-debug"
36
+ ```
37
+
38
+ Call them with a white background for stable artifacts:
39
+
40
+ ```ts
41
+ const svg = getSvgFromGraphicsObject(graphics, { backgroundColor: "white" })
42
+ const png = await getPngBufferFromGraphicsObject(graphics, {
43
+ backgroundColor: "white",
44
+ pngWidth: 1536,
45
+ pngHeight: 1536,
46
+ })
47
+ ```
48
+
49
+ ## Existing Repo Helpers
50
+
51
+ - `tests/fixtures/matcher.ts`: `expect(solver).toMatchSolverSnapshot(import.meta.path)` converts `solver.visualize()` to SVG and snapshots it.
52
+ - `tests/fixtures/getLastStepGraphicsObject.ts`: filters a `GraphicsObject` to its highest numbered `step`.
53
+ - `tests/fixtures/getLastStepSvg.ts`: converts only the last step to SVG.
54
+ - `lib/testing/PipelineStageDebugRunner.ts`: runs `SchematicTracePipelineSolver` and writes per-stage PNG/SVG/graphics JSON artifacts.
55
+ - `scripts/debug-pipeline-stages.ts`: CLI wrapper around the stage debug runner.
56
+
57
+ ## Minimal SVG Tests
58
+
59
+ Prefer focused solver SVG snapshots over broad dumps:
60
+
61
+ ```ts
62
+ const solver = new SchematicTracePipelineSolver(inputProblem as any)
63
+ solver.solve()
64
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
65
+ ```
66
+
67
+ For multi-step graphics where only the final stage matters:
68
+
69
+ ```ts
70
+ expect(getLastStepSvg(solver.visualize())).toMatchSvgSnapshot(import.meta.path)
71
+ ```
72
+
73
+ Update snapshots with:
74
+
75
+ ```bash
76
+ BUN_UPDATE_SNAPSHOTS=1 bun test
77
+ ```
78
+
79
+ ## Headless Stage Debugging
80
+
81
+ To inspect a pipeline run without Cosmos:
82
+
83
+ ```bash
84
+ bun run debug:pipeline tests/assets/example01.json
85
+ ```
86
+
87
+ Useful options:
88
+
89
+ ```bash
90
+ bun run debug:pipeline tests/assets/example17.json --out tmp/schematic-trace-debug/example17
91
+ bun run debug:pipeline tests/assets/example17.json --stop-after schematicTraceLinesSolver
92
+ bun run debug:pipeline tests/assets/example17.json --no-json
93
+ bun run debug:pipeline tests/assets/example17.json --svg --step-pngs
94
+ ```
95
+
96
+ The runner writes:
97
+
98
+ - `logs.txt`
99
+ - `stageNN-stageName.png`
100
+ - `stageNN-stageName.graphics.json` unless `--no-json` is passed
101
+
102
+ Pass `--svg` when SVG companion artifacts are useful.
103
+ Pass `--step-pngs` to split graphics with `step` values into per-step PNGs.
104
+
105
+ Stage numbers are important because this pipeline can reuse the same solver name more than once, for example `netLabelPlacementSolver`.
106
+
107
+ ## Choosing The View
108
+
109
+ Use solver-native `visualize()` for internal state, candidates, failed geometry, staged progress, net labels, and trace cleanup state.
110
+
111
+ Use `SchematicTracePipelineSolver.visualize()` for final combined output after `solve()`.
112
+
113
+ Use `visualizeInputProblem(inputProblem)` when the question is about the raw input geometry, schematic components, pins, direct connections, or net connection labels before routing.
114
+
115
+ Keep SVG backgrounds white. Treat colors, labels, line widths, opacity, and `step` values as snapshot-sensitive.
@@ -48,15 +48,19 @@ async function main() {
48
48
  const reportId = `bug-report-${timestamp}`
49
49
  const branchName = `json-bug-report/${reportId}`
50
50
  const reportDir = path.join("tests", "bug-reports", reportId)
51
+ const siteReportDir = path.join("site", "bug-reports")
51
52
  const reportDirAbs = path.join(repoRoot, reportDir)
52
53
  const jsonPath = path.join(reportDir, `${reportId}.json`)
53
54
  const testPath = path.join(reportDir, `${reportId}.test.ts`)
55
+ const pagePath = path.join(siteReportDir, `${reportId}.page.tsx`)
54
56
 
55
57
  writeOutput("report_id", reportId)
56
58
  writeOutput("branch_name", branchName)
57
59
  writeOutput("report_dir", reportDir)
60
+ writeOutput("site_report_dir", siteReportDir)
58
61
  writeOutput("json_path", jsonPath)
59
62
  writeOutput("test_path", testPath)
63
+ writeOutput("page_path", pagePath)
60
64
 
61
65
  if (fs.existsSync(reportDirAbs)) {
62
66
  writeOutput("created", "false")
@@ -70,15 +74,17 @@ async function main() {
70
74
  assertInputProblem(jsonAttachment.value)
71
75
 
72
76
  fs.mkdirSync(reportDirAbs, { recursive: true })
77
+ fs.mkdirSync(path.join(repoRoot, siteReportDir), { recursive: true })
73
78
  fs.writeFileSync(
74
79
  path.join(repoRoot, jsonPath),
75
80
  `${JSON.stringify(jsonAttachment.value, null, 2)}\n`,
76
81
  )
77
82
  fs.writeFileSync(path.join(repoRoot, testPath), getTestFile(reportId))
83
+ fs.writeFileSync(path.join(repoRoot, pagePath), getPageFile(reportId))
78
84
 
79
85
  writeOutput("created", "true")
80
86
  writeOutput("attachment_url", jsonAttachment.url)
81
- console.log(`Generated ${jsonPath} and ${testPath}`)
87
+ console.log(`Generated ${jsonPath}, ${testPath}, and ${pagePath}`)
82
88
  }
83
89
 
84
90
  function isJsonBugReportIssue(issue: IssueEvent["issue"], body: string) {
@@ -458,6 +464,14 @@ test("${reportId}", () => {
458
464
  `
459
465
  }
460
466
 
467
+ function getPageFile(reportId: string) {
468
+ return `import { PipelineDebugger } from "site/components/PipelineDebugger"
469
+ import inputProblem from "../../tests/bug-reports/${reportId}/${reportId}.json"
470
+
471
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
472
+ `
473
+ }
474
+
461
475
  function writeOutput(key: string, value: string) {
462
476
  if (!githubOutputPath) return
463
477
  fs.appendFileSync(githubOutputPath, `${key}=${value}\n`)
@@ -64,7 +64,7 @@ jobs:
64
64
 
65
65
  - name: Format generated test
66
66
  if: steps.import.outputs.created == 'true'
67
- run: ./node_modules/.bin/biome format --write "${{ steps.import.outputs.test_path }}"
67
+ run: ./node_modules/.bin/biome format --write "${{ steps.import.outputs.test_path }}" "${{ steps.import.outputs.page_path }}"
68
68
 
69
69
  - name: Open pull request
70
70
  id: pr
@@ -82,6 +82,7 @@ jobs:
82
82
  git config user.email "tscircuit-bot@users.noreply.github.com"
83
83
  git checkout -B "$BRANCH_NAME"
84
84
  git add "${{ steps.import.outputs.report_dir }}"
85
+ git add "${{ steps.import.outputs.page_path }}"
85
86
 
86
87
  if git diff --cached --quiet; then
87
88
  echo "No changes to commit."
@@ -96,7 +97,7 @@ jobs:
96
97
  {
97
98
  echo "Generated from #${ISSUE_NUMBER}."
98
99
  echo
99
- echo "Adds a snapshot-only regression test for the attached JSON solver input."
100
+ echo "Adds a snapshot-only regression test and debugger page for the attached JSON solver input."
100
101
  echo
101
102
  echo "Workflow run: ${RUN_URL}"
102
103
  } > "$pr_body_file"
package/AGENTS.md ADDED
@@ -0,0 +1,22 @@
1
+ # Agent Notes
2
+
3
+ - Use `bunx tsc --noEmit` to type check.
4
+ - Use `bun test path/to/test.ts` to run a test.
5
+ - Use `bun add <package>` to add a package.
6
+
7
+ ## Solver Visualization
8
+
9
+ For schematic trace solver visualization work, use the repo-local skill:
10
+
11
+ - `.agents/skills/schematic-trace-visualization/SKILL.md`
12
+
13
+ This repo supports headless stage artifact generation without starting Cosmos:
14
+
15
+ ```bash
16
+ bun run debug:pipeline tests/assets/example01.json
17
+ ```
18
+
19
+ The command writes per-stage PNG and graphics JSON artifacts under `tmp/schematic-trace-debug/...`.
20
+ Pass `--svg` when SVG companion files are needed.
21
+
22
+ Interactive debugging still exists through `site/**/*.page.tsx`, but agents should prefer the headless debug runner when they need to inspect solver state programmatically.
package/dist/index.js CHANGED
@@ -5777,6 +5777,57 @@ var getPointDistanceFromRect = (point, rect) => {
5777
5777
  return dx + dy;
5778
5778
  };
5779
5779
 
5780
+ // lib/solvers/Example28Solver/countCrossingsWithOtherTraces.ts
5781
+ var countCrossingsWithOtherTraces = (params) => {
5782
+ const { trace, outputTraces } = params;
5783
+ let crossingCount = 0;
5784
+ for (const otherTrace of outputTraces) {
5785
+ if (otherTrace.mspPairId === trace.mspPairId) continue;
5786
+ if (otherTrace.globalConnNetId === trace.globalConnNetId) continue;
5787
+ crossingCount += countPathIntersections(
5788
+ trace.tracePath,
5789
+ otherTrace.tracePath
5790
+ );
5791
+ }
5792
+ return crossingCount;
5793
+ };
5794
+
5795
+ // lib/solvers/Example28Solver/doesPathRunAlongChipBoundary.ts
5796
+ var doesPathRunAlongChipBoundary = (path, chipObstacles) => {
5797
+ for (let i = 0; i < path.length - 1; i++) {
5798
+ const start = path[i];
5799
+ const end = path[i + 1];
5800
+ for (const obstacle of chipObstacles) {
5801
+ if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
5802
+ if (getSegmentOverlapWithRectSpan(start, end, obstacle) > EPS7) {
5803
+ return true;
5804
+ }
5805
+ }
5806
+ }
5807
+ return false;
5808
+ };
5809
+ var getSegmentOverlapWithRectSpan = (start, end, rect) => {
5810
+ const isVertical4 = Math.abs(start.x - end.x) < EPS7;
5811
+ if (isVertical4) {
5812
+ return Math.min(Math.max(start.y, end.y), rect.maxY) - Math.max(Math.min(start.y, end.y), rect.minY);
5813
+ }
5814
+ return Math.min(Math.max(start.x, end.x), rect.maxX) - Math.max(Math.min(start.x, end.x), rect.minX);
5815
+ };
5816
+
5817
+ // lib/solvers/Example28Solver/scoreSolutionTracePath.ts
5818
+ var scoreSolutionTracePath = (params) => {
5819
+ const { traces, outputTraces, chipObstacles } = params;
5820
+ let chipEdgeRuns = 0;
5821
+ let traceCrossings = 0;
5822
+ for (const trace of traces) {
5823
+ if (doesPathRunAlongChipBoundary(trace.tracePath, chipObstacles)) {
5824
+ chipEdgeRuns += 1;
5825
+ }
5826
+ traceCrossings += countCrossingsWithOtherTraces({ trace, outputTraces });
5827
+ }
5828
+ return { chipEdgeRuns, traceCrossings };
5829
+ };
5830
+
5780
5831
  // lib/solvers/Example28Solver/visualize.ts
5781
5832
  var CANDIDATE_SELECTED_COLOR = "blue";
5782
5833
  var CANDIDATE_REJECTED_COLOR = "red";
@@ -5849,6 +5900,13 @@ ${label.netId ?? label.globalConnNetId}`
5849
5900
  var LABEL_OUTWARD_STEP = 0.1;
5850
5901
  var LABEL_MAX_OUTWARD_STEPS = 10;
5851
5902
  var LABEL_TRACE_CLEARANCE = 0.1;
5903
+ var isScoreBetter = (rerouteScore, labelMoveScore) => {
5904
+ if (!rerouteScore) return false;
5905
+ if (rerouteScore.chipEdgeRuns !== labelMoveScore.chipEdgeRuns) {
5906
+ return rerouteScore.chipEdgeRuns < labelMoveScore.chipEdgeRuns;
5907
+ }
5908
+ return rerouteScore.traceCrossings < labelMoveScore.traceCrossings;
5909
+ };
5852
5910
  var Example28Solver = class extends BaseSolver {
5853
5911
  inputProblem;
5854
5912
  traces;
@@ -5951,7 +6009,7 @@ var Example28Solver = class extends BaseSolver {
5951
6009
  );
5952
6010
  if (traceIndex === -1) return;
5953
6011
  const currentTrace = this.outputTraces[traceIndex];
5954
- if (this.tryMoveLabelOutward(overlap.label)) return;
6012
+ const labelMove = this.tryMoveLabelOutward(overlap.label);
5955
6013
  const rerouteResult = findBestReroutePath({
5956
6014
  trace: currentTrace,
5957
6015
  obstacleLabel: overlap.label,
@@ -5961,6 +6019,26 @@ var Example28Solver = class extends BaseSolver {
5961
6019
  chipObstacles: this.chipObstacles
5962
6020
  });
5963
6021
  this.currentCandidateResults = rerouteResult.candidateResults;
6022
+ let rerouteScore = null;
6023
+ if (rerouteResult.bestPath) {
6024
+ rerouteScore = scoreSolutionTracePath({
6025
+ traces: [{ ...currentTrace, tracePath: rerouteResult.bestPath }],
6026
+ outputTraces: this.outputTraces,
6027
+ chipObstacles: this.chipObstacles
6028
+ });
6029
+ }
6030
+ if (labelMove) {
6031
+ const labelMoveScore = scoreSolutionTracePath({
6032
+ traces: [labelMove.connectorTrace, currentTrace],
6033
+ outputTraces: this.outputTraces,
6034
+ chipObstacles: this.chipObstacles
6035
+ });
6036
+ if (!isScoreBetter(rerouteScore, labelMoveScore)) {
6037
+ this.outputNetLabelPlacements[labelMove.labelIndex] = labelMove.movedLabel;
6038
+ this.outputTraces.push(labelMove.connectorTrace);
6039
+ return;
6040
+ }
6041
+ }
5964
6042
  if (!rerouteResult.bestPath) return;
5965
6043
  const originalTracePath = currentTrace.tracePath;
5966
6044
  this.outputTraces[traceIndex] = {
@@ -5978,17 +6056,17 @@ var Example28Solver = class extends BaseSolver {
5978
6056
  const labelIndex = this.outputNetLabelPlacements.findIndex(
5979
6057
  (label2) => label2.globalConnNetId === labelToMove.globalConnNetId && label2.anchorPoint.x === labelToMove.anchorPoint.x && label2.anchorPoint.y === labelToMove.anchorPoint.y && label2.pinIds.join(",") === labelToMove.pinIds.join(",")
5980
6058
  );
5981
- if (labelIndex === -1) return false;
6059
+ if (labelIndex === -1) return null;
5982
6060
  const label = this.outputNetLabelPlacements[labelIndex];
5983
- if (label.mspConnectionPairIds.length > 0) return false;
5984
- if (label.pinIds.length !== 1) return false;
6061
+ if (label.mspConnectionPairIds.length > 0) return null;
6062
+ if (label.pinIds.length !== 1) return null;
5985
6063
  if (this.outputNetLabelPlacements.filter(
5986
6064
  (otherLabel) => otherLabel.globalConnNetId === label.globalConnNetId
5987
6065
  ).length !== 1) {
5988
- return false;
6066
+ return null;
5989
6067
  }
5990
6068
  const outward = dir(label.orientation);
5991
- if (outward.x === 0 && outward.y === 0) return false;
6069
+ if (outward.x === 0 && outward.y === 0) return null;
5992
6070
  for (let step = 1; step <= LABEL_MAX_OUTWARD_STEPS; step++) {
5993
6071
  const distance3 = step * LABEL_OUTWARD_STEP;
5994
6072
  const candidate = {
@@ -6024,11 +6102,9 @@ var Example28Solver = class extends BaseSolver {
6024
6102
  }).length > 0) {
6025
6103
  continue;
6026
6104
  }
6027
- this.outputNetLabelPlacements[labelIndex] = candidate;
6028
- this.outputTraces.push(connectorTrace);
6029
- return true;
6105
+ return { labelIndex, movedLabel: candidate, connectorTrace };
6030
6106
  }
6031
- return false;
6107
+ return null;
6032
6108
  }
6033
6109
  visualize() {
6034
6110
  return visualizeExample28Solver({
@@ -20,6 +20,10 @@ import type {
20
20
  import { dir } from "lib/utils/dir"
21
21
  import { moveAttachedLabelsToReroutedTrace } from "./labelMovement"
22
22
  import { findBestReroutePath } from "./reroute"
23
+ import {
24
+ scoreSolutionTracePath,
25
+ type TracePathScore,
26
+ } from "./scoreSolutionTracePath"
23
27
  import type { Example28SolverParams, RerouteCandidateResult } from "./types"
24
28
  import { visualizeExample28Solver } from "./visualize"
25
29
 
@@ -27,6 +31,17 @@ const LABEL_OUTWARD_STEP = 0.1
27
31
  const LABEL_MAX_OUTWARD_STEPS = 10
28
32
  const LABEL_TRACE_CLEARANCE = 0.1
29
33
 
34
+ const isScoreBetter = (
35
+ rerouteScore: TracePathScore | null,
36
+ labelMoveScore: TracePathScore,
37
+ ) => {
38
+ if (!rerouteScore) return false
39
+ if (rerouteScore.chipEdgeRuns !== labelMoveScore.chipEdgeRuns) {
40
+ return rerouteScore.chipEdgeRuns < labelMoveScore.chipEdgeRuns
41
+ }
42
+ return rerouteScore.traceCrossings < labelMoveScore.traceCrossings
43
+ }
44
+
30
45
  export class Example28Solver extends BaseSolver {
31
46
  inputProblem: InputProblem
32
47
  traces: SolvedTracePath[]
@@ -160,7 +175,7 @@ export class Example28Solver extends BaseSolver {
160
175
  if (traceIndex === -1) return
161
176
 
162
177
  const currentTrace = this.outputTraces[traceIndex]!
163
- if (this.tryMoveLabelOutward(overlap.label)) return
178
+ const labelMove = this.tryMoveLabelOutward(overlap.label)
164
179
 
165
180
  const rerouteResult = findBestReroutePath({
166
181
  trace: currentTrace,
@@ -172,6 +187,29 @@ export class Example28Solver extends BaseSolver {
172
187
  })
173
188
  this.currentCandidateResults = rerouteResult.candidateResults
174
189
 
190
+ let rerouteScore: TracePathScore | null = null
191
+ if (rerouteResult.bestPath) {
192
+ rerouteScore = scoreSolutionTracePath({
193
+ traces: [{ ...currentTrace, tracePath: rerouteResult.bestPath }],
194
+ outputTraces: this.outputTraces,
195
+ chipObstacles: this.chipObstacles,
196
+ })
197
+ }
198
+
199
+ if (labelMove) {
200
+ const labelMoveScore = scoreSolutionTracePath({
201
+ traces: [labelMove.connectorTrace, currentTrace],
202
+ outputTraces: this.outputTraces,
203
+ chipObstacles: this.chipObstacles,
204
+ })
205
+ if (!isScoreBetter(rerouteScore, labelMoveScore)) {
206
+ this.outputNetLabelPlacements[labelMove.labelIndex] =
207
+ labelMove.movedLabel
208
+ this.outputTraces.push(labelMove.connectorTrace)
209
+ return
210
+ }
211
+ }
212
+
175
213
  if (!rerouteResult.bestPath) return
176
214
 
177
215
  const originalTracePath = currentTrace.tracePath
@@ -195,21 +233,21 @@ export class Example28Solver extends BaseSolver {
195
233
  label.anchorPoint.y === labelToMove.anchorPoint.y &&
196
234
  label.pinIds.join(",") === labelToMove.pinIds.join(","),
197
235
  )
198
- if (labelIndex === -1) return false
236
+ if (labelIndex === -1) return null
199
237
 
200
238
  const label = this.outputNetLabelPlacements[labelIndex]!
201
- if (label.mspConnectionPairIds.length > 0) return false
202
- if (label.pinIds.length !== 1) return false
239
+ if (label.mspConnectionPairIds.length > 0) return null
240
+ if (label.pinIds.length !== 1) return null
203
241
  if (
204
242
  this.outputNetLabelPlacements.filter(
205
243
  (otherLabel) => otherLabel.globalConnNetId === label.globalConnNetId,
206
244
  ).length !== 1
207
245
  ) {
208
- return false
246
+ return null
209
247
  }
210
248
 
211
249
  const outward = dir(label.orientation)
212
- if (outward.x === 0 && outward.y === 0) return false
250
+ if (outward.x === 0 && outward.y === 0) return null
213
251
 
214
252
  for (let step = 1; step <= LABEL_MAX_OUTWARD_STEPS; step++) {
215
253
  const distance = step * LABEL_OUTWARD_STEP
@@ -252,12 +290,10 @@ export class Example28Solver extends BaseSolver {
252
290
  continue
253
291
  }
254
292
 
255
- this.outputNetLabelPlacements[labelIndex] = candidate
256
- this.outputTraces.push(connectorTrace)
257
- return true
293
+ return { labelIndex, movedLabel: candidate, connectorTrace }
258
294
  }
259
295
 
260
- return false
296
+ return null
261
297
  }
262
298
 
263
299
  override visualize(): GraphicsObject {
@@ -0,0 +1,19 @@
1
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
2
+ import { countPathIntersections } from "./geometry"
3
+
4
+ export const countCrossingsWithOtherTraces = (params: {
5
+ trace: SolvedTracePath
6
+ outputTraces: SolvedTracePath[]
7
+ }) => {
8
+ const { trace, outputTraces } = params
9
+ let crossingCount = 0
10
+ for (const otherTrace of outputTraces) {
11
+ if (otherTrace.mspPairId === trace.mspPairId) continue
12
+ if (otherTrace.globalConnNetId === trace.globalConnNetId) continue
13
+ crossingCount += countPathIntersections(
14
+ trace.tracePath,
15
+ otherTrace.tracePath,
16
+ )
17
+ }
18
+ return crossingCount
19
+ }
@@ -0,0 +1,26 @@
1
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
2
+ import { countCrossingsWithOtherTraces } from "./countCrossingsWithOtherTraces"
3
+ import { doesPathRunAlongChipBoundary } from "./doesPathRunAlongChipBoundary"
4
+ import type { ChipObstacle } from "./types"
5
+
6
+ export type TracePathScore = {
7
+ chipEdgeRuns: number
8
+ traceCrossings: number
9
+ }
10
+
11
+ export const scoreSolutionTracePath = (params: {
12
+ traces: SolvedTracePath[]
13
+ outputTraces: SolvedTracePath[]
14
+ chipObstacles: ChipObstacle[]
15
+ }): TracePathScore => {
16
+ const { traces, outputTraces, chipObstacles } = params
17
+ let chipEdgeRuns = 0
18
+ let traceCrossings = 0
19
+ for (const trace of traces) {
20
+ if (doesPathRunAlongChipBoundary(trace.tracePath, chipObstacles)) {
21
+ chipEdgeRuns += 1
22
+ }
23
+ traceCrossings += countCrossingsWithOtherTraces({ trace, outputTraces })
24
+ }
25
+ return { chipEdgeRuns, traceCrossings }
26
+ }