/*************************************************************************************
 *
 * Ikea Skadis Twin Vertical Cylinders
 *
 *************************************************************************************
 *
 * 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 AUTHOR OR COPYRIGHT
 * HOLDER 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.
 *
 * IT IS NOT PERMITTED TO MODIFY THIS COMMENT BLOCK.
 *
 * (c)2025, Claude "Tryphon" Theroux, Montreal, Quebec, Canada
 * http://www.ctheroux.com/
 *
 *************************************************************************************/
 
 // Inside diameter of container 1 in mm
 InsideDiameter1 = 40;
 
 // Inside diameter of container 2 in mm
 InsideDiameter2 = 45;
 
 // Outside height in mm
 InsideHeight = 90;
 
 // Wall thickness in mm
 WallThickness = 1.6;
 
 // Bottom thickness in mm
 BottomThickness = 2;
 

module __Customizer_Limit__ () {}

/*************************************************************************************
 *
 * Nothing shall be modified below this line
 *
 *************************************************************************************/
 
$fn = 90;

module lctHCL_Cylinder( p_OuterDiameter, p_OuterHeight, p_WallThickness, p_BottomThickness ) {
    difference() {
        cylinder(d = p_OuterDiameter, h = p_OuterHeight);
        translate( [ 0, 0, p_BottomThickness ] ) cylinder(d = p_OuterDiameter - 2 * p_WallThickness, h = p_OuterHeight - p_BottomThickness);
    }
}

module lctHCL_Bucket( p_OuterWidth, p_OuterDepth, p_OuterHeight, p_WallThickness, p_BottomThickness ) {
    translate( [ -p_OuterWidth / 2, -p_OuterDepth / 2, 0 ] ) difference() {
        cube( [ p_OuterWidth, p_OuterDepth, p_OuterHeight ] );
        translate( [ p_WallThickness, p_WallThickness, p_BottomThickness ] ) cube( [ p_OuterWidth - 2 * p_WallThickness, p_OuterDepth - 2 * p_WallThickness, p_OuterHeight - p_BottomThickness ] );;
    }
}

/*************************************************************************************
 *
 * Library inner parameters
 *
 *************************************************************************************/

// Skadis hole length in mm
lctSFC_SkadisHoleLength = 15;

// Skadis hole radius in mm
lctSFC_SkadisHoleRadius = 2.5;

// Skadis thickness in mm
lctSFC_SkadisThickness = 5;

// Skadis distance between two vertical holes in mm
lctSFC_SkadisVerticalHoleDistance = 40;

// Back Holder thickness in mm
lctSFC_BackHolderThickness = 4;

// Holder link height in mm
lctSFC_HolderLinkHeight = 6;

// French cheat bar width in mm
lctSFC_FrontHolderWidth = 10;

// French cleat depth in mm
lctSFC_FrontHolderThickness = 4;

// General clearance in mm
lctSFC_Clearance = 0.5;

//Clearance between the holder and the size of the container in mm
lctSFC_HolderSideClearance = 3;

/*************************************************************************************
 *
 * Nothing shall be modified below this line
 *
 *************************************************************************************/
 
// Back holder width in mm
lctSFC_BackHolderWidth = lctSFC_SkadisHoleRadius * 2 - lctSFC_Clearance;

// Back holder core length in mm
lctSFC_BackHolderCoreLength = lctSFC_SkadisHoleLength - 2 * lctSFC_SkadisHoleRadius;

// French cheat bar length in mm
lctSFC_FrontHolderLength = lctSFC_BackHolderCoreLength + lctSFC_SkadisHoleRadius - lctSFC_Clearance / 2;

// Clearance between each generated holder in mm
lctSFC_HolderClearance = lctSFC_FrontHolderLength + lctSFC_FrontHolderThickness * 2;

module lctSFC_Debug(p_EnableDebug, p_HolderNumber, p_FrontHolderLength, p_FrontHolderWidth, p_FrontHolderThickness) {
    if( p_EnableDebug ) {
        echo("lctSFC_SkadisHoleLength:", lctSFC_SkadisHoleLength);
        echo("lctSFC_SkadisHoleRadius:", lctSFC_SkadisHoleRadius);
        echo("lctSFC_SkadisThickness:", lctSFC_SkadisThickness);
        echo("lctSFC_SkadisVerticalHoleDistance:", lctSFC_SkadisVerticalHoleDistance);
        echo("p_HolderNumber:", p_HolderNumber);
        echo("p_FrontHolderLength:", p_FrontHolderLength);
        echo("p_FrontHolderWidth:", p_FrontHolderWidth);
        echo("p_FrontHolderThickness:", p_FrontHolderThickness);
        echo("lctSFC_BackHolderThickness:", lctSFC_BackHolderThickness);
        echo("lctSFC_BackHolderWidth:", lctSFC_BackHolderWidth);
        echo("lctSFC_BackHolderCoreLength:", lctSFC_BackHolderCoreLength);
        echo("lctSFC_HolderLinkHeight:", lctSFC_HolderLinkHeight);
        echo("lctSFC_Clearance:", lctSFC_Clearance);
        echo("lctSFC_HolderSideClearance:", lctSFC_HolderSideClearance);
    }
}

module lctSFC_BackHolder() {
    hull() {
        translate([ -lctSFC_BackHolderCoreLength / 2, 0, 0 ]) cylinder(d = lctSFC_BackHolderWidth, h = lctSFC_BackHolderThickness);
        translate([ lctSFC_BackHolderCoreLength / 2, 0, 0 ]) cylinder(d = lctSFC_BackHolderWidth, h = lctSFC_BackHolderThickness);
    } 
}

module lctSFC_HolderLink() {
    difference() {
        cylinder( d = lctSFC_BackHolderWidth, h = lctSFC_SkadisThickness + lctSFC_Clearance / 2);
        translate([ 0, -lctSFC_BackHolderWidth / 2, 0 ]) cube([ lctSFC_BackHolderWidth / 2, lctSFC_BackHolderWidth, lctSFC_SkadisThickness + lctSFC_Clearance / 2 ]);
    }
    translate([ 0, -lctSFC_BackHolderWidth / 2, 0 ]) cube( [ lctSFC_HolderLinkHeight - lctSFC_BackHolderWidth / 2, lctSFC_BackHolderWidth, lctSFC_SkadisThickness + lctSFC_Clearance / 2 ] );
}

module lctSFC_FrontHolder( p_FrontHolderLength, p_FrontHolderWidth, p_FrontHolderThickness)
{
    translate([ 0, p_FrontHolderWidth / 2, 0 ]) rotate([ 90, 0, 0 ])
            linear_extrude(p_FrontHolderWidth) polygon([ 
                                                    [ 0, 0 ], 
                                                    [ p_FrontHolderLength, 0 ], 
                                                    [ p_FrontHolderLength + p_FrontHolderThickness, p_FrontHolderThickness ], 
                                                    [ 0, p_FrontHolderThickness ]
                                                       ]);    
}

module lctSFC_FrenchCleatHolder( p_HolderNumber = 1, p_FrontHolderLength = lctSFC_FrontHolderLength, 
                                p_FrontHolderWidth = lctSFC_FrontHolderWidth, 
                                p_FrontHolderThickness = lctSFC_FrontHolderThickness, 
                                p_EnableDebug = false)
{
    lctSFC_Debug(p_EnableDebug, p_HolderNumber, p_FrontHolderLength, p_FrontHolderWidth, p_FrontHolderThickness);
    translate([ 0, 0, lctSFC_BackHolderWidth / 2 ]) rotate([ 90, 0, 180 ]) union() {

        for( n = [ 0 : p_HolderNumber - 1 ] ) {
            translate([ -lctSFC_SkadisVerticalHoleDistance * n, 0, 0 ]) lctSFC_BackHolder();
            translate([ lctSFC_SkadisHoleLength / 2 - lctSFC_Clearance / 2 - lctSFC_HolderLinkHeight - lctSFC_SkadisVerticalHoleDistance * n, 0, lctSFC_BackHolderThickness ]) lctSFC_HolderLink();
        }

        translate([ -p_FrontHolderLength -  + lctSFC_SkadisVerticalHoleDistance * (p_HolderNumber - 1) + lctSFC_BackHolderCoreLength / 2, p_FrontHolderWidth / 2 - lctSFC_BackHolderWidth / 2, lctSFC_BackHolderThickness + lctSFC_SkadisThickness + lctSFC_Clearance / 2 ]) lctSFC_FrontHolder(p_FrontHolderLength + lctSFC_SkadisVerticalHoleDistance * (p_HolderNumber - 1), p_FrontHolderWidth, p_FrontHolderThickness);    
    }
}

function lctSFC_FrenchCleatHolderKnockoutLength(p_HolderNumber = 1, 
                                            p_FrontHolderLength = lctSFC_FrontHolderLength, 
                                            p_FrontHolderWidth = lctSFC_FrontHolderWidth, 
                                            p_FrontHolderThickness = lctSFC_FrontHolderThickness) = p_FrontHolderLength + lctSFC_SkadisVerticalHoleDistance * (p_HolderNumber - 1) + lctSFC_Clearance / 2;

function lctSFC_FrenchCleatHolderKnockoutThickness(p_FrontHolderThickness = lctSFC_FrontHolderThickness) = p_FrontHolderThickness + lctSFC_Clearance / 2;

function lctSFC_FrenchCleatHolderKnockoutWidth(p_FrontHolderWidth = lctSFC_FrontHolderWidth) = p_FrontHolderWidth + lctSFC_Clearance / 2;

function lctFrenchCleatHolderOffset(p_InnerSkadisHoles = 0, 
                                    p_FrontHolderWidth = lctSFC_FrontHolderWidth) = (lctSFC_SkadisVerticalHoleDistance * p_InnerSkadisHoles) - p_FrontHolderWidth + lctSFC_BackHolderWidth;

function lctSFC_NumberOfHolders(p_width) = floor((p_width - lctSFC_FrenchCleatHolderKnockoutWidth() * 1.5 - lctSFC_HolderSideClearance * 2) / lctSFC_SkadisVerticalHoleDistance) + 1;

module lctSFC_FrenchCleatHolderKnockout(p_HolderNumber = 1, 
                                p_FrontHolderLength = lctSFC_FrontHolderLength, 
                                p_FrontHolderWidth = lctSFC_FrontHolderWidth, 
                                p_FrontHolderThickness = lctSFC_FrontHolderThickness, 
                                p_EnableDebug = false)
{
    lctSFC_Debug(p_EnableDebug, p_HolderNumber, p_FrontHolderLength, p_FrontHolderWidth, p_FrontHolderThickness);
    
    lctSFC_FrontHolder(p_FrontHolderLength + lctSFC_SkadisVerticalHoleDistance * (p_HolderNumber - 1) + lctSFC_Clearance / 2, p_FrontHolderWidth + lctSFC_Clearance / 2, p_FrontHolderThickness + lctSFC_Clearance / 2);

}


module lctSFC_generateHolders(p_width, p_sets = 1) {
    for( i =  [ 0 : 1 : (lctSFC_NumberOfHolders(p_width) * p_sets) - 1 ] ) {
        translate( [ i * lctSFC_HolderClearance, 0, 0 ] ) lctSFC_FrenchCleatHolder();
    }
}

module generateFrenchCleatBar(p_width, p_height, p_depth) {
    numberFrenchCleat = lctSFC_NumberOfHolders(p_width);

    offset = (p_width - lctSFC_SkadisVerticalHoleDistance * (numberFrenchCleat - 1)) / 2 + lctSFC_FrontHolderWidth / 2- lctSFC_BackHolderWidth / 2;

    difference() {
        cube([ p_width, p_depth, p_height ]);
        translate( [ offset, 0, 0 ] ) for( i = [ 0 : 1 : numberFrenchCleat - 1 ] ) {
            translate( [ lctSFC_SkadisVerticalHoleDistance * i, 0, 0 ] ) rotate( [ 0, -90, -90 ] ) lctSFC_FrenchCleatHolderKnockout(1);
        }
    }
}

/*************************************************************************************
 *
 * Library inner parameters
 *
 *************************************************************************************/

FrenchCleatBarHeight = 20;

/*************************************************************************************
 *
 * Nothing shall be modified below this line
 *
 *************************************************************************************/

FrenchCleatBarLength = sqrt(pow(InsideDiameter1 / 2 + InsideDiameter2 / 2 + WallThickness, 2) - pow(abs(InsideDiameter1 - InsideDiameter2) , 2)) + (InsideDiameter1 + InsideDiameter2) / 2 + 2 * WallThickness;

FrenchCleatBarDepth = lctSFC_BackHolderThickness + 2 * WallThickness;

OutsideHeight = InsideHeight + BottomThickness;

BarCount = floor((OutsideHeight - FrenchCleatBarHeight) / lctSFC_SkadisVerticalHoleDistance) + 1;
 
module generateContainers() {
    translate( [ 0, max(InsideDiameter1, InsideDiameter2) / 2, 0 ] ) union() {
        translate( [ (InsideDiameter2 + WallThickness) / 2, 0, 0 ] ) lctHCL_Cylinder( InsideDiameter2 + WallThickness * 2, OutsideHeight, WallThickness, BottomThickness );
        
        translate( [ -(InsideDiameter1 + WallThickness) / 2, -abs(InsideDiameter1 - InsideDiameter2) / 2, 0 ] ) lctHCL_Cylinder( InsideDiameter1 + WallThickness * 2, OutsideHeight, WallThickness, BottomThickness );
    }
}

module generateFrenchCleatBars(p_height, p_frenchCleatBarLength, p_frenchCleatBarHeight, p_frenchCleatBarDepth) {
    for( i = [ 0 : 1 : BarCount - 1 ] ) {
        translate( [ 0, 0, i * lctSFC_SkadisVerticalHoleDistance ] ) generateFrenchCleatBar(p_frenchCleatBarLength, p_frenchCleatBarHeight, p_frenchCleatBarDepth);
    }
}

generateContainers();

translate( [ -InsideDiameter1 - WallThickness, -FrenchCleatBarDepth, 0 ] ) generateFrenchCleatBars(OutsideHeight, FrenchCleatBarLength, FrenchCleatBarHeight, FrenchCleatBarDepth);

translate( [ 0, -30, 0 ] ) lctSFC_generateHolders(FrenchCleatBarLength, BarCount);
