學習 Onshape 可以從 614 頁的 pdf 格式導引手冊: https://cad.onshape.com/help/PDF/Onshape.pdf 下手, 在手動操作逐漸熟悉之後, 就可以透過下列的 FeatureScript 程式客製化理想中的各種零件, 這裡先來看看漸開線正齒輪輪廓繪圖, 有關齒輪的參考資料, 請參閱 http://khkgears.net/gear-knowledge/.

機械設計工程師的設計模擬與實作 文章中, 可以看到利用 Brython 直接在網際畫布上以小段直線繪製漸開線正齒輪輪廓的程式原始碼. 類似的正齒輪繪圖可以參考 Approximation of Involute Curves for CAD-System Processing 論文中的說明, 完成 靜態與動態的漸開線正齒輪繪圖與模擬 .

以下主要以教導 FeatureScript 為目的, 依據下列圖示作為參考:

假設齒數為 n, 模數為 m, 壓力角為 pa, 則正齒輪的節圓半徑為 \(rp = m*n/2\), 且基圓半徑 \(rb = rp*cos(pa)\), 假設齒根 \(d = 2.5*rp/n\), 齒頂圓半徑 \(ra = rp + m\), 齒根圓半徑 \(rd = rp - d\), 接著當齒數 n 小於 \(2.5/(1-cos(pa))\) 時就能夠從基圓開始繪製漸開線到齒頂圓, 若 n 大於 \(2.5/(1-cos(pa))\), 則漸開線必須從齒根圓畫起, 而不是從基圓畫起 (因為基圓半徑已經小於齒根圓半徑). 有關這一點, Onshape 官方釋出的漸開線正齒輪繪圖 FeatureScript 程式第1版也未能納入考量, 因此當選擇壓力角 20 度時, 若齒數超過 43 齒, 或者選擇壓力角 15.5 度, 當齒數超過 70 齒, 正齒輪的輪廓就會出錯. 之後當以下的練習完成後, 就可以將漸開線納入 spline 中, 並且加上齒輪底部的導倒圓角後, 就能夠修正上述的錯誤.

以下為 17 齒, 模數 20 mm, 壓力角 20 度的漸開線正齒輪輪廓繪圖:

以下為 170 齒, 模數 20 mm, 壓力角 20 度的漸開線正齒輪輪廓繪圖:

以下為對應的 FeatureScript 正齒輪輪廓繪圖程式碼:

FeatureScript 355;
import(path : "onshape/std/geometry.fs", version : "355.0");

annotation { "Feature Type Name" : "Spur2" }
export const spur = defineFeature(function(context is Context, id is Id, definition is map)
    precondition
    {
        // 正齒輪齒數, 由使用者輸入, 型別為整數
        annotation { "Name" : "Number of Gear Tooth" }
        isInteger(definition.n, POSITIVE_COUNT_BOUNDS);
        // 正齒輪模數, 由使用者輸入, 型別為長度, 為內建尺寸單位
        annotation { "Name" : "Module" }
        isLength(definition.module, LENGTH_BOUNDS);
        // 正齒輪壓力角, 由使用者輸入, 型別為角度, 為內建角度單位
        annotation { "Name" : "Pressure Angle" }
        isAngle(definition.pa, ANGLE_360_BOUNDS);
        // 正齒輪圓心座標點, 由使用者選擇
        annotation { "Name" : "Select a point", "Filter" : EntityType.VERTEX, "MaxNumberOfPicks" : 1 }
        definition.point is Query;
    }
    {
        // Precondition 中的查詢, 需要 evalue 才能傳回對應的 entity
        var location = evaluateQuery(context, definition.point)[0];
        // location 變數為點座標對應的 entity, 必須透過 evVetexPoint 的評量才能傳回對應的點座標
        var center3D = evVertexPoint(context, {
                "vertex" : location
        });

        // 作圖平面利用 evOwnerSketch 評量, 與 location entity 同一個平面
        var sketchPlane = evOwnerSketchPlane(context, {
                "entity" : location
        });
        // 作圖畫
        const gearSketch = newSketchOnPlane(context, id + "gearSketch", {
                "sketchPlane" : sketchPlane
        });
        // 利用 worldToPlane, 將 center3D 轉換為 sketchPlane 上的平面點座標
        const center2D = worldToPlane(sketchPlane, center3D);
        //print(center2D);
        // 漸開線近似點數
        var imax = 5;
        // 使用者所選的齒輪圓心 x 座標
        var midx = center2D[0];
        // 使用者所選的齒輪圓心 y 座標
        var midy = center2D[1];
        // 齒數
        var n = definition.n;
        // 模數
        var m = definition.module;
        // 壓力角, 單位為角度
        var pa = definition.pa;
        // 齒輪的節圓半徑
        var rp = m*n/2;
        skLineSegment(gearSketch, "line", {
        "start" : vector(midx,midy),
        "end" : vector(midx,midy+rp)
        });
        // 齒根
        var d = 2.5*rp/n;
        // 齒頂圓半徑
        var ra = rp + m;
        // 基圓半徑
        var rb = rp*cos(pa);
        //print(rb);
        // 齒根圓半徑
        var rd = rp - d;
        // 分段後齒頂與齒根半徑差增量
        var dr = 0*meter;
        // 若 rb > rd 時從基圓開始繪製漸開線, 但是若 rd > rb, 則漸開線從 rd 畫到齒頂圓
        if (rd > rb)
        {
            // 半徑差的分段, 由齒根圓到齒頂圓
            dr = (ra-rd)/imax;
        }
        else
        {
            // 半徑差的分段, 由基圓到齒頂圓
            dr = (ra-rb)/imax;
        }
        // PI 為實數值沒有單位, tan(pa)也沒有單位, pa 已經設定單位為 degree, 這裡為了與 radian 運算
        // 系統會自動轉為 radian
        var rot = PI/(2*n)*radian;
        //print(sigma);
        // 分別用來設定 entity id 用的增量變數
        var nameId = 1;
        var nameId2 = 1;
        var r = 0*meter;
        // theta 為浮點數字
        var theta = 0;
        var inv = 0*radian;
        var inc = 0*radian;
        // 當 r=rp 時 ,計算 inv_rp 用來旋轉漸開線用
        // theta 為沒有單位的實數
        theta = sqrt((rp*rp)/(rb*rb)-1);
        // atan(theta) 為 radian
        var inv_rp = theta*radian-atan(theta);
        // 漸開線上點的 x 座標
        var xpt = 0*meter;
        // 漸開線上點的 y 座標
        var ypt = 0*meter;
        // 左側漸開線第1點座標 left first x and y
        var lfx = 0*meter;
        var lfy = 0*meter;
        // 右側漸開線第1點座標 right first x and y
        var rfx = 0*meter;
        var rfy = 0*meter;
        // 左側齒根圓上點座標 left x of dedendum point
        var lxd = 0*meter;
        var lyd = 0*meter;
        // 右側齒根圓上點座標 right x of dedendum point
        var rxd = 0*meter;
        var ryd = 0*meter;
        // 左側齒根圓上點座標 right x of dedendum point (advanced)
        var lxd_ad = 0*meter;
        var lyd_ad = 0*meter;
        var inc_ad = 0*radian;
        for (var j=0;j<n;j+=1)
        {
            // 當 j 增量時, 按照齒數輪廓繞行旋轉增量角度
            inc = (2.*j*PI/n)*radian;
            inc_ad = (2.*(j+1)*PI/n)*radian;
            if (rd>rb)
            {
                // 當齒根半徑因為齒數增多後大於基圓半徑時, 漸開線從齒根圓長起
                theta = sqrt((rd*rd)/(rb*rb)-1.);
                inv = theta*radian-atan(theta);
                // 左側漸開線第1點座標
                // 左側輪廓線配合逆時針旋轉 inc 角度
                lfx = midx+rd*sin(inv-rot-inv_rp+inc);
                lfy = midy+rd*cos(inv-rot-inv_rp+inc);
                lxd = lfx;
                lyd = lfy;
                lxd_ad = midx+rd*sin(inv-rot-inv_rp+inc_ad);
                lyd_ad = midy+rd*cos(inv-rot-inv_rp+inc_ad);
                // 右側漸開線第1點座標
                // 右側輪廓線配合順時針旋轉 inc 角度
                rfx = midx-rd*sin(inv-rot-inv_rp-inc);
                rfy = midy+rd*cos(inv-rot-inv_rp-inc);
                rxd = rfx;
                ryd = rfy;
                // 齒根圓上的直線 on dedendum points
                skLineSegment(gearSketch, "line_dd" ~ nameId, {
                "start" : vector(rxd,ryd),
                "end" : vector((lxd_ad),(lyd_ad))
                });
            }
            else
            {
                // 當基圓半徑大於齒根圓時, 漸開線從基圓長起
                theta = sqrt((rb*rb)/(rb*rb)-1.);
                inv = theta*radian-atan(theta);
                // 左側漸開線第1點座標
                lfx = midx+rb*sin(inv-rot-inv_rp+inc);
                lfy = midy+rb*cos(inv-rot-inv_rp+inc);
                lxd = midx+rd*sin(inv-rot-inv_rp+inc);
                lyd = midy+rd*cos(inv-rot-inv_rp+inc);
                lxd_ad = midx+rd*sin(inv-rot-inv_rp+inc_ad);
                lyd_ad = midy+rd*cos(inv-rot-inv_rp+inc_ad);
                // 從基圓點到齒根圓點, 畫直線 left from base point to dedendum point
                skLineSegment(gearSketch, "line_lbd" ~ nameId, {
                "start" : vector(lfx,lfy),
                "end" : vector((lxd),(lyd))
                });
                // 右側漸開線第1點座標
                rfx = midx-rb*sin(inv-rot-inv_rp-inc);
                rfy = midy+rb*cos(inv-rot-inv_rp-inc);
                rxd = midx-rd*sin(inv-rot-inv_rp-inc);
                ryd = midy+rd*cos(inv-rot-inv_rp-inc);
                // 從基圓點到齒根圓點, 畫直線 right from base point to dedendum point
                skLineSegment(gearSketch, "line_rbd" ~ nameId, {
                "start" : vector(rfx,rfy),
                "end" : vector((rxd),(ryd))
                });
                // 齒根圓上的直線 on dedendum points
                skLineSegment(gearSketch, "line_dd" ~ nameId, {
                "start" : vector(rxd,ryd),
                "end" : vector((lxd_ad),(lyd_ad))
                });
            }

            for (var i=1; i<imax+1; i+= 1)
            {
                // 先處理中線左側的漸開線
                // 當 rd 大於 rb 時, 漸開線並非畫至 rb, 而是 rd
                if (rd>rb)
                {
                    r = rd+i*dr;
                }
                else
                {
                    r = rb+i*dr;
                }
                theta = sqrt((r*r)/(rb*rb)-1);
                var inv = theta*radian-atan(theta);
                // 漸開線上的點座標
                xpt = midx+r*sin(inv-rot-inv_rp+inc);
                ypt = midy+r*cos(inv-rot-inv_rp+inc);
                // lxd, lyd 為漸開線上的繪線起點座標
                skLineSegment(gearSketch, "lineb" ~ nameId, {
                "start" : vector(lfx,lfy),
                "end" : vector((xpt),(ypt))
                });
                // 更新漸開線點座標
                lfx = xpt;
                lfy = ypt;
                nameId += 1;
            }
            // 紀錄左側漸開線的最後一點, 也就是齒頂圓上的點座標
            var lastlx = xpt;
            var lastly = ypt;
            // another side
            for (var i=1; i<imax+1; i+= 1)
            {
                if (rd>rb)
                {
                    r = rd+i*dr;
                }
                else
                {
                    r = rb+i*dr;
                }
                theta = sqrt((r*r)/(rb*rb)-1);
                var inv = theta*radian-atan(theta);
                // 漸開線上的點座標
                xpt = midx-r*sin(inv-rot-inv_rp-inc);
                ypt = midy+r*cos(inv-rot-inv_rp-inc);
                // rxd, ryd 為漸開線上的繪線起點座標
                skLineSegment(gearSketch, "linec" ~ nameId, {
                "start" : vector(rfx,rfy),
                "end" : vector((xpt),(ypt))
                });
                // 更新漸開線點座標
                rfx = xpt;
                rfy = ypt;
                nameId += 1;
            }
            var lastrx = xpt;
            var lastry = ypt;
            // 齒頂連線
            skLineSegment(gearSketch, "lined" ~ nameId2, {
            "start" : vector(lastlx,lastly),
            "end" : vector(lastrx,lastry)
            });
            nameId2 += 1;
        }
    skSolve(gearSketch);
    });