#!/usr/bin/env python3
"""
Oneshot STL generator — custom geometry via Python stdlib, no pip, no OpenSCAD.
Pattern: solid blocks + grid-approximation for non-rectangular features.
Copy and modify for each new part.
"""
import math

class Vec3:
    __slots__ = ('x','y','z')
    def __init__(self,x,y,z): self.x, self.y, self.z = x, y, z

def _tri(facets, n, v1, v2, v3):
    facets.append((n, v1, v2, v3))

def _quad(facets, n, v1, v2, v3, v4):
    _tri(facets, n, v1, v2, v3)
    _tri(facets, n, v1, v3, v4)

def _nrm(dx,dy,dz):
    l = math.sqrt(dx*dx+dy*dy+dz*dz)
    return Vec3(dx/l, dy/l, dz/l)

def _box(facets, x0, y0, z0, x1, y1, z1):
    """Add a solid axis-aligned box to the facet list."""
    P = [
        Vec3(x0,y0,z0), Vec3(x1,y0,z0), Vec3(x1,y1,z0), Vec3(x0,y1,z0),
        Vec3(x0,y0,z1), Vec3(x1,y0,z1), Vec3(x1,y1,z1), Vec3(x0,y1,z1),
    ]
    _quad(facets, _nrm(0,0,-1), P[0], P[3], P[2], P[1])  # bottom
    _quad(facets, _nrm(0,0, 1), P[4], P[5], P[6], P[7])  # top
    _quad(facets, _nrm(0,-1,0), P[0], P[1], P[5], P[4])  # front
    _quad(facets, _nrm(0, 1,0), P[2], P[3], P[7], P[6])  # back
    _quad(facets, _nrm(-1,0,0), P[0], P[4], P[7], P[3])  # left
    _quad(facets, _nrm( 1,0,0), P[1], P[2], P[6], P[5])  # right


def write_stl(path, name, facets):
    with open(path, 'w') as f:
        f.write(f"solid {name}\n")
        for n, v1, v2, v3 in facets:
            f.write(f"  facet normal {n.x:.6f} {n.y:.6f} {n.z:.6f}\n")
            f.write("    outer loop\n")
            for v in (v1, v2, v3):
                f.write(f"      vertex {v.x:.6f} {v.y:.6f} {v.z:.6f}\n")
            f.write("    endloop\n")
            f.write("  endfacet\n")
        f.write(f"endsolid {name}\n")


# ════════════════════════════════════════════════════════
# USER GEOMETRY STARTS HERE — modify for your part
# ════════════════════════════════════════════════════════

# Pattern for rounded features: grid-approximation.
# Iterate over a grid, test each cell against the circle/shape equation,
# only place boxes (wall material) where the cell centre is OUTSIDE
# the desired cutout shape.

facets = []

# ── Box dimensions (mm) ──
L, W, H = 105.0, 35.0, 32.0          # outer
slot_x0, slot_x1 = 7.5, 97.5         # slot length
slot_y0, slot_y1 = 10.0, 25.0        # slot width
slot_z_bottom = 19.0                 # slot floor z
hc_r = 5.0                           # half-circle radius
cx = (slot_x0 + slot_x1) / 2.0       # notch centre x
cz = 24.0                            # notch centre z

# 1. Solid base
_box(facets, 0, 0, 0, L, W, slot_z_bottom)

# 2. End caps (outside slot x-range, full height)
_box(facets, 0, 0, slot_z_bottom, slot_x0, W, H)
_box(facets, slot_x1, 0, slot_z_bottom, L, W, H)

# 3. Slot walls with half-circle grip notches (grid approx)
# Wall thickness on each side: slot_y0 and W-slot_y1
base_thick_left = slot_y0 - hc_r         # 5 mm
base_thick_right = W - slot_y1 - hc_r    # 5 mm

_box(facets, slot_x0, 0, slot_z_bottom, slot_x1, base_thick_left, H)
_box(facets, slot_x0, W - base_thick_right, slot_z_bottom, slot_x1, W, H)

# Front layer: only place boxes where cell centre is OUTSIDE circle
x_step, z_step = 2.0, 2.0
for x0 in [slot_x0 + i * x_step for i in range(int((slot_x1 - slot_x0) / x_step))]:
    x1 = min(x0 + x_step, slot_x1)
    for z0 in [slot_z_bottom + j * z_step for j in range(int((H - slot_z_bottom) / z_step))]:
        z1 = min(z0 + z_step, H)
        xm, zm = (x0 + x1) / 2.0, (z0 + z1) / 2.0
        if (xm - cx)**2 + (zm - cz)**2 > hc_r**2:
            # left wall strip
            _box(facets, x0, base_thick_left, z0, x1, slot_y0, z1)
            # right wall strip
            _box(facets, x0, slot_y1, z0, x1, W - base_thick_right, z1)

out = "/tmp/part_v1.stl"
write_stl(out, "part_v1", facets)
print(f"Wrote {out}  (facets: {len(facets)})")
