Open jmwright opened 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
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
Here are some experiences from Onshape users about a thread creator i made there:
Very popular features
Less popular features
Here's a copy of the docs for the Onshape thread creator:
If you're an onshape user, you can play with it here: https://cad.onshape.com/documents/6b640a407d78066bd5e41c7a/w/4693805578a72f40ebfb4ea3/e/f8aea9e5c33e02eab0854a4f
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 });
}
}
Generating threads can often lead to invalid geometry. It would be nice to capture all of the best practices into a thread generator plugin.