C#ATIA

↑タイトル詐欺 主にCATIA V5 の VBA(最近はPMillマクロとFusion360APIが多い)

Dual Geodesic Icosahedra

こちらに楽しそうなネタが。
draw with a catia all the Dual Geodesic Icosahedra - DASSAULT: CATIA products - Eng-Tips

リンクされているHelpのページ・・・探しましたよ。
f:id:kandennti:20180619172750p:plain
GSDの真ん中ぐらいにありました。

何処にマクロがあるのかな? と思っていたらExcelにマクロが入ってました。
ご興味あれば是非。
(僕が使用しているPCでは動かないです)

'Dual Geodesic Icosahedra' を、マクロじゃなくてパラメータで
イロイロと変更出来るファイルが作れると、カッコイイと思うのですが。
これみたいに。
3D CAD Model Collection | GrabCAD Community Library

賛成

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

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