C#ATIA

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

計算済みコーナーペンシル仕上げを逆方向にする

PowerMillの機能の中でちょっとだけ不満なのが、コーナーペンシル仕上げ加工。

大体の3DCAMソフトには付いている機能なのですが、今まで経験した中で
唯一PowerMillだけ、下から上にツールパスを作成してくれません。
(使いたい理由は秘密)

サポートさんには機能をつけて欲しいと要望を出しましたが、所詮小市民の
戯言なので、期待は出来ないと思ってます。

上から下へのツールパスを手動で方向逆転させられるので、作れなくはないの
ですが、この操作がメニューの深いところにあるため非常に面倒です。

その為、マクロを作成しまとめて処理させる事にしました。

//pm2017 macro sample_All_Pencil_corner_Rev.mac
//全ての計算済みコーナーペンシル仕上げ加工の方向を逆にする

function Main() {
	//ペンシル検索
	STRING LIST PathLst = {}
	call GetPencilLIST($PathLst)
	
	//チェック
	STRING msg = ''
	if is_empty($PathLst) {
		$msg = '処理可能なペンシルパスがありません!!'
		message error $msg
		return
	}
	
	//メッセージ
	BOOL yesno = 0
	call ListToString(PathLst, $msg)
	$msg = '以下のペンシルパスを逆方向にします。宜しいですか?' + crlf + $msg
	$yesno = QUERY $msg
	IF not yesno {
		return
	}
	
	//リバース
	STRING ActPathName = TOOLPATH.NAME
	call SetReverse(PathLst)
	
	//アクティブツールパスを実行前に戻す
	call ReActToolPath($PathLst, $ActPathName)
}

//実行前アクティブツールパスに戻す
function ReActToolPath(STRING LIST lst, STRING PathName) {
	STRING ReActName = ''
	if member($lst, $PathName) {
		call GetAfterPathName($PathName, $ReActName)
	} else {
		$ReActName = $PathName
	}
	ACTIVATE TOOLPATH $ReActName
}

//変換後のツールパス名取得
function GetAfterPathName(STRING PathName, output STRING NewName) {
	STRING Key = '_Rev'
	STRING Before = $PathName
	INT RevPos = position($Before, $Key)
	STRING After = ''
	if $RevPos == -1 {
		$After = $Before + $Key
	} else {
		$After = replace($Before, $Key, '')		
	}
	$NewName = $After
}

//パス名修正
function RenamePath(STRING PathName) {
	STRING After = ''
	call GetAfterPathName($PathName, $After)
	RENAME Toolpath $PathName $After
}

//逆方向
function SetReverse(STRING LIST lst) {
	foreach itm in lst {
		ACTIVATE TOOLPATH $itm
		EDIT TOOLPATH REVERSE
		call RenamePath(itm)
	}
}

//計算済みペンシル
function GetPencilLIST(output STRING LIST Out_lst) {
	STRING LIST lst = {}
	foreach itm in folder('Toolpath') {
		if itm.computed {
			if itm.Strategy == 'pencil_corner' {
				INT dmy = add_last(lst, itm.name)
			}
		}
	}
	INT dmy = remove_duplicates(lst)
	$Out_lst = lst
	return
}

//リストの文字化
function ListToString(STRING LIST lst, output STRING Out_msg) {
	STRING msg = ""
	foreach itm in lst {
		$msg = msg + itm + crlf
	}
	$Out_msg = msg
}

ツリーのツールパスフォルダ内の計算済みコーナーペンシル仕上げ加工を全て探し出し、
逆転させます。その際、ツールパスの名称を逆転させたものには、
"ツールパス名" + "_Rev" の名称に変更し、再度実行した際は "_Rev"
の名称を削除します。
(手動でゴチョゴチョした際は、もちろんそうなりません・・・)

実際に使用した感じです。 最初に通常に作成したシミュレーションを
実行し、マクロ実行後のシミュレーションを実行してみました。


"Macro Programming Guide" に計算済みツールパスを判断するためのサンプル
が記載されているのですが、パラメータ部分が

IF entity(‘toolpath’,Name) .Calculated {

となっているのですが、正しくは

IF entity(‘toolpath’,Name).computed {

です。 参考に出来るものが少ないだけに誤記は勘弁して欲しい。
(他にも幾つかあります)

PowerMillに同位置の作業平面を作る

加工する際、世の中の方々は加工原点をどうやって決めているんでしょうか?
部品の原点が決まっているようであれば、そこを原点にするのかも知れない
のですが、基準まで加工して作る場合は・・・バイスを使用していたら材料の
上側の左奥(か、右奥・・・わかりにくいですね)になるのだろうと思いますが、
社内で使用している治具の関係で、うちの場合は材料底面の中心部分にする
と言う、暗黙なルールが見え隠れしています。

こんな感じです。
f:id:kandennti:20170210190831p:plain

悩むのが、CADの原点と加工原点が違う際にどうするのか?
世間の皆様はどうしているのでしょう???
恐らくCAM側で加工原点を作成して作業をするのだろうと思うのですが、
個人的にPowerMillの作業平面(加工原点)の操作が、思ったより
やりにくい・・・(うちが材料の中心部にしているせいです)

なので、今まではCATIAで座標変換させてIgesエクスポートさせていたのですが、
後々に確認したい場合に結構不便で困っていました。



PowerMillのマクロは独自言語なのですが、外部からマクロの実行が出来ます。
VBAでも出来ちゃうので、CATIAの座標系情報を元にPowerMillの作業平面を
作成してしまおう! と思いつき、マクロを作ってみました。

'vba sample_PM_Push_AxisSystem Ver0.0.1
'using-'KCL0.0.10' 'PowerMill'
Option Explicit

Sub CATMain()
    'ドキュメントのチェック
    If Not CanExecute("PartDocument") Then Exit Sub
    
    'パワーミル取得
    Dim Pm As Object: Set Pm = GetPowerMill()
    If KCL.IsNothing(Pm) Then
        MsgBox "パワーミルを起動して下さい!"
        Exit Sub
    End If
    
    'エクスポートする座標系選択
    Dim Axis As AxisSystem
    Set Axis = KCL.SelectItem("座標系を選択して下さい", "AxisSystem")
    If KCL.IsNothing(Axis) Then Exit Sub

    '座標系データ取得
    Dim AxisCoord As Variant: AxisCoord = GetAxisCoord(Axis)
    
    'パワーミル 作業平面作成
    Call CreateWorkPlane_Pm(Pm, AxisCoord)
End Sub


' *** Catia ***
'座標系データ取得
Private Function GetAxisCoord(ByVal Axis As AxisSystem) As Variant
    Dim Ax: Set Ax = Axis
    Dim Ori(2), VecX(2), VecY(2)
    Call Ax.GetOrigin(Ori)
    Call Ax.GetVectors(VecX, VecY)
    
    GetAxisCoord = Array(Axis.Name, _
                         Ori(0), Ori(1), Ori(2), _
                         VecX(0), VecX(1), VecX(2), _
                         VecY(0), VecY(1), VecY(2))
End Function


'*** PowerMill ***
'パワーミル取得
Private Function GetPowerMill() As Object
    On Error Resume Next
        Set GetPowerMill = GetObject(, "PMILL.Document")
    On Error GoTo 0
End Function

'ダイアログ類オン
Private Sub DialogOn_Pm(ByVal Pm As Object)
    With Pm
        Call .Docommand("DIALOGS MESSAGE ON")
        Call .Docommand("DIALOGS ERROR ON")
        Call .Docommand("ECHO ON DCPDEBUG TRACE COMMAND ACCEPT")
    End With
End Sub

'ダイアログ類オフ
Private Sub DialogOff_Pm(ByVal Pm As Object)
    With Pm
        Call .Docommand("DIALOGS MESSAGE OFF")
        Call .Docommand("DIALOGS ERROR OFF")
        Call .Docommand("ECHO OFF DCPDEBUG UNTRACE COMMAND ACCEPT")
    End With
End Sub

'パワーミルに作業平面作成
Private Sub CreateWorkPlane_Pm(ByVal Pm As Object, ByVal Axcoord As Variant)
    Call DialogOff_Pm(Pm)
    
    With Pm
        '新エンティティ名
        Call .Execute("string NewName = new_entity_name('Workplane')", 1)
        
        '作業平面作成・編集開始
        Call .Execute("CREATE WORKPLANE ; EDITOR", 1)
        Call .Execute("ACTIVATE WORKPLANE $NewName", 1)
        Call .Execute("MODE WORKPLANE_EDIT START $NewName", 1)
        
        'リネーム
        Dim ReName$: ReName$ = Get_NewWPName_Pm(Pm, Axcoord(0))
        If Not ReName$ = vbNullString Then
            Call .Execute("MODE WORKPLANE_EDIT NAME """ & ReName & """ ", 1)
        End If
    
        '原点
        Call .Execute("MODE WORKPLANE_EDIT POSITION", 1)
        Call .Execute("MODE POSITION WORKSPACE WORLD", 1)
        Call .Execute("MODE POSITION PLANE XY", 1)
        Call .Execute("MODE POSITION CARTESIAN X """ & CStr(Axcoord(1)) & """ ", 1)
        Call .Execute("MODE POSITION CARTESIAN Y """ & CStr(Axcoord(2)) & """ ", 1)
        Call .Execute("MODE POSITION CARTESIAN Z """ & CStr(Axcoord(3)) & """ ", 1)
        Call .Execute("POSITION APPLY", 1)
        
        'X軸
        Call .Execute("MODE WORKPLANE_EDIT DIRECTION X", 1)
        Call .Execute("MODE DIRECTION WORKSPACE WORLD", 1)
        Call .Execute("MODE DIRECTION COMPONENT I """ & CStr(Axcoord(4)) & """ ", 1)
        Call .Execute("MODE DIRECTION COMPONENT J """ & CStr(Axcoord(5)) & """ ", 1)
        Call .Execute("MODE DIRECTION COMPONENT K """ & CStr(Axcoord(6)) & """ ", 1)
        Call .Execute("DIRECTION ACCEPT", 1)
        
        'Y軸
        Call .Execute("MODE WORKPLANE_EDIT DIRECTION Y", 1)
        Call .Execute("MODE DIRECTION WORKSPACE WORLD", 1)
        Call .Execute("MODE DIRECTION COMPONENT I """ & CStr(Axcoord(7)) & """ ", 1)
        Call .Execute("MODE DIRECTION COMPONENT J """ & CStr(Axcoord(8)) & """ ", 1)
        Call .Execute("MODE DIRECTION COMPONENT K """ & CStr(Axcoord(9)) & """ ", 1)
        Call .Execute("DIRECTION ACCEPT", 1)
        
        '編集終了
        Call .Execute("MODE WORKPLANE_EDIT FINISH ACCEPT", 1)
    End With
    
    Call DialogOn_Pm(Pm)
End Sub

'重複を避けた作業平面名を取得
Private Function Get_NewWPName_Pm(ByVal Pm As Object, ByVal Name As String) As String
    Get_NewWPName_Pm = Name
    
    If Name = vbNullString Then Exit Function
    
    Dim Ret, CallBackMsg$
    With Pm
        Call .Docommand("STRING LIST $lst={}")
        Ret = .Execute("$lst = extract(folder('Workplane'), 'name')", 1)
        Ret = .ExecuteEx("PRINT PAR '$lst'", 0, CallBackMsg)
    End With
    
    Dim WPNameDic As Object: Set WPNameDic = Get_WPNameList_Pm(CallBackMsg)
    If WPNameDic.Count < 1 Then Exit Function
    
    Dim TmpName$: TmpName = Name
    Dim Count&: Count = 0
    Do
        If Not WPNameDic.Exists(TmpName) Then Exit Do
        Count = Count + 1
        TmpName = Name & "_" & CStr(Count)
    Loop
    
    Get_NewWPName_Pm = TmpName
End Function

'パワーミルの作業平面名辞書
Private Function Get_WPNameList_Pm(ByVal Txt As String) As Object
    Set Get_WPNameList_Pm = Nothing
    Const Key = "(STRING) "
    Dim KeyLng: KeyLng = Len(Key)
    
    Dim Ary: Ary = Split(Txt, vbNewLine)
    Dim Dic As Object: Set Dic = KCL.InitDic()
    
    Dim i&, KeyIdx&
    For i = 0 To UBound(Ary)
        KeyIdx = InStr(Ary(i), Key)
        If KeyIdx > 0 Then
            Call Dic.Add(Mid(Ary(i), KeyIdx + KeyLng), 1)
        End If
    Next
    
    Set Get_WPNameList_Pm = Dic
End Function

CreateWorkPlane_Pm関数が、ビックリするぐらい酷い状況・・・。
結果的に、GetObjectでPowerMillを所得しマクロのコードを垂れ流しです。



実際に試してみた動画がこちら
(予め、CADデータはインポートしてあります)

作業平面を作る過程を止める事も出来るようですが、動画に箔がつくのでそのままです。
本当は、作成した作業平面をアクティブにしたかったのですが・・・上手く行ってません。
それでも便利です。 悩みが一個解消しました。


忘れちゃいそうなので、覚書としてのコード的な部分です。

外部からの操作で、関数の戻り値が取得できるものかどうか疑問でしたが、
フォーラムの記載を参考にしました。 作業平面名の重複を避けるため作業平面フォルダ
から全作業平面名を取得する必要が有りました。
Get_NewWPName_Pm・Get_WPNameList_Pm関数でその辺の処理をしています。

こちらのコードはエコーコマンドの表示を止めるもののようで、
中にはこの表示を止めないとマクロが上手く動作しないものがあるようです。
(よくわかっていないのですが、エラーがらみだと思います)

ECHO OFF DCPDEBUG UNTRACE COMMAND ACCEPT

ところが逆のエコーコマンドに表示させる為のコードが、探しても探しても
見つかりませんでした。 何となく感で打ち込んだところ
こちらで再表示させられるようになりました。

ECHO ON DCPDEBUG TRACE COMMAND ACCEPT

ひょっとしたら何処かに記載されているかも。

フォルダ名リストを取得する

想像以上に苦しく、進まない PowerMillマクロです。
でも、メンドクサイ類似作業の繰り返しはコリゴリなので、進めてます。

以前はメモ帳で作っていたのですがw AutodeskのForumでこんな記述を発見。
Notepad++ Language for editing PowerMill macros. - Autodesk Community

Notepad++と言うエディタの、PowerMillマクロ言語用のものがあるなんてありがたいです。
確かにマニュアルにもチラチラ出てくる名前のエディタなので、使おうと思ったのですが
日本語が未対応っぽい。

探すとこちらがHitしました。
Notepad++ ja-pack 1 (日本語パック) を公開してみた。 | OFF-SOFT.net

ん~よく読んでないせいか、画面が日本語になっていない・・・。 "Shift-JIS" は
使えているので、まぁこのままで行きます。



で、本題の方ですが、僕の場合Treeが長くなるのがイヤなので、
ツールパスや工具フォルダ内にフォルダを作って作業してます。 こんな感じです。
f:id:kandennti:20170209191356p:plain

面倒なんですが、ダッダーっと長いのはイヤなんです。 何れはマクロやテンプレートで
作るし。

後々に利用する事になりそうなので、組み込みフォルダ内のフォルダ名リストを取得する
マクロです。

//pm2017 macro
//フォルダ名取得 クズテスト

function Main() {
	STRING LIST lst = {}
	//ツールパス
	call GetFolderNameLIST('Toolpath', $lst)
	call Dump(lst)
	
	//工具
	call GetFolderNameLIST('Tool', $lst)
	call Dump(lst)
}

//指定した組み込みルートフォルダ内のフォルダ名を取得
function GetFolderNameLIST(STRING Lst_name, output STRING LIST Folder_lst ) {
	STRING LIST lst = {}
	foreach itm in folder(Lst_name) {
		INT dmy = add_last(lst, basename(dirname(pathname(itm))))
	}
	INT dmy = remove_duplicates(lst)
	$Folder_lst = lst
	return
}

function Dump(STRING LIST lst) {
	STRING msg = ""
	foreach itm in lst {
		$msg = msg + itm + crlf
	}
	print = msg
}


エコーコマンドで出力させてます。 結果はこんな感じ。

 ・・・

Process Command : [\tprint = msg\n]

r2s_base
r1s_base
r05s_base

 ・・・

Process Command : [\tprint = msg\n]

path
ref

 ・・・

ん~ 理由はわかっているのですが、空のフォルダ名が取得できていないです。
ネストしているフォルダ名も実は取れないんですけど、それは禁止にしよう・・・。

PowerMill 言語環境の切り替え

Space-eをバージョンアップしたのですが、ありがたい事にPowerMillも
バージョンアップしてR2017が使えるようになりました。
(Fusion360CAMの利用を考えなくなったのはその為です)

個人的にはPowerMillは非常に優秀だと思っているのですが、
こちらの件だけは、ちょっと頂けないです。
PowerMILLのVortex - C#ATIA


出来上がる経路の優秀さはもちろんなのですが、マクロがかなり強力で
手動の操作の大半が出来るのではないのかな? と思っています。

マクロの言語は独自ですが、CATIA同様に記録が録れるのでスタートするには
十分な程の環境です。
(マクロだけでなく、プラグイン作成用のサンプルがVBC#等でも用意されています)

DELCAM時代からメーカーさんも、積極的に情報やツールを公開し、メーカーさんの
フォーラムでも積極的なアドバイスをしていた為、非常に良い印象を持って
おります。(Autodeskさんになってからも、フォーラムは好印象です)


こちらでも楽をしたいので、マクロを作成したいのですが日本語環境の
ままではやりにくい部分も有り、何とか英語環境で起動したいところ。

Helpに記載が見当たらなかったので、調べてみるとこちらが
Hitしました。(ロシア語起動)
Solved: How to change language in Powermill - Autodesk Community


理解力が乏しい為か、これはちょっと違っておりこんな感じでした。

PowerMill > lang

Enter language to use > english

大文字小文字は無関係で、一度 "lang" でEnter、"english" でEnterです。
他の言語の場合は、インストールフォルダ内に言語別のフォルダがあったので
恐らくそのフォルダ名でOKのような気がしています。(未確認です)

日本語に戻す場合は

PowerMill > lang

Enter language to use > japanese

です。

・・・これもメンドクサイのでマクロにしました。
英語へ

//pm2017 ChangeLang_Eng.mac
//英語環境へ切り替え

LANG
English

日本語へ

//pm2017 ChangeLang_JPN.mac
//日本語環境へ切り替え

LANG
Japanese

これだけなんですけどね。

CATIAと異なり、切り替えてもPowerMillの再起動は必要有りませんでした。

Drawのテキストサイズを変更する

"Drawのテキストサイズを変更したけど反映されない?" のような
ご相談を頂きましたが、ちょっと状況がわからないので、
現象を再現できないでいます。

クリックしたテキストのサイズを2倍にするだけのテストコードです。

'vba test_DrawTextSize_Double
'using-'KCL0.0.10'
'DrawTextサイズを2倍にする

Sub CATMain()
    'ドキュメントのチェック
    If Not KCL.CanExecute("DrawingDocument") Then Exit Sub
    
    Dim Msg$: Msg = "テキストを選択して下さい : ESCキー 終了"
    Dim Dt As DrawingText
    Dim DtSize#
    
    Do
        Set Dt = KCL.SelectItem(Msg, "DrawingText")
        If KCL.IsNothing(Dt) Then Exit Do
        DtSize = Dt.GetFontSize(0, 0) 'サイズ取得
        Dt.SetFontSize 0, 0, DtSize * 2 'サイズ設定
    Loop
End Sub

イロイロあり、今までR2012を使用していたのでKCLがVBA7に未対応な
状態でしたので、修正しました。
他にも修正したい部分はあるのですが、時間が無く・・・・。

2Dコンポーネントを展開する

こちらの続きです。
指定した2D要素を、指定した原点位置でコピペする - C#ATIA


予告していた、2Dコンポーネント(2D構成要素)を展開するマクロです。

アクティブなDrawシート全てのビュー内(メイン・背景・ロック・非表示)にある
2Dコンポーネントが対象です。

'vba sample_Comp2DExplode ver0.0.3
'using-'KCL0.09'
'アゥティブなシートのメインと背景以外のビューにある2Dコンポーネントを全て展開

Option Explicit

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

    'コンポーネント2Dの存在するビュー取得
    Dim ViewLst As Collection: Set ViewLst = GetExistComp2DView()
    If ViewLst.Count < 1 Then
        MsgBox "アクティブなシート内のアンロックされたビュー内には、" & vbNewLine & _
               "コンポーネント2Dが存在しませんでした", vbInformation
        Exit Sub
    End If
    
    'コンポーネント2D取得
    Dim CompLst As Collection: Set CompLst = GetComp2DList(ViewLst)
    
    'ユーザー選択
    Dim Msg$: Msg = "アクティブなシートには" & _
                    CreateMsg(ViewLst, CompLst) & vbNewLine & _
                    "のコンポーネント2Dが存在します。 全て展開しますか?"
    If MsgBox(Msg, vbOKCancel + vbInformation) = vbCancel Then Exit Sub
    
    '展開
    Call ExecuteExplode(CompLst)
    
    '終了
    MsgBox "終了"
End Sub

'展開
Private Sub ExecuteExplode(ByVal CompLst As Collection)
    Dim Lst As Collection
    Dim Cmp As DrawingComponent
    Dim Sel As Selection: Set Sel = CATIA.ActiveDocument.Selection
    
    CATIA.HSOSynchronized = False
        Sel.Clear
        For Each Lst In CompLst
            For Each Cmp In Lst
                Cmp.Explode
                Sel.Add Cmp
            Next
        Next
        Sel.Delete
    CATIA.HSOSynchronized = True
End Sub

'メッセージ作成
Private Function CreateMsg(ByVal ViewLst As Collection, _
                            ByVal CompLst As Collection) As String
    Dim Ary(): ReDim Ary(ViewLst.Count)
    Dim i&
    For i = 1 To ViewLst.Count
        Ary(i) = "ビュー '" & ViewLst(i).Name & "' - " & _
                    CStr(CompLst(i).Count) & "個"
    Next
    CreateMsg = Join(Ary, vbNewLine)
End Function

'コンポーネント2D取得
Private Function GetComp2DList(ByVal ViewLst As Collection) As Collection
    Set GetComp2DList = Nothing
    
    Dim Vw As DrawingView
    Dim Lst As Collection: Set Lst = New Collection
    For Each Vw In ViewLst
        Lst.Add DeepCopyCatCollection(Vw.Components, "DrawingComponents")
    Next
    Set GetComp2DList = Lst
End Function

'コンポーネント2Dの存在するビュー取得  メイン・背景・ロック・非表示除外
Private Function GetExistComp2DView() As Collection
    Set GetExistComp2DView = Nothing
    
    Dim Vws As DrawingViews
    Set Vws = CATIA.ActiveDocument.Sheets.ActiveSheet.Views
    If Vws.Count < 3 Then Exit Function
    
    Dim ViewLst As Collection
    Set ViewLst = DeepCopyCatCollection(Vws, "DrawingViews")
    Call ViewLst.Remove(1) 'メイン削除
    Call ViewLst.Remove(1) '背景削除
    
    
    Dim Vw As DrawingView
    Dim Lst As Collection: Set Lst = New Collection
    For Each Vw In ViewLst
        If Vw.LockStatus = False And IsShow(Vw) And _
                                Vw.Components.Count > 0 Then
            Lst.Add Vw
        End If
    Next
    Set GetExistComp2DView = Lst
End Function

'表示?
Private Function IsShow(ByVal Oj As AnyObject) As Boolean
    Dim Sel As Selection: Set Sel = CATIA.ActiveDocument.Selection
    Dim Vis As VisPropertySet:
    Set Vis = Sel.VisProperties
    Dim ShowState As CatVisPropertyShow
    
    CATIA.HSOSynchronized = False
        Sel.Clear
        Sel.Add Oj
        Call Vis.GetShow(ShowState)
        Sel.Clear
    CATIA.HSOSynchronized = True
    
    IsShow = IIf(ShowState = catVisPropertyDefined, True, False)
End Function

'クローン
Private Function DeepCopyCatCollection(ByVal CatCol As AnyObject, _
                                        ByVal OjType$) As Collection
    Set DeepCopyCatCollection = Nothing
    
    Dim Col As Variant
    Select Case OjType '面倒・・・
        Case "DrawingViews"
            Dim Vws As DrawingViews: Set Vws = CatCol
            Set Col = Vws
            
        Case "DrawingComponents"
            Dim Cmps As DrawingComponents: Set Cmps = CatCol
            Set Col = Cmps
            
        Case Else
            Set Col = CatCol
    End Select
    
    Dim Lst As Collection: Set Lst = New Collection
    Dim v
    For Each v In Col
        Lst.Add v
    Next
    Set DeepCopyCatCollection = Lst
End Function

これぐらいなら、それ程苦労せずに作成出来るのですが。

2D曲線の折れ線化を利用し、重複線の選択2

こちらの続きです。
2D曲線の折れ線化を利用し、重複線の選択1 - C#ATIA


昨日の4分木ライブラリを利用して、前回の重複線を選択した状態にする
マクロを改良しました。

'vba test_Select_Overlap_Curve2D ver0.0.2
'using-'KCL0.09' -'KCL_Quadtree0.0.1'
'指定ビュー内の重複線を選択

Option Explicit

'*** 設定 ***
Private Const POLY_TOL = 0.1        '折れ線化トレランス
Private Const OVER_TOL = 0.1        '重複判断トレランス
Private Const EPS = 0.0001          'イコール判断
'************

Sub CATMain()
    'ドキュメントのチェック
    If Not KCL.CanExecute("DrawingDocument") Then Exit Sub
    
    '選択
    Dim View As DrawingView: Set View = KCL.SelectItem("ビューを選択してください", "DrawingView")
    If KCL.IsNothing(View) Then Exit Sub
    
    'ドキュメント取得
    Dim Doc As DrawingDocument: Set Doc = KCL.GetParent_Of_T(View, "DrawingDocument")
    
    '線取得
    KCL.SW_Start: Debug.Print "** Obj Start 002 ** :" & vbNewLine & "POLY_TOL-" & POLY_TOL & " : OVER_TOL-" & OVER_TOL
    Dim CrvLst As Collection: Set CrvLst = GetCurveList_Obj(View)
    Debug.Print "CrvLst- " & CrvLst.Count & "個 : " & KCL.SW_GetTime & "s"
    If KCL.IsNothing(CrvLst) Then Exit Sub
    
    '範囲取得
    Dim RngLst As Collection: Set RngLst = GetRangeBoxList(CrvLst)
    Debug.Print "RngLst- " & RngLst.Count & "個 : " & KCL.SW_GetTime & "s"
    
    '長さ取得
    Dim LngLst As Collection: Set LngLst = GetLength_Prm(CrvLst)
    Debug.Print "LngLst- " & LngLst.Count & "個 : " & KCL.SW_GetTime & "s"
    
    '折れ線化
    Dim PolyLst As Collection: Set PolyLst = GetPolyList(CrvLst)
    Debug.Print "PolyLst- " & PolyLst.Count & "個 : " & KCL.SW_GetTime & "s"
    
    '4分木
    Dim QuadLst As Collection: Set QuadLst = Kcl_Quadtree.GetLinerQuadtreeList(RngLst, OVER_TOL)
    Debug.Print "QuadLst- " & QuadLst.Count & "個 : " & KCL.SW_GetTime & "s"
    
    '重複線Idx取得
    Dim OverLst As Collection: Set OverLst = GetOverlapList(QuadLst, PolyLst, LngLst)
    Debug.Print "OverLst- " & OverLst.Count & "個 : " & KCL.SW_GetTime & "s"
    
    '選択
    Call SelectOverCrv(OverLst, CrvLst, Doc.Selection)
    Debug.Print "SelectOverCrv - " & Doc.Selection.Count2 & "個 : " & KCL.SW_GetTime & "s"
End Sub


'*** catia ***
'コレクション要素の選択
Private Sub SelectOverCrv(ByVal OverList As Collection, ByVal CrvList As Collection, ByVal Sel As Selection)
    Dim Idx
    CATIA.HSOSynchronized = False
    With Sel
        .Clear
        For Each Idx In OverList
            .Add CrvList(Idx)
        Next
    End With
    CATIA.HSOSynchronized = True
End Sub

'線取得
Private Function GetCurveList_Obj(ByVal Vew As DrawingView) As Collection
    Dim Lst As Collection: Set Lst = New Collection
    Dim Geos As GeometricElements: Set Geos = Vew.GeometricElements
    Dim Geo As GeometricElement
    For Each Geo In Geos
        Select Case Geo.GeometricType
            Case catGeoTypeUnknown, catGeoTypeAxis2D, catGeoTypeControlPoint2D, catGeoTypePoint2D
                '処理無し
            Case Else
                Lst.Add Geo
        End Select
    Next
    Set GetCurveList_Obj = Lst
End Function

'長さリスト取得
Private Function GetLength_Prm(ByVal Geos As Collection) As Collection
    Set GetLength_Prm = Nothing
    
    Dim Lst As Collection: Set Lst = New Collection
    Dim Geo As GeometricElement
    Dim Prm(1)
    For Each Geo In Geos
        With Geo
            Call .GetParamExtents(Prm)
            Lst.Add .GetLengthAtParam(Prm(0), Prm(1))
        End With
    Next
    Set GetLength_Prm = Lst
End Function

'領域リスト取得
Private Function GetRangeBoxList(ByVal Geos As Collection) As Collection
    Set GetRangeBoxList = Nothing
    
    Dim Lst As Collection: Set Lst = New Collection
    Dim Geo As GeometricElement
    Dim Range(3)
    For Each Geo In Geos
        Call Geo.GetRangeBox(Range)
        Lst.Add Array(Array(Range(0), Range(1)), Array(Range(2), Range(3)))
    Next
    Set GetRangeBoxList = Lst
End Function

'折れ線化リスト取得
Private Function GetPolyList(ByVal Geos As Collection) As Collection
    Set GetPolyList = Nothing
    
    Dim Lst As Collection: Set Lst = New Collection
    Dim Geo As GeometricElement
    For Each Geo In Geos
        Select Case Geo.GeometricType
            Case catGeoTypeLine2D '"Line2D"
                Lst.Add Line2Poly(Geo)
            Case catGeoTypeCircle2D '"Circle2D"
                Lst.Add Circle2Poly(Geo)
            Case Else '"Spline2D", "Curve2D"
                Lst.Add Curve2Poly(Geo)
        End Select
    Next
    Set GetPolyList = Lst
End Function

'PolyAに対しPolyBが重複しているか?
Private Function IsOverlap(PolyA As Collection, PolyB As Collection) As Boolean
    IsOverlap = False

    Dim MinLng#, TempLng#, i&, j&
    For i = 1 To PolyB.Count
        MinLng = OVER_TOL + 1#
        For j = 1 To PolyA.Count - 1
            TempLng = Dist_AB_C(PolyA(j), PolyA(j + 1), PolyB(i))
            If MinLng > TempLng Then MinLng = TempLng
        Next
        If MinLng > OVER_TOL Then
            Exit Function
        End If
    Next
    IsOverlap = True
End Function

'重複線リスト取得
Private Function GetOverlapList(ByVal QuadList As Collection, _
                                ByVal PolyList As Collection, _
                                ByVal LngList As Collection) As Collection
    Set GetOverlapList = Nothing

    Dim i&, j&, Spe As Collection
    Dim OvList As Collection: Set OvList = New Collection
    
    Dim EnumLst_T As Collection
    Dim EnumLst_U As Collection
    Dim OvAry '重複Fg 0-しない 1-参照のみ 2-重複
    OvAry = InitValueAry(PolyList.Count, 0)
    
    For Each Spe In QuadList
        '空間-空間
        Set EnumLst_T = Spe.Item(1)
        Call Q_ISort_List(EnumLst_T, LngList)
        For i = 1 To EnumLst_T.Count '重複線を判断する側(長いほうの線)
            For j = i + 1 To EnumLst_T.Count '重複線を判断される側
                If OvAry(EnumLst_T(j)) > 0 Then Exit For
                If IsOverlap(PolyList(EnumLst_T(i)), PolyList(EnumLst_T(j))) Then
                    OvList.Add EnumLst_T(j)
                    OvAry(EnumLst_T(j)) = 2
                End If
            Next
        Next
        
        '上位-空間
        Set EnumLst_U = Spe.Item(2)
        Call Q_ISort_List(EnumLst_U, LngList)
        For i = 1 To EnumLst_U.Count '重複線を判断する側
            For j = 1 To EnumLst_T.Count '重複線を判断される側
                If OvAry(EnumLst_T(j)) > 0 Then Exit For
                If LngList(EnumLst_U(i)) > LngList(EnumLst_T(j)) Then
                    If IsOverlap(PolyList(EnumLst_U(i)), PolyList(EnumLst_T(j))) Then
                        OvList.Add EnumLst_T(j)
                        OvAry(EnumLst_T(j)) = 2
                    End If
                End If
            Next
        Next
    Next
    Set GetOverlapList = OvList
End Function

'*** PolyLine ***
'線分折れ線化
Private Function Line2Poly(ByVal Geo As AnyObject) As Collection
    Set Line2Poly = Nothing
    
    Dim Prm(1) '始点終点パラメータ
    Dim Pos(3) '座標
    Dim StPos '始点座標
    Dim EnPos '終点座標
    
    'スプライン情報
    Call Geo.GetEndPoints(Pos)
    StPos = Array(Pos(0), Pos(1))
    EnPos = Array(Pos(2), Pos(3))
    
    'コピペされた座標軸対策
    'If Lng >= 2000000 Then Exit Function
    
    '折れ線化
    Dim List As Collection: Set List = New Collection
    Call List.Add(StPos)
    Call List.Add(EnPos)
    
    Set Line2Poly = List
End Function

'円弧折れ線化
Private Function Circle2Poly(ByVal Geo As AnyObject) As Collection
    Set Circle2Poly = Nothing
    
    '円弧情報
    Dim Prm(1) '始点終点パラメータ
    Dim StPos(1) '始点座標
    Dim EnPos(1) '終点座標
    Dim CnPos(1) '中心座標
    Dim R# '半径
    
    With Geo
        Call .GetParamExtents(Prm)
        Call .GetPointAtParam(Prm(0), StPos)
        Call .GetPointAtParam(Prm(1), EnPos)
        Call .GetCenter(CnPos)
        R = .Radius
    End With
    
    'トレランス内の増分パラメータ算出
    Dim IncPara# 'パラメータ増分
    Dim E_SPara# '終点-始点パラメータ
    Dim LoopCount& 'カウンタ
    If R * 0.5 < POLY_TOL Then
        '小さな円弧への対応
        IncPara = (Prm(1) - Prm(0)) * 0.5
    Else
        '通常の円弧
        IncPara = ArcCos(1 - POLY_TOL / R) * 2
        E_SPara = Prm(1) - Prm(0)
        LoopCount = Fix(E_SPara / IncPara) + 1
        IncPara = E_SPara / LoopCount
    End If
    
    '増分の三角関数
    Dim SinTheta#, CosTheta#
    SinTheta = Sin(IncPara)
    CosTheta = Cos(IncPara)
    
    '折れ線化
    Dim AD#, BD# '回転前の点と中心点の距離
    Dim List As Collection: Set List = New Collection
    Dim i&
    Call List.Add(Array(StPos(0), StPos(1)))
    For i = 2 To LoopCount
        AD = List(i - 1)(0) - CnPos(0)
        BD = List(i - 1)(1) - CnPos(1)
        Call List.Add(Array(AD * CosTheta - BD * SinTheta + CnPos(0), _
                            AD * SinTheta + BD * CosTheta + CnPos(1)))
    Next
    Call List.Add(Array(EnPos(0), EnPos(1)))
    Set Circle2Poly = List
End Function

'スプライン折れ線化
Private Function Curve2Poly(ByVal Geo As AnyObject) As Collection
    Set Curve2Poly = Nothing
    
    Const CutCount = 4 '分割数
    
    'スプライン情報
    Dim Prm(1) '始点終点パラメータ
    Dim Pos(1) '座標
    
    With Geo
        Call .GetParamExtents(Prm)
        Call .GetPointAtParam(Prm(0), Pos)
    End With
    
    'ループ準備
    Dim PntList As Collection '折れ線化リスト
    Set PntList = New Collection: Call PntList.Add(Pos)
    Dim CrvSPara#: CrvSPara = Prm(0) 'カーブ始点パラメータ
    Dim CrvEPara#: CrvEPara = Prm(1) 'カーブ終点パラメータ
    Dim LoopSPara#: LoopSPara = CrvSPara 'ループ始点パラメータ
    Dim LoopEPara#: LoopEPara = CrvEPara 'ループ終点パラメータ
    
    '非再帰折れ線近似化
    Dim SumPara# '増分パラメータ
    Dim LoopSPos(1) 'ループ始点
    Dim LoopEPos(1) 'ループ終点
    Dim Unit_Vec 'ループ始点からループ終点の単位ベクトル
    Dim i&
    Dim CutPara#(CutCount) '分割パラメータ
    Dim CutPos(CutCount) '分割座標
    Dim CutMax: CutMax = Array(-1#, -1&) '分割点の最大距離とID
    Dim TempLng#  '一時距離
    Do
        'ループ初期設定
        SumPara = (LoopEPara - LoopSPara) / (CutCount + 2)
        Call Geo.GetPointAtParam(LoopSPara, LoopSPos)
        Call Geo.GetPointAtParam(LoopEPara, LoopEPos)
        Unit_Vec = Normaliz2d(LoopSPos, LoopEPos)
        
        
        '閉じた曲線-強制的に1点を登録
        If IsEmpty(Unit_Vec) Then
            CutPara(0) = Geo.GetParamAtLength(LoopSPara, POLY_TOL)
            Call Geo.GetPointAtParam(CutPara(0), Pos)
            Call PntList.Add(Pos)
            LoopSPara = CutPara(0)
            LoopEPara = CrvEPara
            GoTo Continue_Close
        End If
        
        '分割点作成 距離チェック
        For i = 0 To CutCount
            CutPara(i) = LoopSPara + SumPara * (i + 1)
            Call Geo.GetPointAtParam(CutPara(i), Pos)
            CutPos(i) = Pos
            TempLng = Lng_V_P(Unit_Vec, Sub2d(CutPos(i), LoopSPos))
            If CutMax(0) < TempLng Then '最大分割点更新
                CutMax(1) = i: CutMax(0) = TempLng
            End If
        Next
        
        '最大距離から節確定 LoopEParaが終点ならループ終了
        If CutMax(0) < POLY_TOL Then
            If LoopEPara >= CrvEPara Then
                Call Geo.GetPointAtParam(CrvEPara, Pos)
                Call PntList.Add(Pos)
                Exit Do 'ループ抜ける
            Else
                Call PntList.Add(LoopEPos)
                LoopSPara = LoopEPara
                LoopEPara = CrvEPara
            End If
        Else
            LoopEPara = CutPara(CutMax(1)) '再度処理
        End If
Continue_Close:
        CutMax(0) = -1# '距離初期化
        
        If EQ(LoopSPara, LoopEPara) Then
            '始点と終点がほぼ同一 未対応
            Stop
        End If
    Loop
    Set Curve2Poly = PntList
End Function


'*** Math ***
'ArcCos
Private Function ArcCos(ByVal V As Double) As Double
    ArcCos = Atn(-V / Sqr(-V * V + 1)) + 2 * Atn(1)
End Function

'2点距離の平方数
Private Function LengSqr(ByVal P1 As Variant, ByVal P2 As Variant) As Double
    Dim A#: A = P2(0) - P1(0)
    Dim B#: B = P2(1) - P1(1)
    LengSqr = A * A + B * B
End Function

'イコール
Private Function EQ(ByVal A As Double, ByVal B As Double) As Boolean
    EQ = IIf(Abs((A) - (B)) < EPS, True, False)
End Function


'*** Vecter ***
'参考サイト:http://www.deqnotes.net/acmicpc/2d_geometry/lines#intersection_of_lines
'参考サイト:http://marupeke296.com/COL_main.html
'点A,Bを端点とする線分と点Cとの距離
Private Function Dist_AB_C(ByVal A As Variant, ByVal B As Variant, ByVal C As Variant) As Double
    If Dot2d(Sub2d(B, A), Sub2d(C, A)) < EPS Then
        Dist_AB_C = Abs(Sqr(LengSqr(C, A)))
        Exit Function
    End If
    If Dot2d(Sub2d(A, B), Sub2d(C, B)) < EPS Then
        Dist_AB_C = Abs(Sqr(LengSqr(C, B)))
        Exit Function
    End If
    Dist_AB_C = Lng_AB_C(A, B, C)
End Function

'ベクトルABと点Cの距離
Private Function Lng_AB_C(ByVal A As Variant, ByVal B As Variant, ByVal C As Variant) As Double
    Lng_AB_C = Abs(Cross2d(Sub2d(B, A), Sub2d(C, A))) / Abs(Sqr(LengSqr(B, A)))
End Function

'単位ベクトルVと点Pの距離
Private Function Lng_V_P(ByVal V As Variant, ByVal P As Variant) As Double
    Lng_V_P = Abs(Cross2d(V, P))
End Function

'単位ベクトル
Private Function Normaliz2d(ByVal V1 As Variant, ByVal V2 As Variant) As Variant
    Dim vec: vec = Sub2d(V2, V1)
    Dim tmp: tmp = Sqr(Dot2d(vec, vec))
    If EQ(tmp, 0#) Then
        Normaliz2d = Empty
        Exit Function
    End If
    Normaliz2d = Array(vec(0) / tmp, vec(1) / tmp)
End Function

'差2D
Private Function Sub2d(ByVal V1 As Variant, ByVal V2 As Variant) As Variant
    Sub2d = Array(V1(0) - V2(0), V1(1) - V2(1))
End Function

'内積2D
Private Function Dot2d(ByVal V1 As Variant, ByVal V2 As Variant) As Double
    Dot2d = V1(0) * V2(0) + V1(1) * V2(1)
End Function

'外積2D
Private Function Cross2d(ByVal V1 As Variant, ByVal V2 As Variant) As Double
    Cross2d = V1(0) * V2(1) - V1(1) * V2(0)
End Function


'*** etc ***
'初期化済みコレクション生成
Private Function InitRangeList(ByVal Count&) As Collection
    Dim List As Collection: Set List = New Collection
    Dim i&
    For i = 1 To Count
        List.Add i
    Next
    Set InitRangeList = List
End Function

'初期化済み配列生成
Private Function InitValueAry(ByVal Count&, ByVal Value&) As Variant
    Dim Ary(): ReDim Ary(Count + 1)
    Dim i&
    For i = 1 To Count
        Ary(i) = Value
    Next
    InitValueAry = Ary
End Function

'長さ順の列挙用Idxを取得するQIソート
Private Sub Q_ISort_List(ByRef IdxList As Collection, ByVal LngList As Collection)
    Dim THREASHOLD&: THREASHOLD = 16 '64
    Dim Stack As Collection: Set Stack = New Collection
    Stack.Add 1, CStr(Stack.Count + 1)
    Stack.Add IdxList.Count, CStr(Stack.Count + 1)
    
    Dim Pivot, Temp1, Temp2
    Dim LeftIdx&, RightIdx&, i&, j&
    Do While Stack.Count > 0
        LeftIdx = Stack(CStr(Stack.Count - 1))
        RightIdx = Stack(CStr(Stack.Count))
        Stack.Remove Stack.Count
        Stack.Remove Stack.Count
        'クイックソート
        If LeftIdx < RightIdx Then
            Pivot = LngList(IdxList((LeftIdx + RightIdx) / 2))
            i = LeftIdx
            j = RightIdx
            
            Do While i <= j
                Do While LngList(IdxList(i)) > Pivot
                    i = i + 1
                Loop
                Do While LngList(IdxList(j)) < Pivot
                    j = j - 1
                Loop
                If i <= j Then
                    Temp1 = IdxList(i)
                    Temp2 = IdxList(j)
                    IdxList.Add Temp1, After:=j
                    IdxList.Remove j
                    IdxList.Add Temp2, After:=i
                    IdxList.Remove i
                    i = i + 1
                    j = j - 1
                End If
            Loop
            
            If RightIdx - i >= 0 Then
                If RightIdx - i <= THREASHOLD Then
                    ComboInsertionSort IdxList, i, RightIdx, LngList
                Else
                    Stack.Add i, CStr(Stack.Count + 1)
                    Stack.Add RightIdx, CStr(Stack.Count + 1)
                End If
            End If
            
            If j - LeftIdx >= 0 Then
                If j - LeftIdx <= THREASHOLD Then
                    ComboInsertionSort IdxList, LeftIdx, j, LngList
                Else
                    Stack.Add LeftIdx, CStr(Stack.Count + 1)
                    Stack.Add j, CStr(Stack.Count + 1)
                End If
            End If
        End If
    Loop
End Sub

'長さ順の列挙用Idxを取得するQIソート用
Private Sub ComboInsertionSort(ByRef IdxList, ByVal MinIdx&, ByVal MaxIdx&, ByVal LngList As Collection)
    Dim Temp1, Temp2
    Dim i&, j&: j = 1
    For j = MinIdx To MaxIdx
        i = j - 1
        Do While i >= 1
        
            If LngList(IdxList(i + 1)) > LngList(IdxList(i)) Then
                Temp1 = IdxList(i + 1)
                Temp2 = IdxList(i)
                IdxList.Add Temp2, After:=i + 1
                IdxList.Remove i + 1
                IdxList.Add Temp1, After:=i
                IdxList.Remove i
            Else
                Exit Do
            End If
            i = i - 1
        Loop
    Next
End Sub

重複線をチェックしている "GetOverlapList" のクズっぷりが半端じゃないの
ですが・・・。(一時的に配列に入れて、長さの短いものだけを抜き出すとか
すれば良いかも)

前回のものと比較する為、各2回実行してみました。

Ver0.0.1

** Obj Start 001 ** :
POLY_TOL-0.1 : OVER_TOL-0.1
CrvLst- 3955個 : 2.021s
RngLst- 3955個 : 2.6s
LngLst- 3955個 : 4.21s
PolyLst- 3955個 : 15.424s
EnumLst- 3955個 : 17.562s
OverLst- 21779個 : 746.115s
SelectOverCrv - 2876個 : 748.404s

** Obj Start 001 ** :
POLY_TOL-0.1 : OVER_TOL-0.1
CrvLst- 3955個 : 3.537s
RngLst- 3955個 : 4.155s
LngLst- 3955個 : 5.442s
PolyLst- 3955個 : 18.873s
EnumLst- 3955個 : 21.02s
OverLst- 21779個 : 752.552s
SelectOverCrv - 2876個 : 755.09s

---------------------
Ver0.0.2
(分割最大レベル Lv5)

** Obj Start 002 ** :
POLY_TOL-0.1 : OVER_TOL-0.1
CrvLst- 3955個 : 2.533s
RngLst- 3955個 : 3.148s
LngLst- 3955個 : 4.44s
PolyLst- 3955個 : 16.873s
QuadLst- 290個 : 40.142s
OverLst- 2487個 : 75.37s
SelectOverCrv - 2487個 : 76.099s

** Obj Start 002 ** :
POLY_TOL-0.1 : OVER_TOL-0.1
CrvLst- 3955個 : 3.575s
RngLst- 3955個 : 4.204s
LngLst- 3955個 : 5.51s
PolyLst- 3955個 : 19.187s
QuadLst- 290個 : 43.989s
OverLst- 2487個 : 79.469s
SelectOverCrv - 2487個 : 80.05s

処理速度は1割ぐらいまで短縮できたのですが、
SelectOverCrvの行の個数が重複線と判断したものです。
総当りに比べて、400個ぐらい見落としている・・・。
何処が悪いんだろう?

又、QuadLstの行の290個は、分割した空間内に要素が存在していた
空間数なのですが、最大レベル5の場合の空間数は1365個です。
(殆どが利用されていない)
良く考えなかったので(と言うかわかっていなかった為)オリジナルの
ように大量の配列を用意していたのですが、空間番号をキーとした
ハッシュテーブルを利用すれば、無駄に大きな配列を用意する必要が無い事に、
今朝の通勤中に気が付きました。 ん~直すかな・・・。