C#ATIA

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

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

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

前回はマウスカーソルの3D位置は正しい値が取得できていませんでしたが、
今回は正しい値が取得出来ているはずです。

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

import adsk.core, adsk.fusion, traceback

_ui  = None
_handlers = []
_faces = []
_covunit = 0

#このコマンドのイベント・ダイアログ作成
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)

            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)
            inputs.addTextBoxCommandInput('3D_Pos', 'マウス3D仮座標値', '-', 1, True)
            inputs.addTextBoxCommandInput('Cam_Vec', 'カメラ向き(単位ベクトル)', '-', 1, True)
            inputs.addTextBoxCommandInput('Hit', 'マウス3D座標値', 'Non!', 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)
        cmd = eventArgs.firingEvent.sender
        inputs = cmd.commandInputs
        
        #ビューポイント
        vp = eventArgs.viewport
        
        #Fusion360画面上の座標値
        vppos = eventArgs.viewportPosition
        ui_vp = inputs.itemById('Vp_Pos')
        ui_vp.text = 'x:[%d] y:[%d]'%(vppos.x, vppos.y)
        
        #ディスプレイ上の座標値
        scrpos = vp.viewToScreen(vppos)
        ui_sc = inputs.itemById('Sc_Pos')
        ui_sc.text = 'x:[%d] y:[%d]'%(scrpos.x, scrpos.y)
        
        #マウス3D仮座標値-奥行きがNGな為正しくない(通過点)
        d3pos = vp.viewToModelSpace(vppos)
        d3_sc = inputs.itemById('3D_Pos')
        d3_sc.text = 'x:{:.3f} y:{:.3f} z:{:.3f}'.format(
            d3pos.x * _covunit, 
            d3pos.y * _covunit, 
            d3pos.z * _covunit)
        
        #カメラ向き(単位ベクトル)
        cam = vp.camera
        vec = cam.eye.vectorTo(cam.target)
        vec.normalize()
        vec_sc = inputs.itemById('Cam_Vec')
        vec_sc.text = 'x:{:.3f} y:{:.3f} z:{:.3f}'.format(vec.x, vec.y, vec.z)

        #マウス3D座標値
        hit_sc = inputs.itemById('Hit')
        onface = OnFace(vp, eventArgs.viewportPosition)
        if onface == None:
            hit_sc.text = 'Non!'
        else:
            hit_sc.text = 'x:{:.3f} y:{:.3f} z:{:.3f}'.format(
                onface[1].x * _covunit,
                onface[1].y * _covunit,
                onface[1].z * _covunit)

#マウスカーソルの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:
        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
        unitsMgr = des.unitsManager
        defLenUnit = unitsMgr.defaultLengthUnits
        _covunit = unitsMgr.convert(1, unitsMgr.internalUnits, defLenUnit)


        cmdDef.execute()

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

コードが汚い。特にOnFace関数。
スプリクト実行後、ダイアログが出ますが一番下の項目が正確であろう3D座標値です。
マウスカーソルが面の上に無い時は "Non!" と表示されています。

確認しやすそうな数値でBodyを作り、マウスを動かし
確認してみました。

動画の最後の方ですが、スプリクトを中止しマウスを動かしているのですが
(要は何のコマンドにも入っていない状態です)、マウスカーソル下の
面がハイライト状態になります。(スプリクトではなりません)
・・・と言うことはFusion360自体でどの面の上にマウスカーソルが
認識しているのではないかな? と感じているのですが・・・。
現状、ファイル内の全ての面との交差を取得し、複数交差した点と
カメラとの距離を測定し、最短となった点の座標値を取得し表示させています。
効率が非常に悪いんです。どうしたらこの面を取得できるのだろう???

全ての面を取得する

細々とFusion360APIも挑戦しているのですが、
イマイチFusion360APIも理解できていない上に、Pythonも理解できていないです。

考えている処理で、全ての面を取得したいのですが出来るだけ
素早く取得したいです。 ドキュメントから辿って行く事になるのですが
結構深い位置にあります。大体こんな感じですね。

Application
 L Product
   L Component
     L bRepBody
          L bRepFace

リスト? コレクション? 類は省いているので、正しくは無いのですが…。
実際にはComponentやBodyが複数あるので、良くあるListのflattenに
近い感覚なのですが、ん~思うようには書けませんでした。


先日のPowerMill同様、5000枚程面のあるプロジェクトで幾つかの書き方で
速度を比較してみました。

import adsk.core, adsk.fusion, traceback

def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        
        des = adsk.fusion.Design.cast(app.activeProduct)
        import time
        testtimes = []
        
        #test1
        faces = []
        s = time.time()
        for comp in des.allComponents:
            for bBody in comp.bRepBodies:
                for face in bBody.faces:
                    faces.append(face)
        t = time.time()
        testtimes.append('test1 : All Face Count {} : time {:.2f}s'.format(len(faces),t - s))
        
        #test2
        faces = []
        s = time.time()
        faces = [face
                    for comp in des.allComponents
                    for bBody in comp.bRepBodies
                    for face in bBody.faces]
        t = time.time()
        testtimes.append('test2 : All Face Count {} : time {:.2f}s'.format(len(faces),t - s))
        
        #test3
        faces = []
        from itertools import chain
        s = time.time()
        bBodies = [comp.bRepBodies for comp in des.allComponents]
        faceslst = [bBody.faces for bBody in list(chain.from_iterable(bBodies))]
        faces = list(chain.from_iterable(faceslst))
        t = time.time()
        testtimes.append('test3 : All Face Count {} : time {:.2f}s'.format(len(faces),t - s))
        
        #test4
        faces = []
        s = time.time()
        faces = list(chain.from_iterable(
                [bBody.faces for bBody in 
                list(chain.from_iterable(
                [comp.bRepBodies for comp in des.allComponents]))]))
        t = time.time()
        testtimes.append('test4 : All Face Count {} : time {:.2f}s'.format(len(faces),t - s))
        ui.messageBox('\n'.join(testtimes))
        
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

test1 : for を利用した無難な?書き方。
test2 : Pythonならではの内包表記。
test3 : イテレ-タと内包表記の組み合わせ。
test4 : test3をムリムリ一行に書いたもの。

結果はこんな感じなのですが、
f:id:kandennti:20180603005908p:plain
イマイチ結果が安定しないです。
test2~4のどれが一番速いのか? が良くわからないのですが
test1だけではなさそうです。

デフォルト [コンポーネント/残り代] と異なる設定のツールパス名を取得3

こちらの続きです。
デフォルト [コンポーネント/残り代] と異なる設定のツールパス名を取得2 - C#ATIA

イロイロと探していた末、目的のコンポーネント数を取得する関数を発見しました。
f:id:kandennti:20180601155707p:plain
しかもやりたかった事がそのまま例題に・・・。

念の為お伝えしておくと

 object obj = model_components_thicknessset(entity('toolpath', '1'), 'surfaces', 0)

の様にしているとPMill落ちます。(恐らくマクロでは扱えない型で返って来るのでしょう)
sizeで利用するしか方法は無さそうです。

又、前回のマクロですがコンポーネント数をチェックしていたにも関わらず、正しく処理
されていませんでした。

//pm2018 macro
//Find_un_match_ToolPath.mac ver0.0.3
//デフォルト [コンポーネント/残り代] と異なる設定のツールパス名取得
//---------------------------------------------------------------------------------------------------
//ver0.0.1-完成
//ver0.0.2-コンポーネントチェック追加
//ver0.0.3-model_components_thicknessset関数使用
//               -GetCompMatchList関数 不具合修正
//---------------------------------------------------------------------------------------------------

function main() {
	//debug mode
	bool debug = 1 
	
	//check
	call CanExec()
	
	//start
	call Sw_Start()
	graphics lock

	INFOBOX NEW "***** Toolpath ThicknessSetValues info *****"
	INFOBOX CLEAR FORMAT
	
	//defalt ThicknessSetValues
	call PushInfobox('Getting Surface Defaults Info.' + crlf)
	string list def_tvInfo = {}
	int list def_cmp = {}
	call Get_defalt_ThicknessSetValuesInfo($def_tvInfo, $def_cmp)
	
	//filter
	call PushInfobox('Getting Toolpath Surface Parameter.' + crlf)
	string filter = ''
	call Initfilter($def_tvInfo, $filter)
	if $debug == 1 {
		call WriteFile(macro_path(false) + '\filter.txt' , $filter)
	}
	
	//pram match toolpath
	call PushInfobox('Getting Parameter match toolpath.' + crlf)
	entity list tps = folder('toolpath')
	$tps =  filter($tps , $filter)
	if $debug == 1 {
		call WriteFile(macro_path(false) + '\Parammatch.txt' , join(extract($tps, 'name'), crlf))
	}
	
	//components match toolpath
	call PushInfobox('Getting Components match toolpath.' + crlf)
	call GetCompMatchList($tps, $def_cmp, $tps)
	
	//un match
	call PushInfobox('Getting un match Toolpath.' + crlf + crlf)
	string list all_tps =  extract(folder('toolpath'), 'name') 
	string list match_tps =  extract($tps, 'name') 
	string list unmatch_tps =  subtract($all_tps, $match_tps)
	
	call PushInfobox('デフォルト [コンポーネント/残り代] と異なる設定のツールパスは以下のものです' + crlf)
	//call PushInfobox("It's toolpath that un match the 'Surface Defaults' values." + crlf)
	call PushInfobox(join($unmatch_tps, crlf))
	
	//finish
	if $debug == 1 {
		string tm = ''
		call Sw_GetTime($tm)
		call PushInfobox(crlf + $tm)
	}
	message info  'Done'
}

//start check
function CanExec() {
	string list msg = {}
	int dmy = 0
	//toolpath
	if size(folder('toolpath')) <1 {
		 $dmy = add_last($msg, 'There is no toolpath to check.')
	}
	//model
	if size(folder('model')) <1 {
		 $dmy = add_last($msg, 'The model has not been imported.')
	}
	if not is_empty($msg) {
		message warn join($msg, crlf)
		macro abort
	}
}

//Match components
function GetCompMatchList(entity list tps, int list def_cmp, output entity list out) {
	int list rng = {}
	call GetRangeLst(0, size($tps), $rng)
	$rng = reverse($rng)
	int dmy = 0
	string def = join($def_cmp,',')
	string list removeLst = {}
	foreach idx in $rng {
		int list cmp = {}
		call GetToolpathCompsCount($tps[$idx], $cmp) 
		if join($cmp,',') != $def {
			$dmy = add_last($removeLst,$tps[$idx].name)
		}
	}
	
	if is_empty($removeLst) {
		$out = $tps
		return
	}

	string list txts = {}
	foreach tp_name in $removeLst {
		$dmy = add_last($txts, 'name != "' + $tp_name +'"')
	}
	$out = filter($tps, join($txts, ' and '))
}

//Toolpath components
function GetToolpathCompsCount(entity tp, output int list comps) {
	int list rng = {}
	call GetRangeLst(0, size($tp.ThicknessSetValues), $rng)
	
	$comps = {}
	int dmy = 0
	foreach idx in $rng {
		$dmy = add_last($comps,  size(model_components_thicknessset($tp, 'surfaces', $idx)))
	}
}

//filter
function Initfilter(string list tvs, output string out) {
	string list prms = {'Mode', 'UseAxialThickness', 'Thickness', 'AxialThickness'}
	int list prm_rng = {}
	call GetRangeLst(0, size($prms), $prm_rng)
	
	int list tv_rng = {}
	call GetRangeLst(0, size($tvs), $tv_rng)
	
	$out = ''
	string list tmp = {}
	int dmy = 0
	string value = ''
	foreach tv_idx in $tv_rng {
		string list info = tokens($tvs[$tv_idx], ',')
		foreach prm_idx in $prm_rng {
			if $prm_idx == 0 {
				$value = '"' + $info[$prm_idx] + '"'
			} else {
				$value = $info[$prm_idx]
			}
			$dmy = add_last($tmp, 'ThicknessSetValues[' + string($tv_idx) + '].' + $prms[$prm_idx] + 

' == ' + $value)
		}
	}
	$out = join($tmp, ' and ')
	print = $out
}

//defalt ThicknessSetValues info
function Get_defalt_ThicknessSetValuesInfo(output string list prms, output int list comps) {
	string dmy_tp = ''
	call InitDmyTP($dmy_tp)
	$prms = {}
	$comps = {}
	call GetThickSetValInfo(entity('toolpath', $dmy_tp), $prms, $comps)
	DELETE TOOLPATH  $dmy_tp
}

//ThicknessSetValues info
function GetThickSetValInfo(entity tp, output string list prms, output int list comps) {
	object list tvs = $tp.ThicknessSetValues
	
	int list rng = {}
	call GetRangeLst(0, size($tvs), $rng)
	
	//string list mode = extract($tvs, 'Mode') //NG
	bool list useax = extract($tvs, 'UseAxialThickness')
	real list thic = extract($tvs, 'Thickness')
	real list axthic = extract($tvs, 'AxialThickness')
	
	$prms = {}
	int dmy = 0
	foreach idx in $rng {
		object tv =  $tvs[$idx]
		$dmy = add_last($prms,  $tv.Mode + ',' + $useax[$idx] + ',' + $thic[$idx] + ',' + $axthic[$idx])
	}
	$comps = {}
	call GetToolpathCompsCount($tp, $comps)
}	

//dammy toolpath
function InitDmyTP(output string out) {
	$out = new_entity_name('toolpath')
	IMPORT TEMPLATE ENTITY TOOLPATH TMPLTSELECTORGUI 'Finishing/Constant-Z-Finishing.002.ptf'
	FORM CANCEL STRATEGYSELECTOR\nFORM TOOLPATHIMPORT
	EDIT TOOLPATH $out REAPPLYFROMGUI
	YES
	FORM ACCEPT SFConstZFinishing
}

//** Support Function **

//range
function GetRangeLst(int start, int count, output int list lst) {
	int num = 0
	$lst = {}
	do {
		int dmy = add_last($lst, $start + $num)
		$num = $num + 1
	} while $num < $count
}

//infobox
function PushInfobox(string msg) {
	INFOBOX STYLE "NORMAL"
	INFOBOX APPEND  $msg
}

//clock on
function Sw_Start() {
	CLOCK RESET QUIT
	CLOCK ON QUIT	
}

//clock off
function Sw_GetTime(output string out) {
	string $TraceFilePath = macro_path(false) + "\clock.txt"
	string list txts = {}
	ECHO OFF DCPDEBUG UNTRACE COMMAND ACCEPT
	TRACEFILE OPEN $TraceFilePath
	CLOCK OFF QUIT
	CLOCK PRINT QUIT
	TRACEFILE CLOSE
	ECHO ON DCPDEBUG TRACE COMMAND ACCEPT
	
	FILE OPEN $TraceFilePath FOR READ AS Input
	FILE READ $txts FROM Input
	FILE CLOSE Input
	DELETE FILE $TraceFilePath

	$out = $txts[0]
}

//WriteFile
function WriteFile(string path ,string s) {
	FILE OPEN $path FOR WRITE AS file
	FILE WRITE $s TO file
	FILE CLOSE file
}

前回のコードでの結果
f:id:kandennti:20180601155740p:plain
今回のコードでの結果
f:id:kandennti:20180601155750p:plain
体感的には、十分使ってみようかな?と思えるぐらいになりました。
もうちょっと速くなって欲しいのと、デフォルト値の取得のために
ダミーのツールパスを作っているのを止めたい。方法あるかな?

PMillマクロ速度比べ

今更ながら、PMillマクロコードの書き方の違いでの実行速度の違いを
比較してみました。

適当なツールパスを20本作り、
・tool.name
・Rapid.IncrementalStartZ
・Thickness
の3項目を抜き出します。

〇テスト1
全てのツールパスをforeachでグルグル回し、取得したパラメータを
String型に追加していきます。ベタベタなヤツです。

	foreach tp in folder('toolpath') {
		$toolname = $toolname + ',' + $tp.tool.name
		$rapid = $rapid + ',' + $tp.Rapid.IncrementalStartZ
		$thickness = $thickness + ',' + $tp.Thickness
	}

rapid と thickness は本来Real型なのですが、何故かString型にStringとして
追加出来ました。暗黙変換でしょうか? 危険ですね・・・。
確か、多くの言語はString型に追加された際には、既存の文字分+追加分
の領域を新たに確保しコピーした後に、追加分が入るような処理をしていた
ような気がしたので、安易に考えても効率悪そうです。

〇テスト2
foreachでグルグル回し、String型に追加するのではなく一旦Listに
確保し、最後にJoinします。

	foreach tp in folder('toolpath') {
		$dmy = add_last($toolnameLst, $tp.tool.name)
		$dmy = add_last($rapidLst, string($tp.Rapid.IncrementalStartZ))
		$dmy = add_last($thicknessLst, string($tp.Thickness))
	}
	string test2a = join($toolnameLst, ',')
	string test2b = join($rapidLst, ',')
	string test2c = join($thicknessLst, ',')

String型に追加しない分Joinの処理があるので、どちらが効率が良いものか?

〇テスト3
グルグルやるのを止め、関数を利用し狙ったパラメータをガツンと抜き出しJoinします。

	$toolname2  = join(extract(folder('toolpath'), 'tool.name'), ',')
	$rapid2  = join(extract(folder('toolpath'), 'Rapid.IncrementalStartZ'), ',')
	$thickness2  = join(extract(folder('toolpath'), 'Thickness'), ',')

2つの作業を1行で行うので、コード自体も短いです。


全体的にはこんな感じです。

//pm2018 macro

function main() {
	string tm = ''	
	string list msg = {}
	int dmy = 0

	//test1
	string toolname = ''
	string rapid = ''
	string thickness = ''

	call Sw_Start()
	foreach tp in folder('toolpath') {
		$toolname = $toolname + ',' + $tp.tool.name
		$rapid = $rapid + ',' + $tp.Rapid.IncrementalStartZ
		$thickness = $thickness + ',' + $tp.Thickness
	}
	call Sw_GetTime($tm)	
	$dmy = add_last($msg, 'test1 :' + $tm)
	
	//test2
	string list toolnameLst = {}
	string list rapidLst = {}
	string list thicknessLst = {}
	
	call Sw_Start()
	foreach tp in folder('toolpath') {
		$dmy = add_last($toolnameLst, $tp.tool.name)
		$dmy = add_last($rapidLst, string($tp.Rapid.IncrementalStartZ))
		$dmy = add_last($thicknessLst, string($tp.Thickness))
	}
	string test2a = join($toolnameLst, ',')
	string test2b = join($rapidLst, ',')
	string test2c = join($thicknessLst, ',')
	call Sw_GetTime($tm)	
	$dmy = add_last($msg, 'test2 :' + $tm)

	//test3
	string toolname2 = ''
	string rapid2  = ''
	string thickness2 = ''
	
	call Sw_Start()
	$toolname2  = join(extract(folder('toolpath'), 'tool.name'), ',')
	$rapid2  = join(extract(folder('toolpath'), 'Rapid.IncrementalStartZ'), ',')
	$thickness2  = join(extract(folder('toolpath'), 'Thickness'), ',')
	call Sw_GetTime($tm)	
	$dmy = add_last($msg, 'test3 :'+ $tm)
	
	message info  join($msg, crlf)
}

//clock on
function Sw_Start() {
	CLOCK RESET QUIT
	CLOCK ON QUIT	
}

//clock off
function Sw_GetTime(output string out) {
	string $TraceFilePath = macro_path(false) + "\clock.txt"
	string list txts = {}
	ECHO OFF DCPDEBUG UNTRACE COMMAND ACCEPT
	TRACEFILE OPEN $TraceFilePath
	CLOCK OFF QUIT
	CLOCK PRINT QUIT
	TRACEFILE CLOSE
	ECHO ON DCPDEBUG TRACE COMMAND ACCEPT
	
	FILE OPEN $TraceFilePath FOR READ AS Input
	FILE READ $txts FROM Input
	FILE CLOSE Input
	DELETE FILE $TraceFilePath

	$out = $txts[0]
}

f:id:kandennti:20180530172816p:plain

圧倒的です。

過去にこちらを書きましたが、
PowerMillマクロを高速化? - C#ATIA
個人的には

GRAPHICS LOCK
GRAPHICS UKLOCK

は、ほぼ効果無いです。CATIAの様に面等を選択した際のハイライトを
止めてもらえる方が効果が有りそうな気もするのですが。
(大きな計算済みツールパスを、アクティブにしなければならない)

他にも、モデルを非表示、エクスプローラ-を非表示等試した事も有りますが
ほぼ効果がありません。

foreach piyo in hoge {
	if huga {
		dmy = add_last(lst, piyo.name)
	}
}

のような処理は

lst = extract(filter(hoge, huga), 'name')

のようなコードにした方が、圧倒的に速いです。(VBAにも欲しい)
これの逆で、同一パラメータに同一の値を設定する関数無いかなぁ。

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

こちらのh2さんのコメントで、非常にありがたい情報を頂きました。
DMUスペースアナリシスのセッション - C#ATIA

結論から書くと、当方は素のHD2で DMU スペース・アナリシス のライセンス無しですが
セクションの作成が出来ました。
恐らく、

  Set s = Sect.Export

の状態は、Partファイルをロード(オープンじゃない)した状態になっているのでは
無いかと思います。

折角なので、サンプルを作ってみました。
確かに高速で断面を大量に作ります。

'vba sample_GuideCurve_DMU_Sections_ver0.0.1  using-'KCL0.0.12'  by Kantoku
'DMUスペースアナリシスのセクション
'ガイドカーブ指定し、分割数を入力することで
'セクションパートをTreeにぶら下げた状態で終了します

Option Explicit

Const DeflutSplitCount = 3 '分割数デフォルト

Sub CATMain()

    'ドキュメントのチェック
    If Not CanExecute("ProductDocument") Then Exit Sub
    
    'ガイドライン選択
    Dim SelElm As SelectedElement
    Set SelElm = SelectGuideCurve()
    If SelElm Is Nothing Then Exit Sub

    '分割数
    Dim SplitCount As Long
    SplitCount = InputSplitCount(DeflutSplitCount)
    If SplitCount < 1 Then Exit Sub
    Dim Ratios As Collection
    Set Ratios = InitRange(SplitCount)
    
    'マトリックス
    Dim AryMat As Variant
    AryMat = GetMat3dLst(SelElm, Ratios)
    
    'プロダクト
    Dim Prod As Product
    Set Prod = CATIA.ActiveDocument.Product
    
    'セクションコレクション
    Dim Sects As Object 'Sections
    Set Sects = Prod.GetTechnologicalObject("Sections")
    
    'セクションパート
    Dim SectDocs As Collection
    Set SectDocs = GetSectionDoc(Sects, AryMat)
    
    'インポート先
    Dim IptProd As Product
    Set IptProd = Prod.Products.AddNewComponent("Product", "")
    
    'インポート
    Call InportDoc(IptProd, SectDocs)
    
    'セクション削除
    Call Sects.Remove(Sects.Count)
    
    MsgBox "Done"
    
End Sub

'セクションインポート
Private Sub InportDoc(ByRef Prod As Product, ByRef SectDocs As Collection)
    Dim ProdsVar As Variant
    Set ProdsVar = Prod.Products
    
    Dim Doc As PartDocument
    For Each Doc In SectDocs
        ProdsVar.AddComponent Doc.Product
    Next
End Sub

'セクション-PartDoc
Private Function GetSectionDoc(ByVal Sects As Object, _
                               ByVal AryMat As Variant) As Collection
    Dim Sect As Object 'Section
    Set Sect = InitSection(Sects)
    
    Dim Docs As Collection
    Set Docs = New Collection
    
    Dim i As Long
    For i = 0 To UBound(AryMat)
        Call Sect.SetPosition(AryMat(i))
        If Not Sect.IsEmpty Then
            Call Docs.Add(Sect.Export())
        End If
    Next
    Set GetSectionDoc = Docs
End Function

'Section OJ
Private Function InitSection(ByVal Sects As Object) As Object 'Section
    'セクション追加
    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 = 0
    
    Set InitSection = Sect
End Function

'断面用マトリックス
Private Function GetMat3dLst(ByVal CrvElm As SelectedElement, _
                             ByVal Ratios As Collection) As Variant
    Dim Pt As Part
    Set Pt = KCL.GetParent_Of_T(CrvElm.value, "Part")
    
    Dim Pnt As Variant 'HybridShapePointOnCurve
    Set Pnt = InitCrvOnPnt(CrvElm)
    
    Dim Pln As Variant 'HybridShapePlaneNormal
    Set Pln = InitCrvOnPlane(CrvElm, Pnt)
    
    Dim Drt As HybridShapeDirection
    Set Drt = InitDirection(Pln, Pt)
    
    Dim ratio As RealParam
    Set ratio = Pnt.ratio
    
    Dim AryMat() As Variant
    ReDim AryMat(Ratios.Count - 1)
    
    Dim Mat(11) As Variant
    Dim Ary(3) As Variant
    
    Dim idx As Long
    idx = 0
    Dim v As Variant
    For Each v In Ratios
        ratio.value = v
        Call Pt.UpdateObject(Pnt)
        Call Pt.UpdateObject(Pln)
        Call Pt.UpdateObject(Drt)
        
        '0-2
        Call Pln.GetFirstAxis(Mat)
        
        '3-5
        Call Pln.GetSecondAxis(Ary)
        Mat(3) = Ary(0)
        Mat(4) = Ary(1)
        Mat(5) = Ary(2)
        
        '6-8
        Mat(6) = Drt.GetXVal
        Mat(7) = Drt.GetYVal
        Mat(8) = Drt.GetZVal
        
        '9-11
        Call Pnt.GetCoordinates(Ary)
        Mat(9) = Ary(0)
        Mat(10) = Ary(1)
        Mat(11) = Ary(2)
        
        AryMat(idx) = Mat
        idx = idx + 1
    Next
    
    GetMat3dLst = AryMat
    
    '削除
    Dim fact As HybridShapeFactory
    Set fact = Pt.HybridShapeFactory
    
    Call fact.DeleteObjectForDatum(Drt)
    Call fact.DeleteObjectForDatum(Pln)
    Call fact.DeleteObjectForDatum(Pnt)
End Function

'範囲
Private Function InitRange(ByVal Count As Long) As Collection
    Dim Lst As Collection
    Set Lst = New Collection
    
    Dim stp As Double
    stp = 1# / (Count + 1)
    
    Dim i As Long
    For i = 0 To Count + 1
        Lst.Add i * stp
    Next
    Set InitRange = Lst
End Function

'方向
Private Function InitDirection(ByVal Pln As HybridShapePlaneNormal, _
                               ByVal Pt As Part) _
                               As HybridShapeDirection
    Dim fact As HybridShapeFactory
    Set fact = Pt.HybridShapeFactory

    Dim Ref As Reference
    Set Ref = Pt.CreateReferenceFromObject(Pln)
    
    Dim Drt As HybridShapeDirection
    Set Drt = fact.AddNewDirection(Ref)
    
    Call Pt.UpdateObject(Drt)
    Set InitDirection = Drt
End Function

'平面
Private Function InitCrvOnPlane(ByVal CrvElm As SelectedElement, _
                                ByVal Pnt As HybridShapePointOnCurve) _
                                As HybridShapePlaneNormal
    Dim Pt As Part
    Set Pt = KCL.GetParent_Of_T(CrvElm.value, "Part")
    
    Dim fact As HybridShapeFactory
    Set fact = Pt.HybridShapeFactory

    Dim Ref As Reference
    Set Ref = Pt.CreateReferenceFromObject(Pnt)

    Dim Pln As HybridShapePlaneNormal
    Set Pln = fact.AddNewPlaneNormal(CrvElm.Reference, Ref)

    Call Pt.UpdateObject(Pln)
    Set InitCrvOnPlane = Pln
End Function

'点
Private Function InitCrvOnPnt(ByVal CrvElm As SelectedElement) _
                              As HybridShapePointOnCurve
    Dim Pt As Part
    Set Pt = KCL.GetParent_Of_T(CrvElm.value, "Part")
    
    Dim fact As HybridShapeFactory
    Set fact = Pt.HybridShapeFactory

    Dim Pnt As HybridShapePointOnCurve
    Set Pnt = fact.AddNewPointOnCurveFromPercent(CrvElm.Reference, 0, False)
    
    Call Pt.UpdateObject(Pnt)
    Set InitCrvOnPnt = Pnt
End Function

'入力
Private Function InputSplitCount(ByVal def As Long) As Long
    Dim msg As String
    Dim tmp As Variant
    
    msg = "分割数を指定してください / 空白で終了" & vbCrLf & "両端は作成します"
    Do
        tmp = InputBox(msg, , def)
        Select Case True
            Case tmp = vbNullString
                InputSplitCount = -1
                Exit Function
            Case IsNumeric(tmp)
                If tmp >= 1 Then
                    InputSplitCount = CLng(tmp)
                    Exit Function
                End If
        End Select
        MsgBox "1以上の数字を入力して下さい", vbOKOnly + vbExclamation
    Loop
End Function

'ガイドライン選択
Private Function SelectGuideCurve() As SelectedElement
    Set SelectGuideCurve = Nothing
    Dim msg$
    msg = "ガイドラインを選択してください : ESCキー 終了"
    
    Dim SelElm As SelectedElement
    Dim Pt As Part
    
    Dim fact As HybridShapeFactory
    Dim Hs As HybridShape
    Do
        Set SelElm = KCL.SelectElement(msg, "HybridShape")
        If SelElm Is Nothing Then Exit Function
        
        Set Hs = SelElm.value
        Set Pt = KCL.GetParent_Of_T(Hs, "Part")
        Set fact = Pt.HybridShapeFactory
        Select Case fact.GetGeometricalFeatureType(SelElm.Reference)
            Case 2, 3, 4
                Set SelectGuideCurve = SelElm
                Exit Function
            Case Else
                MsgBox "直線,円弧,曲線 を選択してください"
        End Select
    Loop
End Function

ガイドラインを指定しラインの分割数を入力する事で分割数 + 始点 + 終点 分の
断面のCATPartをSubAssy状態でぶら下げて終了します。

実際にテストした感じです。

imihitoさん、h2さん の情報無しではここまで出来ませんでしたよ。
非常に感謝しております。
ライセンス無しでも出来る事が、少しはあるものですね。

無限直線と面の交差した座標の取得2

こちらの続きです。
無限直線と面の交差した座標の取得 - C#ATIA

まず、こんなボディを作ります。底面の中心が原点です。
f:id:kandennti:20180528190203p:plain

前回のコードを修正しました。

#FusionAPI_python
#Author-kantoku
#Description-無限直線と選択面の交差テスト

import adsk.core, adsk.fusion, traceback
_app = adsk.core.Application.get()
_ui = _app.userInterface

def run(context):
    try:
        #面の選択
        selFilters = 'Faces'
        sel = _ui.selectEntity('面を選択/ESC-中止', selFilters)
        if sel is None: return
        brep = sel.entity
        surf = adsk.core.Surface.cast(brep.geometry)
        
        #交点座標
        GetIntersectPosition(surf, [0,0,0], [0,0,10.0])
        
        GetIntersectPosition(surf, [500,0,0], [500,0,10.0])
        
        GetIntersectPosition(surf, [0,0,0], [0,500,0])
        
    except:
        if _ui:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def GetIntersectPosition(plane, start, end):
    try:
        #直線
        st_pnt = adsk.core.Point3D.create(start[0]*0.1, start[1]*0.1, start[2]*0.1)
        ed_pnt = adsk.core.Point3D.create(end[0]*0.1, end[1]*0.1, end[2]*0.1)
        line = adsk.core.Line3D.create(st_pnt, ed_pnt).asInfiniteLine()
        
        lst = line.intersectWithSurface(plane)
        if lst.count > 0 :
            pnt3d = lst[0]
            msg = 'x:{:.3f}  y:{:.3f}  z:{:.3f}'.format(pnt3d.x*10, pnt3d.y*10, pnt3d.z*10)           
        else:
             msg = '交差しません'
        _ui.messageBox(msg)
        
    except:
        if _ui:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

本来、単位に関してはunitsManagerで行うのがFusion360APIのお作法ですが
そこが重要なのではないのでムリムリにやってます。

スプリクト実行後、選択した面と
1)[0,0,0], [0,0,10.0]
2)[500,0,0], [500,0,10.0]
3)[0,0,0], [0,500,0]
をそれぞれ通過する無限直線との座標値を表示させます。

今回は、ボディの天面を選択してみます。
結果としては、
f:id:kandennti:20180528190217p:plain
左から、1)2)3)です。
1)は正しい値です。3)も交差しないので正しいです。
問題は2)なんです。天面とはかけ離れた位置を表示します。
原因は単純で、面側の情報が無限平面となってしまっているからです。

        brep = sel.entity #←選択した面のエンティティを取得(BRepFace型)
        surf = adsk.core.Surface.cast(brep.geometry)#←BRepFaceのジオメトリを取得

surfをキャストしてもしなくても、Plane型でした。
f:id:kandennti:20180528190228p:plain
名称からしていかにも無限平面っぽいです。

これぐらい単純な面と線であれば、何かしら計算すれば境界内に入っているか?
どうか? を判断できますが、傾いていた場合は・・・と言うところで
悩んでます。
点がサーフェス上に存在しているかどうかを判断出来れば良いのですが・・・。

無限直線と面の交差した座標の取得

タイトルが異なりますが、こちらの続きです。
マウスカーソルの座標値を取得する2 - C#ATIA
不人気ですが、こちら諦めていないです。

自宅で細々と(半分寝ながら)作っていた時は上手くいかなかったの
ですが、何故か会社では上手くいきました。

#FusionAPI_python
#Author-kantoku
#Description-無限直線と面の交差テスト

import adsk.core, adsk.fusion, traceback

def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui = app.userInterface
        
        #水平面
        pnt_ori = adsk.core.Point3D.create(0, 0, 0)
        vec_h = adsk.core.Vector3D.create(0, 0, 1)
        surf_h = adsk.core.Plane.create(pnt_ori, vec_h)        
        
        GetIntersectPosition(surf_h, [0,0,-10.0], [0,0,10.0])
        GetIntersectPosition(surf_h, [5,0,-10.0], [5,0,10.0])
        
        #垂直面
        vec_v = adsk.core.Vector3D.create(1, 0, 0)
        surf_v = adsk.core.Plane.create(pnt_ori, vec_v)           
        
        GetIntersectPosition(surf_v, [0,0,-10.0], [0,0,10.0])
        GetIntersectPosition(surf_v, [5,0,-10.0], [5,0,10.0])
        
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def GetIntersectPosition(plane, start, end):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui = app.userInterface
        
        #直線
        st_pnt = adsk.core.Point3D.create(start[0], start[1], start[2])
        ed_pnt = adsk.core.Point3D.create(end[0], end[1], end[2])
        line = adsk.core.Line3D.create(st_pnt, ed_pnt).asInfiniteLine()
        
        lst = line.intersectWithSurface(plane)
        if lst.count > 0 :
            pnt3d = lst[0]
            msg = 'x:{:.3f}  y:{:.3f}  z:{:.3f}'.format(pnt3d.x, pnt3d.y, pnt3d.z)
            ui.messageBox(msg)
        else:
            ui.messageBox('交差しません')
        
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

単位はCmなので役にたたないです。