Skip to content

Instantly share code, notes, and snippets.

@michaelmob
Created January 12, 2026 05:12
Show Gist options
  • Select an option

  • Save michaelmob/7e5e660360d3316ed4209daf14f936cb to your computer and use it in GitHub Desktop.

Select an option

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.
<!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&nbsp;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