/*
////////////////////////////////////////////////////
See my other designs:
https://www.thingiverse.com/steeveeet/designs
////////////////////////////////////////////////////

This file is designed to be entirely customisable.

The easiest way to use this is to copy the top sections (Basic and Advanced Customisation) into a new file
and add `include <U_Box.scad>;` at the top of that file. That will bring all the functionality of this file into a new file
so that you can define the box specific value for it.

There are 3 main sections to this file:

*** Basic Customisation ***
Top section defines the variables for the box - 
these are all exposed through the Customizer UI.
The last section (display settings) is used to control what is displayed 
(and thus exported to STL when the time comes)


*** Advanced Customisation ***
The configuratino fo the end panels are here.
These modules define the shapes to use to cut out holes from the front and back panels,
and to add (text) decoration to the front and back panels

In the PCB mount locations you'll find an array of point constructed 
from the rectangle defined by the PCB Width and Height defined in the UI.
If you wish to ovverride this (if you need more than 4 mounting points, 
or they are not in a rectangle) simply construct that array in directly and the model will update


*** Implementation ***
This section contains the modules and functions that are used to actually construict the parts of the box
It's unlikely you will need to change this, though I'd be very interested if you find that you do!

////////////////////////////////////////////////////
*/

/********************************************************************
***                      Basic Customisation                      ***
*********************************************************************/

/* [Case Settings] */
    Length = 80;
    Width = 60;
    Height = 30;
    WallThickness = 2; // [1:5]  

    FilletRadius = 2; // [0.1:12] 
    // Decorations or ventilation holes
    VentType = "VentHoles"; // [None, Decorations, VentHoles:Ventilation Holes]
    // Width of vent/decoration holes
    VentWidth = 1.5;

/* [Fastening Options] */
    // Methods of securing the closure of the box
    FasteningType = "Push"; // [Screw:Screw Fit, Push:Push Fit]
    // Number for fastenings along thge edge
    NumberOfFastenings = 2; // [0:10]
    // Thickness of the fastening tabs
    FasteningTabThickness = 2; // [1:5]
    // Diameter of fastener hole for screw fitting
    FasteningHoleDiameter = 2; // [0.1:5]

/* [End Panels] */
    BackPanelType = "IntegratedWithBase"; // [None, IntegratedWithTop:Integrated With Top, IntegratedWithBase:Integrated With Base, Discrete]
    FrontPanelType = "IntegratedWithBase"; // [None, IntegratedWithTop:Integrated With Top, IntegratedWithBase:Integrated With Base, Discrete]
    // Panel Text Type
    EmbossPanelDecorations = 1; // [0:Demboss, 1:Emboss]
    // End Panel Tolerance
    PanelTolerance = 0.9;
    // Use example panel decorations
    examplePanels = 1; // [0:No, 1:Yes]


/* [PCB Mounts] */
    // Show PCB Ghost
    ShowPCB = 1; // [0:No, 1:Yes]
    // Add PCB Mounts
    PCBMounts = 1; // [0:No, 1:Yes]
    // Back left corner X position
    PCBPosX = 10;
    // Back left corner Y position
    PCBPosY = 10;
    // PCB Length
    PCBLength = 40;
    // PCB Width
    PCBWidth = 30;
    // Mount height
    MountHeight = 10;
    // Mount diameter
    MountDia = 8;
    // Hole diameter
    MountHoleDia = 2.5;

/* [Display Settings] */
    // Print Layout
    PrintLayout = 0; // [0:No, 1:Yes]
    // Top Case
    ShowTCase = 0; // [0:No, 1:Yes]
    // Bottom Case
    ShowBCase = 1; // [0:No, 1:Yes]
    // Front panel
    ShowFPanel = 1; // [0:No, 1:Yes]
    // Back panel  
    ShowBPanel = 1; // [0:No, 1:Yes]

/* [Hidden] */
    // Case color  
    CaseColour = "Teal";
    // End Panel color    
    PanelColour = "Aqua";
    // Fillet Smootheness  
    Resolution = 50; // [4:100] 


/************************************************************
***                   Advanced Customisation             ***
************************************************************/
{
    //////////////////////////////////////////
    /////////// Panel Configuration //////////
    //////////////////////////////////////////

    module FrontPanelCutouts(){
        if (examplePanels)
        {
            SquareHole  ([10,5],[10,10],1);
            CylinderHole([40,10],5);
        }
    }

    module FrontPanelDecoration(){
        if (examplePanels)
        {
            LText([7.5,17.5], "On/Off");
            CText([40,10], 5, "12345");
        }
    }

    module BackPanelCutouts(){
        if (examplePanels)
        {
            SquareHole  ([5,0],[10,10],1);
            CylinderHole([10,10],10);
        }
    }

    module BackPanelDecoration(){
        if (examplePanels)
        {
            LText([17.5,5], "Power");
        }
    }

    //////////////////////////////////////////
    /////////// PCB Mount locations //////////
    //////////////////////////////////////////

    pcbMountLocations = [[0,0], [PCBWidth,0], [0,PCBLength], [PCBWidth,PCBLength] ];

}

/************************************************************
***                   Implementation                      ***
************************************************************/
{
    //////////////////////////////
    /////////// Helpers //////////
    //////////////////////////////

    $fn = Resolution;

    ////////// CONSTANT: Thickness of end panels //////////
    function PanelThickness() = WallThickness;

    ////////// CONSTANT: Helper to define how much space is needed for end panel ribs, depending on type //////////
    function SpaceForEndPanel(type) = 
        type == "IntegratedWithTop" ? PanelThickness() : 
        type == "IntegratedWithBase" ? PanelThickness() : 
        type == "Discrete" ? 2*WallThickness + PanelThickness() + PanelTolerance : 
        0; // None

    ////////// CONSTANT: How much of the length of the case will be taken up by support ribs for the end panels //////////
    function SpaceForEndPanels() = 2*max(SpaceForEndPanel(FrontPanelType), SpaceForEndPanel(BackPanelType));

    ////////// CONSTANT: Vector describing exterior dimensions of the case //////////
    function CaseDims() = [Length, Width, Height];

    ////////// CONSTANT: The clear space before fittings for end panels //////////
    function InteriorDims() = [Length - SpaceForEndPanels(), Width - 2*WallThickness, Height - 2*WallThickness];

    ////////// Calculated size of tabs. Default to 8mm, but reduce if a small length or height //////////
    function TabDiameter() = 
        min(Height/2, // Don't collide with Vent Holes in reduced height boxes
            min(InteriorDims().x/2, // Don't collide with other tabs in reduced length boxes
                20)); // Sensible max height


    //////////////////////////////////////////////////
    /////////// Maths functions & Utilities //////////
    //////////////////////////////////////////////////


    ////////// Utility to work out how many items you might fit ion a given length //////////
    function numItemsAlongLength(length, itemSpacing) = ceil(length / itemSpacing);

    // Helper functions to calculate the bounding box of an array of vec2s
    function bbox(v) = [minimum(v), maximum(v)];
    {
        // Return the maximum of all the "elem"th elemtns in a vector of vectors
        function minimumElem(v, elem, i = 0) = 
            (i < len(v) - 1) ? 
                min(v[i][elem], minimumElem(v, elem, i+1)) : 
                v[i][elem];

        // Return the maximum of all the "elem"th elemtns in a vector of vectors
        function maximumElem(v, elem, i = 0) = 
            (i < len(v) - 1) ? 
                max(v[i][elem], maximumElem(v, elem, i+1)) : 
                v[i][elem];

        function minimum(v) = 
            [minimumElem(v, 0), minimumElem(v, 1)];
        function maximum(v) = 
            [maximumElem(v, 0), maximumElem(v, 1)];
    }

    /////  Operator: distribute copies of the children evenly along a vector //////////
    module distribute(start, end, numItems){
        vec = end-start;
        length = norm(vec);
        normVec = vec/length;

        itemSpacing = length / (2*numItems);
        
        // Even number of tabs
        for (i=[0: 1: numItems-1])
        {
            tabLoc = start + (itemSpacing * (1 + 2*i)) * normVec;

            translate([tabLoc.x, tabLoc.y, tabLoc.z])
            {
                children();
            }                    
        }
    }

    /////////// Generic rounded box - Aligned along the X axis //////////
    module RoundBox(dims, fillet = 0, center = false){
        if (fillet > 0){
            // Calculate the correct offset depending on whether the cube should be offset

            // Split the total height between cubeDims & cylHeight
            cylHeight = dims.x/2;
            // The fillet radius must (at most) be half either Y, or Z, but we cannot have cubeDims Y or Z equal to zero
            cappedFillet = min(min(fillet, dims.y/2.001), dims.z/2.001);

            cubeDims = [
                dims.x/2,
                dims.y - 2*cappedFillet,
                dims.z - 2*cappedFillet,
            ];

            offset = center ? [0,0,0] : dims/2;
            translate(offset){
                minkowski()
                {
                    rotate([0, 90, 0]){
                        cylinder(r = cappedFillet, h = cylHeight, center = true);
                    }
                    cube(cubeDims, center = true);
                }
            }
        } else {
            cube(dims, center = center);
        }
    }// End of RoundBox Module

    /////////// Generic hollow rounded box - Aligned along the X axis //////////
    module HollowRoundBox(dims, thickness, fillet = 0, center = false){
        offset = center ? [0,0,0] : dims/2;
        translate(offset){
            difference() {
                RoundBox(dims, fillet, true);
                RoundBox([dims.x * 1.1, dims.y - 2*thickness, dims.z - 2*thickness], fillet, true);
            }
        }
    }// End of RoundBox Module

    ////////////////////////////////////////////////////////
    ////////////////////// Main Case ///////////////////////
    ////////////////////////////////////////////////////////

    module PanelRibs(panelType, xMultiplier = 1)
    {
        // For discrete panels, define the ribs to hold them in place
        discreteRibDims = [WallThickness, CaseDims().y - 2*WallThickness, CaseDims().z - 2*WallThickness];
        // Effective thickness of the discrete panel, including tolerance
        discretePanelThickness = PanelTolerance + WallThickness;
        // Location for slot for discrete panels
        slotLocation = CaseDims().x/2 - (discreteRibDims.x + discretePanelThickness/2);
        // Offset (from slotLocation) for retaining ribs
        slotOffset = (discreteRibDims.x + discretePanelThickness)/2;

        // For integrated panels, the rib can be smaller
        integratedRibDims = [WallThickness/2, CaseDims().y - 2*WallThickness, CaseDims().z - 2*WallThickness];
        // For integrated panels, the rib can be smaller
        integratedRibOffset = (WallThickness + PanelTolerance/2) + integratedRibDims.x/2;

        // Front Panel
        if(panelType == "Discrete")
        {
            // Add ribs for supporting discrete panel
            translate([xMultiplier * (slotLocation + slotOffset), 0, 0]){
                HollowRoundBox(discreteRibDims, WallThickness, FilletRadius, true);
            }
            translate([xMultiplier * (slotLocation - slotOffset), 0, 0]){
                HollowRoundBox(discreteRibDims, WallThickness, FilletRadius, true);
            }
        }
        else if(panelType == "IntegratedWithThis"){
            // Add a supporting rib for the panel integrated with this half
            translate([xMultiplier * (CaseDims().x/2 - WallThickness/2), 0, 0]){
                HollowRoundBox([WallThickness, CaseDims().y - 2*WallThickness, CaseDims().z - 2*WallThickness], PanelTolerance/2, FilletRadius, true);
            }
        }
        else if(panelType == "IntegratedWithOther"){
            // Add a supporting rib for the panel integrated with the other half
            translate([xMultiplier * (CaseDims().x/2 - integratedRibOffset), 0, 0]){
                HollowRoundBox(integratedRibDims, WallThickness/2, FilletRadius, true);
            }
        }
    }

    ////////////////////////////////// Case //////////////////////////////////
    module Case(dims, thickness, fillet = 0, fPanelType = "None", bPanelType = "None"){
        // Hull with integrated panels (where appropriate)
        union(){
            // Decorated hull
            difference()
            {
                // Main hull
                union() {

                    // Main Shell 
                    HollowRoundBox(dims, thickness, fillet, true);

                    // Add ribs for supporting the end panels
                    PanelRibs(fPanelType, 1);
                    PanelRibs(bPanelType, -1);
                }
                // Remove the top
                translate([0, 0, Height / 2]){
                    cube([dims.x*1.1, dims.y*1.1, dims.z], true);
                }

                // Add side decoration / vents
                if (VentType != "None")
                {
                    union()
                    {
                        overlap = VentWidth; //make sure that we cleanly overlap the top/bottom/side of the case
                        decDims = [
                            VentType == "VentHoles" ? 
                                (thickness + fillet) + overlap : 
                                thickness/2 + overlap,
                            VentWidth,
                            (dims.z / 4) + overlap
                        ];
                        padding = dims.x/20; // (half) the gap between sets of vents (and between vents and end)
                        ventFillet = VentWidth/3;

                        // Coords for vents
                        xStart = padding;
                        xEnd  = InteriorDims().x/2 - padding;
                        yPos = VentType == "VentHoles" ? 
                            InteriorDims().y/2 + decDims.x/2 - fillet : // Calculated from the inside wall
                            CaseDims().y/2 - decDims.x/2 + overlap; // Calculated from the ouside wall
                        zPos = -0.5*(dims.z - decDims.z) - overlap;

                        numVents = numItemsAlongLength(xEnd-xStart, VentWidth+thickness);

                        distribute([xStart, yPos, zPos], [xEnd, yPos, zPos], numVents)
                        {
                            rotate([0, 0, 90])
                                RoundBox(decDims, ventFillet, true);
                        }
                        distribute([-xStart, yPos, zPos], [-xEnd, yPos, zPos], numVents)
                        {
                            rotate([0, 0, 90])
                                RoundBox(decDims, ventFillet, true);
                        }
                        distribute([xStart, -yPos, zPos], [xEnd, -yPos, zPos], numVents)
                        {
                            rotate([0, 0, 90])
                                RoundBox(decDims, ventFillet, true);
                        }
                        distribute([-xStart, -yPos, zPos], [-xEnd, -yPos, zPos], numVents)
                        {
                            rotate([0, 0, 90])
                                RoundBox(decDims, ventFillet, true);
                        }
                    }
                }
            }
        }
    }

    ////////////////////////////////// Fixing tabs //////////////////////////////////
    module Tab(radius, thickness){
        difference(){
            union(){
                rotate([90, 0, 0])
                {                
                    cylinder(r = radius, thickness, $fn = 6);
                }
            }
            // Chamfer
            translate([0, -radius/2, -2*radius/3]){
                rotate([45, 0, 0]){
                    cube([2*radius, radius, radius], center=true);
                }
            }

            // Tolerance cut
            tolerance = 0.1;
            tolBox = [2*radius, 2*thickness, radius];
            translate([0, (thickness-tolerance), tolBox.z/2]){
                cube(tolBox, center=true);
            }
        }
    }

    ////////////////////////////////// Case with tabs //////////////////////////////////
    module CaseWithTabs(dims, thickness, fillet = 0, fPanelType = "None", bPanelType = "None"){
        cylRad = TabDiameter()/2;
        holeDia = FasteningHoleDiameter;

        // Calculate the number of tabs and their location based on the length of the box.
        // Assume a gap of TabDiameter on each side of each tab before spawning a new tab
        numItems = NumberOfFastenings;

        // For push fitting, offset the registration sphere so it's not a complete hemisphere. Makes it easier to close.
        // Setting this to zero will result in a complete hemisphere
        sphereOffset = 0.125*holeDia;

        difference(){
            union(){
                Case(dims, thickness, fillet, fPanelType, bPanelType);

                // Tabs
                distribute(
                    [InteriorDims().x/2, InteriorDims().y/2, 0], 
                    [-InteriorDims().x/2, InteriorDims().y/2, 0],
                    numItems)
                {
                    Tab(cylRad, FasteningTabThickness);
                }

                if (FasteningType == "Push"){
                    // Add registration spheres
                    distribute(
                        [InteriorDims().x/2, -InteriorDims().y/2 - sphereOffset, -cylRad/3],
                        [-InteriorDims().x/2, -InteriorDims().y/2 - sphereOffset, -cylRad/3],
                        numItems)
                    {
                        difference(){
                            sphere(d = holeDia);
                            // Ensure the sphere doesn't stick out of the other side of the wall!
                            translate([0,-(holeDia - 2.*sphereOffset)/2,0])
                            {
                                cube(holeDia, true);
                            }
                        }
                    }
                }
            }

            if (FasteningType == "Push"){
                // Add cutouts for registration spheres
                distribute(
                    [InteriorDims().x/2, InteriorDims().y/2 + sphereOffset, cylRad/3],
                    [-InteriorDims().x/2, InteriorDims().y/2 + sphereOffset, cylRad/3],
                    numItems
                    )
                {
                    sphere(d = holeDia);
                }
            }
            else if (FasteningType == "Screw"){
                // Screw fitting so add the case holes
                distribute(
                    [InteriorDims().x/2, InteriorDims().y/2, cylRad/3],
                    [-InteriorDims().x/2, InteriorDims().y/2, cylRad/3],
                    numItems
                    )
                {
                    rotate([90, 0, 0]){
                        cylinder(d = holeDia, 3.0*thickness, center=true);
                    }
                }
                distribute(
                    [InteriorDims().x/2, -InteriorDims().y/2, -cylRad/3],
                    [-InteriorDims().x/2, -InteriorDims().y/2, -cylRad/3],
                    numItems)
                {
                    rotate([90, 0, 0]){
                        cylinder(d = holeDia, 3.0*thickness, center=true);
                    }
                }                
            }        
        }
    }

    /////////////////////////////////////////////////////////
    ////////////////////// PCB Mounts ///////////////////////
    /////////////////////////////////////////////////////////

    /////////////////////// PCB Mount /////////////////////////////
    module PCBMount(){
        FilletRadius = 2;
        color(CaseColour)
        union(){
            difference(){
                // Main cylinder
                cylinder(d = MountDia, MountHeight);
                // central hole
                cylinder(d = MountHoleDia, MountHeight*1.01);

                // Chamfer to help centre screws
                chamferRad = (MountDia-MountHoleDia)/4;
                rotate_extrude(){
                    translate([MountHoleDia / 2, (MountHeight-chamferRad)*1.01, 0]){
                        difference(){
                            square(chamferRad);
                            translate([chamferRad,0,0])
                                circle(chamferRad, $fn=4);
                        }
                    }
                }
            }

            // Fillet
            rotate_extrude(){
                translate([MountDia / 2, 0, 0]){
                    difference(){
                        square(FilletRadius);
                        translate([FilletRadius,FilletRadius,0])
                            circle(FilletRadius);
                    }
                }
            }
        }
    }


    module PCBMounts(){
        if (PCBMounts)
        {
            translate(-InteriorDims()/2 + [PCBPosX, PCBPosY, 0]){
                if (ShowPCB)
                {
                    // PCB Ghost
                    pcbGhostPadding = 5;
                    bbox = bbox(pcbMountLocations);
                    w = bbox[1][0] - bbox[0][0];
                    l = bbox[1][1] - bbox[0][1];
                    translate(-[pcbGhostPadding, pcbGhostPadding, -(MountHeight+1)]){
                        % square([l + 2*pcbGhostPadding, w + 2*pcbGhostPadding]);
                    }
                }

                for(footLocation = pcbMountLocations)
                {
                    translate([footLocation.y, footLocation.x, 0])
                    {
                        PCBMount();
                    }
                }
            }
        }
    }


    /////////////////////////////////////////////////////////
    ////////////////////// End Panels ///////////////////////
    /////////////////////////////////////////////////////////

    /////////////// Circular hole helper //////////////////////
    // loc = location of centre
    // d = diameter of holes
    module CylinderHole(loc, d){
        translate([loc.x, loc.y, -PanelThickness()*0.1]){
            cylinder(d = d, PanelThickness() * 1.2, $fn = Resolution);
        }
    }

    /////////////// Rectangular hole helper /////////////////////
    // loc = location of bottom left corner
    // sz = dimensions of box
    // f = radius of fillet
    module SquareHole(loc, sz, f){
        translate([loc.x, loc.y, -PanelThickness()*0.1]){
            rotate([-90, -90, 0])
            {
                RoundBox([PanelThickness() * 1.2, sz.x, sz.y], f);
            }
        }
    }

    /////////////// Text helper /////////////////////
    // loc = location of bottom left corner
    // content = text to print
    // sz = size of text
    // ft = font to use 
    module LText(loc, content, sz = min(Width,Height)/10, ft = "Arial Black"){
        translate([loc.x, loc.y, PanelThickness()]){
            linear_extrude(height = 1, center = true){
                text(content, size = sz, font = ft);
            }
        }
    }

    /////////////// Arc Text helper /////////////////////
    // loc = location of the centre of the arc
    // content = text to print
    // rad = radius of arc
    // a0 = Start angle (default 0 - west)
    // a1 = End angle (default 180 - east)
    // sz = size of text
    // ft = font to use
    /////////////////////////////////////////////////////
    module CText(loc, r, content, a0=0, a1=180, sz = min(Width,Height)/10, ft = "Arial Black"){
        angleStep = (a1-a0) / (len(content)-1);
        translate([loc.x, loc.y, PanelThickness()])
        for (i = [0: len(content) - 1] ) {
            rotate([0, 0, 90 - (i * angleStep)])
            translate([0, r, 0]) {
                linear_extrude(height = 1, center = true){
                    text(content[i], font = ft, size = sz, valign = "baseline", halign = "center");
                }
            }
        }
    }


    ////////////////////// End Panel //////////////////////
    module Panel(){
        panelDims = [PanelThickness(), InteriorDims().y - PanelTolerance, InteriorDims().z - PanelTolerance];
        cutout = true;
        difference(){
            color(PanelColour)
            RoundBox(panelDims, FilletRadius, true);

            // Translate so that bottom left corner of panel is "origin" for controls
            translate(-panelDims/2){
                rotate([90, 0, 90]){
                    children(0);
                }
            }
            if (!EmbossPanelDecorations){
                color(CaseColour){
                    translate(-panelDims/2){
                        rotate([90, 0, 90]){
                            children(1);
                        }
                    }
                }
            }
        }

        if (EmbossPanelDecorations){
            color(CaseColour){
                translate(-panelDims/2){
                    rotate([90, 0, 90]){
                        children(1);
                    }
                }
            }
        }
    }

    /////////////////////////////////////////////////////////////////////////
    ////////////////////// Main Scene Build /////////////////////////////////
    /////////////////////////////////////////////////////////////////////////

    printLayoutPadding = 10;

    // Utility function to establish whether the panel is integated with This half or the Other
    function panelType(type, this, other) = 
        type == this ? "IntegratedWithThis" :
        type == other ? "IntegratedWithOther" :
        type;

    // Utility to work out the offset from the edge of the case depending on panel type.
    function panelOffset(type) = 
        type == "Discrete" ?
            (WallThickness + PanelThickness()/2 + PanelTolerance/2) :  // Discrete
            PanelThickness()/2; // Integrated (or None, in which case the translate doesn't matter)

    ///////////////////////// Calculate the transforms for each part, depening on whether we're visualising or printing /////////////////////////
    BaseTranslate = !PrintLayout ? 
        CaseDims() / 2: 
        CaseDims() / 2;
    BaseRotate = !PrintLayout ?
        [0,0,0]: 
        [0,0,0];


    TopTranslate = !PrintLayout ? 
        [CaseDims().x/2, CaseDims().y/2, CaseDims().z/1.95] : 
        [CaseDims().x/2, CaseDims().y*1.5 + printLayoutPadding, CaseDims().z/2];
    TopRotate = !PrintLayout ? 
        [180, 0, 0] : 
        [0,0,0];


    FPanelTranslate = (!PrintLayout || FrontPanelType != "Discrete") ? 
        FrontPanelType == "IntegratedWithTop" ?
            TopTranslate + [CaseDims().x/2 - panelOffset(FrontPanelType) - 0.01, 0, 0] : 
            BaseTranslate + [CaseDims().x/2 - panelOffset(FrontPanelType) - 0.01, 0, 0] : 
        [CaseDims().x  + CaseDims().z/2 + printLayoutPadding, CaseDims().y/2, PanelThickness()/2];
    FPanelRotate = (!PrintLayout || FrontPanelType != "Discrete") ? 
        FrontPanelType == "IntegratedWithTop" ?
            TopRotate + [180,0,0]: 
            BaseRotate + [0,0,0]: 
        [0,-90,0];


    BPanelTranslate = (!PrintLayout || BackPanelType != "Discrete") ? 
        BackPanelType == "IntegratedWithTop" ?
            TopTranslate + [-(CaseDims().x/2 - panelOffset(BackPanelType) - 0.01), 0, 0] : 
            BaseTranslate + [-(CaseDims().x/2 - panelOffset(BackPanelType) - 0.01), 0, 0] : 
        [CaseDims().x  + CaseDims().z/2 + printLayoutPadding, CaseDims().y*1.5 + printLayoutPadding, PanelThickness()/2];
    BPanelRotate = (!PrintLayout || BackPanelType != "Discrete") ? 
        BackPanelType == "IntegratedWithTop" ?
            TopRotate + [180,0,180]: 
            BaseRotate + [0,0,180]: 
        [0,-90,0];


    PCBTranslate = !PrintLayout ? CaseDims() / 2 : [0,0,0];
    PCBRotate = !PrintLayout ? [0,0,0]: [0,0,0];

    if (ShowTCase)
    {
        union()
        {
            translate(TopTranslate){
                rotate(TopRotate){
                    color(CaseColour){
                        CaseWithTabs(CaseDims(), WallThickness, FilletRadius,
                            fPanelType = panelType(FrontPanelType, "IntegratedWithTop", "IntegratedWithBase"), 
                            bPanelType = panelType(BackPanelType, "IntegratedWithTop", "IntegratedWithBase"));                    
                    }
                }
            }
            // TODO: translated (but don't rotate) the panel with the case - also works for print mode
            // means we have to have the panel translation in local coords (at least not for discrete mode)
            if (FrontPanelType == "IntegratedWithTop")
            {
                translate(FPanelTranslate) {
                    rotate(FPanelRotate){
                        Panel(){
                            FrontPanelCutouts();
                            FrontPanelDecoration();
                        }
                    }
                }
            }
            if (BackPanelType == "IntegratedWithTop")
            {
                translate(BPanelTranslate) {
                    rotate(BPanelRotate){
                        Panel(){
                            BackPanelCutouts();
                            BackPanelDecoration();
                        }
                    }
                }
            }
        }
    }

    if (ShowBCase) {
        union()
        {
            translate(BaseTranslate){
                rotate(BaseRotate){
                    color(CaseColour){
                        CaseWithTabs(CaseDims(), WallThickness, FilletRadius, 
                            fPanelType = panelType(FrontPanelType, "IntegratedWithBase", "IntegratedWithTop"), 
                            bPanelType = panelType(BackPanelType, "IntegratedWithBase", "IntegratedWithTop"));
                    }
                    PCBMounts();
                    
                }
            }
            if (FrontPanelType == "IntegratedWithBase")
            {
                translate(FPanelTranslate) {
                    rotate(FPanelRotate){
                        Panel(){
                            FrontPanelCutouts();
                            FrontPanelDecoration();
                        }
                    }
                }
            }
            if (BackPanelType == "IntegratedWithBase")
            {
                translate(BPanelTranslate) {
                    rotate(BPanelRotate){
                        Panel(){
                            BackPanelCutouts();
                            BackPanelDecoration();
                        }
                    }
                }
            }
        }
    }

    if (FrontPanelType == "Discrete" && ShowFPanel)
    {
        translate(FPanelTranslate) {
            rotate(FPanelRotate){
                Panel(){
                    FrontPanelCutouts();
                    FrontPanelDecoration();
                }
            }
        }
    }

    if (BackPanelType == "Discrete" && ShowBPanel)
    {
        translate(BPanelTranslate) {
            rotate(BPanelRotate){
                Panel(){
                    BackPanelCutouts();
                    BackPanelDecoration();
                }
            }
        }
    }

}
