Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Plugin Request] Thread Generator #2

Open
jmwright opened this issue Mar 8, 2021 · 5 comments
Open

[Plugin Request] Thread Generator #2

jmwright opened this issue Mar 8, 2021 · 5 comments
Labels
enhancement New feature or request

Comments

@jmwright
Copy link
Member

jmwright commented Mar 8, 2021

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

@jmwright jmwright added the enhancement New feature or request label Mar 8, 2021
@marcus7070
Copy link
Member

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
Copy link
Member

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
Copy link
Member

dcowden commented Mar 9, 2021

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

Very popular features

  • Internal/external threads, course and fine series
  • Supported profiles: ansi standard, ansi squre, ansi acme, iso standard, square and DIN 103
  • customize pitch
  • Tapered start, with customized pitch and lead in
  • left/right handed
  • specify length or 'up to face'
  • change depth of thread slightly for 3d printing tweaking
  • automatically detect standard thread pitch based on a circle/diameter

Less popular features

  • multi-start
  • super-accurate/less accurate setting. This made a couple of optimizations to create threads that were 'good enough' but less computationally intensive than the correct profile-- ( specifically, the proper radius at the root vs just using straight lines which is fine for 3d printing)

@dcowden
Copy link
Member

dcowden commented Mar 9, 2021

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
Copy link
Member

dcowden commented Mar 9, 2021

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 });                    
    }        

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants