// ============================================================
// stem_protector.scad — Clip-on Stem Sleeve  Rev A  2026-03-01
// Agent: sl-mechanical
// ============================================================
// Split-sleeve impact protector for the 25 mm OD vertical stem.
// Print in TPU (Shore 95A) for energy absorption.
// Snap-fit closure — no tools, no hardware.
// Install 3–4 sleeves along the stem at 200 mm spacing.
//
// RENDER options:
//   "assembly"     both halves closed around stem (default)
//   "front"        front half for slicing
//   "rear"         rear half for slicing
// ============================================================

RENDER = "assembly";

// ── Stem ─────────────────────────────────────────────────────
STEM_OD   = 25.0;
STEM_BORE = 25.6;            // +0.6 mm — TPU flexible, snaps closed

// ── Sleeve ───────────────────────────────────────────────────
SLEEVE_OD = 38.0;            // outer diameter
SLEEVE_H  = 80.0;            // height (print and trim to taste)

WALL      = (SLEEVE_OD - STEM_BORE) / 2;  // ~6.2 mm wall

// ── Snap-fit ─────────────────────────────────────────────────
// Tab on front half snaps into recess on rear half.
// Placed at both Z = SLEEVE_H*0.25 and Z = SLEEVE_H*0.75.
SNAP_W    =  8.0;            // snap tab width
SNAP_H    =  3.0;            // snap tab height (protrusion)
SNAP_T    =  2.5;            // snap tab thickness
SNAP_CL   =  0.3;            // recess clearance for TPU flex

// ── Ribbing ──────────────────────────────────────────────────
// Optional radial ribs for grip and stiffness
RIB_N     =  8;              // number of ribs around circumference
RIB_H     =  1.2;            // rib height
RIB_W     =  2.0;            // rib width

$fn = 64;
e   = 0.01;

// ─────────────────────────────────────────────────────────────
// sleeve_half(side)
//   side = "front" (+Y) | "rear" (-Y)
//   Split at Y=0 plane.  Print flat-face-down.
// ─────────────────────────────────────────────────────────────
module sleeve_half(side = "front") {
    y_front = (side == "front");
    y_sign  = y_front ? 1 : -1;

    difference() {
        union() {
            // Main D-shaped half cylinder
            intersection() {
                cylinder(d = SLEEVE_OD, h = SLEEVE_H);
                translate([-SLEEVE_OD/2, y_front ? 0 : -SLEEVE_OD/2, 0])
                    cube([SLEEVE_OD, SLEEVE_OD/2, SLEEVE_H]);
            }

            // Radial grip ribs on outer curved face
            for (i = [0 : RIB_N/2 - 1]) {
                ang = (i / (RIB_N/2 - 1)) * 150 - 75;  // spread 150° on each half
                rotate([0, 0, y_sign * ang + (y_front ? 0 : 180)])
                    translate([SLEEVE_OD/2, -RIB_W/2, 0])
                        cube([RIB_H, RIB_W, SLEEVE_H]);
            }

            // Snap tab (front half) — 2 positions
            if (y_front) {
                for (zh = [SLEEVE_H * 0.25, SLEEVE_H * 0.75]) {
                    translate([-SNAP_W/2, SLEEVE_OD/2 - SNAP_T, zh - SNAP_H/2])
                        cube([SNAP_W, SNAP_T + SNAP_H, SNAP_H]);
                }
            }
        }

        // Stem bore
        translate([0, 0, -e])
            cylinder(d = STEM_BORE, h = SLEEVE_H + 2*e);

        // Snap tab recess in rear half
        if (!y_front) {
            for (zh = [SLEEVE_H * 0.25, SLEEVE_H * 0.75]) {
                translate([-(SNAP_W + SNAP_CL)/2,
                           SLEEVE_OD/2 - (SNAP_T + SNAP_H + SNAP_CL + e),
                           zh - (SNAP_H + SNAP_CL)/2])
                    cube([SNAP_W + SNAP_CL, SNAP_H + SNAP_CL + e, SNAP_H + SNAP_CL]);
            }
        }

        // Mating face: very small 0.2 mm chamfer to prevent elephant-foot binding
        translate([0, 0, -e])
            rotate([0, 0, y_front ? 0 : 180])
                translate([-SLEEVE_OD/2, -0.2, 0])
                    cube([SLEEVE_OD, 0.2, SLEEVE_H + 2*e]);
    }
}

// ─────────────────────────────────────────────────────────────
// Render selector
// ─────────────────────────────────────────────────────────────
if (RENDER == "assembly") {
    color("OrangeRed", 0.85) sleeve_half("front");
    color("Tomato",    0.85) mirror([0,1,0]) sleeve_half("rear");

    // Reference stem
    color("Silver", 0.3)
        translate([0, 0, -10])
            cylinder(d = STEM_OD, h = SLEEVE_H + 20);

} else if (RENDER == "front") {
    sleeve_half("front");

} else if (RENDER == "rear") {
    sleeve_half("rear");
}
