C#ATIA

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

賛成

これを読ませて頂きました。

VBA モジュールのプロシージャは呼び出し順に書く - t-hom’s diary

僕の場合、呼び出し順では無いのですが、エントリーポイント(最初に実行される関数)は、
先頭に書いてます。このブログの場合、CATIA・PowerMillではそんな形になってます。
Fusion360は勉強中なので最後にエントリーポイントになっていますが、何れ同じようにします。

アプリケーションの場合は、エントリーポイント自体にあまり意味が無い(と、までは言わないのですが)
ものですが、マクロの場合は大半が一方通行な処理な為、全体的に上から下に流れていくのが
自然に思えます。

僕自身としては、まず全体がどんな処理をするのかを把握したいのが本音です。
それをエントリーポイントに書くようにし、簡単なコメントを入れています。
必要なデータを用意し関数に投げて、それを受け取る。この繰り返し
をエントリーポイントで行うように自然になりました。
(エントリーポイント内ではループは基本しない(ありえない)ですし、IFは関数の戻り値で
 キャンセルするかどうか? の判断ぐらいにしています。)

エントリーポイントは森で、関数が木ですかね、僕の中では。関数が内部的にどんな処理を
するかはどうでも良くて、投げるものがわかっていて戻ってくるものが何か?
さえわかっていれば、あまり気にしていないです。(本音は気になります)

エントリーポイントを最後に記載する習慣は、確かC言語だったような気がします。
先に必要なものを定義してから実行しないと、未定義だよって扱いだったような。
コンパイラの力ですかね。
PowerMillの場合、ライブラリ類のインポートを最後に書いてもOKなんですよね。

もっと早い時間に書こうと思っていたのですが、t-hom’sさんのところコメント出来ないんですよね。
(酔っぱらっていますので、適切な表現ではない部分有ると思いますが、ご了承ください)

ObjectCollectionオブジェクト

先週作っていたスプリクト、行き詰まり感からちょっと放置。
こちらに面白そうなテーマがあったので、取り組んでみました。
How to remove inner sketch lines from an intersection sketch? - Autodesk Community

空のスケッチを選択したらエラーとか、移動しているコンポーネントだと
違う位置に出来上がる(未テスト)とか、駄目な部分は多々あるのですが
動くのでUpしたのですが、前から気になっていた事が
少しわかったので覚書です。

Fusion360APIには、ObjectCollectionオブジェクトと言うのがあるんです。
Help

オブジェクトを格納するものなのですが、Pythonの場合はリスト・セット・タプルがあり、
大したメソッドも無いので態々使う利用も無いかな? と思っていました。

今まで、面やボディを作るようなスプリクトを作った事が無かったのですが
今回はパッチサーフェスを作ってます。この辺です。
Help

returnValue = patchFeatures_var.createInput(boundaryCurve, operation)

このパラメータの "boundaryCurve" に幾つかのタイプのオブジェクトを渡すことが
出来るようなのですが、今回はプロファイルが複数入ったObjectCollection
オブジェクトを渡しています。
手動で操作した時、パッチコマンドでは1回のコマンドで複数のプロファイルを
指定出来ます。要は手動もスプリクトも同じような仕様なんですね。

但し、ここがリスト等じゃダメなんです。ObjectCollectionオブジェクトの
存在理由がやっとわかりました。
…リストとかの方が、遥かに便利なんですけどね。

flatten関数

こちらでチョロッと書いたのですが、覚書です。
Re: Need help with macro to check through NC programs - Autodesk Community
肝になるのは、複数あるNCプログラム内で登録されているツールパスを全て
取得する方法です。

以前、書きましたがPMillのマクロでは、ネストしたList(List内にList)を作る事が
出来ません。(イロイロ試したつもりですが、やっぱり作れません)
複数のツールパスが入っているNCプログラムの場合、ツールパスのリストを取得するには
こんな感じです。(NCプログラム名が 'hoge' の場合)

	object list nc_tps_obj = entity('ncprogram', 'hoge').nctoolpath

nctoolpathメソッド? は、Helpにも記載されていない気がしてます。

デバッガで見るとこんな状態です。
f:id:kandennti:20180611104321p:plain
MAP型は、object型で(大体)取得できます。
でも、これは1つのNCプログラム分なので全てのプログラムのツールパスを取得したい場合
[entity] List([object] List)の状態のネストしたListの形になるため、
直接変数で受け取ることが出来ません。

そこでflatten関数の登場です。
他の言語同様、flatten関数はネストしたListを1次のList化するものです。
その為、全てのNCプログラムの全てのツールパス名を取得する為には

	string list nc_tps = extract(flatten(extract(folder('ncprogram'), 'nctoolpath')), 'name')

の1行で出来ます。
extractがネストしてたりするので、わかりにくいのですけどね。

import neu_dev; neu_dev.list_functions()

Fusion360にテキストコマンドがあるのを知りませんでした。
Fusion 360:テキスト コマンド - Technology Perspective from Japan
でも、何か役に立つかな…。
どちらかと言うと、実際に操作したログを吐き出して見れると
ありがたいのですが、今のところそれらしきファイルも見当たりません。
(サーバーとのやり取りのログファイルは何処かにありました)

今回のタイトルは、”ヘルプを参照するには~” の後に記載されているコマンドです。
薄すぎて見えなかったので。

マウスカーソルの座標値を取得する6

こちらの続きです。
マウスカーソルの座標値を取得する5 - C#ATIA

取り組んでいる時間が無さそうなので、現状のものを公開しておきます。
そこそこ機能するはずです。

#FusionAPI_python
#Author-kantoku
#Description-MouseMoveTest ver0.0.3

import adsk.core, adsk.fusion, traceback
import math

_ui  = None
_handlers = []
_faces = []
_covunit = 0
_defLenUnit = ''
_draftVec = None

#選択面変更
class MyCommandInputChangedHandler(adsk.core.InputChangedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            eventArgs = adsk.core.InputChangedEventArgs.cast(args)
    
            if eventArgs.input.id != 'DraftVec':
                return
                
            selectionInput = adsk.core.SelectionCommandInput.cast(eventArgs.input)
            print(selectionInput.selectionCount)
            
            global _draftVec
            if selectionInput.selectionCount == 0 :
                app = adsk.core.Application.get()
                des = adsk.fusion.Design.cast(app.activeProduct)
                comp = des.rootComponent
                vecZ = comp.zConstructionAxis.geometry.direction
                _draftVec = vecZ
                return
            
            ent = selectionInput.selection(0).entity
            vec = None
            if ent.classType() == 'adsk::fusion::SketchLine':
                vec = ent.worldGeometry.asInfiniteLine().direction
            _draftVec = vec
            
        except:
            global _ui
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

#このコマンドのイベント・ダイアログ作成
class MyCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            global _handlers
            cmd = adsk.core.Command.cast(args.command)
            
            #破棄
            onDestroy = MyCommandDestroyHandler()
            cmd.destroy.add(onDestroy)
            _handlers.append(onDestroy)
            
            #勾配向き
            onInputChanged = MyCommandInputChangedHandler()
            cmd.inputChanged.add(onInputChanged)
            _handlers.append(onInputChanged)
            
            #マウス
            onMouseMove = MyMouseMoveHandler()
            cmd.mouseMove.add(onMouseMove)
            _handlers.append(onMouseMove)
            
            inputs = cmd.commandInputs
            
            inputs.addTextBoxCommandInput('Pos', 'マウス3D座標値 :', 'Non!', 1, True)
            inputs.addTextBoxCommandInput('MinR', '最小R :', '-', 1, True)
            inputs.addTextBoxCommandInput('Ang', '角度 :', '-', 1, True)
            
            selInput = inputs.addSelectionInput('DraftVec', '角度基準', 'DraftVec')
            selInput.setSelectionLimits(0,1)

            selInput.selectionfilters = ['SketchLines']
            
        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
            
#このコマンドの破棄
class MyCommandDestroyHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            adsk.terminate()
        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

#MouseMoveイベント
class MyMouseMoveHandler(adsk.core.MouseEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.MouseEventArgs.cast(args)
        cmd = eventArgs.firingEvent.sender
        inputs = cmd.commandInputs
        
        #ビューポイント
        vp = eventArgs.viewport

        pos_ui = inputs.itemById('Pos')
        minr_ui = inputs.itemById('MinR')
        ang_ui = inputs.itemById('Ang')

        #マウス3D座標値
        onface = OnFace(vp, eventArgs.viewportPosition)
        if onface == None:
            pos_ui.text = 'Non!'
            minr_ui.text = '-'
            ang_ui.text = '-'
            return
        else:
            pos_ui.text = 'x:{:.3f} y:{:.3f} z:{:.3f}'.format(
                onface[1].x * _covunit,
                onface[1].y * _covunit,
                onface[1].z * _covunit)
                
        #法線
        ang_ui.text = '{:.3f} deg'.format(math.degrees(GetNomal(onface[0], onface[1])))
        
        #曲率
        cture = GetCurvature(onface[0], onface[1])
        if cture == None:
            minr_ui.text = '-'
        else:
            if round(cture,4) != 0 :
                crv_info = '{:.3f}{}'.format(
                        1 / cture * _covunit * -1, 
                        _defLenUnit)
                minr_ui.text = crv_info
            else:
                minr_ui.text = '-'

#法線
def GetNomal(face, pnt):
    eva = face.evaluator
    res, pram = eva.getParameterAtPoint(pnt)
    returnValue, normal = eva.getNormalAtPoint(pnt)
    ang = _draftVec.angleTo(normal)
    
    if ang > math.pi * 0.5 :
        ang = (math.pi * -1) + ang
    return ang
    
#曲率
def GetCurvature(face, pnt):
    if face.geometry.classType() == 'adsk::core::Plane':
        return None
    
    eva = face.evaluator
    res, pram = eva.getParameterAtPoint(pnt)
    returnValue, maxTangent, maxCurvature, minCurvature = eva.getCurvature(pram)
    
    if returnValue:
        return maxCurvature
    return None

#マウスカーソルの3D取得
def OnFace(vp, vp_pos):
    d3pos = vp.viewToModelSpace(vp_pos)
    cam = vp.camera
    vec = cam.eye.vectorTo(cam.target)
    
    pnt = adsk.core.Point3D.create(
        d3pos.x + vec.x, 
        d3pos.y + vec.y, 
        d3pos.z + vec.z)
        
    mouse3d = adsk.core.Line3D.create(d3pos, pnt).asInfiniteLine()    
    
    ints = [(face, mouse3d.intersectWithSurface(geo))
            for (face, geo) in _faces if mouse3d.intersectWithSurface(geo).count > 0]
    
    ints = [(face, p) 
            for (face, pnts) in ints
            for p in pnts]

    ints = [(face, p, face.evaluator.getParameterAtPoint(p))
            for (face, p) in ints]
    
    ints = [(face, p, prm)
            for (face, p, (res, prm)) in ints if res]
    
    ints = [(face, p, prm)
            for (face, p, prm) in ints]
            
    ints = [(face, p, cam.eye.distanceTo(p))
            for (face, p, prm) in ints if face.evaluator.isParameterOnFace(prm)]
    
    if len(ints) < 1:
        return None
    
    return min(ints, key = (lambda x: x[2]))
    
def run(context):
    try:
        global _ui
        app = adsk.core.Application.get()
        _ui = app.userInterface
                
        cmdDef = _ui.commandDefinitions.itemById('Mouse_Move_Test')
        if not cmdDef:
            cmdDef = _ui.commandDefinitions.addButtonDefinition(
                'Mouse_Move_Test', 
                'Mouse_Move_Test', 
                'Mouse_Move_Test')

        onCommandCreated = MyCommandCreatedHandler()
        cmdDef.commandCreated.add(onCommandCreated)
        _handlers.append(onCommandCreated)
        
        des = adsk.fusion.Design.cast(app.activeProduct)
        
        #全サーフェス取得
        global _faces
        _faces = [(face, face.geometry)
            for comp in des.allComponents if comp.isBodiesFolderLightBulbOn
            for bBody in comp.bRepBodies if bBody.isLightBulbOn & bBody.isVisible
            for face in bBody.faces]

        #単位準備
        global _covunit, _defLenUnit
        unitsMgr = des.unitsManager
        _defLenUnit = unitsMgr.defaultLengthUnits
        _covunit = unitsMgr.convert(1, unitsMgr.internalUnits, _defLenUnit)
        
        #角度基準
        global _draftVec
        comp = des.rootComponent
        _draftVec = comp.zConstructionAxis.geometry.direction

        cmdDef.execute()

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

スプリクト実行した際、こんなダイアログが出現します。
f:id:kandennti:20180608122844p:plain
マウス3D座標値:マウスカーソル位置の3D座標です。面の上になっていない場合は表示されません。
 又、未テストですがコンポーネントを移動・回転させている場合は、正しい位置を表示しません。
最小R:凸をプラス 凹をマイナスでRサイズを表示します。
角度:"角度基準" 未指定の場合、ルートコンポーネントのZ軸を元に角度を表示します。
 水平を0° 垂直を90° とし、アンダーカット部分ではマイナスで表示します。
角度基準:"角度" を表示させる際の基準となる向きを指定できます。
 現状はスケッチの直線のみです。(ここが現在のお悩み)

マウスカーソルの座標値を取得する5

こちらの続きです。
マウスカーソルの座標値を取得する4 - C#ATIA

前回上手くいかなかった原因が、単にチェックすべきベクトルを間違えていた
と言う、お粗末な内容でした・・・。(Fusion360は正しいです)

現状、ルートコンポーネントのZ軸に対しての角度は望んだ状態で
取得できる様になっているのですが、皆が全てZ軸で調べたいとは思えないので
任意の方向で測定できるようにしたいです。

その為、方向を選択できるように SelectionCommandInput を設置します。
おなじみの矢印のこれです。
f:id:kandennti:20180606191010p:plain
これ、便利なのですがイベントで悩みました。

選択された状態(又は全く選択されていない状態)をイベントで掴みたいのですが
InputChangedEventで行うべきなのか? SelectionEventで行うべきなのか?
が良くわかりませんでした。
わからないと悩んでいても仕方ないので、両方を試したのですが
要らないイベントばかり発生し、肝心なものにたどり着かない状況になり
途方に暮れていたのですが、こちらに答えが記載されていました。
Solved: how to use the SelectionEventHandler - Autodesk Community
InputChangedEventArgsで拾って、inputを SelectionCommandInput に
キャストするんですね。(僕も一ヶ月後これを見ても、何書いているのか理解できない)

もう2個ぐらい機能付ければ、公開したいな。
小原さんがアイデアに記載した、これを解決したい。
検査ー勾配解析、曲率マップ解析等でマウスを乗せたところに詳細な値を表示してほしい。 - Autodesk Community

マウスカーソルの座標値を取得する4

こちらの続きです。
マウスカーソルの座標値を取得する3 - C#ATIA
3D空間でマウスの位置が取得できるようになったので、マウスの位置で
曲率と勾配角度を動的に取得したいです。そう、CATIAのオンザフライの機能を
実現したいと細々とやってます。
テストモデルはこのような感じで、勾配解析を行った状態です。
f:id:kandennti:20180606104408p:plain
勾配角度はZ軸(青い線)方向とします。紫色は垂直面で、青緑?色が水平面です。
最小R(曲率の逆数)は無事に取得できているようなのですが、
勾配が上手く取得出来ていません。

    app = adsk.core.Application.get()
    des = adsk.fusion.Design.cast(app.activeProduct)
    comp = des.rootComponent
    
    #pnt - サーフェス上の任意の点
    #maxTangent - サーフェス上の任意の点の法線3Dベクトル
    axisZ = comp.zConstructionAxis.geometry
    tang = adsk.core.InfiniteLine3D.create(pnt, maxTangent)
    measMng = app.measureManager
    ang = measMng.measureAngle(axisZ, tang).value

比較的最近実装されたmeasureManagerオブジェクトを利用し、
2つの無限直線の角度を求めています。
結果はこちら
f:id:kandennti:20180606104420p:plain
左が水平面で右が垂直面です。 両方90°です…。
R面を測定していても水平に近い部分で90°弱になり、
垂直に近い部分で0°強になる為、個人的な直感とは逆です。
又、アンダーカットとなる部分ではマイナスの値で返ってきて
欲しいのですが、全てプラスです。(atan関数なんだろうなぁ)

よく見たらVecter3Dオブジェクトに2つのベクトル間の角度を求める
メソッドがあった為、コードを修正。

    ・・・
    vecZ = comp.zConstructionAxis.geometry.direction
    ang = vecZ.angleTo(maxTangent)

こちらの方が、はるかにコードが短かったです。
でも結果は同じです。

真面目に計算しなきゃなら無そうなので、悩み中です。