C#ATIA

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

ビューの位置を数値的に移動する

また、下らないものを作りました。

CATIAのDrawでビューの位置をきっちりと決めたいんです!
テーブルだけが入っているビューが複数あり、きっちり揃えたいんです!!

'vba Draw_Draw_MoveView_ver0.0.1  using-'KCL0.0.12'  by Kantoku
'ControlTipText:選択したビュー基準位置で移動

Option Explicit

Sub CATMain()

    'ドキュメントのチェック
    If Not CanExecute(Array("DrawingDocument")) Then Exit Sub

    Dim msg As String
    msg = "位置変更するビューを選択"

    Dim vi As DrawingView
    Set vi = KCL.SelectItem(msg, "DrawingView")
    If vi Is Nothing Then Exit Sub

    msg = "--基準位置--" & vbCrLf & _
        "X:" & vi.x & vbCrLf & _
        "Y:" & vi.y & vbCrLf & _
        "--サイズ--" & vbCrLf & _
        getViewSizeInfo(vi)
        
    Dim res As String
    res = InputBox(msg, "", CStr(vi.x) & "," & CStr(vi.y))
    If res = vbNullString Then Exit Sub
    
    Dim pos As Variant
    pos = convPos(res)
    
    Call execMove(vi, pos)
    
End Sub

Private Sub execMove( _
    ByVal vi As DrawingView, _
    ByVal pos As Variant)

    If vi.x = pos(0) And vi.y = pos(1) Then
        Exit Sub
    End If
    
    vi.x = pos(0)
    vi.y = pos(1)
    
    CATIA.RefreshDisplay = True
    
End Sub

Private Function convPos( _
    ByVal valueTxt As String) As Variant
    
    convPos = Empty
    
    Dim errMsg As String
    errMsg = "入力が不正です(例 5.0,10.0)"
    
    Dim ary As Variant
    ary = Split(valueTxt, ",")
    
    If UBound(ary) < 1 Then
        MsgBox errMsg
        Exit Function
    End If
    
    If Not IsNumeric(ary(0)) Or Not IsNumeric(ary(1)) Then
        MsgBox errMsg
        Exit Function
    End If
    
    convPos = Array(CDbl(ary(0)), CDbl(ary(1)))
    
End Function
    
Private Function getViewSizeInfo( _
    ByVal vi As DrawingView) As String
    
    Dim viVari As Variant
    Set viVari = vi

    Dim viSize(3) As Variant
    Call viVari.size(viSize)
    
    Dim txt As String
    txt = "X:" & viSize(1) - viSize(0) & vbCrLf & _
          "Y:" & viSize(3) - viSize(2)
    
    getViewSizeInfo = txt
    
End Function

間違ってるかなぁ、方向性。

軽量版 ツールバー情報取得

Fusion360でアドインを作成する際、コマンドのアイコンを登録するために
こちらに記載したTabやらPanelやらのIDが必要なのですが、
そんな事イチイチ覚えてられません。
俺コマンドを作る2 - C#ATIA

その為、サンプルとの名目でそれらの情報を取得するための
フルスペックなスクリプトが公開されています。
Fusion 360 Help
(恐らく修正されていなければ、エラー出ると思うので・・・)

これ、フルスペック過ぎて結構処理時間が長いんです。
まぁ一度ファイルにしてしまえば良いだけではあるのですが。

あれの軽量で最低限な機能の物を、テキストコマンド経由で作れました。

#Fusion360API Python script
#Author-kantoku
#Description-CurrentCommandToolbarInfo

import adsk.core, traceback, json

def run(context):
    try:
        app = adsk.core.Application.get()
        info = app.executeTextCommand(u'UI.GetCurrentCommandToolbarInfo')
        data = json.loads(info)
        print(json.dumps(data, indent=2, ensure_ascii=False))
    except:
        print('Failed:\n{}'.format(traceback.format_exc()))

たったこれだけです。何なのこのコード量の差は。

フルスペックの物との違いは
・対象は、現在の表示されているツールバーのみ(逆に探さなくてよい)
デバッグコンソールに垂れ流し
・本当に最低限な情報
なので速いです。
f:id:kandennti:20200522003044p:plain

選択オブジェクト情報可視化スクリプト

Fusion360API開発者向け(ほぼ僕、個人向け)の
オブジェクト情報可視化スクリプトを公開しました。
Fusion360_Small_Tools_for_Developers/GetObjectInfo at master · kantoku-code/Fusion360_Small_Tools_for_Developers · GitHub

結構前に作っていたのですが、平然とエラーの出る状態
だったので、まともに動作するようにしました。

何をするのかと申しますと、画面上の要素を選択すると
それっぽい情報を表示します。

試しにボディを選択するとこのような感じです。
f:id:kandennti:20200521160722p:plain
赤が要素の名前(nameプロパティ)
青がインスタンス名(objectTypeプロパティの簡素化した表示)
緑がメソッド名とプロパティ名(のオブジェクト名)
が表示されます。
選択する際のフィルタは付けていない為、大体の要素は選択出来ます。
APIとして提供されていないものも選択出来ます。)

例えば、APIで提供されていない「選択セット」を選択させてみると
f:id:kandennti:20200521160734p:plain
何も表示されません。つまり「APIでは扱えないよ」と言う
サインだとも受け止められます。

※「選択セット」の作成はテキストコマンド経由でAPIでも可能です。
Re: Unexpected behavior with ui.activeSelections - Autodesk Community


CATIAで使用している、こちら(kcl)のCATMain関数を想定しているのですが。
KCL/KCL.bas at master · kantoku-code/KCL · GitHub


今後、Fusion360APIを覗いてみたい方には少し役に立つかも。

Selections.List

APIのSelectionsとほぼ同等と思われるものがテキストコマンドのSelectionsと
思うのですがちょっと機能が違います。

具体的に何が違うとは表現しにくいのですが、APIの場合はオブジェクトそのものに
対しての操作なのに対して、テキストコマンドはエンティティIDでの操作の様です。

エンティティIDの取得自体がよくわからなかったのですが、選択状態からIDの取得が
出来るようです。こんな感じで

Selections.List

そこでとりあえずIDだけ列挙するために、こんな(役に立たないもの)を作りました。

#Fusion360API Python script
import adsk.core, adsk.fusion, traceback

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)
_root = adsk.fusion.Component.cast(None)

def run(context):
    try:
        global _app, _ui, _root
        _app = adsk.core.Application.get()
        _ui = _app.userInterface
        des  :adsk.fusion.Design = _app.activeProduct
        _root = des.rootComponent

        msg :str = 'Select'
        selFiltter :str = 'Bodies'
        sel :adsk.core.Selection = selectEnt(msg ,selFiltter)
        if not sel: return

        bdy :adsk.fusion.BRepBody = sel.entity
        
        print('--Body--')
        dumpEntityId([bdy])

        print('--faces--')
        dumpEntityId(bdy.faces)

        print('--edges--')
        dumpEntityId(bdy.edges)

        print('--vertices--')
        dumpEntityId(bdy.vertices)

        # _ui.messageBox('Done')

    except:
        if _ui:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def dumpEntityId(lst):

    cmd = u'Selections.List'

    actSels = _ui.activeSelections    
    actSels.clear()

    [actSels.add(i) for i in lst]
    print(_app.executeTextCommand(cmd))

    actSels.clear()

Body自体と全てのface,edge,vertexを垂れ流します。

長ったらしいので単純な形状で試した結果です。
f:id:kandennti:20200520185218p:plain

--Body--
57:3:7:260

--faces--
57:3:7:260
57:3:7:260+0+64
57:3:7:260+1+64
57:3:7:260+2+64
57:3:7:260+3+64
57:3:7:260+4+64
57:3:7:260+5+64

--edges--
57:3:7:260
57:3:7:260+6+128
57:3:7:260+8+128
57:3:7:260+13+128
57:3:7:260+9+128
57:3:7:260+7+128
57:3:7:260+11+128
57:3:7:260+17+128
57:3:7:260+12+128
57:3:7:260+10+128
57:3:7:260+14+128
57:3:7:260+16+128
57:3:7:260+15+128

--vertices--
57:3:7:260
57:3:7:260+6+256
57:3:7:260+15+256
57:3:7:260+16+256
57:3:7:260+10+256
57:3:7:260+7+256
57:3:7:260+11+256
57:3:7:260+17+256
57:3:7:260+12+256

やっている本人が分かってないぐらいなので、他の方は謎しか残らないかと。

幾つか試した上での予測です。
ボディ以外はこんな形で出力されています。
[A:B:C:D+E+F]

恐らく
[A:B:C] が [57:3:7] で出力されるものはBRepBodyなのだろうと思います。
厳密にはABCのどれかだろうと思われます。

[D] に関しては、ボディの個別の番号だと思われます。
Helpを見ると
Fusion 360 Help
face,edge,vertex に関しては tempId プロパティを持っているのですが
ボディにはありません。これが無いと特定のボディを探す際困るのですが、
[D] が実質のtempidのようなに利用出来そうです。(=ハッシュ)

[E]に関しても face,edge,vertex それぞれの個別の番号っぽいです。
偶然なのかface,edge関しては番号の重複がありません。

[F]に関しては、
64 -> face
128 -> edge
256 -> vertex
を表現しているように感じますが、1回だけvertexで512が出たことがあったため
確信が持てません。
数値的にメモリっぽさを感じます。

face,edge,vertex の tempid は [D+E+F] だろうか?
以外に数値が大きい印象はあったけど、そこまで大きくなかったような…。

ん~何がやりたいんだろう? 自分でもわからない。

SelectionEffectテスト

テキストコマンド(txt)の効果を身をもって感じたいので、少しテストしてみたいです。

ちょっと前に感じたことがありまして、Fusion360APIで要素を選択した際の処理が
ちょっと重たいです。
f:id:kandennti:20200520170027p:plain
設定にもよりますが、赤い矢印の状態です。

CATIAの場合はこちらに記載した
SetCATIADotHSOSynchronizedToFalse - C#ATIA

CATIA.HSOSynchronized

に該当するものをFusion360で探しています。

で、それっぽいテキストコマンドが見つかったのでテスト用にこんなものを
作りました。

#Fusion360API Python script
import adsk.core, adsk.fusion, traceback
import time

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)
_root = adsk.fusion.Component.cast(None)

def run(context):
    try:
        global _app, _ui, _root
        _app = adsk.core.Application.get()
        _ui = _app.userInterface
        des  :adsk.fusion.Design = _app.activeProduct
        _root = des.rootComponent

        cmdLst = [
            u'Options.SelectionEffect /normal',
            u'Options.SelectionEffect /fast',
            u'Options.SelectionEffect /simplified',
            u'Options.SelectionEffect /no',
            u'Options.SelectionEffect /degraded'
        ]
        for cmd in cmdLst:
            [execTest(cmd) for i in range(5)]
            print('-----')

    except:
        if _ui:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def execTest(txtCmd :str):

    global _root
    bs = _root.bRepBodies
    actSels = _ui.activeSelections    
    _app.executeTextCommand(txtCmd)

    actSels.clear()
    st = time.time()

    for b in bs:
        actSels.add(b)

    t = time.time() -st

    print('{} -> {}s'.format(txtCmd, t))

アクティブな状態のファイルのボディを全て選択するだけです。
5回づつ繰り返し、その時間を出力してます。

画像を出せないのですが、ボディ1100枚ぐらいのデータで実行した結果がこちら

Options.SelectionEffect /normal -> 2.3836963176727295s
Options.SelectionEffect /normal -> 2.2319092750549316s
Options.SelectionEffect /normal -> 2.222975492477417s
Options.SelectionEffect /normal -> 2.2937395572662354s
Options.SelectionEffect /normal -> 2.2179768085479736s
-----
Options.SelectionEffect /fast -> 2.186089515686035s
Options.SelectionEffect /fast -> 2.3617985248565674s
Options.SelectionEffect /fast -> 2.2748820781707764s
Options.SelectionEffect /fast -> 2.2282962799072266s
Options.SelectionEffect /fast -> 2.25492262840271s
-----
Options.SelectionEffect /simplified -> 2.2594237327575684s
Options.SelectionEffect /simplified -> 2.265821933746338s
Options.SelectionEffect /simplified -> 2.185194969177246s
Options.SelectionEffect /simplified -> 2.2250354290008545s
Options.SelectionEffect /simplified -> 2.200432062149048s
-----
Options.SelectionEffect /no -> 2.2181003093719482s
Options.SelectionEffect /no -> 2.25087571144104s
Options.SelectionEffect /no -> 2.2310099601745605s
Options.SelectionEffect /no -> 2.2421138286590576s
Options.SelectionEffect /no -> 2.1832878589630127s
-----
Options.SelectionEffect /degraded -> 2.1895904541015625s
Options.SelectionEffect /degraded -> 2.3406481742858887s
Options.SelectionEffect /degraded -> 2.218195676803589s
Options.SelectionEffect /degraded -> 2.3040049076080322s
Options.SelectionEffect /degraded -> 2.2557156085968018s
-----

効果無さそうです・・・。
使い方は間違っていると思わないんですけどね。

続Fusion360 Ver2.0.8335

こちらの続きです。
Fusion360 Ver2.0.8335 - C#ATIA

気になってしょうがない "executeTextCommand" メソッド。 試したところ
エラーになりました。(コメント欄に記載)

APIのフォーラムに投げた所、エラーにならないとの事。
と言うことは、エンコードが原因だろうと試したところエラーになりませんでした。
Solved: Re: executeTextCommand errer - Autodesk Community
と言うことは使えます。恐ろしいことに。


こちらのアセンブリコンテキストついてのトピですが、
アセンブリコンテキストの役割について - Autodesk Community
説明書きのあるサイトを何となく読んでいましたが、
ふーん、ん? と言った感じでぼんやりとしか理解出来ませんでしたが
足立さんが丁寧に説明してくれています。

"ふーん" となった最大の理由は、「EIP」や「XREF」と言う用語が突然当たり前のように
使われ説明されていたからなんです。


ちょっと前なのですが、Help付きテキストコマンドの全リストの取得方法が分かったのでこちらに
こっそりコマンドリストをUpしています。
Fusion360_Small_Tools_for_Developers/TextCommands_txt_Ver2_0_8176.txt at master · kantoku-code/Fusion360_Small_Tools_for_Developers · GitHub
Ver2_0_8176の時のリストです。
アセンブリコンテキストはVer2.0.8335からリリースされた機能なんですが、
が、画面上に出てきたのはVer2.0.8335からリリースされてからなのですが
コマンドリストを「EIP」や「XREF」で検索するとヒットします。
リリース前のリストにも拘らず・・・。


今となってはリリース前でコマンドが機能していたかどうかはわかりませんが、
テキストコマンドにはリリース前のコマンドが実装されているものがあるようです。

他にも気になるのは「FusionDoc」はコメントを見ている限りDraw関連の様なのですが
チラチラとAutoCADの文字が見つかります。(AutoCADのコマンドを実行する等)
将来的にはAutoCADの機能も移植されるような気がします。

複数面に線を描きたい6

こちらの続きです。
複数面に線を描きたい5 - C#ATIA

良い方法が思い付かない為、素直に平面で交差を作り
その線を使うことにしました。・・・情けない。
f:id:kandennti:20200513134621p:plain
一本は2点間を繋ぐ3D直線で、もう一本は3D直線と共有している
エッジの最短距離となる部分のベクトルです。

#Fusion360API Python script

import adsk.core, adsk.fusion, traceback

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)

def run(context):
    try:
        global _app, _ui
        _app = adsk.core.Application.get()
        _ui = _app.userInterface
        des  :adsk.fusion.Design = _app.activeProduct
        root :adsk.fusion.Component = des.rootComponent

        # get body
        body :adsk.fusion.BRepBody = root.bRepBodies.item(0)

        # get face
        face1 :adsk.fusion.BRepFace = body.faces.item(0)
        face2 :adsk.fusion.BRepFace = body.faces.item(1)

        # 共有エッジ取得
        shareEdges = getShareEdges(face1, face2)

        # get point
        pnt1 :adsk.fusion.ConstructionPoint = root.constructionPoints.item(1)
        pnt2 :adsk.fusion.ConstructionPoint = root.constructionPoints.item(0)

        # 直線
        line :adsk.core.Line3D = adsk.core.Line3D.create(pnt1.geometry, pnt2.geometry)
        lineVec :adsk.core.Vector3D = pnt1.geometry.vectorTo(pnt2.geometry)

        # 共有エッジと直線の最短取得 - 本当は1本とは限らない
        mersRes :adsk.core.MeasureResults = getMinDist(line, shareEdges[0])
        mersVec :adsk.core.Vector3D = mersRes.positionOne.vectorTo(mersRes.positionTwo)

        # 平面
        plane :adsk.core.Plane = initPlane(mersRes.positionOne, mersVec, lineVec)

        # 交差取得
        crv1 = getCurve(pnt1.geometry, shareEdges[0], plane, face1)
        crv2 = getCurve(pnt2.geometry, shareEdges[0], plane, face2)

        # add sketch
        skt :adsk.fusion.Sketch = root.sketches.add(root.xYConstructionPlane)
        skt.arePointsShown = False

        # 面上に線を描く
        drawSketch(skt, [crv1, crv2])

    except:
        if _ui:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def drawSketch(
    skt :adsk.fusion.Sketch,
    crvs :list):

    skt.isComputeDeferred = True
    sktCrvs :adsk.fusion.SketchCurves = skt.sketchCurves

    for crv in crvs:
        if hasattr(crv,'asNurbsCurve'):
            crv = crv.asNurbsCurve

        if crv.objectType == 'adsk::core::NurbsCurve3D':
            sktElm = sktCrvs.sketchFittedSplines.addByNurbsCurve(crv)

        else:
            s = crv.startPoint
            e = crv.endPoint
            sktElm = sktCrvs.sketchLines.addByTwoPoints(s,e)

    skt.isComputeDeferred = False

def getCurve(
    pnt :adsk.core.Point3D,
    edge :adsk.fusion.BRepEdge,
    pln :adsk.core.Plane,
    face :adsk.fusion.BRepFace):

    crvs :adsk.core.ObjectCollection = getSection(pln, face)
    sect :adsk.core.Curve3D = crvs[0]

    sectEva :adsk.core.CurveEvaluator3D = sect.evaluator
    _, sPnt, ePnt = sectEva.getEndPoints()

    tgtPnts = [pnt]
    tgtPnts.append(sPnt if isOnCurve(edge,sPnt) else ePnt)

    _, prms = sectEva.getParametersAtPoints(tgtPnts)
    list(prms).sort()

    # return sect.extract(prms[0], prms[1]) # err!
    return sect

def isOnCurve(
    crv :adsk.fusion.BRepEdge,
    pnt :adsk.core.Point3D) -> bool:

        eva :adsk.core.CurveEvaluator3D = crv.evaluator
        _, sPrm, ePrm = eva.getParameterExtents()

        _, prm = eva.getParameterAtPoint(pnt)

        return True if sPrm <= prm <= ePrm else False

def getSection(
    pln :adsk.core.Plane,
    face :adsk.fusion.BRepFace) -> adsk.core.ObjectCollection:

    return pln.intersectWithSurface(face.geometry)

def initPlane(
    ori :adsk.core.Point3D,
    vec1 :adsk.core.Vector3D,
    vec2 :adsk.core.Vector3D) -> adsk.core.Plane:

    if vec1.isParallelTo(vec2):
        return None
    
    return adsk.core.Plane.createUsingDirections(ori,vec1,vec2)

def getMinDist(
    crv1 ,
    crv2) -> adsk.core.MeasureResults:

    meas = _app.measureManager
    return meas.measureMinimumDistance(crv1, crv2)

def getShareEdges(
    face1 :adsk.fusion.BRepFace,
    face2 :adsk.fusion.BRepFace) -> list:

    lps1 :adsk.fusion.BRepLoops = face1.loops
    lp1 :adsk.fusion.BRepLoop = lps1[0]
    cos1 = [co for co in lp1.coEdges if hasPartner(co)]

    lps2 :adsk.fusion.BRepLoops = face2.loops
    lp2 :adsk.fusion.BRepLoop = lps2[0]
    cos2 = [co.partner for co in lp2.coEdges if hasPartner(co)]

    # 共有エッジ取得
    rap = []
    for co1 in cos1:
        for co2 in cos2:
            if co1 == co2:
                rap.append(co1.edge)

    return rap

def hasPartner(
    coEdge :adsk.fusion.BRepCoEdge) -> bool:

    try:
        coEdge.partner
        return True
    except:
        return False

段々肥大化してきた・・・。

当然、手動で作ったのと一致しますが、2点間だけで欲しいので
外側が要らないんです。
f:id:kandennti:20200513134636p:plain

この辺にその処理を入れているのですが、エラーになっちゃいます。

     return sect.extract(prms[0], prms[1]) # err!

Nurbs曲線を指定したパラメータ間のみにしている部分なのですが
どうしてもエラーになる・・・。

スケッチに入れてからトリムするの嫌だなぁ。