選択した円の中心座標を取得する(Product.ver)|CATIAマクロの作成方法

今回は「お問い合わせ」から頂いた内容です。

以前記事にした「選択した円の中心座標を取得するマクロ」で少し問題があったようです。

先日の記事でエッジの中心座標は求めることができたのですが、今回ほしかった座標というのが、プロダクト上での絶対座標であり、パート上の座標ではなかったのです。

以前のマクロで取得する中心座標はどうもCATPartの座標系を基準にしているようで、CATProductでCATPartの配置情報を変えたとしても同じ中心座標が取得されてしまう状態になっています。

というわけで、今回は以前紹介したコードを「CATProductの絶対座標系を基準とした円の中心座標を取得するためのコード」に書き換えていきたいと思います。

※今回のコードはCATProductには対応していますがCATPart単体には対応していません。
 両ドキュメントを対応させるにはTypeName関数を使った条件分岐などで行えると思います。

 

マクロ作成の前に

まず大前提して”CATProductの座標系を基準とした座標”を取得するためのメソッドは存在しないようです。(実際はあるのかもしれませんが)

そこで、どのようにすればCATProductの座標系で座標を取得できるかを色々調べていると、以下のページで本件と同じ内容が語られていました。
Cannot get global(assembly) coordinates – COE : Forums

上記ページにもそのようなメソッドは無いと語られていますが、あわせて以下のような解決案も語られていました。

If you want these coordinates given with respect to the CATProduct’s axis system you must apply an axis system transformation to the coordinates.
(座標をCATProductの軸系を基準にして与えたい場合は、座標に軸系変換を適用する必要があります。)

要はCATPartの座標系で取得した座標を、『行列』を使ってCATProductの座標系を基準とした座標に変換しようということです。

上記ページではその座標変換のための計算コードも書かれていたので、本ページではそのコードを解説しつつコピペでも使えるように少し書き直して紹介していきます。

 

完成コード

完成コードは以下の通りです。
※実行するプロシージャはCATMainの部分です。
   コピペで実行可能ですがエラー確認はあまりしていません。

Sub CATMain()

    Dim DOC As ProductDocument
    Set DOC = CATIA.ActiveDocument

    Dim Sel
    Set Sel = DOC.Selection

    Dim InputType(0)
    InputType(0) = "Edge"

    Dim Msg As String
    Msg = "穴(エッジ)を選択してください。"

    Dim Status As String
    Status = Sel.SelectElement2(InputType, Msg, False)
          If ((Status = "Cancel") Or (Status = "Undo")) Then
          Exit Sub
    End If

    Dim SelEdge As AnyObject
    Set SelEdge = Sel.Item(1).Value

    Dim oProduct As Product
    Set oProduct = Sel.Item(1).LeafProduct           '選択したエッジを含んでいるProductを取得

    Dim SPA As Workbench
    Set SPA = DOC.GetWorkbench("SPAWorkbench")

    Dim MyMeasure
    Set MyMeasure = SPA.GetMeasurable(SelEdge)

    Dim getvalues(2) As Variant                          'X,Y,Z座標を取得する用の配列を用意(Variant型)
    MyMeasure.GetCenter getvalues                        'MeasureableオブジェクトのGetCenterメソッドを使用

    Dim aRel(2) As Double                                '取得した座標をDouble型に変換(行列計算のコードで使用可能にするため)
    aRel(0) = getvalues(0)
    aRel(1) = getvalues(1)
    aRel(2) = getvalues(2)

    Dim aAbs() As Double                                 '行列計算結果を受け取る用の配列を用意(中身は空)

    Call Coord_Transform(aRel(), aAbs(), oProduct, True) '行列計算

    Dim OutputCoord(2)
    OutputCoord(0) = Format(aAbs(0), "0.000")            'X座標(少数第4位を四捨五入)
    OutputCoord(1) = Format(aAbs(1), "0.000")            'Y座標(少数第4位を四捨五入)
    OutputCoord(2) = Format(aAbs(2), "0.000")            'Z座標(少数第4位を四捨五入)

    MsgBox "X座標 = " & OutputCoord(0) & vbLf & _
           "Y座標 = " & OutputCoord(1) & vbLf & _
           "Z座標 = " & OutputCoord(2)

End Sub
'--------------------------------------------------------------------------------------------------------'
Sub Coord_Transform(aRel() As Double, aAbs() As Double, oProduct As Product, bRecursively As Boolean)
    
    Dim vProduct As Object, vCoord(11)
    Dim oFatherProduct As Product
    Dim aInv() As Double
    
    'Exit condition, empty object
    If oProduct Is Nothing Then Exit Sub
    
    'Redim absolute coords matrix
    On Error Resume Next
    ReDim aAbs(2)
    On Error GoTo 0
    
    'Calculate product coordinates
    Set vProduct = oProduct
    vProduct.Position.GetComponents vCoord
    
    'Calculate inverse matrix
    If Inv3x3(CDbl(vCoord(0)), CDbl(vCoord(1)), CDbl(vCoord(2)), _
              CDbl(vCoord(3)), CDbl(vCoord(4)), CDbl(vCoord(5)), _
              CDbl(vCoord(6)), CDbl(vCoord(7)), CDbl(vCoord(8)), aInv) Then
    Else
        MsgBox "Error, degenerate transformation", vbOKOnly
        Exit Sub
    End If
    
    'Calculate transformation
    aAbs(0) = vCoord(9) + aInv(0) * aRel(0) + aInv(1) * aRel(1) + aInv(2) * aRel(2)
    aAbs(1) = vCoord(10) + aInv(3) * aRel(0) + aInv(4) * aRel(1) + aInv(5) * aRel(2)
    aAbs(2) = vCoord(11) + aInv(6) * aRel(0) + aInv(7) * aRel(1) + aInv(8) * aRel(2)
    
    'If recursive option sepecified, search for parents and applies the transformation again
    If bRecursively Then
        
        'Try to assign parent
        Set oFatherProduct = Nothing
        On Error Resume Next
        Set oFatherProduct = oProduct.Parent.Parent
        On Error GoTo 0
        
        'If OK, recalculate coords
        If oFatherProduct Is Nothing Then
        Else
            aRel(0) = aAbs(0)
            aRel(1) = aAbs(1)
            aRel(2) = aAbs(2)
            Coord_Transform aRel, aAbs, oFatherProduct, True
        End If
        
    End If
    
End Sub
'--------------------------------------------------------------------------------------------------------'
Function Inv3x3(dX11 As Double, dX12 As Double, dX13 As Double, _
           dX21 As Double, dX22 As Double, dX23 As Double, _
           dX31 As Double, dX32 As Double, dX33 As Double, aInv() As Double) As Boolean
'***********************************************
'*
'* 3x3 matrix inverse calculation (direct)
'*
'***********************************************
    Dim dDet As Double
    
    ReDim aInv(8)
    
    Inv3x3 = False
    
    dDet = Det3x3(dX11, dX12, dX13, dX21, dX22, dX23, dX31, dX32, dX33)
    If dDet = 0 Then Exit Function
    
    aInv(0) = (dX22 * dX33 - dX23 * dX32) / Abs(dDet)
    aInv(1) = (dX13 * dX32 - dX12 * dX33) / Abs(dDet)
    aInv(2) = (dX12 * dX23 - dX13 * dX22) / Abs(dDet)
    aInv(3) = (dX23 * dX31 - dX21 * dX33) / Abs(dDet)
    aInv(4) = (dX11 * dX33 - dX13 * dX31) / Abs(dDet)
    aInv(5) = (dX13 * dX21 - dX11 * dX23) / Abs(dDet)
    aInv(6) = (dX21 * dX32 - dX22 * dX31) / Abs(dDet)
    aInv(7) = (dX12 * dX31 - dX11 * dX32) / Abs(dDet)
    aInv(8) = (dX11 * dX22 - dX12 * dX21) / Abs(dDet)
    
    Inv3x3 = True
    
End Function
'--------------------------------------------------------------------------------------------------------'
Function Det3x3(dX11 As Double, dX12 As Double, dX13 As Double, _
                dX21 As Double, dX22 As Double, dX23 As Double, _
                dX31 As Double, dX32 As Double, dX33 As Double) As Double
'***********************************************
'*
'* 3x3 matrix determinant calculation (direct)
'*
'***********************************************
                
    Det3x3 = dX11 * dX22 * dX33 + dX12 * dX23 * dX31 + dX21 * dX32 * dX13 - _
             dX13 * dX22 * dX31 - dX12 * dX21 * dX33 - dX23 * dX32 * dX11

End Function

かなり長いコードですが、55行以降の内容はすべて先ほどのページのコードをそのままコピペしたもので、行列の計算をするためだけのコードとなっています。

マクロの使用方法は前回と同じで、中心座標を取得したい円のエッジを選択するだけです。
※CATPartが表示モード(Visualization Mode)の場合、エッジを選択することができません。
 エッジを選択するCATPartだけは設計モード(Design Mode)に切り替える必要があります。

 

コード解説

ここでは上記のコードをざっくりと処理別に分けて簡単に説明していきます。
(主に座標変換のコードについての内容ですが)

座標変換のコードは元のページでも説明されているため合わせて読んでみて下さい。
Cannot get global(assembly) coordinates – COE : Forums

英語が苦手な方は「DeepL翻訳」を使えばほとんどを理解できるレベルで翻訳してくれます。
 

中心座標の取得

まず始めに選択した正円エッジから中心座標を取得します。
中心座標を取得する方法は「選択した円の中心座標を取得するマクロ」を参照ください。

取得できる座標はCATPartの座標となってしまいますが、現時点では問題ありません。
この取得した座標を以下の方法で座標変換しCATProductでの座標に変換します。
 

座標変換の計算

座標変換を行うには行列の計算を行う必要があります。
(コード的には55行以降がすべて座標変換の計算式)

まず大前提としてCATProductの最上位のProduct(RootProduct)の座標系の成分を取得し、行列のように表示すると以下のようになります。

そして、CATProduct上で適当に回転させたCATPartの座標系の成分は以下のように取得することができます。(この数値はCATPartの配置情報によって変動します)

 
上記の前提を理解したうえで行列の公式をみてみましょう。

行列A×行列Aの逆行列=単位行列

 
単位行列とはCATProductで表示したような対角成分が1でそれ以外が0である行列のことをいいます。
つまり行列AをCATPartの座標系としたとき、「行列Aの逆行列」さえ求めることができればCATProductの座標系に変換ができることがわかります。

逆行列を求めるには以下の公式を使います。
(※公式の詳しい解説は「逆行列 公式」等で検索してみて下さい)

これにより逆行列を求めることも可能なことがわかりました。

必要なものは取得できるから
後は計算で求めるってことですね!

いろいろ書いてきましたがここでの内容を一言でまとめると
CATPartの座標系の逆行列を求めて、CATPartの座標系の行列にかける
ということです。
 

計算コードの確認

座標変換がどのように行われているかを理解したところでコードを見ていきます。
メインプロシージャで「Coord_Transform」を呼び出せば計算が行われます。

Coord_Transformで必要な材料は以下のとおりです。

aRel() As Double              ⇒ 変換したい座標の入った配列
aAbs() As Double             ⇒ 配列だけ宣言しておけばOK(ここに計算結果が入る)
oProduct As Product        ⇒ エッジを含むCATPart
bRecursively As Boolean  ⇒ TrueにしておけばOK

詳しくはこのコードが書かれていたページを参照ください。
 

「Coord_Transform」の処理の流れはざっくりいうと以下のとおりです。

① CATPartの座標系の逆行列を求める
「Coord_Transform」内の「Function Inv3x3」では先ほどの公式を使い逆行列を求めています。
ただ公式をそのまますべて書くとコードがごちゃごちゃするため「Function Det3x3」を用意して複数に分けています。ちなみに「Function Det3x3」では公式の分母の部分の計算をしているだけです。

② 取得した中心座標を逆行列を使って変換する
最終的にはここで求めた逆行列を使って座標変換を行っています。

 icon-code 83~86行目 

'Calculate transformation
aAbs(0) = vCoord(9) + aInv(0) * aRel(0) + aInv(1) * aRel(1) + aInv(2) * aRel(2)
aAbs(1) = vCoord(10) + aInv(3) * aRel(0) + aInv(4) * aRel(1) + aInv(5) * aRel(2)
aAbs(2) = vCoord(11) + aInv(6) * aRel(0) + aInv(7) * aRel(1) + aInv(8) * aRel(2)

変換後の座標はaAbs()に入ります。
※aAbs(0) → X座標 、aAbs(1) → Y座標 、aAbs(2) → Z座標
 

取得した座標をユーザー向けに変換

上記のままでは変換した座標は非常に細かい数値で取得されてしまいます。
このままではメッセージボックスで表示してもゴチャゴチャしますし、計算するにしてもユーザー目線で考えると非常に扱いづらいです。

そこでCATIAのデフォルトの有効数字の小数第3位になるように小数第4位を四捨五入します。

 icon-code 46~48行目

OutputCoord(0) = Format(aAbs(0), “0.000")  'X座標(少数第4位を四捨五入)
OutputCoord(1) = Format(aAbs(1), “0.000")  'Y座標(少数第4位を四捨五入)
OutputCoord(2) = Format(aAbs(2), “0.000")  'Z座標(少数第4位を四捨五入)

“0.000"の部分を"0.00″にすれば小数第3位を四捨五入、"0.0000″にすれば小数第5位を四捨五入となります。取得した座標の使用用途に合わせて任意で書き換えましょう。

最終的に取得した座標はOutputCoord()の中に格納されています。
※OutputCoord(0) → X座標 、OutputCoord(1) → Y座標 、OutputCoord(2) → Z座標

 

まとめ

今回はCATProduct上で選択したエッジ(正円)の中心座標値を取得すマクロの紹介でした。
前回のマクロに比べかなり内容が濃くなりましたがやっていることはあまり難しいものではありません。取得したCATPartの座標系を単位行列、つまりはCATProductの座標系に変換するために計算をしているだけです。

マクロさわりたての方は理解するまでに時間がかかると思いますが、1文ずつどのような処理がされているのかを確認していけば必ず理解はできます。前回のマクロと合わせて今回の応用的な内容も勉強してみて下さい。
 

【前回】選択した円の中心座標を取得するマクロ
目次へ戻る
 

icon-book CATIAマクロを本気で勉強するなら

2024年8月26日CATIA,CATIAマクロ