/*************************************************************************************
 *
 * Parametric Dowel Bracket
 *
 *************************************************************************************
 *
 * 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/
 *
 *************************************************************************************/
 
// If true, one side of the bracket will be closed by a DowelStopperThickness wall
DowelStopper = true;
 
// Wood screw model such as #6, #8, ...
ScrewModel = "#8";

// Dowel center distance from the back of the bracket in mm
DowelCenterBackDistance = 120;

// Dowel diameter in mm
DowelDiameter = 25;

// Dowel Diameter Clearance in mm
DowelDiameterClearance = 2;

// Dowel Holder Thickness in mm
DowelHolderThickness = 10;

// Dowel Stopper Thickness in mm
DowelStopperThickness = 3;

// *Bracket thickness in mm
BracketThickness = 15;

// *Screw hole clearance with bracket in mm
ScrewHoleClearanceWithBracket = 5;

// *Screw driver hole diameter in mm
ScrewDriverHoleDiameter = 8;

lct_Debug = true;

module __Customizer_Limit__ () {}

lctWSL_Debug = false;
lctWSL_Test = false;

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

lctWSL_HeadAngle = 82;

lctWSL_HeadHolderHoleClearance = 0.3;

lctWSL_MajorDiameterClearance = 10 / 100;

lctWSL_HeadHolderBorder = 10;

lctWSL_HeadHolderBase = 5;

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

// All dimensions in mm
lctWSL_Screws = [
    [ "#0", [    [ "MajorDiameter", 1.52 ], [ "HeadDiameter",  3.18 ] ] ],
    [ "#1", [    [ "MajorDiameter", 1.85 ], [ "HeadDiameter",  3.71 ] ] ],
    [ "#2", [    [ "MajorDiameter", 2.18 ], [ "HeadDiameter",  4.37 ] ] ],
    [ "#3", [    [ "MajorDiameter", 2.51 ], [ "HeadDiameter",  5.03 ] ] ],
    [ "#4", [    [ "MajorDiameter", 2.84 ], [ "HeadDiameter",  5.69 ] ] ],
    [ "#5", [    [ "MajorDiameter", 3.18 ], [ "HeadDiameter",  6.35 ] ] ],
    [ "#6", [    [ "MajorDiameter", 3.51 ], [ "HeadDiameter",  7.01 ] ] ],
    [ "#7", [    [ "MajorDiameter", 3.84 ], [ "HeadDiameter",  7.67 ] ] ],
    [ "#8", [    [ "MajorDiameter", 4.17 ], [ "HeadDiameter",  8.33 ] ] ],
    [ "#9", [    [ "MajorDiameter", 4.5  ], [ "HeadDiameter",  8.99 ] ] ],
    [ "#10", [   [ "MajorDiameter", 4.83 ], [ "HeadDiameter",  9.65 ] ] ],
    [ "#12", [   [ "MajorDiameter", 5.49 ], [ "HeadDiameter", 10.97 ] ] ],
    [ "#14", [   [ "MajorDiameter", 6.15 ], [ "HeadDiameter", 12.29 ] ] ]
];

function lctWSL_GetScrew(p_ID) = lctWSL_Screws[search([ p_ID ], lctWSL_Screws, 1)[0]][1];

function lctWSL_getMajorDiameter(p_Screw) = p_Screw[search( [ "MajorDiameter" ], p_Screw, 1)[0]][1];

function lctWSL_getHeadDiameter(p_Screw) = p_Screw[search( [ "HeadDiameter" ], p_Screw, 1)[0]][1];

function lctWSL_getHeadHeight(p_Screw) = (lctWSL_getHeadDiameter(p_Screw) - lctWSL_getMajorDiameter(p_Screw)) / 2 / tan(lctWSL_HeadAngle / 2);

function lctWSL_getKnockoutHeight(p_Screw, p_Length) = lctWSL_getHeadHeight(screw) + lctWSL_HeadHolderBase + p_Length;

function lctWSL_getScrewHeadHoleDiameter(p_Screw) = lctWSL_getHeadDiameter(p_Screw) + lctWSL_HeadHolderBorder;

function lctWSL_getScrewHeadHoleHeight(p_Screw) = lctWSL_getHeadHeight(p_Screw) + lctWSL_HeadHolderBase;
   
module lctWSL_Debug(p_EnableDebug = false) {
    if( p_EnableDebug ) {
    }
}

module lctWSL_Knockout(p_ScrewID, p_Length) {
    screw = lctWSL_GetScrew(p_ScrewID);
    assert( !is_undef(screw), str("Unknown screw ", p_ScrewID));
    
    cylinder(d = lctWSL_getMajorDiameter(screw) * (1 + lctWSL_MajorDiameterClearance), h = p_Length);
    translate( [ 0, 0, p_Length ] ) cylinder(d = lctWSL_getScrewHeadHoleDiameter(screw), h = lctWSL_getScrewHeadHoleHeight(screw));
}

module lctWSL_ScrewHolder(p_ScrewID) {
    screw = lctWSL_GetScrew(p_ScrewID);

    diameter = lctWSL_getScrewHeadHoleDiameter(screw);
    
    difference() {
        cylinder(d2 = diameter, d1 = diameter - lctWSL_HeadHolderHoleClearance, h = lctWSL_getScrewHeadHoleHeight(screw));
        
        translate( [ 0, 0, lctWSL_getHeadHeight(screw) + lctWSL_HeadHolderBase - lctWSL_getHeadHeight(screw) ] ) cylinder(d2 = lctWSL_getHeadDiameter(screw) * (1 +  lctWSL_MajorDiameterClearance), d1 = lctWSL_getMajorDiameter(screw) * (1 +  lctWSL_MajorDiameterClearance), h = lctWSL_getHeadHeight(screw) );
        
        cylinder(d = lctWSL_getMajorDiameter(screw) * (1 + lctWSL_MajorDiameterClearance), h = lctWSL_getScrewHeadHoleHeight(screw));
    }
}

module lctWSL_Test(p_ScrewID) {
    screwID = "#8";
    
    screw = lctWSL_GetScrew(p_ScrewID);
    screwHeadHoleHeight = lctWSL_getScrewHeadHoleHeight(screw);
    screwHeadHoleDiameter = lctWSL_getScrewHeadHoleDiameter(screw) + 5;
    
    echo(str("Screw head hole diameter in mm: ", lctWSL_getScrewHeadHoleDiameter(screw)));
    echo(str("Screw head hole in height mm: ", screwHeadHoleHeight));

    
    majorDiameterHeight = 20;
    
    translate( [ 0, screwHeadHoleDiameter + 10, 0 ] ) difference() {
        translate( [ 0, 0, (screwHeadHoleHeight + majorDiameterHeight) / 2 ] ) cube( [ screwHeadHoleDiameter, screwHeadHoleDiameter, screwHeadHoleHeight + majorDiameterHeight ], center = true );
        lctWSL_Knockout(p_ScrewID, 20);
    }
    
    lctWSL_ScrewHolder(p_ScrewID);
}

lctWSL_Debug(lctWSL_Debug);

if( lctWSL_Test == true ) {
    lctWSL_Test("#6");
}

screw = lctWSL_GetScrew(ScrewModel);

// Bracket width in mm (multiple of 5 mm)
BracketWidth = floor((lctWSL_getScrewHeadHoleDiameter(screw) + 4.99999) / 5) * 5;

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

lct_MainParameters = true;

$fn = 60;

lct_EffectiveDowelDiameter = DowelDiameter + DowelDiameterClearance;
lct_EffectiveBracketDepth = DowelCenterBackDistance + lct_EffectiveDowelDiameter / 2 + DowelHolderThickness;
lct_EffectiveBracketHeight = lct_EffectiveBracketDepth;


lct_LowerAngle = atan(lct_EffectiveBracketDepth / lct_EffectiveBracketHeight);
lct_UpperAngle = 180 - 90 - lct_LowerAngle;
lct_LowerInnerHeightOffset = BracketThickness * tan(90 - lct_LowerAngle) + BracketThickness / sin(lct_LowerAngle);
lct_UpperInnerDepthOffset = BracketThickness / tan(lct_UpperAngle) + BracketThickness / sin(lct_UpperAngle);

module lct_Debug(p_EnableDebug = false) {
    if( p_EnableDebug ) {
        echo("BracketWidth:", BracketWidth);
        echo("lct_EffectiveBracketDepth:", lct_EffectiveBracketDepth);
        echo("lct_EffectiveDowelDiameter:", lct_EffectiveDowelDiameter);
        echo("lct_LowerAngle:", lct_LowerAngle);
        echo("lct_UpperAngle:", lct_UpperAngle);
        echo("lct_LowerInnerHeightOffset:", lct_LowerInnerHeightOffset);
        echo("lct_UpperInnerDepthOffset:", lct_UpperInnerDepthOffset);
        echo(str("Screw head hole diameter in mm: ", lctWSL_getScrewHeadHoleDiameter(screw)));
        echo(str("Screw head hole in height mm: ", lctWSL_getScrewHeadHoleHeight(screw)));
    }
}

module lct_GenerateScrewHolder(p_HeightOffset) {
    
    knockoutOffset = lctWSL_getScrewHeadHoleHeight(screw) - 0.001;

    translate( [ -knockoutOffset, p_HeightOffset, BracketWidth / 2 ] ) {
        rotate( [ 0, 90, 0 ] ) {
            lctWSL_Knockout(ScrewModel, BracketThickness);
            translate( [ 0, 0, BracketThickness ] ) cylinder(h = lct_EffectiveBracketDepth, d = ScrewDriverHoleDiameter);
        }
    }
}



module lct_GenerateDowelHolderxxx() {
    
    lct_DowelHolderWidth = lct_EffectiveDowelDiameter + 2 * DowelHolderThickness;
    
    difference() {
        cylinder(h = BracketWidth, d = lct_DowelHolderWidth);
        cylinder(h = BracketWidth, d = lct_EffectiveDowelDiameter);
        translate( [ -lct_DowelHolderWidth / 2, 0, 0 ] ) cube( [ lct_DowelHolderWidth, lct_DowelHolderWidth / 2, BracketWidth ] );
    }
    
    translate( [ lct_DowelHolderWidth / 2 - DowelHolderThickness, 0, 0 ] ) cube( [ DowelHolderThickness, lct_DowelHolderWidth / 2, BracketWidth ] );
    translate( [ -lct_DowelHolderWidth / 2, 0, 0 ] ) cube( [ DowelHolderThickness, lct_DowelHolderWidth / 2, BracketWidth ] );
    
    cylinder(h = DowelStopperThickness, d = lct_DowelHolderWidth);
    translate( [ -lct_DowelHolderWidth / 2, 0, 0 ] ) cube( [ lct_DowelHolderWidth, lct_DowelHolderWidth / 2, DowelStopperThickness ] );   
}


module lct_GenerateDowelHolder(p_DowelStopper) {
    
    lct_DowelHolderWidth = lct_EffectiveDowelDiameter + 2 * DowelHolderThickness;
    
    difference() { 
        translate( [ 0, 0, BracketWidth / 2 ] ) cube( [ lct_DowelHolderWidth, lct_DowelHolderWidth, BracketWidth ], center = true );
        cylinder(h = BracketWidth, d = lct_EffectiveDowelDiameter);
        translate( [ -lct_EffectiveDowelDiameter / 2, 0, 0 ] ) cube( [ lct_EffectiveDowelDiameter, lct_DowelHolderWidth / 2, BracketWidth ] ) ;
    }
    if( p_DowelStopper ) {
        translate( [ 0, 0, DowelStopperThickness / 2 ] ) cube( [ lct_DowelHolderWidth, lct_DowelHolderWidth, DowelStopperThickness ], center = true );   
    }
}

module lct_GenerateFrame() {
    
    frameOutline = [ 
        [ 0, 0 ], 
        [ lct_EffectiveBracketDepth, lct_EffectiveBracketHeight ], 
        [ 0, lct_EffectiveBracketHeight ] 
    ];
  
    frameKnockout = [ 
        [ BracketThickness, lct_LowerInnerHeightOffset ],
        [ BracketThickness, lct_EffectiveBracketHeight - BracketThickness ],
        [ lct_EffectiveBracketDepth - lct_UpperInnerDepthOffset, lct_EffectiveBracketHeight - BracketThickness ],
    ];
    
    knockoutOffset = lctWSL_getScrewHeadHoleHeight(screw) - 0.001;

    difference() {
        linear_extrude(BracketWidth) 
            union() {
                difference() {
                    polygon(frameOutline);
                    polygon(frameKnockout);
                }
                *translate( [ lct_EffectiveBracketDepth - ShelfHolderDepth, lct_EffectiveBracketHeight, 0 ] ) square([ ShelfHolderDepth, ShelfHolderHeight ]);
            }
            
            lct_GenerateScrewHolder(lct_EffectiveBracketHeight - BracketThickness - lctWSL_getScrewHeadHoleDiameter(screw) / 2 - ScrewHoleClearanceWithBracket);

            offsetY = ((BracketThickness / cos(lct_LowerAngle)) + lctWSL_getScrewHeadHoleDiameter(screw) + 2 + BracketThickness) / tan(lct_LowerAngle);
            lct_GenerateScrewHolder(offsetY);
        }            
}

lct_Debug(lct_Debug);

module lct_GenerateBacket(p_DowelStopper) {
    difference() {
        union() {
            lct_GenerateFrame();
            translate( [ DowelCenterBackDistance, lct_EffectiveBracketHeight + lct_EffectiveDowelDiameter / 2, 0 ] ) lct_GenerateDowelHolder(p_DowelStopper);
        }
        
        lct_CutOffPolygon = [ [ 0, 0 ], [ lct_EffectiveBracketDepth, lct_EffectiveBracketHeight ], [ lct_EffectiveBracketDepth, 0 ] ];
      
        linear_extrude(BracketWidth) polygon(lct_CutOffPolygon);
    }
}

*translate( [ 120, 0, 4 ] ) rotate( [ -90, 0, 0 ] ) cylinder(h = 150, d = 2);

lct_GenerateBacket(DowelStopper);
translate( [  lct_EffectiveBracketHeight / 2, 0, 0 ] ) lctWSL_ScrewHolder(ScrewModel);

