Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
"""
Verify our generated bins against the official-style Gridfinity Specification
(grizzie17, Printables 417152). All numeric checks are derived directly from
the PDF dimension drawings.
Run: ./venv/bin/python verify_spec.py
"""
from __future__ import annotations
import struct
import sys
from dataclasses import dataclass
import gridfinity as G
# ---------------------------------------------------------------------------
# STL utilities (read-only)
# ---------------------------------------------------------------------------
def stl_vertices(path: str):
"""Return (Nx3) float32 vertex list and bbox of an STL file."""
with open(path, "rb") as f:
f.seek(80)
n = struct.unpack("<I", f.read(4))[0]
verts = []
mn = [1e18] * 3
mx = [-1e18] * 3
for _ in range(n):
f.read(12) # normal
for _v in range(3):
v = struct.unpack("<3f", f.read(12))
verts.append(v)
for i in range(3):
if v[i] < mn[i]: mn[i] = v[i]
if v[i] > mx[i]: mx[i] = v[i]
f.read(2)
return verts, mn, mx
def vmin_at(verts, predicate):
return min((v for v in verts if predicate(v)), key=lambda v: tuple(v), default=None)
# ---------------------------------------------------------------------------
# Spec-derived expected values
# ---------------------------------------------------------------------------
@dataclass
class SpecBin:
"""Reference values from grizzie17 PDF."""
grid_xy_mm: float = 42.0
grid_z_mm: float = 7.0
tolerance_per_side: float = 0.25
foot_step1_h: float = 0.8 # bottom step
foot_step2_h: float = 1.8 # mid vertical
foot_chamfer_h: float = 2.15 # 45° transition
foot_total_h: float = 4.75
bin_corner_radius: float = 3.75 # outer fillet (4.0 - tolerance)
foot_radius_bot: float = 0.8 # = 1.05 - 0.25
foot_radius_mid: float = 1.6 # = 1.85 - 0.25
lip_above_top_h: float = 4.4
lip_section_h: list = None # [0.7, 1.8, 1.9]
def __post_init__(self):
if self.lip_section_h is None:
self.lip_section_h = [0.7, 1.8, 1.9]
def expected_outer_xy(self, grids_x, grids_y):
return (grids_x * self.grid_xy_mm - 2 * self.tolerance_per_side,
grids_y * self.grid_xy_mm - 2 * self.tolerance_per_side)
def expected_total_z(self, grids_z, has_lip=True, rounded_lip=True):
h = grids_z * self.grid_z_mm
if has_lip:
h += self.lip_above_top_h
if rounded_lip:
# SCAD source clips the very top by 0.75 mm (h5 in stacklip)
h -= 0.75
return h
SPEC = SpecBin()
# ---------------------------------------------------------------------------
# Checks
# ---------------------------------------------------------------------------
PASS = "OK"
FAIL = "FAIL"
results = []
def check(name: str, actual, expected, tol=0.05):
if abs(actual - expected) <= tol:
results.append((PASS, name, f"{actual:.3f} ≈ {expected:.3f} (Δ={actual-expected:+.3f})"))
else:
results.append((FAIL, name, f"{actual:.3f} vs {expected:.3f} (Δ={actual-expected:+.3f}, tol={tol})"))
def verify_bin(grids_x, grids_y, grids_z, label):
print(f"\n=== {label} ({grids_x}×{grids_y}×{grids_z}) ===")
p = G.GridfinityParams(grids_x=grids_x, grids_y=grids_y, grids_z=grids_z)
path = f"/tmp/spec_{label}.stl"
G.export(p, path)
verts, mn, mx = stl_vertices(path)
# 1. Outer footprint
ex_x, ex_y = SPEC.expected_outer_xy(grids_x, grids_y)
actual_x = mx[0] - mn[0]
actual_y = mx[1] - mn[1]
check(f" outer X (expect {grids_x}×42 - 0.5)", actual_x, ex_x)
check(f" outer Y (expect {grids_y}×42 - 0.5)", actual_y, ex_y)
# 2. Total Z (with stacking lip, rounded top accounted for)
ex_z = SPEC.expected_total_z(grids_z)
actual_z = mx[2] - mn[2]
check(f" total Z (lip top, after 0.75mm chop)", actual_z, ex_z, tol=0.1)
# 3. Foot bottom z = 0
check(f" foot bottom z", mn[2], 0.0, tol=0.01)
# 4. Foot top transition: at z = foot_total_h, body cylinder begins
# Find vertices near z = 4.75; their min XY radius from corner should equal bin_corner_radius
z_target = SPEC.foot_total_h
near_foot_top = [v for v in verts if abs(v[2] - z_target) < 0.05]
print(f" vertices near z={z_target}: {len(near_foot_top)}")
if near_foot_top:
# Most-central vertex should be inside the rounded rect; outermost X
# should be outer_x/2
max_x_at_top = max(v[0] for v in near_foot_top)
# bin centered at 0
check(f" foot top outer X (= {ex_x/2})", max_x_at_top, ex_x / 2, tol=0.05)
# 5. Foot bottom outline: at z=0, inset by ~2.95 mm from outer
near_floor = [v for v in verts if abs(v[2]) < 0.01]
if near_floor:
max_x_floor = max(v[0] for v in near_floor)
# spec: outer at z=0 is inset by 2.95 from bin's outer plane
# bin outer half-width = ex_x/2; foot bottom extends to ex_x/2 - (foot_inset)
# foot_inset comes from the 45° chamfer + step + tolerance
# spec dimension "2.95" is horizontal length of foot profile from outer
# to where the 45° chamfer starts at the bottom
expected_floor_x = ex_x / 2 - 2.95
check(f" foot bottom outline X (spec 2.95 inset)", max_x_floor, expected_floor_x, tol=0.1)
# ---------------------------------------------------------------------------
# Run checks
# ---------------------------------------------------------------------------
verify_bin(1, 1, 1, "1x1x1")
verify_bin(1, 1, 3, "1x1x3")
verify_bin(2, 2, 2, "2x2x2")
verify_bin(3, 2, 6, "3x2x6")
print("\n" + "=" * 60)
n_pass = sum(1 for r in results if r[0] == PASS)
n_fail = len(results) - n_pass
print(f" {n_pass} passed, {n_fail} failed of {len(results)} checks")
print("=" * 60)
for status, name, info in results:
marker = "✓" if status == PASS else "✗"
print(f" {marker} {name:50s} {info}")
sys.exit(0 if n_fail == 0 else 1)
5efd0557aThis file is pinned to the selected release. Download the original to open it in your design tool.