FlowCad is the fast, free, and precise web app for 3D design and printing. Build professional parametric models with ease-no programming required, but it's there if you need it.
/** name: Gridfinity */
const { min, max, PI, floor } = Math
interface GridfinityParams {
type: 'bin' | 'baseplate' | { value: 'bin' | 'baseplate'; options: string[] }
dim: 'disabled'
box: {
x: number
y: number
z: number
}
grid: {
rowCount: number | { value: number; step: number; min: number }
columnCount: number | { value: number; step: number; min: number }
}
wallThickness: number
bottomThickness: number
}
const PARAMS: GridfinityParams = {
// This turns the input into a select dropdown in the properties panel
type: {
value: 'bin',
options: ['bin', 'baseplate'],
},
dim: 'disabled',
box: {
x: 42,
y: 42,
z: 35,
},
grid: {
rowCount: { value: 2, step: 1, min: 1 },
columnCount: { value: 3, step: 1, min: 1 },
},
wallThickness: 1.2,
bottomThickness: 0,
}
function coneHull(cx: number, cy: number, h: number, rLow: number, rHigh: number) {
if (h <= 0) h = 0.001
return Manifold.hull([
cylinder(h, rLow, rHigh).translate([cx, cy, 0]),
cylinder(h, rLow, rHigh).translate([-cx, cy, 0]),
cylinder(h, rLow, rHigh).translate([cx, -cy, 0]),
cylinder(h, rLow, rHigh).translate([-cx, -cy, 0]),
])
}
function roundedBoxHull(w: number, d: number, h: number, rLow: number, rHigh: number) {
if (h <= 0) h = 0.001
const cx = w / 2 - rHigh
const cy = d / 2 - rHigh
const clx = Math.max(0.01, cx)
const cly = Math.max(0.01, cy)
return Manifold.hull([
cylinder(h, rLow, rHigh).translate([clx, cly, 0]),
cylinder(h, rLow, rHigh).translate([-clx, cly, 0]),
cylinder(h, rLow, rHigh).translate([clx, -cly, 0]),
cylinder(h, rLow, rHigh).translate([-clx, -cly, 0]),
])
}
function createMalePad(pitchX: number, pitchY: number) {
const cx = (pitchX - 8) / 2
const cy = (pitchY - 8) / 2
const lowerTaper = coneHull(cx, cy, 0.8, 0.8, 1.6)
const riser = coneHull(cx, cy, 1.8, 1.6, 1.6).translate([0, 0, 0.8])
const upperTaper = coneHull(cx, cy, 2.15, 1.6, 3.75).translate([0, 0, 2.6])
return union([lowerTaper, riser, upperTaper])
}
function generate(params: GridfinityParams) {
const { type, box, grid, wallThickness, bottomThickness } = params
const getVal = (v: any) => (v !== null && typeof v === 'object' && 'value' in v ? v.value : v)
const rows = getVal(grid.rowCount)
const cols = getVal(grid.columnCount)
const activeType = getVal(type)
const pitchX = box.x
const pitchY = box.y
const cl = 0.5
const w = rows * pitchX - cl
const d = cols * pitchY - cl
const h = box.z
if (activeType === 'bin') {
const pads = []
const padCutters = []
const floorZ = wallThickness + bottomThickness
for (let x = 0; x < rows; x++) {
for (let y = 0; y < cols; y++) {
const px = (x - rows / 2 + 0.5) * pitchX
const py = (y - cols / 2 + 0.5) * pitchY
pads.push(createMalePad(pitchX, pitchY).translate([px, py, 0]))
if (floorZ < 4.75) {
const innerPad = createMalePad(
pitchX - 2 * wallThickness,
pitchY - 2 * wallThickness
).translate([px, py, floorZ])
padCutters.push(innerPad)
}
}
}
let solid = union(pads)
const baseSlab = roundedBoxHull(w, d, 0.8, 3.75, 3.75).translate([0, 0, 4.0])
solid = union([solid, baseSlab])
const bodyH = h - 4.75
if (bodyH > 0) {
let body = roundedBoxHull(w, d, bodyH, 3.75, 3.75).translate([0, 0, 4.75])
solid = union([solid, body])
const innerW = w - 2 * wallThickness
const innerD = d - 2 * wallThickness
const innerH = bodyH + 10
const cavityZ = Math.max(4.75, floorZ)
const cavity = roundedBoxHull(
innerW,
innerD,
innerH,
Math.max(0.1, 3.75 - wallThickness),
Math.max(0.1, 3.75 - wallThickness)
).translate([0, 0, cavityZ])
const lipDepth = 4.75
const lipZ = h - lipDepth
const lipCutter = roundedBoxHull(
w - wallThickness,
d - wallThickness,
lipDepth + 1,
3.75,
3.75
).translate([0, 0, lipZ])
solid = difference([solid, cavity, lipCutter, ...padCutters])
}
return solid
} else if (activeType === 'baseplate') {
const bpHeight = 4.75 // Lowered from 5.0 to remove the 0.25mm bottom lip
let plate = roundedBoxHull(rows * pitchX, cols * pitchY, bpHeight, 3.75, 3.75)
const holes = []
const frameCutters = []
for (let x = 0; x < rows; x++) {
for (let y = 0; y < cols; y++) {
const px = (x - rows / 2 + 0.5) * pitchX
const py = (y - cols / 2 + 0.5) * pitchY
const cx = (pitchX - 8) / 2
const cy = (pitchY - 8) / 2
const gap = 0.25
const lowerTaper = coneHull(cx, cy, 0.8, 0.8 + gap, 1.6 + gap)
const riser = coneHull(cx, cy, 1.8, 1.6 + gap, 1.6 + gap).translate([0, 0, 0.8])
const upperTaper = coneHull(cx, cy, 2.15, 1.6 + gap, 3.75 + gap).translate([
0, 0, 2.6,
])
const through = coneHull(cx, cy, 2.0, 3.75 + gap, 3.75 + gap).translate([
0, 0, 4.75,
])
const hole = union([lowerTaper, riser, upperTaper, through])
holes.push(hole.translate([px, py, bpHeight - 4.75])) // Z translation naturally hits 0
// Apply cutouts unconditionally so it looks like the frame "grid"
const cutout = roundedBoxHull(
pitchX - 16,
pitchY - 16,
bpHeight + 2,
2,
2
).translate([px, py, -1])
frameCutters.push(cutout)
}
}
return difference([plate, ...holes, ...frameCutters])
}
return cube([0, 0, 0])
}
// @ts-ignore: Environment-specific return
return { result: generate(PARAMS), PARAMS }