@tscircuit/rectdiff 0.0.1

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 (40) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.github/workflows/bun-formatcheck.yml +26 -0
  3. package/.github/workflows/bun-pver-release.yml +71 -0
  4. package/.github/workflows/bun-test.yml +31 -0
  5. package/.github/workflows/bun-typecheck.yml +26 -0
  6. package/CLAUDE.md +23 -0
  7. package/README.md +5 -0
  8. package/biome.json +93 -0
  9. package/bun.lock +29 -0
  10. package/bunfig.toml +5 -0
  11. package/components/SolverDebugger3d.tsx +833 -0
  12. package/cosmos.config.json +6 -0
  13. package/cosmos.decorator.tsx +21 -0
  14. package/dist/index.d.ts +111 -0
  15. package/dist/index.js +921 -0
  16. package/experiments/rect-fill-2d.tsx +983 -0
  17. package/experiments/rect3d_visualizer.html +640 -0
  18. package/global.d.ts +4 -0
  19. package/index.html +12 -0
  20. package/lib/index.ts +1 -0
  21. package/lib/solvers/RectDiffSolver.ts +158 -0
  22. package/lib/solvers/rectdiff/candidates.ts +397 -0
  23. package/lib/solvers/rectdiff/engine.ts +355 -0
  24. package/lib/solvers/rectdiff/geometry.ts +284 -0
  25. package/lib/solvers/rectdiff/layers.ts +48 -0
  26. package/lib/solvers/rectdiff/rectsToMeshNodes.ts +22 -0
  27. package/lib/solvers/rectdiff/types.ts +63 -0
  28. package/lib/types/capacity-mesh-types.ts +33 -0
  29. package/lib/types/srj-types.ts +37 -0
  30. package/package.json +33 -0
  31. package/pages/example01.page.tsx +11 -0
  32. package/test-assets/example01.json +933 -0
  33. package/tests/__snapshots__/svg.snap.svg +3 -0
  34. package/tests/examples/__snapshots__/example01.snap.svg +121 -0
  35. package/tests/examples/example01.test.tsx +65 -0
  36. package/tests/fixtures/preload.ts +1 -0
  37. package/tests/incremental-solver.test.ts +100 -0
  38. package/tests/rect-diff-solver.test.ts +154 -0
  39. package/tests/svg.test.ts +12 -0
  40. package/tsconfig.json +30 -0
@@ -0,0 +1,3 @@
1
+ <svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
2
+ <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
3
+ </svg>
@@ -0,0 +1,121 @@
1
+ <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><rect data-type="rect" data-label="board" data-x="0" data-y="0" x="86.66666666666666" y="40" width="466.66666666666674" height="560" fill="none" stroke="#111827" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-5.856893060790263" data-y="-8.275436101920713" x="205.07132953191507" y="472.9348072358533" width="11.200000000000017" height="3.079999999999984" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-5.856893060790263" data-y="-8.425436101920713" x="205.07132953191507" y="475.73480723585334" width="11.200000000000017" height="3.079999999999984" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-5.856893060790263" data-y="-8.575436101920712" x="205.07132953191507" y="478.5348072358533" width="11.200000000000017" height="3.079999999999984" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-5.856893060790263" data-y="-8.725436101920712" x="205.07132953191507" y="481.3348072358533" width="11.200000000000017" height="3.079999999999984" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-6.856893060790263" data-y="-8.275436101920713" x="186.4046628652484" y="472.9348072358533" width="11.200000000000017" height="3.079999999999984" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-6.856893060790263" data-y="-8.425436101920713" x="186.4046628652484" y="475.73480723585334" width="11.200000000000017" height="3.079999999999984" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-6.856893060790263" data-y="-8.575436101920712" x="186.4046628652484" y="478.5348072358533" width="11.200000000000017" height="3.079999999999984" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-6.856893060790263" data-y="-8.725436101920712" x="186.4046628652484" y="481.3348072358533" width="11.200000000000017" height="3.079999999999984" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-6.040227999999999" data-y="12.073" x="181.58241066666668" y="75.97066666666663" width="51.33333333333334" height="37.33333333333334" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-6.040227999999999" data-y="9.533000000000001" x="181.58241066666668" y="123.38399999999996" width="51.33333333333334" height="37.33333333333334" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-6.040227999999999" data-y="6.993" x="181.58241066666668" y="170.7973333333333" width="51.33333333333334" height="37.33333333333334" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-6.040227999999999" data-y="4.453" x="181.58241066666668" y="218.21066666666667" width="51.33333333333334" height="37.333333333333314" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-6.040227999999999" data-y="1.9129999999999998" x="181.58241066666668" y="265.624" width="51.33333333333334" height="37.333333333333314" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-6.040227999999999" data-y="-0.6269999999999998" x="181.58241066666668" y="313.0373333333333" width="51.33333333333334" height="37.33333333333337" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-6.040227999999999" data-y="-3.167" x="181.58241066666668" y="360.4506666666667" width="51.33333333333334" height="37.333333333333314" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="10.124772" data-y="-3.167" x="483.3290773333333" y="360.4506666666667" width="51.33333333333337" height="37.333333333333314" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="10.124772" data-y="-0.6269999999999998" x="483.3290773333333" y="313.0373333333333" width="51.33333333333337" height="37.33333333333337" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="10.124772" data-y="1.9129999999999998" x="483.3290773333333" y="265.624" width="51.33333333333337" height="37.333333333333314" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="10.124772" data-y="4.453" x="483.3290773333333" y="218.21066666666667" width="51.33333333333337" height="37.333333333333314" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="10.124772" data-y="6.993" x="483.3290773333333" y="170.7973333333333" width="51.33333333333337" height="37.33333333333334" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="10.124772" data-y="9.533000000000001" x="483.3290773333333" y="123.38399999999996" width="51.33333333333337" height="37.33333333333334" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="10.124772" data-y="12.073" x="483.3290773333333" y="75.97066666666663" width="51.33333333333337" height="37.33333333333334" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-2.4302279999999996" data-y="4.835" x="251.30241066666667" y="219.48000000000002" width="46.666666666666686" height="20.533333333333303" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-2.4302279999999996" data-y="6.74" x="251.30241066666667" y="183.92" width="46.666666666666686" height="20.53333333333333" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="0.7447720000000002" data-y="13.085" x="318.035744" y="59.879999999999995" width="31.73333333333335" height="31.73333333333329" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="3.2847720000000002" data-y="13.085" x="365.4490773333333" y="59.879999999999995" width="31.73333333333335" height="31.73333333333329" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="0.7447720000000002" data-y="10.545" x="318.035744" y="107.29333333333332" width="31.73333333333335" height="31.73333333333332" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="3.2847720000000002" data-y="10.545" x="365.4490773333333" y="107.29333333333332" width="31.73333333333335" height="31.73333333333332" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="0.7447720000000002" data-y="8.004999999999999" x="318.035744" y="154.70666666666668" width="31.73333333333335" height="31.73333333333332" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="3.2847720000000002" data-y="8.004999999999999" x="365.4490773333333" y="154.70666666666668" width="31.73333333333335" height="31.73333333333332" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="0.7447720000000002" data-y="5.465" x="318.035744" y="202.12" width="31.73333333333335" height="31.73333333333332" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="3.2847720000000002" data-y="5.465" x="365.4490773333333" y="202.12" width="31.73333333333335" height="31.73333333333332" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="3.6885631738662035" data-y="-10.96420520580684" x="374.8531792455025" y="510.6651638417277" width="28" height="28" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="3.6885631738662035" data-y="-8.96420520580684" x="374.8531792455025" y="473.3318305083943" width="28" height="28" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-10.727385294299685" data-y="-11.579453643315949" x="105.75547450640587" y="522.1498013418977" width="28" height="28" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-10.727385294299685" data-y="-8.079453643315949" x="105.75547450640587" y="456.81646800856436" width="28" height="28" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-10.727385294299685" data-y="-4.579453643315949" x="105.75547450640587" y="391.48313467523104" width="28" height="28" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-10.727385294299685" data-y="-1.079453643315949" x="105.75547450640587" y="326.1498013418977" width="28" height="28" fill="#fee2e2" stroke="#ef4444" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
2
+ z:0,1,2,3" data-x="3.7847720000000002" data-y="-1.7996026029034198" x="297.96907733333336" y="233.85333333333332" width="185.35999999999996" height="239.478497175061" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
3
+ z:0,1,2,3" data-x="-2.9227279999999993" data-y="-5.3575" x="232.91574400000002" y="240.01333333333332" width="65.05333333333334" height="359.9866666666667" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
4
+ z:0,1,2,3" data-x="-2.3852279999999992" data-y="11.145" x="232.91574400000002" y="40" width="85.12" height="143.92" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
5
+ z:0,1,2,3" data-x="6.442272" data-y="9.807500000000001" x="397.18241066666667" y="40" width="86.14666666666665" height="193.8533333333333" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
6
+ z:0,1,2,3" data-x="-7.321306647149842" data-y="-6.179968050960356" x="133.75547450640587" y="397.784" width="99.16026949359414" height="75.15080723585334" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
7
+ z:0,1,2,3" data-x="-9.957614" data-y="7.372841999999999" x="86.66666666666666" y="40" width="94.91574400000002" height="284.74723200000005" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
8
+ z:0,1,2,3" data-x="-7.321306647149842" data-y="-11.903968050960355" x="133.75547450640587" y="484.41480723585323" width="99.16026949359414" height="115.58519276414677" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
9
+ z:0,1,2,3" data-x="8.469281586933102" data-y="-11.60710260290342" x="402.8531792455025" y="473.3318305083944" width="150.4801540878309" height="126.66816949160562" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
10
+ z:0,1,2,3" data-x="-8.696306647149843" data-y="-2.2106580000000013" x="133.75547450640585" y="324.74723200000005" width="47.8269361602608" height="73.03676799999994" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
11
+ z:0,1,2,3" data-x="0.8791675869331019" data-y="-11.60710260290342" x="297.96907733333336" y="473.3318305083944" width="76.88410191216911" height="126.66816949160562" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
12
+ z:0,1,2,3" data-x="2.8747719999999974" data-y="9.274999999999999" x="350.1424106666666" y="139.02666666666664" width="47.04000000000008" height="15.680000000000064" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
13
+ z:0,1,2,3" data-x="10.624886" data-y="-6.19060260290342" x="483.3290773333333" y="397.784" width="70.00425600000005" height="75.54783050839433" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
14
+ z:0,1,2,3" data-x="0.7547719999999978" data-y="9.274999999999999" x="318.035744" y="139.02666666666664" width="32.10666666666657" height="15.680000000000064" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
15
+ z:0,1,2,3" data-x="-11.238692647149843" data-y="-13.664726821657975" x="86.66666666666666" y="550.1498013418977" width="47.08880783973919" height="49.85019865810227" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
16
+ z:0,1,2,3" data-x="-11.238692647149843" data-y="-9.829453643315949" x="86.66666666666666" y="484.81646800856436" width="47.08880783973919" height="37.33333333333337" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
17
+ z:0,1,2,3" data-x="-11.238692647149843" data-y="-6.329453643315949" x="86.66666666666666" y="419.48313467523104" width="47.08880783973919" height="37.333333333333314" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
18
+ z:0,1,2,3" data-x="-11.238692647149843" data-y="-2.829453643315949" x="86.66666666666666" y="354.1498013418977" width="47.08880783973919" height="37.333333333333314" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
19
+ z:0,1,2,3" data-x="3.6885631738662035" data-y="-13.35710260290342" x="374.8531792455025" y="538.6651638417277" width="28" height="61.33483615827231" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
20
+ z:0,1,2,3" data-x="2.6799999999999997" data-y="11.815000000000001" x="346.5066666666666" y="91.61333333333326" width="47.040000000000134" height="15.680000000000064" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
21
+ z:0,1,2,3" data-x="10.624886" data-y="14.0365" x="483.3290773333333" y="40" width="70.00425600000005" height="35.97066666666663" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
22
+ z:0,1,2,3" data-x="-4.172727999999999" data-y="5.812500000000002" x="232.91574400000002" y="183.92" width="18.386666666666656" height="55.15999999999994" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
23
+ z:1,3" data-x="-2.4302279999999996" data-y="5.7875000000000005" x="251.30241066666667" y="183.92" width="46.666666666666686" height="56.093333333333334" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
24
+ z:0,1,2,3" data-x="-6.040227999999999" data-y="14.0365" x="181.58241066666668" y="40" width="51.33333333333334" height="35.97066666666663" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
25
+ z:2" data-x="-2.4302279999999996" data-y="5.7875000000000005" x="251.30241066666667" y="183.92" width="46.666666666666686" height="56.093333333333334" fill="#d1fae5" stroke="#10b981" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
26
+ z:0,1,2,3" data-x="-0.6427279999999997" data-y="5.952500000000001" x="297.96907733333336" y="183.92" width="20.066666666666663" height="49.93333333333334" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
27
+ z:0,1,2,3" data-x="0.6573859999999989" data-y="11.815000000000001" x="318.035744" y="91.61333333333326" width="28.47092266666658" height="15.680000000000064" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
28
+ z:2" data-x="0.32477200000000095" data-y="13.6175" x="318.035744" y="40" width="16.05333333333334" height="51.613333333333316" fill="#d1fae5" stroke="#10b981" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
29
+ z:0,1,2,3" data-x="-11.988692647149843" data-y="-11.579453643315949" x="86.66666666666666" y="522.1498013418977" width="19.08880783973919" height="28" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
30
+ z:0,1,2,3" data-x="-11.988692647149843" data-y="-8.079453643315949" x="86.66666666666666" y="456.81646800856436" width="19.08880783973919" height="28" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
31
+ z:0,1,2,3" data-x="-11.988692647149843" data-y="-4.579453643315949" x="86.66666666666666" y="391.48313467523104" width="19.08880783973919" height="28" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
32
+ z:0,1,2,3" data-x="-11.988692647149843" data-y="-1.041884821657976" x="86.66666666666666" y="324.74723200000005" width="19.08880783973919" height="29.402569341897674" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
33
+ z:0,1,2,3" data-x="2.0147720000000002" data-y="7.494999999999999" x="349.76907733333337" y="154.70666666666668" width="15.67999999999995" height="50.77333333333331" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
34
+ z:1,3" data-x="3.2847720000000002" data-y="6.734999999999999" x="365.4490773333333" y="154.70666666666668" width="31.73333333333335" height="79.14666666666665" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
35
+ z:2" data-x="3.2847720000000002" data-y="6.734999999999999" x="365.4490773333333" y="154.70666666666668" width="31.73333333333335" height="79.14666666666665" fill="#d1fae5" stroke="#10b981" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
36
+ z:1" data-x="0.7447720000000004" data-y="6.734999999999999" x="318.035744" y="154.70666666666668" width="31.73333333333335" height="79.14666666666665" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
37
+ z:2" data-x="0.7447720000000004" data-y="6.734999999999999" x="318.035744" y="154.70666666666668" width="31.73333333333335" height="79.14666666666665" fill="#d1fae5" stroke="#10b981" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
38
+ z:3" data-x="0.7447720000000004" data-y="6.734999999999999" x="318.035744" y="154.70666666666668" width="31.73333333333335" height="79.14666666666665" fill="#e9d5ff" stroke="#a855f7" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
39
+ z:0,1,2,3" data-x="2.0147720000000002" data-y="10.545" x="349.76907733333337" y="107.29333333333332" width="15.67999999999995" height="31.73333333333332" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
40
+ z:0,1,2,3" data-x="2.3522720000000006" data-y="14.467500000000001" x="334.08907733333336" y="40" width="59.639999999999986" height="19.87999999999994" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
41
+ z:2" data-x="1.0873859999999993" data-y="13.085" x="334.08907733333336" y="59.879999999999995" width="12.41758933333324" height="31.73333333333329" fill="#d1fae5" stroke="#10b981" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
42
+ z:0,1,2,3" data-x="2.0147720000000002" data-y="13.085" x="349.76907733333337" y="59.879999999999995" width="15.67999999999995" height="31.73333333333329" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
43
+ z:0" data-x="-6.040227999999999" data-y="0.643" x="181.58241066666668" y="302.95733333333334" width="51.33333333333334" height="10.079999999999984" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
44
+ z:0" data-x="0.324772000000001" data-y="14.467500000000001" x="318.035744" y="39.99999999999994" width="16.05333333333334" height="19.880000000000052" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
45
+ z:0" data-x="0.7447720000000002" data-y="6.734999999999999" x="318.035744" y="186.44" width="31.73333333333335" height="15.680000000000007" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
46
+ z:0,1,2,3" data-x="11.999886" data-y="8.463000000000001" x="534.6624106666667" y="75.97066666666663" width="18.670922666666684" height="172.10666666666668" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
47
+ z:0,1,2,3" data-x="11.999886" data-y="-0.09699999999999953" x="534.6624106666667" y="248.0773333333333" width="18.670922666666684" height="147.46666666666667" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
48
+ z:0" data-x="10.124772" data-y="0.643" x="483.3290773333333" y="302.95733333333334" width="51.33333333333337" height="10.079999999999984" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
49
+ z:0" data-x="-6.356893060790263" data-y="-8.500436101920712" x="197.60466286524843" y="472.9348072358533" width="7.46666666666664" height="11.480000000000018" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
50
+ z:0" data-x="-6.040227999999999" data-y="10.803" x="181.58241066666668" y="113.30399999999997" width="51.33333333333334" height="10.079999999999984" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
51
+ z:0" data-x="-6.040227999999999" data-y="8.263000000000002" x="181.58241066666668" y="160.71733333333327" width="51.33333333333334" height="10.080000000000041" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
52
+ z:0" data-x="-6.040227999999999" data-y="5.723000000000001" x="181.58241066666668" y="208.13066666666663" width="51.33333333333334" height="10.080000000000041" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
53
+ z:0" data-x="-6.040227999999999" data-y="3.183" x="181.58241066666668" y="255.54399999999998" width="51.33333333333334" height="10.080000000000041" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
54
+ z:0" data-x="-6.040227999999999" data-y="-1.8969999999999998" x="181.58241066666668" y="350.3706666666667" width="51.33333333333334" height="10.079999999999984" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
55
+ z:0" data-x="10.124772" data-y="-1.8969999999999998" x="483.3290773333333" y="350.3706666666667" width="51.33333333333337" height="10.079999999999984" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
56
+ z:0" data-x="10.124772" data-y="3.183" x="483.3290773333333" y="255.54399999999998" width="51.33333333333337" height="10.080000000000041" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
57
+ z:0" data-x="10.124772" data-y="5.723000000000001" x="483.3290773333333" y="208.13066666666663" width="51.33333333333337" height="10.080000000000041" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
58
+ z:0" data-x="10.124772" data-y="8.263000000000002" x="483.3290773333333" y="160.71733333333327" width="51.33333333333337" height="10.080000000000041" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
59
+ z:0" data-x="10.124772" data-y="10.803" x="483.3290773333333" y="113.30399999999997" width="51.33333333333337" height="10.079999999999984" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
60
+ z:0,1,2,3" data-x="2.0147720000000002" data-y="5.375" x="349.76907733333337" y="205.48" width="15.67999999999995" height="28.373333333333335" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
61
+ z:0" data-x="3.6885631738662035" data-y="-9.96420520580684" x="374.8531792455025" y="501.3318305083943" width="28" height="9.333333333333371" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
62
+ z:0" data-x="-2.4302279999999996" data-y="5.7875" x="251.30241066666667" y="204.45333333333332" width="46.666666666666686" height="15.0266666666667" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
63
+ z:0" data-x="3.2847720000000002" data-y="6.734999999999999" x="365.4490773333333" y="186.44" width="31.73333333333335" height="15.680000000000007" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
64
+ z:0" data-x="-8.567139177544973" data-y="-8.500436101920712" x="133.75547450640587" y="472.9348072358533" width="52.649188358842565" height="11.480000000000018" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
65
+ z:0" data-x="-5.111060530395131" data-y="-8.500436101920712" x="216.27132953191511" y="472.9348072358533" width="16.644414468084904" height="11.480000000000018" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
66
+ z:1" data-x="3.6885631738662035" data-y="-9.96420520580684" x="374.8531792455025" y="501.3318305083943" width="28" height="9.333333333333371" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
67
+ z:1" data-x="-7.321306647149842" data-y="-8.500436101920712" x="133.75547450640587" y="472.9348072358533" width="99.16026949359414" height="11.480000000000018" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
68
+ z:2" data-x="3.6885631738662035" data-y="-9.96420520580684" x="374.8531792455025" y="501.3318305083943" width="28" height="9.333333333333371" fill="#d1fae5" stroke="#10b981" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
69
+ z:2" data-x="-7.321306647149842" data-y="-8.500436101920712" x="133.75547450640587" y="472.9348072358533" width="99.16026949359414" height="11.480000000000018" fill="#d1fae5" stroke="#10b981" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
70
+ z:3" data-x="3.6885631738662035" data-y="-9.96420520580684" x="374.8531792455025" y="501.3318305083943" width="28" height="9.333333333333371" fill="#e9d5ff" stroke="#a855f7" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
71
+ z:3" data-x="-7.321306647149842" data-y="-8.500436101920712" x="133.75547450640587" y="472.9348072358533" width="99.16026949359414" height="11.480000000000018" fill="#e9d5ff" stroke="#a855f7" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
72
+ z:1,2,3" data-x="0.7447720000000004" data-y="10.545" x="318.035744" y="107.29333333333332" width="31.73333333333335" height="31.73333333333332" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
73
+ z:1,2,3" data-x="3.2847720000000002" data-y="10.545" x="365.4490773333333" y="107.29333333333332" width="31.73333333333335" height="31.73333333333332" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
74
+ z:1,2,3" data-x="3.2847720000000002" data-y="13.085" x="365.4490773333333" y="59.879999999999995" width="31.73333333333335" height="31.73333333333329" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
75
+ z:1,2,3" data-x="-6.040227999999999" data-y="4.453000000000001" x="181.58241066666668" y="75.9706666666666" width="51.33333333333334" height="321.8133333333334" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
76
+ z:1,2,3" data-x="10.124772" data-y="4.453000000000001" x="483.3290773333333" y="75.9706666666666" width="51.33333333333337" height="321.8133333333334" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
77
+ z:1,3" data-x="0.32477200000000095" data-y="13.617500000000001" x="318.035744" y="39.99999999999994" width="16.05333333333334" height="51.613333333333344" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g><rect data-type="rect" data-label="free
78
+ z:1,3" data-x="1.1747720000000008" data-y="13.085" x="334.08907733333336" y="59.879999999999995" width="15.680000000000007" height="31.73333333333329" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.05357142857142857"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
79
+ document.currentScript.parentElement.addEventListener('mousemove', (e) => {
80
+ const svg = e.currentTarget;
81
+ const rect = svg.getBoundingClientRect();
82
+ const x = e.clientX - rect.left;
83
+ const y = e.clientY - rect.top;
84
+ const crosshair = svg.getElementById('crosshair');
85
+ const h = svg.getElementById('crosshair-h');
86
+ const v = svg.getElementById('crosshair-v');
87
+ const coords = svg.getElementById('coordinates');
88
+
89
+ crosshair.style.display = 'block';
90
+ h.setAttribute('x1', '0');
91
+ h.setAttribute('x2', '640');
92
+ h.setAttribute('y1', y);
93
+ h.setAttribute('y2', y);
94
+ v.setAttribute('x1', x);
95
+ v.setAttribute('x2', x);
96
+ v.setAttribute('y1', '0');
97
+ v.setAttribute('y2', '640');
98
+
99
+ // Calculate real coordinates using inverse transformation
100
+ const matrix = {"a":18.666666666666668,"c":0,"e":320,"b":0,"d":-18.666666666666668,"f":320};
101
+ // Manually invert and apply the affine transform
102
+ // Since we only use translate and scale, we can directly compute:
103
+ // x' = (x - tx) / sx
104
+ // y' = (y - ty) / sy
105
+ const sx = matrix.a;
106
+ const sy = matrix.d;
107
+ const tx = matrix.e;
108
+ const ty = matrix.f;
109
+ const realPoint = {
110
+ x: (x - tx) / sx,
111
+ y: (y - ty) / sy // Flip y back since we used negative scale
112
+ }
113
+
114
+ coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
115
+ coords.setAttribute('x', (x + 5).toString());
116
+ coords.setAttribute('y', (y - 5).toString());
117
+ });
118
+ document.currentScript.parentElement.addEventListener('mouseleave', () => {
119
+ document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
120
+ });
121
+ ]]></script></svg>
@@ -0,0 +1,65 @@
1
+ import { expect, test } from "bun:test"
2
+ import simpleRouteJson from "../../test-assets/example-simple-route.json"
3
+ import { RectDiffSolver } from "../../lib/solvers/RectDiffSolver"
4
+ import { getSvgFromGraphicsObject } from "graphics-debug"
5
+
6
+ test("example01", () => {
7
+ const solver = new RectDiffSolver({ simpleRouteJson })
8
+
9
+ solver.solve()
10
+
11
+ expect(getSvgFromGraphicsObject(solver.visualize())).toMatchSvgSnapshot(
12
+ import.meta.path,
13
+ )
14
+
15
+ // Gap detection test - check coverage with a fine grid
16
+ const bounds = simpleRouteJson.bounds
17
+ const step = 0.004
18
+ const layerCount = simpleRouteJson.layerCount || 2
19
+ const state = (solver as any).state
20
+ const obstacles = state.obstaclesByLayer
21
+ const placed = state.placed
22
+
23
+ const gapPoints: Array<{ x: number; y: number; z: number }> = []
24
+
25
+ for (let z = 0; z < layerCount; z++) {
26
+ const layerObstacles = obstacles[z] || []
27
+ const layerPlaced = placed
28
+ .filter((p: any) => p.zLayers.includes(z))
29
+ .map((p: any) => p.rect)
30
+
31
+ for (let x = bounds.minX; x <= bounds.maxX; x += step) {
32
+ for (let y = bounds.minY; y <= bounds.maxY; y += step) {
33
+ // Skip if in obstacle
34
+ if (
35
+ layerObstacles.some(
36
+ (o: any) =>
37
+ x >= o.x && x <= o.x + o.width && y >= o.y && y <= o.y + o.height,
38
+ )
39
+ )
40
+ continue
41
+
42
+ // Check if covered by any placed rectangle
43
+ const covered = layerPlaced.some(
44
+ (r: any) =>
45
+ x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height,
46
+ )
47
+
48
+ if (!covered) {
49
+ gapPoints.push({ x, y, z })
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ const totalArea = (bounds.maxX - bounds.minX) * (bounds.maxY - bounds.minY)
56
+ const gapArea = gapPoints.length * step * step
57
+ const coveragePercent = ((totalArea - gapArea) / totalArea) * 100
58
+
59
+ console.log(
60
+ `Coverage: ${coveragePercent.toFixed(2)}%, Rectangles: ${placed.length}`,
61
+ )
62
+
63
+ // We expect >99% coverage (with the improved edge analysis)
64
+ expect(coveragePercent).toBeGreaterThan(99)
65
+ })
@@ -0,0 +1 @@
1
+ import "bun-match-svg"
@@ -0,0 +1,100 @@
1
+ import { expect, test } from "bun:test"
2
+ import { RectDiffSolver } from "../lib/solvers/RectDiffSolver"
3
+ import type { SimpleRouteJson } from "../lib/types/srj-types"
4
+
5
+ test("RectDiffSolver supports incremental stepping", () => {
6
+ const simpleRouteJson: SimpleRouteJson = {
7
+ bounds: { minX: 0, maxX: 10, minY: 0, maxY: 10 },
8
+ obstacles: [
9
+ {
10
+ type: "rect",
11
+ layers: ["top"],
12
+ center: { x: 5, y: 5 },
13
+ width: 2,
14
+ height: 2,
15
+ connectedTo: [],
16
+ },
17
+ ],
18
+ connections: [],
19
+ layerCount: 2,
20
+ minTraceWidth: 0.15,
21
+ }
22
+
23
+ const solver = new RectDiffSolver({ simpleRouteJson })
24
+
25
+ // Setup initializes state
26
+ solver.setup()
27
+ expect(solver.solved).toBe(false)
28
+ expect(solver.stats.phase).toBe("GRID")
29
+
30
+ // Step advances one candidate at a time
31
+ let stepCount = 0
32
+ const maxSteps = 1000 // safety limit
33
+
34
+ while (!solver.solved && stepCount < maxSteps) {
35
+ solver.step()
36
+ stepCount++
37
+
38
+ // Progress should increase (or stay at 1.0 when done)
39
+ if (!solver.solved) {
40
+ expect(solver.stats.phase).toBeDefined()
41
+ }
42
+ }
43
+
44
+ expect(solver.solved).toBe(true)
45
+ expect(stepCount).toBeGreaterThan(0)
46
+ expect(stepCount).toBeLessThan(maxSteps)
47
+
48
+ const output = solver.getOutput()
49
+ expect(output.meshNodes.length).toBeGreaterThan(0)
50
+ })
51
+
52
+ test("RectDiffSolver.solve() still works (backward compatibility)", () => {
53
+ const simpleRouteJson: SimpleRouteJson = {
54
+ bounds: { minX: 0, maxX: 10, minY: 0, maxY: 10 },
55
+ obstacles: [],
56
+ connections: [],
57
+ layerCount: 1,
58
+ minTraceWidth: 0.1,
59
+ }
60
+
61
+ const solver = new RectDiffSolver({ simpleRouteJson })
62
+
63
+ // Old-style: just call solve()
64
+ solver.solve()
65
+
66
+ expect(solver.solved).toBe(true)
67
+ const output = solver.getOutput()
68
+ expect(output.meshNodes.length).toBeGreaterThan(0)
69
+ })
70
+
71
+ test("RectDiffSolver exposes progress during solve", () => {
72
+ const simpleRouteJson: SimpleRouteJson = {
73
+ bounds: { minX: 0, maxX: 20, minY: 0, maxY: 20 },
74
+ obstacles: [],
75
+ connections: [],
76
+ layerCount: 3,
77
+ minTraceWidth: 0.2,
78
+ }
79
+
80
+ const solver = new RectDiffSolver({ simpleRouteJson })
81
+ solver.setup()
82
+
83
+ const progressValues: number[] = []
84
+
85
+ // Step and collect progress
86
+ for (let i = 0; i < 20 && !solver.solved; i++) {
87
+ solver.step()
88
+ const progress = solver.computeProgress()
89
+ progressValues.push(progress)
90
+ }
91
+
92
+ // Progress should generally increase (or stay at 1.0)
93
+ expect(progressValues.length).toBeGreaterThan(0)
94
+ expect(progressValues[0]).toBeGreaterThanOrEqual(0)
95
+ expect(progressValues[0]).toBeLessThanOrEqual(1)
96
+
97
+ // Finish
98
+ solver.solve()
99
+ expect(solver.computeProgress()).toBe(1)
100
+ })
@@ -0,0 +1,154 @@
1
+ import { expect, test } from "bun:test"
2
+ import { RectDiffSolver } from "../lib/solvers/RectDiffSolver"
3
+ import type { SimpleRouteJson } from "../lib/types/srj-types"
4
+
5
+ test("RectDiffSolver creates mesh nodes with grid-based approach", () => {
6
+ const simpleRouteJson: SimpleRouteJson = {
7
+ bounds: {
8
+ minX: 0,
9
+ maxX: 10,
10
+ minY: 0,
11
+ maxY: 10,
12
+ },
13
+ obstacles: [
14
+ {
15
+ type: "rect",
16
+ layers: ["top"],
17
+ center: { x: 2.5, y: 2.5 },
18
+ width: 2,
19
+ height: 2,
20
+ connectedTo: [],
21
+ },
22
+ ],
23
+ connections: [],
24
+ layerCount: 2,
25
+ minTraceWidth: 0.15,
26
+ }
27
+
28
+ const solver = new RectDiffSolver({
29
+ simpleRouteJson,
30
+ mode: "grid",
31
+ })
32
+
33
+ solver.solve()
34
+
35
+ const output = solver.getOutput()
36
+
37
+ // Should have created some mesh nodes
38
+ expect(output.meshNodes.length).toBeGreaterThan(0)
39
+
40
+ // All mesh nodes should have valid dimensions
41
+ for (const node of output.meshNodes) {
42
+ expect(node.width).toBeGreaterThan(0)
43
+ expect(node.height).toBeGreaterThan(0)
44
+ expect(node.availableZ).toBeDefined()
45
+ expect(Array.isArray(node.availableZ)).toBe(true)
46
+ }
47
+ })
48
+
49
+ test("RectDiffSolver handles multi-layer spans", () => {
50
+ const simpleRouteJson: SimpleRouteJson = {
51
+ bounds: {
52
+ minX: 0,
53
+ maxX: 10,
54
+ minY: 0,
55
+ maxY: 10,
56
+ },
57
+ obstacles: [],
58
+ connections: [],
59
+ layerCount: 3,
60
+ minTraceWidth: 0.2,
61
+ }
62
+
63
+ const solver = new RectDiffSolver({
64
+ simpleRouteJson,
65
+ mode: "grid",
66
+ gridOptions: {
67
+ minSingle: { width: 0.4, height: 0.4 },
68
+ minMulti: { width: 1.0, height: 1.0, minLayers: 2 },
69
+ preferMultiLayer: true,
70
+ },
71
+ })
72
+
73
+ solver.solve()
74
+
75
+ const output = solver.getOutput()
76
+
77
+ // Should have created mesh nodes
78
+ expect(output.meshNodes.length).toBeGreaterThan(0)
79
+
80
+ // Check if any nodes span multiple layers
81
+ const multiLayerNodes = output.meshNodes.filter(
82
+ (n) => n.availableZ && n.availableZ.length >= 2
83
+ )
84
+
85
+ // With no obstacles and preferMultiLayer=true, we should get multi-layer nodes
86
+ expect(multiLayerNodes.length).toBeGreaterThan(0)
87
+ })
88
+
89
+ test("RectDiffSolver respects single-layer minimums", () => {
90
+ const simpleRouteJson: SimpleRouteJson = {
91
+ bounds: {
92
+ minX: 0,
93
+ maxX: 5,
94
+ minY: 0,
95
+ maxY: 5,
96
+ },
97
+ obstacles: [],
98
+ connections: [],
99
+ layerCount: 1,
100
+ minTraceWidth: 0.1,
101
+ }
102
+
103
+ const minWidth = 0.5
104
+ const minHeight = 0.5
105
+
106
+ const solver = new RectDiffSolver({
107
+ simpleRouteJson,
108
+ mode: "grid",
109
+ gridOptions: {
110
+ minSingle: { width: minWidth, height: minHeight },
111
+ minMulti: { width: 1.0, height: 1.0, minLayers: 2 },
112
+ },
113
+ })
114
+
115
+ solver.solve()
116
+
117
+ const output = solver.getOutput()
118
+
119
+ // All nodes should meet minimum requirements
120
+ for (const node of output.meshNodes) {
121
+ expect(node.width).toBeGreaterThanOrEqual(minWidth - 1e-6)
122
+ expect(node.height).toBeGreaterThanOrEqual(minHeight - 1e-6)
123
+ }
124
+ })
125
+
126
+ test("disruptive placement resizes single-layer nodes", () => {
127
+ const srj: SimpleRouteJson = {
128
+ bounds: { minX: 0, maxX: 10, minY: 0, maxY: 10 },
129
+ obstacles: [],
130
+ connections: [],
131
+ layerCount: 3,
132
+ minTraceWidth: 0.2,
133
+ }
134
+ const solver = new RectDiffSolver({ simpleRouteJson: srj, mode: "grid" })
135
+ solver.setup()
136
+
137
+ // Manually seed a soft, single-layer node occupying center (simulate early placement)
138
+ const state = (solver as any).state
139
+ const r = { x: 4, y: 4, width: 2, height: 2 }
140
+ state.placed.push({ rect: r, zLayers: [1] })
141
+ state.placedByLayer[1].push(r)
142
+
143
+ // Run to completion
144
+ solver.solve()
145
+
146
+ // Expect at least one node spanning multiple layers at/through the center
147
+ const mesh = solver.getOutput().meshNodes
148
+ const throughCenter = mesh.find(n =>
149
+ Math.abs(n.center.x - 5) < 0.6 &&
150
+ Math.abs(n.center.y - 5) < 0.6 &&
151
+ (n.availableZ?.length ?? 0) >= 2
152
+ )
153
+ expect(throughCenter).toBeTruthy()
154
+ })
@@ -0,0 +1,12 @@
1
+ import { expect, test } from "bun:test"
2
+
3
+ const testSvg = `<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
4
+ <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
5
+ </svg>`
6
+
7
+ test("svg snapshot example", async () => {
8
+ // First run will create the snapshot
9
+ // Subsequent runs will compare against the saved snapshot
10
+ await expect(testSvg).toMatchSvgSnapshot(import.meta.path)
11
+ })
12
+
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext", "DOM"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+ "resolveJsonModule": false,
17
+
18
+ // Best practices
19
+ "strict": true,
20
+ "skipLibCheck": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUncheckedIndexedAccess": true,
23
+ "noImplicitOverride": true,
24
+
25
+ // Some stricter flags (disabled by default)
26
+ "noUnusedLocals": false,
27
+ "noUnusedParameters": false,
28
+ "noPropertyAccessFromIndexSignature": false
29
+ }
30
+ }