// Mirror camera mount v2: 38 mm 4K USB board camera, tilted down through the round opening.
// Sits straight on the three threaded studs around the opening, held by your M3 nuts.
// The body is a light-tight shroud: four walls from the plate up to the tilted board, open at the bottom over the glass.
//
// Render:  openscad -D 'part="print"' -o angled_mount.stl angled_mount.scad
// part = "print" (pads on the bed, no supports) | "skeleton" (fast fit test: same mount, walls cut to corner posts) | "preview" (scene + camera) | "jig" (flat stud template) | "clash" (camera ∩ mount, must be empty)
part = "preview";

// ---- Mirror side, from IMG_0061..0066. Origin = stud P2 on the panel side.
// +X along the bay toward P1, +Y across the bay toward the outer wall, Z up from the plate.
// v2 numbers from the printed jig sitting on the studs (2026-11-02 photo, scale from P1-P2 = caliper 58.83).
// v1 photo guess had the opening 3.7 mm too close to the panel and P3 1.2 mm too far left.
studs   = [[0, 0], [58.83, 0], [-0.8, 56.4]];   // P2, P1, P3
hole_c  = [42.1, 23.8];                          // opening centre
hole_d  = 27.1;   bowl_d = 35.5;                 // clear glass / top of the bevel ring
stud_d  = 4.0;  stud_h = 6;                      // M4 studs (height is a guess: pad 2.4 + M4 nut 3.2 must fit under it)
panel_y = -5.5;  // jig v1 photo: the panel lip starts ~7.7 mm from P1/P2; nothing goes past this line
ceiling = 34;                                    // ASSUMED plate-to-back-cover clearance, preview only. Measure it.

// ---- Camera, from IMG_0069..0072 (local: board front face at z 0, lens toward -z, plug edge at +y)
brd = 38.4;  brd_t = 1.6;  brd_pitch = 34;        // corner holes, M2
lens_h = 25.7;  ring_d = 18;  barrel_d = 14;  holder = 19;  holder_h = 7.3;
plug = [13, 6.5, 12];  plug_y = 13;               // right-angle USB-C on the back, 13 mm from centre toward one edge

// ---- Fit
tilt = 20;          // down-tilt. 20 deg is where the high board corner and the plug top come out level (lowest stack)
dip  = 2.5;         // lens ring's low edge sinks this far below the plate top, into the clear hole
wall = 2.2;  fit = 0.25;  e = brd/2 + fit;     // pocket half-width: the walls' inner faces are the board's edges + fit
rim_h = 0.8;        // walls rise this far past the board's front face, so the board drops into a pocket. Half the board's thickness: the high edge stays under the plug top
// No screws: a spring tongue cut into each side wall hooks over the board's back edge.
tongue_w = 12;  tongue_len = 14;  slit = 0.8;  lip = 1.2;  lip_z = brd_t + 0.1;  lip_top = lip_z + 1.6;   // v3: 9 wide / 0.7 lip let go when flipped or tugged
pad_d = 13;  pad_t = 2.4;  clr_d = 5.0;   /* jig v1: 4.5 printed holes would not go over the M4 studs */  arm_w = 7;  flange = 2.5;  r_in = 4;
$fn = 64;

s = sin(tilt);  c = cos(tilt);
F = [hole_c[0], hole_c[1] + 1.5, ring_d/2*s - dip];   // lens front centre; +1.5 Y so the axis crosses the glass on the hole centre
module place() translate(F) rotate([-tilt, 0, 0]) translate([0, 0, lens_h]) children();   // camera local -> world
function w(p) = let(y = p[1]*c + (p[2] + lens_h)*s, z = -p[1]*s + (p[2] + lens_h)*c) [F[0] + p[0], F[1] + y, F[2] + z];   // same, for points
// rotate([-tilt]) sends local (0,y,z) to (0, y c + z s, -y s + z c)

echo(board_high_back_z = w([0, -brd/2, brd_t])[2], plug_top_z = w([0, plug_y, brd_t + plug[2]])[2], seat_high_z = w([0, -brd/2, 0])[2]);

module camera() place() {
    color("#1a1a1a") difference() {
        translate([-brd/2, -brd/2, 0]) cube([brd, brd, brd_t]);
        for (x=[-1,1], y=[-1,1]) translate([x*brd_pitch/2, y*brd_pitch/2, -1]) cylinder(d=2.2, h=5);
    }
    color("#333") translate([-holder/2, -holder/2, -holder_h]) cube([holder, holder, holder_h]);
    color("#222") translate([0, 0, -lens_h + 8]) cylinder(d=barrel_d, h=lens_h - 8 - holder_h + 0.01);
    color("#111") translate([0, 0, -lens_h]) cylinder(d=ring_d, h=8);
    color("#88a") translate([0, 0, -lens_h - 0.1]) cylinder(d=ring_d - 6, h=0.2);
    color("silver") translate([-plug[0]/2 - 2, plug_y - plug[1]/2, brd_t]) cube(plug);
}

// seat footprint = the shroud's top square seen from above. At the plate the footprint also takes in the lens ring,
// so the shroud is a loft: square at the top, wider foot at the bottom (front wall leans out over the lens).
seat = [for (p=[[-1,-1], [1,-1], [1,1], [-1,1]]) let(q = w([p[0]*(e + wall), p[1]*(e + wall), 0])) [q[0], q[1]]];
module seat2d() polygon(seat);
module foot2d() offset(r=2) offset(delta=-2) hull() { seat2d(); translate([F[0], F[1]]) circle(d=ring_d + 3 + 2*wall); }
module loft(off, ztop) hull() {
    place() translate([0, 0, ztop]) linear_extrude(0.01) offset(r=3 - off) square(2*(e + wall) - 6, center=true);   // rounded vertical edges
    translate([0, 0, off > 0 ? -1 : 0]) linear_extrude(0.01) offset(delta=-off) foot2d();
}
collar_h = 4;       // straight section square to the board, so the pocket walls meet the board edges cleanly
module ring2d(off) offset(r=3 - off) square(2*(e + wall) - 6, center=true);

module pads() place() for (x=[-1,1], y=[-1,1]) hull() {   // corner pads: the board's only contact (front SMDs clear), sloped undersides print without support
    translate([x*(e - 2), y*(e - 2), -0.011]) cube([4, 4, 0.01], center=true);   // top 6 microns under the board so the clash test stays clean
    translate([x*(e - 0.5), y*(e - 0.5), -collar_h]) cube([1, 1, 0.01], center=true);
}
module shroud() union() {
    difference() {
        union() { loft(0, -collar_h); place() translate([0, 0, -collar_h]) linear_extrude(collar_h + rim_h) ring2d(0); }
        loft(wall, -collar_h + 0.01);
        place() translate([0, 0, -collar_h - 0.5]) linear_extrude(collar_h + rim_h + 1) ring2d(wall);
        place() for (x=[-1,1], y=[-1,1])   // two slits per side wall free a tongue
            translate([x*(e + wall/2), y*(tongue_w + slit)/2, rim_h - tongue_len/2 + 1]) cube([wall + 2, slit, tongue_len + 2], center=true);
    }
    pads();
}

// tongue tops with their lips, added above the shroud's rim (camera-local)
module tongue_tops() place() for (sx=[-1,1]) mirror([sx < 0 ? 1 : 0, 0, 0]) {
    translate([e, -tongue_w/2, rim_h - 0.01]) cube([wall, tongue_w, lip_top - rim_h + 0.01]);
    hull() {   // flat underside just over the board back, ramp on top so the board pushes the tongue out
        translate([e - fit - lip, -tongue_w/2, lip_z]) cube([lip + fit + wall, tongue_w, 0.3]);
        translate([e, -tongue_w/2, lip_top]) cube([wall, tongue_w, 0.01]);
    }
}

// base: flange around the shroud plus three arms out to the stud pads, filleted into one flat web
attach = [[seat[0][0] + 2, seat[0][1] + 2], [seat[1][0] - 2, seat[1][1] + 2], [seat[3][0] + 2, seat[3][1] - 2]];  // P2, P1, P3 land on the nearest corner
module seg(p, q, wp, wq) hull() { translate(p) circle(d=wp); translate(q) circle(d=wq); }
module base2d() intersection() { translate([-100, panel_y]) square(300); base2d_(); }
module base2d_() offset(r=-r_in) offset(r=r_in) union() {
    offset(r=flange) foot2d();
    for (i=[0:2]) { translate(studs[i]) circle(d=pad_d); seg(studs[i], attach[i], arm_w, arm_w + 4); }
}
module base() difference() {
    union() {
        linear_extrude(pad_t - 0.6) base2d();
        for (k=[0:2]) translate([0, 0, pad_t - 0.6 + 0.2*k]) linear_extrude(0.2) offset(r=-0.2*(k + 1)) base2d();   // chamfered top edge
    }
    translate([0, 0, -1]) linear_extrude(10) offset(delta=-wall) foot2d();     // open over the glass
}
// gusset fins along each arm, rising from nut clearance into the shroud wall
module fins() for (i=[0:2]) let(p = studs[i], q = attach[i], d = q - p, L = norm(d), u = d/L, a0 = p + u*7)
    hull() {
        translate([a0[0], a0[1], 0]) cylinder(d=2.4, h=pad_t);
        translate([q[0], q[1], 0]) cylinder(d1=5, d2=2, h=16);
    }

module mount() difference() {
    union() { shroud(); base(); difference() { fins(); loft(wall, -collar_h + 0.01); } tongue_tops(); }   // fins stop at the wall, not inside the shroud
    for (p=studs) translate([p[0], p[1], -1]) cylinder(d=clr_d, h=20);
    for (p=studs) translate([p[0], p[1], pad_t]) cylinder(d=12, h=20);         // M4 nut + washer + socket room above each pad
}

module scene() {
    color("#2a2a2a") difference() {
        translate([-15, -8, -1.5]) cube([105, 74, 1.5]);
        translate([hole_c[0], hole_c[1], -2]) cylinder(d1=hole_d, d2=bowl_d, h=1.51);
    }
    color("#bcd", 0.5) translate([hole_c[0], hole_c[1], -1.6]) cylinder(d=hole_d + 2, h=0.1);
    color("silver") for (p=studs) translate([p[0], p[1], 0]) cylinder(d=stud_d, h=stud_h);
    color("#555") for (p=studs) translate([p[0], p[1], pad_t]) cylinder(d=8.1, h=3.2, $fn=6);
    color("#111") for (p=[[72.6, 22.9], [72.9, 55.4]]) translate([p[0], p[1], 0]) difference() { cylinder(d=4.5, h=14); cylinder(d=2.5, h=15); }  // spare standoffs
    color("#222") translate([-15, 66, 0]) cube([105, 1.5, 28]);                                             // outer wall
    %translate([-15, -8, ceiling]) cube([105, 74, 0.5]);
}

// flat template: three stud holes, the opening ring, the shroud outline. Drop it on the studs to check before the real print.
module jig() difference() {
    linear_extrude(1.2) union() {
        intersection() { translate([-100, panel_y]) square(300); union() for (i=[0:2]) { translate(studs[i]) circle(d=11); seg(studs[i], attach[i], 5, 5); } }
        difference() { offset(r=1.5) foot2d(); foot2d(); }
        difference() { offset(r=1) seat2d(); seat2d(); }
        translate(hole_c) difference() { circle(d=bowl_d + 3); circle(d=bowl_d); }
        translate(hole_c) for (a=[0, 90]) rotate(a) square([bowl_d + 2, 1.2], center=true);   // crosshair on the opening centre
    }
    for (p=studs) translate([p[0], p[1], -1]) cylinder(d=clr_d, h=5);
}

// fast fit-test version: same mount with big pointed-arch windows in the walls (vertical sides, 45 deg tops: no supports).
// Leaves corner posts, the base and the collar with its tongues. Not light-tight.
module arch2d(w, z0, apex) polygon([[-w/2, z0], [w/2, z0], [w/2, apex - w/2], [0, apex], [-w/2, apex - w/2]]);
module win_y(x, w, z0, apex, y0, y1) translate([x, y1, 0]) rotate([90, 0, 0]) linear_extrude(y1 - y0) arch2d(w, z0, apex);           // walls facing Y
module win_x(y, w, z0, apex) translate([-20, y, 0]) rotate([90, 0, 90]) linear_extrude(120) arch2d(w, z0, apex);                       // both side walls
module skeleton() difference() {
    mount();
    win_y(F[0], 33, 3.5, 26.5, -10, F[1]);                                  // high wall (collar starts ~28)
    for (dx=[-8.5, 8.5]) win_y(F[0] + dx, 13, 3.5, 12, F[1] + 14, 70);       // low wall (collar starts ~13.6)
    win_x(F[1] - 5.5, 7.5, 3.5, 22);                                        // side walls, either side of the tongue
    win_x(F[1] + 16.2, 10.5, 3.5, 14.5);
}

// the stud/hole numbers came out mirror-imaged (v2 print: third leg landed on the spare standoff), so the printed parts are flipped in X
if (part == "print") mirror([1, 0, 0]) mount();
else if (part == "skeleton") mirror([1, 0, 0]) skeleton();
else if (part == "jig") mirror([1, 0, 0]) jig();
else if (part == "clash") intersection() { mount(); camera(); }
else mirror([1, 0, 0]) { scene(); color("#d8d8d0") mount(); camera(); }   // flipped too, so previews match the real bay
