経緯
Fusion360で作成したモデルを使った文書作成時、画面コピーを使っていますが、これが見にくくて、なんとかならないかと思っていました。
そこで、Fusion360のスケッチとボディの曲線(直線含む)を、SVG形式で出力するPythonスクリプトにトライしてみました。結果は、自分で使う分にはこれでもいいか、というレベルには達したので、公開段階ではないのですが紹介します。
なお、SVG出力に興味のある方は、「Shaper utilities」というフリーのアドインがAppStoreにあります。私は使ったことがないのですが、自社のCNCのために作ったようです。また、githubに「ExportToSVG」というフリーのアドインがあります。自分でも一度試してみようと思います。
結果
スケッチの出力
図1は、Fusion360のスケッチ画面で、図形として「円」「円弧」「楕円」「線分」を使用しています。
下図は図1を変換したSVGファイルを、直接埋め込んだものです。Fusion360の「円Circle」「円弧Arc」「楕円Ellipse」「線分Line」は、SVGの「円circle」「円弧arc」「楕円ellipse」「線分line」に変換しているので、ベクトルデータのままです。それ以外の要素は、すべてスプラインというかピッチの細かい折れ線pathに変換します。
SVGはベクトルデータで、2017年からPowerPointで読めるようになっています。読み込んだSVGは、PowerPointの図形として書式設定可能です。つまり色や太さ、破線/実線などをあとから変更できます。これができることがSVG化のねらいの一つです。
図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」で「パス」「簡略化」を実行した結果です。ほとんど形状を損なうことなく、曲線にできるので、必要に応じて使っています。
ボディのエッジ出力
図5は歯車のエッジを出力したもので、CADの「ワイヤフレーム」のようになります。ただし、この図そのものは画面のコピーなので、本来のSVGのもつシャープな線ではなく、ややぼやけています。
こちらもPowerPointで編集可能なのは同じです。
その他の例
図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')
以上です。現時点で公開していないのであしからず。