Gridfinity bin&grid (basics) - 3D CAD Model
blueprint

Gridfinity bin&grid (basics) - 3D CAD Model

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.

3D Model Components

Blueprint Script (TypeScript)

/** 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 }