CadQuery / cadquery-plugins

A collection of community contributed plugins that extend the functionality of CadQuery
Apache License 2.0
36 stars 15 forks source link

[Plugin Request] Thread Generator #2

Open jmwright opened 3 years ago

jmwright commented 3 years ago

Generating threads can often lead to invalid geometry. It would be nice to capture all of the best practices into a thread generator plugin.

marcus7070 commented 3 years ago

For anyone looking into this, note that most users have trouble modelling threads using helical sweeps (which is the obvious way to model them in CadQuery). The main OCCT example (the bottle) models a thread using a different method, I think it would be worth looking into basing a plugin off that technique rather than helical sweeps. Seems that if it is OCCT's main example usage that it would be more robust.

PythonOCC has a demo that might help: https://github.com/tpaviot/pythonocc-demos/blob/master/examples/core_classic_occ_bottle.py

adam-urbanczyk commented 3 years ago

Actually there is an example of using this approach posted on the groups and it has been worked out further here: https://github.com/winksaville/cq-thread-experiments

dcowden commented 3 years ago

Here are some experiences from Onshape users about a thread creator i made there:

Very popular features

Less popular features

dcowden commented 3 years ago

Here's a copy of the docs for the Onshape thread creator: image

If you're an onshape user, you can play with it here: https://cad.onshape.com/documents/6b640a407d78066bd5e41c7a/w/4693805578a72f40ebfb4ea3/e/f8aea9e5c33e02eab0854a4f

dcowden commented 3 years ago

Here are some implementation details that might be useful: I ended up considering these thread classes:

export enum ASMEThreadClass{
    _1A,
    _2A,
    _3A,
    _1B,
    _2B,
    _3B
}
export enum ISOThreadClass{
    _6e,
    _6g,
    _6h,
    _6G,
    _6H
}

These functions ( excuse odd Onshape javascript-like syntax) compute thread tolerances for the classes above:


export function calculateASMEThreadTolerances( pitch, majorDiameter, threadClass is ASMEThreadClass ){

    //expanded words have units, letters are dimensionless for the ASME formulas
    var threadHeight = 0.8660254 * pitch;
    var pitchDiameter = majorDiameter - ( 3.0 * threadHeight / 4 );
    var minorDiameter = majorDiameter - (5.0 * threadHeight / 4);    

    println("pitchDiameter=" ~ getDimensionlessValue(pitchDiameter,UOM.INCH) );
    //short letters all are dimensionless
    var P = getDimensionlessValue(pitch,UOM.INCH);

    var D = getDimensionlessValue(majorDiameter,UOM.INCH);
    var H = 0.8660254 * P;
    var LE = 9 * P;
    var n = (1 / P);
    println("P=" ~ P ~ ",n=" ~ n ~ ",H=" ~ H ~ ",D=" ~ D ~ "LE=" ~ LE );
    var internal = ( threadClass == ASMEThreadClass._1B || threadClass == ASMEThreadClass._2B || threadClass == ASMEThreadClass._3B );
    var Td2 = 0.0015 * (D ^ (1/3)) + (0.0015 * sqrt(LE)) + (0.015 * P ^ ( 2/3 ));    
    println("Td2=" ~ Td2 );

    if ( internal ){

        var PT = undefined;          
        if ( threadClass == ASMEThreadClass._1B ){
            PT = 1.95 * Td2;            
        }
        else if ( threadClass == ASMEThreadClass._2B ){
            PT = 1.3 * Td2;            
        }
        else{  //3B
            PT = Td2 * 0.975;            
        }

        var maT = 0.14433757 * P;

        var MAX_TOLERANCE = 0.394 * P;
        var STD_TOLERANCE = (0.05 * P ^ ( 2/3 ) + 0.03 * P / D ) - 0.002; 
        var Td1 = STD_TOLERANCE;

        if ( threadClass == ASMEThreadClass._3B ){
            var MIN_TOLERANCE = undefined;
            if ( n <= 12 ){
                MIN_TOLERANCE = 0.12 * P;   
            }
            else{
                MIN_TOLERANCE = 0.23 * P - 1.5 * P ^ 2;
            }            
            if ( STD_TOLERANCE > MAX_TOLERANCE ){
                Td1 = MAX_TOLERANCE;   
            }
            else if ( STD_TOLERANCE < MIN_TOLERANCE ){
                Td1 = MIN_TOLERANCE;
            }
            else{
                Td1 = STD_TOLERANCE;
            }
        }
        else{  //1B and 2B
            var MIN_TOLERANCE = 0.25 * P - 0.4 * P^2;            
            if ( D <= 0.25 ){

                if ( STD_TOLERANCE > MAX_TOLERANCE ){
                    Td1 = MAX_TOLERANCE;   
                }
                else if ( STD_TOLERANCE < MIN_TOLERANCE ){
                    Td1 = MIN_TOLERANCE;
                }
                else{
                    Td1 = STD_TOLERANCE;
                }                 
            }
            else{
                if ( n < 4 ){
                    Td1 = 0.15 * P;
                }
                else{
                    Td1 = 0.25 * P - 0.4 * P^2;   
                }
            }
        }
        println("Td1=" ~ Td1 );
        return {
            'maxMinor' : (getDimensionlessValue(minorDiameter,UOM.INCH) + Td1)*inch,
            'minMinor' : (getDimensionlessValue(minorDiameter,UOM.INCH) )*inch,
            'minPitch' : (getDimensionlessValue(pitchDiameter,UOM.INCH) )*inch,
            'maxPitch' : (getDimensionlessValue(pitchDiameter,UOM.INCH) + PT )*inch,
            'maxMajor' : (getDimensionlessValue(majorDiameter,UOM.INCH)+ maT)*inch,
            'minMajor' : (getDimensionlessValue(majorDiameter,UOM.INCH))*inch               
        };

    }
    else{ //external

        var maT = undefined;
        var PT = undefined;
        var PA = undefined;

        if ( threadClass == ASMEThreadClass._1A ){
            maT = 0.090 * P ^ ( 2 /3 );
            PT = 1.5 * Td2;
            PA = 0.3 * Td2;
        }
        else if ( threadClass == ASMEThreadClass._2A ){
            maT = 0.060* P ^ ( 2/3 );
            PT = Td2;
            PA = 0.3 * Td2;
        }
        else{  //3A
            maT = 0.060* P ^ ( 2/3 );
            PT = Td2 * 0.75;
            PA = 0.0;
        }
        var miT = 0.216506 * P;

        return {
            'minMinor' : (getDimensionlessValue(minorDiameter,UOM.INCH) - PA - miT)*inch,
            'maxMinor' : (getDimensionlessValue(minorDiameter,UOM.INCH) - PA)*inch,
            'minPitch' : (getDimensionlessValue(pitchDiameter,UOM.INCH) - PA - PT)*inch,
            'maxPitch' : (getDimensionlessValue(pitchDiameter,UOM.INCH) - PA)*inch,
            'minMajor' : (getDimensionlessValue(majorDiameter,UOM.INCH) - PA - maT)*inch,
            'maxMajor' : (getDimensionlessValue(majorDiameter,UOM.INCH) - PA)*inch,
        };
    }

}

export function calculateISOThreadTolerances(pitch, majorDiameter, threadClass is ISOThreadClass ){

    //expanded words have units, letters are dimensionless for the ASME formulas
    var threadHeight = 0.8660254 * pitch;
    var pitchDiameter = majorDiameter - 3.0 * threadHeight / 4;
    var minorDiameter = majorDiameter - 5.0 * threadHeight / 4;

    var P = getDimensionlessValue(pitch, UOM.MM);
    var D = getDimensionlessValue(majorDiameter, UOM.MM);
    var H = 0.8660254 * P;
    var LE = 9 / P;
    var internal = ( threadClass == ISOThreadClass._6H || threadClass == ISOThreadClass._6G );

    println("P=" ~ P  ~ ",H=" ~ H ~ ",D=" ~ D ~ "LE=" ~ LE );    
    //ISO formulals all compute micrometers, so results are converted to MM
    //Td -> major diameter Tolerance for bolt
    //Td1 -> minor diameter Tolerance for nut
    //Td2 -> pitch diameter tolerance for bolt

    var NORMAL_DEVIATION = (15 + 11 * P) / 1000; 

    var Td = (180 * P ^ (2/3) - ( 3.15 / sqrt(P) )) / 1000; 
    var Td2 = (90 * P ^ 0.4 * D ^ 0.1) / 1000; 

    var Td1 = undefined;
    if ( P < 0.8 ){
        Td1 = (433 * P - 190 * P ^ 1.22) / 1000; 
    }
    else{
        Td1 = (230 * P ^ 0.7 ) / 1000;   
    } 
    println("Td=" ~ Td ~ " mm, Td1=" ~ Td1 ~ " mm, Td2=" ~ Td2 ~ " mm" );
    if ( internal ){
        var EI=undefined;
        if ( threadClass == ISOThreadClass._6G ){
            EI = NORMAL_DEVIATION;
        }
        else{ //6H
            EI = 0.0;
        }

        var PT = Td2 * 1.32;

        return {
            'maxMinor' : (getDimensionlessValue(minorDiameter,UOM.MM)+EI + Td1)*millimeter,
            'minMinor' : (getDimensionlessValue(minorDiameter,UOM.MM)+EI )*millimeter,
            'minPitch' : (getDimensionlessValue(pitchDiameter,UOM.MM)+EI )*millimeter,
            'maxPitch' : (getDimensionlessValue(pitchDiameter,UOM.MM) +EI + PT )*millimeter,
            'maxMajor' : (getDimensionlessValue(majorDiameter,UOM.MM) +EI + Td1)*millimeter,
            'minMajor' : (getDimensionlessValue(majorDiameter,UOM.MM)+EI )*millimeter               
        };        

    }
    else{
        var cmin = 0.35 * P; //approximated, actual rounding formula is a pain
        var es = undefined;
        if ( threadClass == ISOThreadClass._6g ){
            es = NORMAL_DEVIATION;
        }
        else { //_6h
            es = 0.0;
        }

        return {
            'minMinor' : (getDimensionlessValue(minorDiameter,UOM.MM) - es - cmin)*millimeter,
            'maxMinor' : (getDimensionlessValue(minorDiameter,UOM.MM) - es)*millimeter,
            'minPitch' : (getDimensionlessValue(pitchDiameter,UOM.MM) - es - Td2)*millimeter,
            'maxPitch' : (getDimensionlessValue(pitchDiameter,UOM.MM) - es)*millimeter,
            'minMajor' : (getDimensionlessValue(majorDiameter,UOM.MM) - es - Td)*millimeter,
            'maxMajor' : (getDimensionlessValue(majorDiameter,UOM.MM) - es)*millimeter,
        };        

    }

}

These functions create the profiles ( again, Onshape syntax, but maybe helpful to save some work ):

function createButtressThreadProfile(sketch is Sketch, pitch, majorDiameter, internal,diameterAllowance, pitchDiameterAllowance ){
    var P = pitch;
    var baseLine = computeBaseLine ( majorDiameter, pitch, internal);
    var BL = baseLine.BL;
    var DIR = baseLine.DIR;    
    var f =  0.125 * P;
    var d = 2/3*P;
    var h = P - 2*f;
    var p1 = vector(BL , f);
    var p2 = vector(BL - d*DIR, h);
    var p3 = vector(BL - d*DIR, h + f );
    var p4 = vector(BL , h + f );
    println("points=" ~ [p1, p2, p3, p4]);
    skPolyline(sketch, "polyline1", {"points" : [ p1,p2,p3, p4, p1] });   

}

function createSquareThreadProfile(sketch is Sketch, pitch, majorDiameter,internal,diameterAllowance, pitchDiameterAllowance)
{
    var P = pitch;
    var baseLine = computeBaseLine ( majorDiameter, pitch, internal);
    var BL = baseLine.BL;
    var DIR = baseLine.DIR;    
    //BL = BL + LITTLE_BIT;
    var p1 = vector(BL,  0 * meter);
    var p2 = vector(BL - (P / 2.0)*DIR, 0 * meter);
    var p3 = vector(BL - (P / 2.0)*DIR, P / 2.0);
    var p4 = vector(BL, P / 2.0);
    println("points=" ~ [p1, p2, p3, p4]);
    skPolyline(sketch, "polyline1", {"points" : [ p1,p2,p3, p4, p1] });
}

function createTrapezoidThreadProfile(sketch is Sketch, pitch, majorDiameter, threadAngle,internal,diameterAllowance, pitchDiameterAllowance)
{
    var P = pitch;
    var baseLine = computeBaseLine ( majorDiameter, pitch, internal);
    var BL = baseLine.BL;
    var DIR = baseLine.DIR;

    var T = tan(threadAngle / 2.0);
    var x = 0.3707 * P;
    var s = (T * P / 2.0);
    var p1 = vector(BL, 0 * meter);
    var p2 = vector(BL - P / 2.0*DIR, T * P / 2.0);
    var p3 = vector(BL - P / 2.0*DIR, s + x);
    var p4 = vector(BL, x + (2.0 * s));

    skPolyline(sketch, "polyline1", { "points" : [ p1,p2, p3,p4,p1 ] });
}
function createStandardThreadProfile(sketch is Sketch,internal,definition)
{

    var pitch = definition.pitch;
    var majorDiameter = definition.majorDiameter;
    var minorDiameter = definition.minorDiameter;
    var pitchAllowance = definition.pitchAllowance;
    var superAccurate = definition.superAccurate;
    var A_TINY_BIT=0.00001*meter;
    var P = pitch;
    var H = 0.8660254 * P;    

    var adjustedMajorDiameter = majorDiameter - pitchAllowance;
    var adjustedMinorDiameter = minorDiameter - pitchAllowance;

    var A=undefined;
    var B=undefined;
    var C=undefined;
    var BL=undefined;
    var DIR=undefined;

    var TAN30 = tan(30*degree);
    var P2 = P/2.0;
    if ( internal ){
        A = H/4.0;
        B = 7*H/8.0;
        C = 15*H/16.0;
        BL = adjustedMinorDiameter /2.0;
        DIR = 1.0;        
    }
    else{
        A = A_TINY_BIT;
        B = 3*H/4.0;
        C = 7*H/8.0;
        BL = adjustedMajorDiameter /2.0;
        DIR = -1.0;                
    }
    var p1= vector(BL + DIR*A, -P2 + A*TAN30);
    var p2 =vector(BL + DIR*B, -P2 + B*TAN30 );    
    var p3 =vector(BL  + DIR*C,-P2 + C*TAN30 );        
    var p4 =vector(BL + DIR*C, 0*meter );
    var p5 =vector(BL + DIR*C,P2 - C*TAN30);
    var p6 =vector(BL  + DIR*B,P2 - B*TAN30);
    var p7=vector(BL + DIR*A , P2 - A*TAN30 );

    if ( superAccurate ){
        skLineSegment(sketch,"line1", { "start": p1, "end": p2 });
        skArc(sketch, "arc1", { "start" : p2, "mid" : p4,  "end" : p6 }); 
        skLineSegment(sketch, "line2", { "start" : p6, "construction" : false, "end" : p7 });    
        skLineSegment(sketch, "line3", { "start" : p7, "construction" : false, "end" : p1 });
    }
    else{
        skLineSegment(sketch,"line1", { "start": p1, "end": p3 });
        skLineSegment(sketch,"line2", { "start": p3, "end": p5 });
        skLineSegment(sketch, "line3", { "start" : p5, "end" : p7 });    
        skLineSegment(sketch, "line4", { "start" : p7, "end" : p1 });                    
    }        

}