Created
January 12, 2026 05:12
-
-
Save michaelmob/7e5e660360d3316ed4209daf14f936cb to your computer and use it in GitHub Desktop.
A browser‑based Gridfinity planner that converts drawer dimensions into printer‑safe base plates, guaranteeing full coverage with no weak 1‑row or 1‑column prints.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <title>Gridfinity Base Plate Planner</title> | |
| <style> | |
| :root { | |
| --bg:#0f172a; | |
| --panel:#1e293b; | |
| --panel2:#020617; | |
| --text:#e5e7eb; | |
| --muted:#94a3b8; | |
| --accent:#38bdf8; | |
| --unit:42px; | |
| } | |
| body { | |
| background:var(--bg); | |
| color:var(--text); | |
| font-family:system-ui,sans-serif; | |
| padding:24px; | |
| line-height:1.5; | |
| } | |
| h1, h2 { | |
| color:#f8fafc; | |
| } | |
| p { | |
| max-width:900px; | |
| } | |
| table { | |
| width:100%; | |
| max-width:900px; | |
| background:var(--panel); | |
| border-radius:12px; | |
| border-collapse:collapse; | |
| margin:20px 0; | |
| overflow:hidden; | |
| } | |
| th, td { | |
| padding:10px 12px; | |
| border-bottom:1px solid #334155; | |
| vertical-align:middle; | |
| } | |
| th { | |
| text-align:left; | |
| font-weight:600; | |
| } | |
| input { | |
| width:100%; | |
| background:var(--panel2); | |
| color:var(--text); | |
| border:1px solid #334155; | |
| border-radius:6px; | |
| padding:6px; | |
| } | |
| select { | |
| background:var(--panel2); | |
| color:var(--text); | |
| border:1px solid #334155; | |
| border-radius:6px; | |
| padding:6px; | |
| } | |
| small { | |
| color:var(--muted); | |
| } | |
| .summary { | |
| margin:16px 0; | |
| font-weight:600; | |
| color:var(--accent); | |
| } | |
| .drawer { | |
| display:grid; | |
| gap:4px; | |
| padding:12px; | |
| background:var(--panel2); | |
| border-radius:14px; | |
| width:max-content; | |
| box-shadow:0 20px 40px rgba(0,0,0,.4); | |
| } | |
| .plate { | |
| position:relative; | |
| display:grid; | |
| border-radius:10px; | |
| overflow:hidden; | |
| box-shadow: inset 0 0 0 2px rgba(0,0,0,.45); | |
| } | |
| .cell { | |
| background: | |
| linear-gradient(135deg, | |
| rgba(255,255,255,.25), | |
| rgba(255,255,255,.05)); | |
| box-shadow: inset 0 0 0 1px rgba(0,0,0,.25); | |
| } | |
| .label { | |
| position:absolute; | |
| inset:0; | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| font-weight:700; | |
| font-size:16px; | |
| background:rgba(255,255,255,.35); | |
| color:#020617; | |
| pointer-events:none; | |
| } | |
| .unit-toggle { | |
| display:flex; | |
| gap:6px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Gridfinity Base Plate Planner</h1> | |
| <p> | |
| Plan Gridfinity base plates that fully fill a drawer while respecting 3D printer | |
| bed limits. All calculations are performed internally in millimeters, even when | |
| imperial units are selected. | |
| </p> | |
| <h2>Inputs</h2> | |
| <table> | |
| <tr> | |
| <th>Input</th> | |
| <th>Value</th> | |
| <th>Units</th> | |
| <th>Description</th> | |
| </tr> | |
| <tr> | |
| <td>Drawer Width</td> | |
| <td><input id="dw" value="12"></td> | |
| <td> | |
| <select id="dwUnit"> | |
| <option value="mm">mm</option> | |
| <option value="in" selected>in</option> | |
| </select> | |
| </td> | |
| <td><small>Inside drawer width</small></td> | |
| </tr> | |
| <tr> | |
| <td>Drawer Depth</td> | |
| <td><input id="dd" value="9.375"></td> | |
| <td> | |
| <select id="ddUnit"> | |
| <option value="mm">mm</option> | |
| <option value="in" selected>in</option> | |
| </select> | |
| </td> | |
| <td><small>Inside drawer depth</small></td> | |
| </tr> | |
| <tr> | |
| <td>Gridfinity Unit Size</td> | |
| <td><input id="unit" value="42"></td> | |
| <td> | |
| <select id="unitUnit"> | |
| <option value="mm" selected>mm</option> | |
| <option value="in">in</option> | |
| </select> | |
| </td> | |
| <td><small>Standard Gridfinity = 42 mm</small></td> | |
| </tr> | |
| <tr> | |
| <td>Printer Bed X</td> | |
| <td><input id="bx" value="200"></td> | |
| <td> | |
| <select id="bxUnit"> | |
| <option value="mm" selected>mm</option> | |
| <option value="in">in</option> | |
| </select> | |
| </td> | |
| <td><small>Maximum printable width</small></td> | |
| </tr> | |
| <tr> | |
| <td>Printer Bed Y</td> | |
| <td><input id="by" value="200"></td> | |
| <td> | |
| <select id="byUnit"> | |
| <option value="mm" selected>mm</option> | |
| <option value="in">in</option> | |
| </select> | |
| </td> | |
| <td><small>Maximum printable depth</small></td> | |
| </tr> | |
| </table> | |
| <h2>Computed Layout</h2> | |
| <div class="summary" id="summary"></div> | |
| <div class="drawer" id="drawer"></div> | |
| <script> | |
| const IN_TO_MM = 25.4; | |
| function toMM(value, unit) { | |
| return unit === "in" ? value * IN_TO_MM : value; | |
| } | |
| function split(n, max) { | |
| const parts = []; | |
| while (n > 0) { | |
| let s = Math.min(max, n); | |
| if (s === 1) { | |
| parts[parts.length - 1]--; | |
| s = 2; | |
| } | |
| parts.push(s); | |
| n -= s; | |
| } | |
| return parts; | |
| } | |
| function plan() { | |
| const drawerW = toMM(+dw.value, dwUnit.value); | |
| const drawerD = toMM(+dd.value, ddUnit.value); | |
| const unitMM = toMM(+unit.value, unitUnit.value); | |
| const bedXMM = toMM(+bx.value, bxUnit.value); | |
| const bedYMM = toMM(+by.value, byUnit.value); | |
| const cols = Math.floor(drawerW / unitMM); | |
| const rows = Math.floor(drawerD / unitMM); | |
| summary.textContent = `Overall Grid: ${cols} × ${rows} units`; | |
| drawer.style.gridTemplateColumns = `repeat(${cols}, ${unitMM}px)`; | |
| drawer.style.gridTemplateRows = `repeat(${rows}, ${unitMM}px)`; | |
| drawer.innerHTML = ""; | |
| const maxCols = Math.floor(bedXMM / unitMM); | |
| const maxRows = Math.floor(bedYMM / unitMM); | |
| const colParts = split(cols, maxCols); | |
| const rowParts = split(rows, maxRows); | |
| let y = 1; | |
| rowParts.forEach(r => { | |
| let x = 1; | |
| colParts.forEach(c => { | |
| const plate = document.createElement("div"); | |
| plate.className = "plate"; | |
| plate.style.gridColumn = `${x} / span ${c}`; | |
| plate.style.gridRow = `${y} / span ${r}`; | |
| plate.style.gridTemplateColumns = `repeat(${c}, ${unitMM}px)`; | |
| plate.style.gridTemplateRows = `repeat(${r}, ${unitMM}px)`; | |
| plate.style.background = `hsl(${Math.random()*360},70%,65%)`; | |
| for (let i = 0; i < c * r; i++) | |
| plate.appendChild(document.createElement("div")).className = "cell"; | |
| const label = document.createElement("div"); | |
| label.className = "label"; | |
| label.textContent = `${c} × ${r}`; | |
| plate.appendChild(label); | |
| drawer.appendChild(plate); | |
| x += c; | |
| }); | |
| y += r; | |
| }); | |
| } | |
| document.querySelectorAll("input, select").forEach(el => | |
| el.addEventListener("input", plan) | |
| ); | |
| plan(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment