// ---- licence ----
// MIT License
//
// Copyright (c) 2026 Gale Lab, University of Utah
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ---- end of licence ----

// =============================================================================
//  channel_200x200um_58p5mm.scad  --  Straight-channel chip, 200 x 200 um, 58.5 mm long
//
//  A single monolithic solid block, 3D-printed on a resin SLA machine (Phrozen
//  Sonic Mighty 12K, 19 x 24 um), in which one straight rectangular channel
//  (H deep x W wide) runs port-to-port at mid-height, with a solid floor below,
//  a solid ceiling above, and two vertical cylindrical access bores to the top
//  surface. It is the straight-channel sibling of the 20 uL, 500 um chamber chip
//  (chamber_20ul_500um): the same block, floor, ceiling, ports and checks, with a
//  straight channel in place of the stadium chamber.
//
//  Target: channel depth 200 um, width 200 um, length 58.5 mm.
//  The ports are straight cylindrical bores.
//
//  Design: Ali Tahmasbizadeh, Gale Lab, University of Utah.
//
//  Units: millimetres (1 uL == 1 mm^3). Channel long axis = X; block base z = 0;
//  channel centred on the origin in XY, at mid-height.
//
//  USAGE:
//    use <channelchip.scad>;  channelchip(channel_h_um=200, channel_w_um=200, ...);
//  Opening THIS file directly renders the default-parameter chip (guarded call
//  at the bottom; `use <>` imports the module without running it).
// =============================================================================
//
//  PITCH NOTE (2026-09-16).  This header used to name the target machine as a
//  "Phrozen 12K class, ~22 um/px" part.  That was wrong: 22 um is the pixel
//  pitch of a Phrozen Sonic Mini 8K, a different machine the lab's printer had
//  been misidentified as.  The actual machine is a Phrozen Sonic Mighty 12K
//  with NON-SQUARE pixels, 19 um in X and 24 um in Y.
//
//  NO GEOMETRY IN THIS FILE CHANGED.  Every dimension below is in millimetres
//  and the printer rasterises millimetres on its own grid, so the part is the
//  same physical size it always was.  What changes is the pixel COUNT a given
//  feature occupies, and the rounding: worst case half a pixel, +/-9.5 um in X
//  and +/-12 um in Y.  Layer-height dimensions are unaffected.
//  See docs/process-record/ and /hardware/#printers.
module channelchip(
    // --- channel cross-section (target: H x W) ---
    channel_h_um   = 200,     // channel DEPTH  (z)  -- target
    channel_w_um   = 200,     // channel WIDTH  (y)  -- target
    // --- design ---
    channel_len_mm = 20,      // port-to-port straight run (bore centre to bore centre)
    // --- structure ---
    floor_mm    = 1.0,
    ceiling_mm  = 0.8,
    sidewall_mm = 1.5,
    // --- ports (straight access bore; Mini Luer taper DEFERRED, see stub) ---
    port_bore_mm   = 1.5,
    port_boss_d_mm = 0,       // raised collar OD; 0 = flush (no collar)
    port_boss_h_mm = 0,       // collar height above top; 0 = flush
    // --- render ---
    render_target  = "chip",  // "chip" | "void" | "chip_cut" | "channel" | "bores"
    do_echo        = true
) {
    $fn = 96;

    // ---- derived ----
    ch_h = channel_h_um / 1000;                                  // mm  (depth, z)
    ch_w = channel_w_um / 1000;                                  // mm  (width, y)
    port_x      = channel_len_mm / 2;                            // ports at +/- port_x
    port_margin = sidewall_mm + max(port_bore_mm, port_boss_d_mm)/2;
    foot_Lx = channel_len_mm + 2*port_margin;
    foot_Wy = max(ch_w, port_bore_mm, port_boss_d_mm) + 2*sidewall_mm;
    foot_Hz = floor_mm + ch_h + ceiling_mm;

    boss_active = (port_boss_d_mm > 0) && (port_boss_h_mm > 0);
    bore_bot_z  = floor_mm - 0.1;                                // 0.1 into floor for clean overlap
    bore_top_z  = foot_Hz + (boss_active ? port_boss_h_mm : 0) + 0.1;

    channel_analytic_uL = ch_h * ch_w * channel_len_mm;         // nominal port-to-port
    channel_roof_mm     = foot_Hz - floor_mm - ch_h;            // solid roof over the channel

    // ---- robustness assert: fail loudly rather than emit a wrong-but-watertight chip ----
    assert(channel_roof_mm >= 0,
           str("channel roof breached (", channel_roof_mm,
               " mm): channel vents to the top face — raise ceiling_mm or reduce channel_h_um"));

    if (do_echo) {
        echo("=================== channelchip derived dims ===================");
        echo(str("channel_h_um / ch_h_mm    = ", channel_h_um, " / ", ch_h));
        echo(str("channel_w_um / ch_w_mm    = ", channel_w_um, " / ", ch_w));
        echo(str("channel_len_mm            = ", channel_len_mm));
        echo(str("channel_analytic_uL       = ", channel_analytic_uL));
        echo(str("port_x_mm                 = ", port_x));
        echo(str("port_margin_mm            = ", port_margin));
        echo(str("foot_Lx_mm                = ", foot_Lx));
        echo(str("foot_Wy_mm                = ", foot_Wy));
        echo(str("foot_Hz_mm                = ", foot_Hz));
        echo(str("channel_roof_mm           = ", channel_roof_mm));
        echo(str("bore_bot_z / bore_top_z   = ", bore_bot_z, " / ", bore_top_z));
        echo(str("boss_active               = ", boss_active));
        echo("===============================================================");
    }

    // ---- geometry (voids) ----
    // Straight channel: cube [channel_len+0.4, ch_w, ch_h], centred XY, on the
    // floor. The 0.2 mm overrun past each bore CENTRE guarantees a watertight
    // bore<->channel join (no coincident faces).
    module channel_void() {
        translate([-(channel_len_mm + 0.4)/2, -ch_w/2, floor_mm])
            cube([channel_len_mm + 0.4, ch_w, ch_h]);
    }

    // Two vertical access bores from just below the channel floor up through the
    // top surface (and through the collar, if a raised boss is enabled).
    module port_bores() {
        for (s = [-1, +1])
            translate([s*port_x, 0, bore_bot_z])
                cylinder(d = port_bore_mm, h = bore_top_z - bore_bot_z);
    }

    module all_voids() { union() { channel_void(); port_bores(); } }

    // ---- solid body ----
    module chip_block() {
        translate([-foot_Lx/2, -foot_Wy/2, 0])
            cube([foot_Lx, foot_Wy, foot_Hz]);
    }
    module bosses() {
        if (boss_active)
            for (s = [-1, +1])
                translate([s*port_x, 0, foot_Hz])
                    cylinder(d = port_boss_d_mm, h = port_boss_h_mm);
    }
    module chip() {
        difference() {
            union() { chip_block(); bosses(); }
            all_voids();
        }
    }
    // Long-axis section: remove the y > 0 half to expose the channel + bores.
    module chip_cut() {
        difference() {
            chip();
            translate([-foot_Lx, 0, -1])
                cube([2*foot_Lx, foot_Wy, foot_Hz + port_boss_h_mm + 2]);
        }
    }

    // ---- dispatch ----
    if      (render_target == "chip")      chip();
    else if (render_target == "void")      all_voids();
    else if (render_target == "chip_cut")  chip_cut();
    else if (render_target == "channel")   channel_void();   // verification-only isolated feature
    else if (render_target == "bores")     port_bores();     // verification-only isolated feature
    else    echo(str("UNKNOWN render_target: ", render_target));
}


// ---------------------------------------------------------------------------
// [DEFERRED] Mini Luer female socket — NOT IMPLEMENTED (stub only)
// ---------------------------------------------------------------------------
//   Mini Luer is a common fluidic interconnect. THIS design ships
//   only a plain straight cylindrical access bore (diameter = port_bore_mm).
//   A true Mini Luer FEMALE luer socket (tapered seat + shoulder) is
//   deliberately left unimplemented — the exact seat geometry is not verified
//   against a Mini Luer drawing. Do NOT enable without spec data.
//
//   // module mini_luer_female(sgn, port_x, foot_Hz) {
//   //     // seat_d_top, seat_d_bot, seat_depth ... [UNVERIFIED]
//   //     translate([sgn*port_x, 0, foot_Hz])
//   //         cylinder(d1 = seat_d_top, d2 = seat_d_bot, h = seat_depth);
//   // }
// ---------------------------------------------------------------------------

// ---- baked parameters for this preset ----
channel_h_um   = 200;     // target: channel depth
channel_w_um   = 200;     // target: channel width
channel_len_mm = 58.5;     // target: channel length
floor_mm = 1.0; ceiling_mm = 0.8; sidewall_mm = 1.5;
port_bore_mm = 1.5; port_boss_d_mm = 0; port_boss_h_mm = 0;
$fn = 96;
render_target = "chip";   // chip | void | chip_cut | channel | bores
channelchip(channel_h_um=channel_h_um, channel_w_um=channel_w_um, channel_len_mm=channel_len_mm,
            floor_mm=floor_mm, ceiling_mm=ceiling_mm, sidewall_mm=sidewall_mm,
            port_bore_mm=port_bore_mm, port_boss_d_mm=port_boss_d_mm, port_boss_h_mm=port_boss_h_mm,
            render_target=render_target);
