歯車の形に興味のある人に

Fusion360のスケッチ線SVG出力にトライ

経緯

Fusion360で作成したモデルを使った文書作成時、画面コピーを使っていますが、これが見にくくて、なんとかならないかと思っていました。

そこで、Fusion360のスケッチとボディの曲線(直線含む)を、SVG形式で出力するPythonスクリプトにトライしてみました。結果は、自分で使う分にはこれでもいいか、というレベルには達したので、公開段階ではないのですが紹介します。

なお、SVG出力に興味のある方は、「Shaper utilities」というフリーのアドインがAppStoreにあります。私は使ったことがないのですが、自社のCNCのために作ったようです。また、githubに「ExportToSVG」というフリーのアドインがあります。自分でも一度試してみようと思います。

結果

スケッチの出力

図1は、Fusion360のスケッチ画面で、図形として「円」「円弧」「楕円」「線分」を使用しています。

図1.元のFusion360画面

下図は図1を変換したSVGファイルを、直接埋め込んだものです。Fusion360の「円Circle」「円弧Arc」「楕円Ellipse」「線分Line」は、SVGの「円circle」「円弧arc」「楕円ellipse」「線分line」に変換しているので、ベクトルデータのままです。それ以外の要素は、すべてスプラインというかピッチの細かい折れ線pathに変換します。



"

SVGはベクトルデータで、2017年からPowerPointで読めるようになっています。読み込んだSVGは、PowerPointの図形として書式設定可能です。つまり色や太さ、破線/実線などをあとから変更できます。これができることがSVG化のねらいの一つです。

図3.PowerPointで編集

図2のデータは次のようなテキストです。

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 300">
<g fill="none" stroke="orange" stroke-width="0.1" transform="scale(3.77949,3.77949)">
 <ellipse cx="74.31897" cy="38.806101" rx="40.153478" ry="14.109392" fill="none" stroke="navy" stroke-width="0.1" transform="rotate(61.88213027798403,74.318968,38.806101)"/>
<line x1="55.395157" y1="3.391540" x2="93.242780" y2="74.220663" stroke="black" stroke-width="0.1"/>
<line x1="86.763168" y1="32.156529" x2="61.874768" y2="45.455674" stroke="black" stroke-width="0.1"/>
<circle cx="30.523862" cy="109.094544" r="24.519122" fill="none" stroke="navy" stroke-width="0.1"/>
<line x1="119.192849" y1="89.359712" x2="134.875164" y2="89.359712" stroke="black" stroke-width="0.1"/>
(途中省略)
</g>
</svg>

スプライン出力

Fusion360のスプラインは、SVGで対応するものがないので、折れ線で近似しています。図4の上は、一見滑らかですが、拡大すると直線近似だと分かります。その下は、同じデータをフリーのSVG図形編集ソフト「Inkscape」で「パス」「簡略化」を実行した結果です。ほとんど形状を損なうことなく、曲線にできるので、必要に応じて使っています。

図4.スプラインの出力とinkscapeで曲線化

ボディのエッジ出力

図5は歯車のエッジを出力したもので、CADの「ワイヤフレーム」のようになります。ただし、この図そのものは画面のコピーなので、本来のSVGのもつシャープな線ではなく、ややぼやけています。

図5.歯車のエッジ取り出し、SVG出力

こちらもPowerPointで編集可能なのは同じです。

図6.PowerPointで編集

その他の例

図7.のように、細い線が多数並ぶ場合でも、美しい出力が得られます(これは画面コピー)。

図7.その他の例

プログラムの主要部分

まだ未完成なので全部は載せられませんが、主要部はこんな感じです。

曲線の抽出

このスクリプトは最初に曲線を抽出するのですが、次のいずれかを対象とします。

  • スケッチ編集時は、編集中のスケッチ内の曲線
    for crv in sketch.sketchCurves:
        curves.add(crv) 
  • 編集時以外は、ファイルに含まれるすべてのスケッチ内の曲線
  • マウスで選択したボディのエッジ
    for edge in body.edges:
        curves.add(edge)

曲線の判別

線を検出したら、その種類(円、円弧、楕円、直線、その他)によって処理内容を振り分けます。

       if curves.count > 0 :
            fileId = svgFileCreate(fileName)
            initSvgOut(fileId, boundBox)

            for crv in curves:    

                if isinstance(crv.geometry, adsk.core.Ellipse3D):
                    ellipseSvgOut(fileId,crv)

                elif (isinstance(crv.geometry, adsk.core.Circle3D)):
                    circleSvgOut(fileId,crv)

                elif isinstance(crv.geometry, adsk.core.Line3D):
                    lineSvgOut(fileId,crv)
                    
                elif isinstance(crv.geometry, adsk.core.Arc3D):
                    arcSvgOut(fileId,crv)

                else:
                    splineSvgOut(fileId,crv)
            
            closeSvgOut(fileId)

曲線の種類ごとの処理内容

円、円弧、楕円、直線の処理内容です。それぞれ対応するSVG要素に値をセットしていくのですが、円弧と楕円は形状の定義方法が違うので、変換しなければなりません。円と直線の定義は同じです。
値に"*10"しているのは、Fusion360はcm単位なのでmm単位にするためです。

def ellipseSvgOut(fileId,crv):
    (returnValue, center, normal, majorAxis, majorRadius, minorRadius) \
        = crv.geometry.getData()
    angle_deg = math.degrees(math.atan2(majorAxis.y,majorAxis.x))
    fileId.write(f' <ellipse cx="{center.x*10:.5f}" cy="{center.y*10:.6f}"'+
                 f' rx="{majorRadius*10:.6f}" ry="{minorRadius*10:.6f}"'+
                 f' fill="none" stroke="navy" stroke-width="0.1"'+
                 f' transform="rotate({angle_deg},{center.x*10:.6f},{center.y*10:.6f})"/>\n')

def circleSvgOut(fileId,crv):
    (returnValue, center, normal, radius) = crv.geometry.getData()
    fileId.write(f'<circle cx="{center.x*10:.6f}" cy="{center.y*10:.6f}"'+
                 f' r="{radius*10:.6f}"'+
                 ' fill="none" stroke="navy" stroke-width="0.1"/>\n')

def lineSvgOut(fileId,crv):
    (returnValue, startPoint, endPoint) = crv.geometry.getData()
    fileId.write(f'<line x1="{startPoint.x*10:.6f}" y1="{startPoint.y*10:.6f}"'+
                 f' x2="{endPoint.x*10:.6f}" y2="{endPoint.y*10:.6f}"'+
                 ' stroke="black" stroke-width="0.1"/>\n')

def arcSvgOut(fileId,crv):
    (returnValue, center,  axis, refVector, radius, startAngle, endAngle) =\
        crv.geometry.getData()
    refAngle = math.atan2(refVector.y, refVector.x)
    x1 = radius * math.cos(startAngle + refAngle) + center.x
    x2 = radius * math.cos(endAngle + refAngle) + center.x
    y1 = radius * math.sin(startAngle + refAngle) + center.y
    y2 = radius * math.sin(endAngle + refAngle) + center.y
    f1 = 1 if endAngle - startAngle > math.pi else 0
    fileId.write('<path d="M')
    fileId.write(f' {x1*10:.6f} {y1*10:.6f} A {radius*10:.6f} {radius*10:.6f}'+
                 f' 0 {f1} 1 {x2*10:.6f} {y2*10:.6f}\n')
    fileId.write('"\n')
    fileId.write('fill="none" stroke="red" stroke-width="0.1"/>\n')

以上です。現時点で公開していないのであしからず。