C#ATIA

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

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

先日、ちょこっとFusion360APIやった際に色々と調べていたら
発見したので記載。

よく探してみたら、同様のことをこちらでやってました…。
MouseMoveEvent サンプル - C#ATIA

前回やった際は、ディスプレイ上のマウスカーソル位置座標(左上が0,0)の取得しか
出来なかったのですが、今回はメソッドやプロパティが増えていたのでFusion360
ウィンドウ上の座標値が取得できました。

#FusionAPI_python
#Author-kantoku
#Description-MouseMoveTest
import adsk.core, traceback

_app = None
_ui  = None
_inputs  = None
_handlers = []

#このコマンドのイベント・ダイアログ作成
class MyCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            global _inputs, _handlers
            cmd = adsk.core.Command.cast(args.command)

            onDestroy = MyCommandDestroyHandler()
            cmd.destroy.add(onDestroy)
            _handlers.append(onDestroy)

            onMouseMove = MyMouseMoveHandler()
            cmd.mouseMove.add(onMouseMove)
            _handlers.append(onMouseMove)

            _inputs = cmd.commandInputs
            
            _inputs.addTextBoxCommandInput('Vp_Pos', 'Fusion360画面上の座標値', '-', 1, True)
            _inputs.addTextBoxCommandInput('Sc_Pos', 'ディスプレイ上の座標値', '-', 1, True)
        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)
        
        global _ui, _inputs
        vppos = eventArgs.viewportPosition
        ui_vp = _inputs.itemById('Vp_Pos')
        ui_vp.text = "x:[%d] y:[%d]"%(vppos.x, vppos.y)
        
        scrpos = eventArgs.viewport.viewToScreen(vppos)
        ui_sc = _inputs.itemById('Sc_Pos')
        ui_sc.text = "x:[%d] y:[%d]"%(scrpos.x, scrpos.y)
        
def run(context):
    try:
        global _app, _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)
        
        cmdDef.execute()

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

MouseMoveイベントからダイアログのテキストに座標値を書き込んでるのですが、
どうやったらダイアログを取得できるのだろう?と悩んだ末、[_inputs](ダイアログ自体)
のスコープを大きくするぐらいしか思いつかなかったのですが、前回の
コードを見たら・・・同じようにしてました。良いのか不安。

本当は、3Dの座標値が欲しかったのですが、上手くいきませんでした。
Help
こちらのViewportオブジェクトに 「viewToModelSpace」メソッドがあるのですが
例外にも引っかからないし…。
画面の奥行きに対しての座標値が定まらないので、無理なのはわかっているのですが。
cameraオブジェクトから計算したりするのかな?

DMUスペースアナリシスのセッション

「DMUスペースアナリシスのセッションを利用して、多くの断面を取得したい」
と御相談を頂きました。
が、当方にはDMUスペースアナリシスのライセンスが無い為、手も足も出ないのが
本音なのですが、過去にこちらを試した事があったため
出来る限りのことは記載しておきます。
技術的なオブジェクト?1 - C#ATIA

こんな感じのコードを作りました。

'vba
'DMUスペースアナリシスのセッションのテスト

Sub CATMain()
    'プロダクト
    Dim Prod As Product
    Set Prod = CATIA.ActiveDocument.Product
    
    'セクションコレクション
    Dim Sects As Object 'Sections
    Set Sects = Prod.GetTechnologicalObject("Sections")
    
    'セクション追加
    Call Sects.Add
    Dim Sect As Object 'Section
    Set Sect = Sects.Item(Sects.Count)
    
    'モード変更
    '0-catSectionBehaviorManual
    '1-catSectionBehaviorAutomatic
    '2-catSectionBehaviorFreeze
    Sect.Behavior = 1
    
    '0-without clipping  1-clipping
    Sect.CutMode = 1
    
    'マトリックス
    Dim Mat(11) As Variant ' Double
    Call Sect.GetPosition(Mat)
    
    Stop
    
    'マトリックス変更
    Mat(11) = Mat(11) + 1#
    Call Sect.SetPosition(Mat)

    'エクスポート
    '何処に何をエクスポートしているのか不明
    'Call Sec.Export

    Stop
End Sub

このマクロを実行すると、ライセンスが無いにも関わらずTree部分に
セクションが残ります。(使い道は無いのですが)
f:id:kandennti:20180516161755p:plain

Exportが全くの謎で、ライセンスがあれば可能だと思うのですが
手動操作も良くわかっていないため、この辺が限界です。

SetPositionで断面位置調節し、Exportをジャンジャン行えば良いの
だろうとは思うのですが・・・。

モデル再インポート機能

客先からデータが支給された、後に変更後のデータと差し替える
事になった場合、変更箇所を見付け出すためにこちらのマクロを
作りました。
二つのボディ/形状セットを比較して、差分を抽出する2 - C#ATIA

機能はしているのですが、やはり遅いです。(でも使ってます)

PowerMillには、取り込んだCADデータはファイル単位でツリーに
入りますが、コンテキストメニューの中に「モデル再インポート...」と言う
コマンドが有ります。
f:id:kandennti:20180510153343p:plain
元はレベル[General]だけの状態ですが、
f:id:kandennti:20180510153353p:plain
このコマンドで再インポートした際に変更箇所があった場合、
f:id:kandennti:20180510153400p:plain
PowerMillが "変更されているよ" と認識した部分だけが
[New components from ~] と言うセットに入った状態になります。
比較的処理も速いので、これを利用したら変更箇所を素早く
確認出来るのでは? と言う素人の安易な考えを実験してみました。

〇ファイル名の変更
形は単純に、四角のスケッチを作り押し出しました。
f:id:kandennti:20180510153418p:plain
これを [A_Part.CATPart] として保存します。

このファイルを修正しないで、そのまま
[B_Part.CATPart] として保存します。

続いてPowerMillに読み込みます。
まず [A_Part] を読み込み、続いて [B_Part] を再インポートします。
その結果は、
f:id:kandennti:20180510153435p:plain
正直に書くと、画像は使い回しです・・・。
ファイル名の違いは変更扱いにはならないです。

〇フューチャーを変える
[A_Part] のパッドを削除し、再度パッドします。
f:id:kandennti:20180510153448p:plain
ハイライトしていて見辛いのですが、名称が "パッド.1" から "パッド.2"
に変更されていますが、ジオメトリ的には変更はありません。
これを [C_Part.CATPart] として保存します。

続いてPowerMillに、[A_Part] → [C_Part] で再インポートします。
結果はこちら
f:id:kandennti:20180510153504p:plain
残念。"変更なし" として扱って欲しかったのですが、6面全てが
変更されたものとして判断されています。

〇形状を変える
[A_Part] のパッドの値を 20 → 30 に変更します。
f:id:kandennti:20180510153513p:plain
これを [D_Part.CATPart] として保存します。

続いてPowerMillに、[A_Part] → [D_Part] で再インポートします。
結果はこちら
f:id:kandennti:20180510153524p:plain
こちらは無事に変更されたと認識されましたが、6面全てです。
底面はジオメトリ的には変更無いのですが・・・。

〇形状を変更し、戻す
[A_Part] のパッドの値を 20 → 30 → 20 に変更し戻します。
これを [E_Part.CATPart] として保存します。

[A_Part] → [E_Part] で再インポートします。
どうせ画像は使いまわすので、もう言葉だけで。
結果は、6面全て変更となりました。


では、何を根拠に変更されたものと判断しているのでしょうか?
[A_Part] → [C_Part] ではジオメトリな変更は無いのですが、
変更されていると判断されているのは確かです。

ヒントはモデルのプロパティに有りました。
[A_Part] と [B_Part] のプロパティを見比べると、
f:id:kandennti:20180510153547p:plain
コンポーネント部分は面の名前だと思うのですが、順番こそ違いますが
一致しています。
このネーミングルールなのですが、CATIAのこんなマクロを作り
イミディエイトウィンドウに面の名前を書き出します。

'VBA using-'KCL0.0.12'
'指定Bodyから面を検索し名前をプリント  by Kantoku

Sub CATMain()
    Dim Doc As Document
    Set Doc = CATIA.ActiveDocument
    
    Dim msg As String
    msg = "Bodyを選択 : ESCキー 終了"
    
    Dim Bd As Body
    Set Bd = SelectItem(msg, Array("Body"))
    If IsNothing(Bd) Then Exit Sub
    
    Dim Sel As Selection
    Set Sel = Doc.Selection
    Sel.Add Bd
    Sel.Search "Topology.CGMFace,sel"
    
    Dim i As Long
    For i = 1 To Sel.Count2
        Debug.Print Sel.Item(i).value.Name
    Next
End Sub

[A_Part] で実行すると

Selection_RSur:(Face:(Brp:(Pad.1;2);None:();Cf11:());Pad.1_ResultOUT;Z0;G6258)
Selection_RSur:(Face:(Brp:(Pad.1;1);None:();Cf11:());Pad.1_ResultOUT;Z0;G6258)
Selection_RSur:(Face:(Brp:(Pad.1;0:(Brp:(Sketch.1;4)));None:();Cf11:());Pad.1_ResultOUT;Z0;G6258)
Selection_RSur:(Face:(Brp:(Pad.1;0:(Brp:(Sketch.1;3)));None:();Cf11:());Pad.1_ResultOUT;Z0;G6258)
Selection_RSur:(Face:(Brp:(Pad.1;0:(Brp:(Sketch.1;2)));None:();Cf11:());Pad.1_ResultOUT;Z0;G6258)
Selection_RSur:(Face:(Brp:(Pad.1;0:(Brp:(Sketch.1;1)));None:();Cf11:());Pad.1_ResultOUT;Z0;G6258)

" Pad.1 " は直感的に InternalName だろうとは感じますが、その後ろの
" _c453 " 等は、このマクロではわかりませんね。 何となく数値は
Factoryオブジェクトが作り出したフューチャー内全ての要素に割り当てられた、
通し番号のようなものの気はするのですが、確認する方法がわかりません。
(その番号が、ハッシュコードのような役割とか・・・)

[A_Part] と [C_Part] が変更されたと判断されるのは、InternalName が変わり
PowerMill内の面の名前が変わってしまうためだろうと思います。
(その他のテストも、そんな感じだろうと)
つまり、モデル再インポートでの変更判断は
形状で判断されずに、取り込まれた際の面の名前だけで判断されている
と思っていて良さそうです。最初の安易な考えは、無駄でした。

結論が出ているのですが、CATIA限定ではあまりにニッチな内容過ぎるので。

[A_Part] → [A_Igs]
[C_Part] → [C_Igs] (ジオメトリは同じ。CATPartでは変更と判断)
[D_Part] → [D_Igs] (ジオメトリが異なる。CATPartでは変更と判断)
とIgesでエクスポートし、同様に再インポートしてみます。

〇[A_Igs] → [C_Igs]
これはCATPartの時と異なり、変更無しと判断されます。

〇[A_Igs] → [D_Igs]
これもCATPartの時と異なり、変更無しと判断されます。
変更されていて欲しかったのですが・・・。

こちらも面の名前原因です。
f:id:kandennti:20180510153615p:plain
" ig " は恐らくIgesから来るのだろうと思います。
後ろの数値は、Igesのフォーマットの関係でしょう。
こちらのサイトがわかりやすいのですが
AFsoft WebSite (AFsoft.JP)
パラメータデータセクション部の説明にチラッと記載されている
ディレクトリエントリへの索引番号なのだろうと思います。
[A_Igs] → [D_Igs] で変更無しと判断されたのは
エンティティ数が変更無かった為、索引番号が変わらなかった
事が原因でしょう。きっと。

Igesの場合は面の名前を記載する部分が、ディレクトリエントリセクションに
あるのですが、書き出すCADと書き出さないCADがあるので
索引番号を利用したのだろうと思います。
(CATIAは書き出さない、Fusion360は書き出します)

CADデータのフォーマットが異なれば、面の名前が変わりますし
同じフォーマットでもCADによって書き出す順番が変わり、
インポート時の名前が変わってしまうため、形状的な変更の有無は
この機能では判断出来ないとわかりました。
(手元にある他フォーマットでは Step-st~ , Parasolid-@~ , Creo-rf_~)


今年一番長く書いたけど、当初の目論見は甘かった・・・。

ポストの修正

こちらにチラッと書いた続きです。
Autodesk CAM Challenge - C#ATIA
先日に引き続きポストの修正です。細々と。

〇小数点表記
元のポストでは座標値類が小数点で表記されていないのが
気に入らないんです。結構探しまくりました。
f:id:kandennti:20180509184025p:plain
Formatsのこの辺です。恐らくこの辺の名称は任意で決められる
ような気がするので、他人にはあまり役立たないとは思ってます。

〇実績稼働時間
以前こちらで記載した内容です。
加工時間の実績を取る - C#ATIA
ベタに書くのもおしゃれじゃない為、[User Commands] に
「Time_Reset」と「Time_Get」のコマンドを作ります。
[User Commands] ですが、何となくLisp等のマクロ展開みたいな
感じで便利ですね。

「Time_Reset」で時間をリセットします。ついでに使っていなかった
コモン変数#505を 0 にします。
f:id:kandennti:20180509184053p:plain
未だにアイテムの背景色の違いが、何を意味しているものか不明です・・・。

「Time_Get」では以前同様に、コモン変数#502~#504に時間を出力し
#505はカウンタとして+1します。
f:id:kandennti:20180509184104p:plain

「Time_Reset」は 「Program Start」に入れておきます。
f:id:kandennti:20180509184117p:plain
この背景色が黄土色は [Command] と言う事だけは覚えました。

「Time_Get」は 「Toolpath End」に入れます。
f:id:kandennti:20180509184128p:plain

この状態でポスト処理すると、

%
・・・
「Time_Reset」コマンド
・・・
1個目のツールパス
・・・
「Time_Get」コマンド
・・・
2個目のツールパス
・・・
「Time_Get」コマンド
・・・
・・・
M30
%

の形で出力されます。

今までの形では、最初に時間をリセットし最後に時間を書き出して
いたので、何も問題無い時は良かったのですが、
何らかのトラブルで機械を止めた際、
1)止めるまでの実績時間が全くわからない
2)何処で止めたのか? が、主軸に付いている工具からのカン
と言う状態でした。

「2」に関しては、"ブロックナンバーを付ければ良いじゃん"
と世間の皆様は思うと思うのですが、N88BASICっぽい上ファイルサイズ
が無駄に巨大になってしまう為付けてません。

これで止めた際でも、#505の番号の工程までは終了している事が
わかるため、リスタートする際の目安になります。
(特に同一工具で複数工程加工している場合でも、はっきりとわかるはず)

〇コメント類
社内の先任者の責任にするつもりは無いのですが、出力されたNCプログラムが
あまりにGコード過ぎるので、もう少し情報をコメント出力するように
しました。
f:id:kandennti:20180509184143p:plain
右が修正前で、左が修正後。 実際にNCプログラムを細かく見るわけではない
のですが、修正前のものは何処までが同一工具なのかすら、わからない・・・。


時間の書き出しは、サブプログラム化しておいてNCプログラムでは呼び出しだけに
するのも良いような気もしましたが、まぁとりあえずこのままで。

ある程度慣れれば「Manufacturing Post Processor Utility」も
ビジュアルエディタとでも呼びたくなるような機能満載なのは理解できました。
使いこなせるとは到底思えませんが・・・。

Autodesk CAM Challenge

Autodesk CAM Challenge と言うものを開催中だそうで。
#autodeskcamchallenge is HERE! - Manufacturing Lounge

何時もカヤの外のPowerMillでもOKらしいです。
商品の工具、全部インチサイズっぽい・・・。



前々からPowerMillのポストファイルがイマイチだったので、
修正しようと試み中。
純情じゃない程のわかり難さに、以前サポートでもらった
日本語の古い(Delcam時代の)PDFを全部印刷したものの
どうやって綴じ様か悩み中。

HTMLファイルからマクロの呼び出し

今のところ、PowerMillのマクロの呼び出しは
・リボンのボタン
・コンテキストメニュー
を用途に応じて行っているのですが、他の方法もある事は薄々知ってます。

こちらにサンプルが添付されているのですが
Discussion: Options to create PowerMILL automation - Autodesk Community
HTML + VBScript で行われています。

DotNetで開発する際もそうだったような気がするのですが、
PMill独自言語以外の場合は、結果的に[DoCommand]
[DoCommandEX] 等で文字列としてコマンドを渡し実行する事になり、
非常にデバッグが行いにくい為、不便です。
その為、リンク先のサンプルを元に、HTMLファイルは外部のマクロを
呼び出すだけのものを作ってみました。


1.実行されるマクロ
何でも良いのですが、こちらのマクロを呼び出すことにします。
水平面高さリストを表示2 - C#ATIA
これを仮に

C:/temp/Hoge.mac

と言うパスで保存しておきます。

2.呼び出し用HTMLファイル
添付されているサンプルを元にしたのですが、不要となる部分を
ごっそりそぎ落とし、最低限のボタン1個だけのものにしました。

<feff><HTML>
<HEAD>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</HEAD>
<BODY><feff> <feff> <feff> <feff>     
	<script language=vbscript>
	<!--
		Set PM=Window.External '必要
		
		Sub Button_CallMacro_Onclick
			PM.DoCommand("MACRO 'C:/temp/Hoge.mac'") 'マクロの呼び出し
			PM.DoCommand("SPLITTER TABEXPLORE") 'エクスプローラツリーの表示を戻す
		End Sub
	-->
	</script>
	
	<input id=Button_CallMacro type=button value="Call Macro">
</BODY>
</HTML>

これを

C:/temp/HTML_Call_Macro.html

として保存します。

3.HTMLファイルの表示ボタン
HTMLファイルをD&Dしても良いのですが面倒な為、ショートカットキーに割り当ててみます。
又、表示させる際は通常のブラウザで利用した場合、複数PMillを
起動していた際、任意のPMillではない方で実行される可能性が
あるため、こちらのPMill内のブラウザで表示させます。
f:id:kandennti:20180507185819p:plain
ショートカットの設定はこんな感じです。(空いていたので F7キー にしています)
f:id:kandennti:20180507185828p:plain
[Command]部分には

PMILLHELP URL 'C:\temp\HTML_Call_Macro.html'

と、しました。
PMill内のブラウザで表示させるのは、Helpファイルとして表示させるようですね。

HTMLファイルが非常に手抜きで、W3C仕様にも程遠いのですが、
・マクロ自体は単体として開発可能
・HTMLの表現力
のメリットが得られそうです。
(特にイロイロと設定・指定した上で実行するマクロにはメリット有りそう)

実際テストしたのは、こんな感じです。


デバッグ考えたらCATIAもVBAが一番楽だと思うんですけどね。
Pythonでやりたい方が、すごいの作ったみたいですが・・・。
GitHub - robertpardillo/rice: Python API to control Catia

PowerMill 言語判断

こちらを作った際、言語判断出来たら良いなぁ と感じました。
水平面高さリストを表示 - C#ATIA

フォーラムで質問しても回答がもらえないので、直接のパラメータ等は
無い様な気がします。
ので、イロイロ調べて何とか判断できるようになりました。多分。

//Powermill Language
//param : -
//return : lang(string)
function GetLang(output string lang) {
	string list lst = values(toolpath.strategy)
	switch trim($lst[0]) {
		case 'Nezn疥'
			//Czech & Slovak
			$lang = 'Czech'
			break
		case 'Unknown'
			$lang = 'English'
			break
		case 'Inconnu'
			$lang = 'French'
			break
		case 'Unbekannt'
			$lang = 'German'
			break
		case 'Ismeretlen'
			$lang = 'Hungarian'
			break
		case 'Sconosciuto'
			$lang = 'Italian'
			break
		case '不明'
			$lang = 'Japanese'
			break
		case 'セヒキチチ・セハタス'
			$lang = 'Korean'
			break
		case 'Nieznany'
			$lang = 'Polish'
			break
		case 'Desconhecido'
			$lang = 'Portuguese'
			break
		case 'ヘ裴鈔褥・'
			$lang = 'Russian'
			break
		case 'Desconocido'
			$lang = 'Spanish'
			break
		case 'Bilinmiyor'
			$lang = 'Turkish'
			break
		default
			$lang = 'Unknown'
			break
	}
}

values関数利用すると、設定されている言語毎の文字リストで戻り値が得られるようです。
上記のコードでは、 toolpath.strategy (ツールパスの種類のパラメータ)で判断しています。
但し、チェコスロバキアの違いが見つかりませんでした。
・・・元々一緒の国だからどっちの表示でも良いのだろう、きっと。

又、エンコードの問題が有り、マクロのファイルはShift-JISで作ってます。
Shift-JISに限らず、準じたものなら大丈夫な気もしますが、エンコード
常に頭の痛い問題です。


念の為、単体テストコードです。エコーコマンドに結果を表示させています。

//unit_test
Function Main( ){
	string list langs ={}
	call GetLangLst($langs)
	
	ECHO OFF DCPDEBUG UNTRACE COMMAND ACCEPT
	PRINT '********'
	PRINT '切り替えた言語/判断した言語/判定'
	string cmd = ''
	string msg = ''
	string res = ''
	 
	foreach lng in $langs {
		$cmd = 'LANG ' + $lng
		docommand $cmd
		string list $prms = values($toolpath.strategy)

		call GetLang($res)
		
		$msg =  $lng + ' / ' + $res+  ' / ' + string($lng == $res)
		PRINT $msg
		}
	}
	LANG japanese
 	ECHO ON DCPDEBUG TRACE COMMAND ACCEPT
}

//ユニットテスト用、言語リスト
//'Chinese-CHN','Chinese-TWN'はエラーになる
function GetLangLst(output string list out) {
	$out  = {'Czech','English','French','German','Hungarian','Italian','Japanese','Korean','Polish','Portuguese','Russian','Slovak','Spanish','Turkish'}
}

Chinese-CHNとChinese-TWNはエラーになる為、言語の切り替えが出来なさそうです。

ソフト起動時は、
起動 → ライセンス承認 → 設定ファイル読み込み → ユーザーマクロ実行 → オペレーションOK
のような流れになっていると思うのですが、設定ファイルを見つけて
判断できれば一番良いような・・・設定ファイルが何処にあるのかわかりません。