C#ATIA

↑タイトル詐欺 主にFusion360API 偶にCATIA V5 VBA(絶賛ネタ切れ中)

全てのファイルを保存せずに閉じる

スプリクト開発時、テストしていると大量のドキュメントを作成してしまい
手動でチマチマ閉じるのが面倒なため作りました。

#FusionAPI_python 開いているファイルを未保存で全て閉じる
#Author-kantoku

import adsk.core, traceback

def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        docs = app.documents
        msg = '{}個のファイルが開いています\n'.format(docs.count)
        msg += '全て保存せずにクローズしますがよろしいですか?'
        
        if not ui.messageBox(msg,'',1,3) == 0:
            return
            
        #逆からじゃないと全ては閉じない
        [doc.close(False) for doc in docs[::-1]]
        
        ui.messageBox('done')
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

くだらない・・・、でも1ファイルにつき、2クリックが面倒なんです。

Fusion360環境でnumpyがインストール出来ました。

やっとわかりました。当方が使用している環境です。
os: win7 64bit
Fusion360 : ver 2.0.4567
python : 3.5.3
※今回偶々この手順で出来ただけです。アップデート等により変わる可能性も
 ありますので、ご注意を!
※ここの記載は一部未完成・未確認な内容です。確認取れ次第追記するかも。



以下の手順で行いました。アカウントには管理者権限が必要かも。

環境変数の設定

Fusion360APIで使用されるpythonは、通常インストールされるものとは
別の位置にあります。まずそのインストールパスを調べる必要があり、
スプリクトを使用して見つけました。

まず、スプリクトファイルを新たに作成します。
(わかる方はコードのところまで読み飛ばしてください)
f:id:kandennti:20180927123145p:plain
雑な説明で申し訳ないのですが、先が長いので。
主に確認・クリックすべきところは赤印部分です。

新たに作成したスプリクトは ”マイ スプリクト” 内に作成されます。
選択し編集を押すと "spyder" と言うIDEが起動します。
f:id:kandennti:20180927123153p:plain
こんな感じです。ひょっとしたらレイアウトが若干違うかもしれません。
f:id:kandennti:20180927123205p:plain

ここでコードを修正します。

ui.messageBox('Hello script')

が書かれている側をゴッソリ消し、以下のコードをコピペします。

import sys
[print(s) for s in str(sys.path).split(',')]

一度保存し(赤印)実行します。(青印)
f:id:kandennti:20180927123217p:plain

実行後Fusion360自体は何も変わらないのですが、spyderの "Console - ~" タブ
部分にズラズラ出力されます。
f:id:kandennti:20180927123225p:plain
spyderを大きく表示させた方が見やすいです。
ここで表示されるものは各々異なると思います。

パスによってバックスラッシュの表記が異なるのは謎ですが、
"site-packages" の記載されている行が必要です。
今回行ったこの行(パッケージがインストールされるパス)

C:\\Users\\(アカウント名)\\AppData\\Local\\Autodesk\\webdeploy\\production\\24c68c01e0965d221a1a390181f4dbc235d252c7\\Python\\lib\\site-packages

と、"Python"までのパス(python.exeがインストールされているパス)

C:\\Users\\(アカウント名)\\AppData\\Local\\Autodesk\\webdeploy\\production\\24c68c01e0965d221a1a390181f4dbc235d252c7\\Python

の2つを環境変数の "path" に追加する必要があります。

環境変数の追加方法は、検索して頂いた方が確実かと思います。
f:id:kandennti:20180927123238p:plain
”%USERPROFILE%~” でも良いのかも知れませんが・・・。

※他にもパスを確認する方法も有りそうですが、実際に実行した環境から
 パスを得る方が確実と思い、スプリクトで取得しました。

pipのインストール

ここで苦労しました。参考にさせて頂いたサイトがこちら。 
pipのインストール方法

”get-pip.py” を入手します。
https://bootstrap.pypa.io/get-pip.py
こちらを "名前をつけてリンク先を保存" でとりあえず保存します。
この ”get-pip.py” を先程の "site-packages" のフォルダ内に入れておきます。

続いて、コマンドプロンプトを起動します。
今さら聞けない!コマンドプロンプトの使い方【初心者向け】 | TechAcademyマガジン

>python (get-pip.pyまでのパス)\get-pip.py

を実行します。(get-pip.pyまでのパスはD&Dだと手間がかかりません)
赤印部分が実行した際の出力です。
f:id:kandennti:20180927123253p:plain
(実行位置が非常に悪く汚い・・・)

とりあえず、他のパッケージがインストール出来る状態になりました。
(ここでエラーの場合は環境変数の設定が正しくない可能性があります)

numpyのインストール

コマンドプロンプトを起動し以下を実行します。

>python -m pip install numpy

"-m" が必要でした。
f:id:kandennti:20180927123302p:plain

確認

新たにスプリクトを作成し、以下のコードで確認しました。

import numpy as np

ary = np.array([1, 2, 3])
print(type(ary))
print(ary)

f:id:kandennti:20180927123311p:plain
エラー無く

<class 'numpy.ndarray'>
[1 2 3]

が出力されたので大丈夫そうです。

外部パッケージ位置と今後の問題

実はFusion360のアップデートによりPythonの実行ファイルが入っている
フォルダが書き換えられる・変更される等の問題があり、このままの運用は
適さないことが指摘されています。この辺かな?
How can I import module? - Autodesk Community

追記で、外部のパッケージを置く場所です。
本家の "Fusion 360 API and Scripts" では、実際に使用するスプリクトと
同一の所にパッケージを置き、配布するような忠告が記載されていますが、
あまりにも不便です。(回答された方も納得していなかったような・・・)

そこで、こちらを参考にしてみることにしました。
Pythonでimportの対象ディレクトリのパスを確認・追加(sys.pathなど) | note.nkmk.me

まず、パスの位置ですがスプリクトのボタンを押した際出てくるダイアログの
”マイ スプリクト” のどれかの上でコンテキストメニューを表示さ
”ファイルの場所を開く” を選択すると(通常であれば)エクスプローラ
が起動します。
f:id:kandennti:20180927171641p:plain
一階層上のフォルダに移動し、この位置にpipでインストールした
numpyのフォルダを移動しました。
(numpy-1.15.2.dist-info 自体が必要なものかどうかわかりませんが
 一緒に移動しました)
f:id:kandennti:20180927171649p:plain

先程の確認用のスプリクトを実行するとエラーとなります。
f:id:kandennti:20180927171658p:plain
移動した為です。

参考にしたサイトのように、コードを付け加えます。

import os,sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

import numpy as np

ary = np.array([1, 2, 3])
print(type(ary))
print(ary)

これであればOKです。
f:id:kandennti:20180927171711p:plain

・コピーの場合、どちらを認識しているのかわからなくなってしまう為
 あえて移動しました。(裏目に出る可能性は有るかも)
・個人的な経験上、アップデートがあってもスプリクトが消えたことが無い為
 比較的安全なパスと考えています。
・"アドインの時の事を考えたら、もう一階層上がいいんじゃないのか?"
 と言う気持ちは有ります。
・実際のところ、pipでインストール必要は無いような気がしてます。
 (wheelが何チャラとか有りそうだったので、この方法が楽なのかな?と)
・必要なパッケージを入手すれば、追記した環境変数のpathは削除しても
 良いような・・・。
実際のところ、アップデートが起きないとどうなるかわからないのが本音です。

※全体的に参考になりました。(但しLinuxのようです)
pythonのimportができなくて頑張った話

スケッチのスピードテスト

先日の迷路や凸包の遅さを改善したい為、方法を模索することにしました。
今回はスケッチです。

スケッチに関しては、3D曲線インポートスプリクトを作成した際に色々と
試したので、どちらかと言うと覚書のようなものです。

こちらをベースにします。

#FusionAPI_python スケッチの速度テスト
#Author-kantoku

import adsk.core, adsk.fusion, traceback
import time, inspect
        
DEBUG = False
def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        
        unit_size = 2.0
        lst = [((v*3, 0.0, 0.0),(unit_size+v*3, unit_size, 0.0)) for v in range(100)]
        
        #test1
        for i in range(5):
            doc = NewDoc(app)
            root = app.activeProduct.rootComponent
            
            t = time.time()
            
            funcname = test1(root,lst)#ここ書き換え
            
            msg='{}-time:{:.2f}s'.format(funcname,time.time()- t)
            print(msg)
            
            if not DEBUG:
                doc.close(False)
                
        if DEBUG:
            ui.messageBox(msg)
        
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def NewDoc(app):
    return app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)

def initBox(skt,ary1,ary2):
    lines = skt.sketchCurves.sketchLines
    p1 = adsk.core.Point3D.create(ary1[0],ary1[1],ary1[2])
    p2 = adsk.core.Point3D.create(ary2[0],ary2[1],ary2[2])
    box = lines.addTwoPointRectangle(p1,p2)
    return box

比較を行うための関数を用意し "test1" の部分を書き換えて速度を測定します。
スケッチの四角の対角線となる2点の座標値を持つ、100個のリストをテスト関数に投げ込みます。
結果的にこんな感じの四角が100個出来上がります。
(実際はドキュメントを新作し、処理後閉じている為何も残りません)
f:id:kandennti:20180926153642p:plain
これを各5回行います。

○テスト1
APIのHelpのサンプルコードにもありそうな、オーソドックスな感じのものです。

#test1
def test1(root,lst):
    skt = root.sketches.add(root.xYConstructionPlane)
    [initBox(skt,a1,a2) for (a1,a2) in lst]
    
    f = inspect.currentframe()
    return inspect.getframeinfo(f)[2]

比較するものも無いので、これが基準です。

test1-time:5.63s
test1-time:5.60s
test1-time:5.58s
test1-time:5.62s
test1-time:5.61s


○テスト2
スケッチオブジェクトのisComputeDeferredプロパティを利用すると、
スケッチ内の演算処理を停止する事が出来、速度向上のメリットかあるようです。

#test2
def test2(root,lst):
    skt = root.sketches.add(root.xYConstructionPlane)
    skt.isComputeDeferred = True
    [initBox(skt,a1,a2) for (a1,a2) in lst]
    skt.isComputeDeferred = False
    
    f = inspect.currentframe()
    return inspect.getframeinfo(f)[2]

結果はこちら。効果絶大です。

test2-time:0.33s
test2-time:0.33s
test2-time:0.33s
test2-time:0.33s
test2-time:0.33s


○テスト3
スケッチオブジェクトのareProfilesShownプロパティは、プロファイルの
表示です。手動ではこちらのチェックの有無の操作になり、OFFにすることで
速度向上のメリットかあるようです。
f:id:kandennti:20180926153656p:plain

#test3
def test3(root,lst):
    skt = root.sketches.add(root.xYConstructionPlane)
    skt.areProfilesShown = False
    [initBox(skt,a1,a2) for (a1,a2) in lst]
    skt.areProfilesShown = True
    
    f = inspect.currentframe()
    return inspect.getframeinfo(f)[2]

結果はこちら。効果大です。

test3-time:0.50s
test3-time:0.48s
test3-time:0.48s
test3-time:0.48s
test3-time:0.48s


○テスト4
通常であれば、履歴有りで使用することの方が多いはずですが
履歴なしに切り替えることで、少し効果が有るような記述を見つけました。

#test4
def test4(root,lst):
    des = adsk.fusion.Design.cast(root.parentDesign)
    des.designType = adsk.fusion.DesignTypes.DirectDesignType
    
    skt = root.sketches.add(root.xYConstructionPlane)
    [initBox(skt,a1,a2) for (a1,a2) in lst]
    
    des.designType = adsk.fusion.DesignTypes.ParametricDesignType
    
    f = inspect.currentframe()
    return inspect.getframeinfo(f)[2]

結果はこちら。効果小です。
デメリットの方が大きい気がします。

test4-time:5.31s
test4-time:5.32s
test4-time:5.31s
test4-time:5.32s
test4-time:5.36s


○テスト5
スケッチのareDimensionsShownプロパティは、拘束の表示です。
手動ではこちらのチェックの有無の操作になり、OFFにすることで
速度向上のメリットかあるようです。
f:id:kandennti:20180926153709p:plain

#test5
def test5(root,lst):
    skt = root.sketches.add(root.xYConstructionPlane)
    skt.areDimensionsShown = False
    
    [initBox(skt,a1,a2) for (a1,a2) in lst]
       
    skt.areDimensionsShown = True

    f = inspect.currentframe()
    return inspect.getframeinfo(f)[2]

結果はこちら。今回拘束を使わなかった為、効果は無さそうです。

test5-time:5.66s
test5-time:5.59s
test5-time:5.59s
test5-time:5.61s
test5-time:5.65s


○テスト6
現実的なものと考えテスト2,3,5を一緒にしてみました。

#test6
def test6(root,lst):
    skt = root.sketches.add(root.xYConstructionPlane)
    
    skt.isComputeDeferred = True
    skt.areProfilesShown = False
    skt.areDimensionsShown = False
    
    [initBox(skt,a1,a2) for (a1,a2) in lst]
    
    skt.areDimensionsShown = True
    skt.areProfilesShown = True
    skt.isComputeDeferred = False      

    f = inspect.currentframe()
    return inspect.getframeinfo(f)[2]

結果はこちら。テスト2と遜色無い為、isComputeDeferredプロパティの
効果が一番大きいようです。

test6-time:0.33s
test6-time:0.32s
test6-time:0.33s
test6-time:0.33s
test6-time:0.33s

3D曲線インポートスプリクトを作っていた頃、フォーラムで出ていた話題で
こちらの ”点の表示” をOFFにすることで速度向上することがわかっており
APIで操作出来ないか? と言うの出ていたのですが、未だに機能が追加されて
いないですね。(スプライン等、通過点が大量に必要な場合は効果が大きいです)
f:id:kandennti:20180926153733p:plain

少し時間に余裕があるので、色々と試しているのですが上手く行かない・・・。

Fusion360API オフラインヘルプ

今日辺りからAPIのオンラインHelpの調子が悪く、かなり作業効率が悪いです。
昨年の秋頃までも悪かったのですが、その後改善されストレス無く使えていました。

フォーラムに記載したところ、Brian EkinsさんがオフラインHelpを教えてくれました。

https://ekinssolutions.com/getting-offline-access-to-the-fusion-360-api-help/

前にもUpされていたんですけどね、"関係ないや" って思っていてDLしてませんでした。
きっと、退社されたから古い方は削除したのだろうなぁ。

迷路を作る

先日、こちらを発見しました。
Random Maze Creator | Fusion 360 | Autodesk App Store
5ドルするんだ・・・。DL数0だから、買った人はいないのだろうけど。

挑戦してみました。
調べてみると、迷路を作成する為のアルゴリズムが色々とある様です。
面倒なためライブラリを探しましたが、前回記載したNumpyの壁等が・・・。
numpy等のライブラリをFusion360APIで利用したい - C#ATIA

結果的に依存度の無い、こちらをチョイスしてみました。
GitHub - boppreh/maze: Simple maze generator in Python

一日取り組んだ結果、これぐらいまでは出来ました。
f:id:kandennti:20180925180024p:plain
この程度で、1分以上かかります。(体感的にはかなり長く感じます)
ライブラリから迷路を受け取るのには、0.01秒もかかっていないのに。
コードもかなり汚いので、もうちょっと直してから。

Fusion360は既に存在しているデータから、何らかの情報を
(幾何的なものも含め)取得するのは軽く感じます。
こんな風にモデルを作るのは、あまり軽い感じがしないですよね。
何か方法があるのかな?

numpy等のライブラリをFusion360APIで利用したい

未だ手段がわかりません。

これではインストール出来ない事がわかっています。

pip install numpy

僕自身が作る部分で利用したいと言うわけではなく、利用したいライブラリ等で
numpyやscipyを使用している場合が多く、インストールの必要が有りそうだからです。

凸包に挑んでみる6

こちらの続きです。
凸包に挑んでみる5 - C#ATIA

結局あっさり諦め、利用しやすそうな軽めのライブラリを探しました。
Start Small: 3D Convex hull in Python
blenderaddons/chull.py at master · varkenvarken/blenderaddons · GitHub
本来、'blender' 向けのようなのですが、他のライブラリとの依存が無く
このファイル 'chull.py' だけで利用できました。

その為、このスプリクトと同一フォルダ内に 'chull.py' を置いておく必要があります。

#FusionAPI_python test_ConvexHull3D
#Author-kantoku
#Description-新たなスケッチを作成し、ランダムに3Dな点を作成し、3Dな凸包を作成

#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.

import adsk.core, adsk.fusion, traceback
import random, os, sys
this_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(this_dir)

#thx-varkenvarken
#chttps://michelanders.blogspot.com/2012/02/3d-convex-hull-in-python.html
#https://github.com/varkenvarken/blenderaddons/blob/master/chull.py
import chull

def run(context):
    #Create Point Count
    p_count = 100
    
    ui = None
    try:
        #extension
        chull.Vertex.toPnt = ToPoint3d
        chull.Face.toSkt = ToSketch
        adsk.core.Point3D.toVec = ToCHullVector
        adsk.fusion.Sketch.initSurf = InitSurface
        
        #preparation
        app = adsk.core.Application.get()
        ui = app.userInterface
        des = app.activeProduct
        root = des.rootComponent
        
        #RandomPoint
        skt = root.sketches.add(root.xYConstructionPlane)
        skt.name = 'points'
        CreateRandomPoint(skt, -10.0, 10.0, p_count)
        
        #time
        import time
        t = time.time()
        
        #coordinate array
        pnts = [p.geometry.asArray() for p in skt.sketchPoints]
        del pnts[0] #remove origin point
        
        #edges sketch
        skt = root.sketches.add(root.xYConstructionPlane)
        skt.name = 'edges'
        
        #ConvexHull
        face_count = InitConvexHull(pnts,skt)
        
        #finish
        ui.messageBox('Point Count:{}\nface Count:{}\ntime:{:.2f}s'
            .format(p_count,face_count,time.time()- t))
        
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

#ConvexHull
def InitConvexHull(point3d_list,target_sketch):
    if len(point3d_list) < 4: return
    
    #hull
    pnts = [chull.Vector(ary[0],ary[1],ary[2]) for ary in point3d_list]
    hull = chull.Hull(pnts)
    
    #edges
    [f.toSkt(target_sketch) for f in hull.faces]
    
    #faces
    target_sketch.initSurf()
    
    return len(hull.faces)

#RandomPoint
def CreateRandomPoint(skt, low, upp, count):
    pnts = [adsk.core.Point3D.create(
            random.uniform(low,upp),random.uniform(low,upp),random.uniform(low,upp)) 
            for dmy in range(count)]
        
    skt_Pnts = skt.sketchPoints
    [skt_Pnts.add(pnt) for pnt in pnts]

# --- extension method ---
#chull.Vertex
def ToPoint3d(self):
    return adsk.core.Point3D.create(
        self.v.x,self.v.y,self.v.z)

#chull.Face
def ToSketch(self, skt):
    #Vertex
    pnts = skt.sketchPoints
    ps = [pnts.add(p.toPnt()) for p in self.vertex]
    
    #edge
    lins = skt.sketchCurves.sketchLines
    edges =[(ps[0],ps[1]),(ps[1],ps[2]),(ps[2],ps[0])]
    [lins.addByTwoPoints(p0,p1) for p0,p1 in edges]
    
#adsk.core.Point3D
def ToCHullVector(self):
    ary = self.asArray()
    return chull.Vector(ary[0],ary[1],ary[2])
    
#adsk.fusion.Sketch
def InitSurface(self,tolerance = 0.001):
    if len(self.profiles) < 1:
        return
        
    newBodyOpe = adsk.fusion.FeatureOperations.NewBodyFeatureOperation
    
    comp = self.parentComponent
    feats = comp.features
    
    objs = adsk.core.ObjectCollection.create()
    [objs.add(v) for v in self.profiles]

    patches = feats.patchFeatures
    pats = patches.add(patches.createInput(objs,newBodyOpe))
    
    objs.clear()
    [objs.add(v) for v in pats.bodies]
    
    tol = adsk.core.ValueInput.createByReal(tolerance)
    stitches = feats.stitchFeatures
    stitches.add(stitches.createInput(objs, tol, newBodyOpe))

結果的に、ベクトル演算等細かなことは一切不要でした。
VBAっぽさを消す為、拡張メソッドを作成しポコポコ呼び出して終わりです。

動画は1分程ですが、半分は処理待ち・・・遅いのは面の作成で
凸包の頂点はほぼ一瞬で求まっています。

質問者さんにちゃんとレスしてあげたかったな。