C#ATIA

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

配列内の重複除去した配列と重複していた値の配列の取得

CATIA V5と言うよりVBAです。

タイトルが分かりにくいのですが、重複した値を持つ配列があります。
そこから重複を除去した配列を取得しつつ、重複した値が何か? も
取得したいので、わがままな関数を作りました。

'vba 配列の重複削除のわがまま仕様

Option Explicit

Sub CATMain()
    
    Dim ary As Variant
    ary = Array(3, 1, 2, 4, 4, 3, 2, 2, "A", "B", "A")
    
    Dim resultArray As Variant
    resultArray = get_remove_duplicates_array(ary)
    
    dump_array resultArray(0)
    dump_array resultArray(1)

End Sub


' 配列の重複削除
' return array(array,array) - 0:重複無し配列, 1:重複した配列
Private Function get_remove_duplicates_array( _
    ByVal ary As Variant) _
    As Variant

    Dim dict_unique As Object
    Set dict_unique = CreateObject("Scripting.Dictionary")

    Dim dict_duplicates As Object
    Set dict_duplicates = CreateObject("Scripting.Dictionary")
    
    Dim i As Long
    Dim value As Variant
    For i = 0 To UBound(ary)
        value = ary(i)

        If Not dict_unique.exists(value) Then
            dict_unique.Add value, 0
            GoTo continue
        End If
        
        If Not dict_duplicates.exists(value) Then
            dict_duplicates.Add value, 0
            GoTo continue
        End If
        
continue:
    Next
    
    get_remove_duplicates_array = Array( _
        dict_unique.keys(), _
        dict_duplicates.keys() _
    )

End Function


Private Sub dump_array( _
    ByVal ary As Variant)
    
    Debug.Print "*****"
    Dim i As Long
    For i = 0 To UBound(ary)
        Debug.Print ary(i)
    Next
End Sub

実行結果はこんな感じです。

*****
 3 
 1 
 2 
 4 
A
B
*****
 4 
 3 
 2 
A

先の出力が重複無しで、後が重複していた値です。

・・・えぇ分かってます。本来であれば
重複除去の関数と重複している値のみを返す関数の二つ作るべきだと。

数字のバルーン文字の最小最大を取得する

CATIA V5です。

業務で測定用の図面を作成するのですが、寸法にバルーンでナンバリング
してます。 ・・・面倒です。

自動化出来れば良いのですが、拘る部分もあり、なかなか良い方法を
思い付かないのでチマチマやってます。

極まれに、修正で番号を追加となった際に "一体幾つが最後の番号なのか?"
を見失わない為に、図面枠外にメモってます。

驚くほどに原始的・・・。ミスする可能性もあります。


重い腰を上げて、数字のバルーン文字の最小最大を確認する為のマクロを
作りました。

'vba Draw_Balloon_Number_Info Ver0.0.1
'バルーン文字の数値のものの先頭と最後を表示

Option Explicit

Sub CATMain()

    'ドキュメントのチェック
    'If Not KCL.CanExecute("DrawingDocument") Then Exit Sub
    
    'バルーン文字の数値のものをソートして取得
    Dim balloonNumbers As Variant
    balloonNumbers = quick_sort( _
        get_balloon_text_numeric_array( _
            get_balloon_list() _
        ) _
    )

    Dim msg As String
    If IsEmpty(balloonNumbers) Then
        msg = "数字のバルーンが見つかりませんでした"
    Else
        msg = "先頭の番号:" & balloonNumbers(0) & vbCrLf & _
        "最後の番号:" & balloonNumbers(UBound(balloonNumbers))
    End If
    
    MsgBox msg

End Sub


Private Function get_balloon_text_numeric_array( _
    ByVal balloonList As Collection) _
    As Variant
    
    Dim numbers As Collection
    Set numbers = New Collection
    
    Dim balloon As DrawingText
    Dim txt As String
    For Each balloon In balloonList
        txt = balloon.Text
        If Not IsNumeric(txt) Then GoTo continue
        
        numbers.Add (Val(txt))

continue:
    Next

    get_balloon_text_numeric_array = collection_to_array(numbers)

End Function


Private Function collection_to_array( _
    lst As Collection) _
    As Variant

    If lst.count < 1 Then
        collection_to_array = Empty
        Exit Function
    End If

    Dim ary() As Variant
    ReDim ary(lst.count - 1)

    Dim i As Long
    For i = 1 To lst.count
        ary(i - 1) = lst(i)
    Next

    collection_to_array = ary

End Function


Private Function get_balloon_list() _
    As Collection

    Dim dDoc As DrawingDocument
    Set dDoc = CATIA.ActiveDocument

    Dim sel As Selection
    Set sel = dDoc.Selection
    
    CATIA.HSOSynchronized = False
    
    sel.Search "CATDrwSearch.DrwBalloon,all"

    Dim balloons As Collection
    Set balloons = New Collection

    Dim i As Long
    For i = 1 To sel.Count2
        balloons.Add sel.Item(i).value
    Next
    
    sel.Clear

    CATIA.HSOSynchronized = True

    Set get_balloon_list = balloons

End Function


'非破壊非再帰クイック&挿入ソート
'参考 https://foolexp.wordpress.com/2011/10/29/%e3%82%af%e3%82%a4%e3%83%83%e3%82%af%e3%82%bd%e3%83%bc%e3%83%88%e3%81%a8%e6%8c%bf%e5%85%a5%e3%82%bd%e3%83%bc%e3%83%88%e3%81%ae%e3%83%8f%e3%82%a4%e3%83%96%e3%83%aa%e3%83%83%e3%83%89/
Private Function quick_sort( _
    ByVal ary As Variant) As Variant

    If IsEmpty(ary) Then
        quick_sort = Empty
        Exit Function
    End If

    Dim stack As Object
    Set stack = CreateObject("Scripting.Dictionary")
   
    Dim leftIdx As Long
    Dim rightIdx As Long
    Dim pivot As Variant
    Dim tPivot(2) As Variant
    Dim temp As Variant
   
    Dim i As Long
    Dim j As Long
    stack.Add stack.count + 1, LBound(ary)
    stack.Add stack.count + 1, UBound(ary)
    Do While stack.count > 0
               
        leftIdx = stack(stack.count - 1)
        rightIdx = stack(stack.count)
        stack.Remove stack.count
        stack.Remove stack.count

        'クイックソート
        If leftIdx < rightIdx Then
       
            pivot = ary((leftIdx + rightIdx) / 2)
           
            i = leftIdx
            j = rightIdx
           
            Do While i <= j
           
                Do While ary(i) < pivot
                    i = i + 1
                Loop
           
                Do While ary(j) > pivot
                    j = j - 1
                Loop
           
                If i <= j Then
                    temp = ary(i)
                    ary(i) = ary(j)
                    ary(j) = temp
                   
                    i = i + 1
                    j = j - 1
                End If
           
            Loop
           
            If rightIdx - i >= 0 Then
                If rightIdx - i <= 10 Then
                    insertion_sort ary, i, rightIdx
                Else
                    stack.Add stack.count + 1, i
                    stack.Add stack.count + 1, rightIdx
                End If
            End If
           
            If j - leftIdx >= 0 Then
                If j * leftIdx <= 10 Then
                    insertion_sort ary, leftIdx, j
                Else
                    stack.Add stack.count + 1, leftIdx
                    stack.Add stack.count + 1, j
                End If
            End If
        End If
   
    Loop

    quick_sort = ary
End Function

Private Function insertion_sort( _
    ary As Variant, _
    minIdx As Long, _
    maxIdx As Long)

    '挿入ソート
    Dim i As Long, j As Long
    Dim temp As Variant
    j = 1
    For j = minIdx To maxIdx
        i = j - 1
        Do While i >= 0
            If ary(i + 1) < ary(i) Then
                temp = ary(i + 1)
                ary(i + 1) = ary(i)
                ary(i) = temp
            Else
                Exit Do
            End If
            i = i - 1
        Loop
    Next
    
    insertion_sort = ary
End Function

ソート処理が必要なのですが、Excelであればあるようなのですが
VBAは無いんですね・・・。

こちらのクイック・挿入ハイブリッドソートをお借りしました。
クイックソートと挿入ソートのハイブリッド | 愚者の経験
破壊的部分や大文字な部分は好みの関係で修正しています。

クイック・挿入ハイブリッドソートが一番高速だと言う事は、
僕も知っています。

実際にこの様な図面で

マクロを使用するとこんな感じです。

本当は番号の重複や欠番等のチェックも入れたいのですが、
ご覧の通り、まだまだバルーン作らなきゃいけないんです。
(しかも3枚組の1枚目)

〇追記
実際使ったら全バルーンが検索されていたので、
アクティブシート内だけにする為、get_balloon_list関数内を
変更しました。

    'sel.Search "CATDrwSearch.DrwBalloon,all"

    sel.Add dDoc.sheets.ActiveSheet
    sel.Search "CATDrwSearch.DrwBalloon,sel"

カスタムスレッド用XMLジェネレーター

これ、すごいですね。
Custom threads xml generator - Autodesk Community

C#製の実行ファイルとソースファイルが添付されています。
・・・せっかくならFusion360のアドインだと良いのにな。

ただ、こちらのモデル化する際の条件があるはずなんです。

XMLファイルで重要なのは、
"Major diameter"
"Pitch diameter"
"Minor diameter"
の3つの数値です。

自分が知っている計算出来るのは、こちらのサイトです。
Metric screw thread: M Profile calculator

計算式を知りたいな。
誰かが、Excelファイルでまとめていたような気がしたのですが・・・。

DrawingTextの"選択可能"を切り替える

CATIA V5です。

DrawingTextを選択出来たり、出来なかったりするのは、プロパティの
こちらを変更すれば可能です。

"選択可能"のチェックを外した際、再設定する為にはGUIの場合は
検索するしかなさそうです。

選択出来ないようにしたのに、検索での選択出来るのは
矛盾しているような気もしますが、これが出来ないとゴミとして
残っちゃうので、助かります。


この"選択可能"を切り替えるサンプルです。

'vba
'Drawのテキストの"選択可能"状態の反転

Option Explicit

Sub CATMain()

    Dim doc As DrawingDocument
    Set doc = CATIA.ActiveDocument
    
    Dim sheet As DrawingSheet
    Set sheet = doc.sheets.ActiveSheet
    
    Dim view As DrawingView
    Set view = sheet.views.ActiveView

    '目的のテキスト
    Dim drawTxt As DrawingText
    Set drawTxt = view.Texts.Item(1)

    'VisPropertySetの取得
    Dim sel As Selection
    Set sel = doc.Selection
    
    Dim vis As VisPropertySet
    Set vis = sel.VisProperties

    'テキストの選択
    sel.Clear
    sel.Add drawTxt

    'プロパティの"選択可能"状態の取得
    'http://catiadoc.free.fr/online/interfaces/enum_CatVisPropertyPick.htm
    Dim pickState As CatVisPropertyPick
    vis.GetPick pickState

    'プロパティの"選択可能"状態の反転
    Dim pickSetting As CatVisPropertyPick
    If pickState = catVisPropertyPickAttr Then
        pickSetting = catVisPropertyNoPickAttr
    Else
        pickSetting = catVisPropertyPickAttr
    End If

    vis.SetPick pickSetting

End Sub

これ、質問がDrawingTextだったのでこの様にしましたが、
ボディ等他の要素でも同じです。

シンボリックリンク

Fusion360のインストールフォルダは、任意の位置に
指定する事が出来ません。

インストール後であればシンボリックリンクを利用して
別の場所に移動できる との事です。
Solved: Re: Can I change the location where local files are stored? - Autodesk Community

シンボリックリンクって使った事が無かったのですが、
あれかな・・・。

平坦な面の外周をオフセットした面を作成する

こちらも上手く行かない部分テストです。


左図の歪な面があります。これを均一にオフセットした外周を持つ
面を作成します。条件はXY平面に平行な平らな面です。

延長コマンドで可能ですが、こちらもTemporaryBRepManagerで
処理したい。

# Fusion360API Python script

import traceback
import adsk
import adsk.core as core
import adsk.fusion as fusion

def run(context):
    ui = core.UserInterface.cast(None)
    try:
        app: core.Application = core.Application.get()
        ui = app.userInterface
        des: fusion.Design = app.activeProduct
        root: fusion.Component = des.rootComponent

        # 各種データの取得
        body: fusion.BRepBody = root.bRepBodies[0]
        targetFace: fusion.BRepFace = body.faces[0]
        offsetValue = 1

        curves = [c.geometry for c in targetFace.edges]

        tmpMgr: fusion.TemporaryBRepManager = fusion.TemporaryBRepManager.get()
        tempWireBody, edgeMap = tmpMgr.createWireFromCurves(curves, False)
        offsetWires = []
        for wire in tempWireBody.wires:
            offsetWires.append(
                wire.offsetPlanarWire(
                    core.Vector3D.create(0,0,1),
                    offsetValue,
                    fusion.OffsetCornerTypes.CircularOffsetCornerType
                    # fusion.OffsetCornerTypes.LinearOffsetCornerType
                    # fusion.OffsetCornerTypes.ExtendedOffsetCornerType
                )
            )

        resBody: fusion.BRepBody = _get_union_body(offsetWires)
        resBody: fusion.BRepBody = tmpMgr.createFaceFromPlanarWires([resBody])


        dump_bodies([resBody])

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))



def _get_union_body(
    bodyLst: list) -> fusion.BRepBody:
    '''
    ブーリアン和のボディ取得
    '''

    return _get_boolean_body(
        bodyLst,
        fusion.BooleanTypes.UnionBooleanType
    )


def _get_boolean_body(
    bodyLst: list,
    booleanType: fusion.BooleanTypes) -> fusion.BRepBody:
    '''
    ブーリアンボディ取得
    '''

    resBody: fusion.BRepBody = None
    if len(bodyLst) < 0:
        resBody = None
    elif len(bodyLst) == 1:
        resBody = bodyLst[0]
    else:
        tmpMgr: fusion.TemporaryBRepManager = fusion.TemporaryBRepManager.get()
        resBody = bodyLst.pop()
        for b in bodyLst:
            try:
                tmpMgr.booleanOperation(
                    resBody,
                    b,
                    booleanType
                )
            except:
                pass

    return resBody


def dump_bodies(bodyLst: list):
    app: core.Application = core.Application.get()
    des: fusion.Design = app.activeProduct
    root: fusion.Component = des.rootComponent

    baseFeat: fusion.BaseFeature = None
    if des.designType == fusion.DesignTypes.ParametricDesignType:
        baseFeat = root.features.baseFeatures.add()

    bodies: fusion.BRepBodies = root.bRepBodies
    if baseFeat:
        try:
            baseFeat.startEdit()
            [bodies.add(body, baseFeat) for body in bodyLst]
        except:
            pass
        finally:
            baseFeat.finishEdit()
    else:
        [bodies.add(body) for body in bodyLst]

で、上の画像の様に上手く行きます。
何故あっちでは上手く行かないのか・・・。

ボディを回転移動させる

こちらが上手く行かないので、色々模索中です。
回転プロファイルを考える2 - C#ATIA

年末に諦めた処理を改めて見直す事にしました。

左図のような状態のボディを、軸を中心として垂直に回転させた
ボディを作り出したいのですが、ちょっとだけ条件があります。

単に回転コマンドで処理するのは簡単なのですが、処理を早めたいので
TemporaryBRepManagerを使って行いたい。


TemporaryBRepManager.transformメソッドで"回転"を"変換"で
行えば良いので、回転させるためのMatrix3Dを求める必要があります。
Fusion 360 Help

回転の為のMatrix3D.setToRotateToメソッドがあるので、これを
利用します。
Fusion 360 Help
二つのVector3Dが必要ですね。
setToRotationメソッドもありますが、2つのベクトルの角度を測定する際
どうしても近い方の角度を取得してしまい、場合によっては意図した方向の
逆の場合もあったため、諦めました。(上手く行けば楽だったのに・・・)
Fusion 360 Help

最初は気が付かなかったのですが、setToRotateToメソッドは常に原点中心で
回転するマトリックスになります。考えてみれば、ベクトルには位置情報が
無いため当然ですね。

最初の画像の状態の場合、原点と回転軸が一致しないため当然不都合です。

その為、
・軸上の位置から原点に移動
・回転
・元の位置に移動
を行う必要があります・・よね?(以前、何処かの数学のサイトで観た記憶があるのですが)


テストコードの為、全く使い道が無いのですがこんな感じです。

# Fusion360API Python script

import traceback
import adsk
import adsk.core as core
import adsk.fusion as fusion

def run(context):
    ui = core.UserInterface.cast(None)
    try:
        app: core.Application = core.Application.get()
        ui = app.userInterface
        des: fusion.Design = app.activeProduct
        root: fusion.Component = des.rootComponent

        # 各種データの取得
        body: fusion.BRepBody = root.bRepBodies[0]
        axis: fusion.ConstructionAxis = root.constructionAxes[0]
        point: core.Point3D = root.constructionPoints[0].geometry

        # クローン作成
        tmpMgr: fusion.TemporaryBRepManager = fusion.TemporaryBRepManager.get()
        clone: fusion.BRepBody = tmpMgr.copy(body)

        # 軸上の点から原点へのマトリックス
        vecToOrigin: core.Vector3D = point.vectorTo(root.originConstructionPoint.geometry)
        matToOrigin: core.Matrix3D = core.Matrix3D.create()
        matToOrigin.translation = vecToOrigin

        # 回転マトリックス
        cloneFace: fusion.BRepFace = clone.faces[0]
        normal: core.Vector3D = cloneFace.geometry.normal
        vecRotate: core.Vector3D = normal.crossProduct(axis.geometry.direction)
        matRotate: core.Matrix3D = core.Matrix3D.create()
        matRotate.setToRotateTo(
            vecRotate,
            core.Vector3D.create(0,0,1),
        )

        # 原点から軸上の点へのマトリックス
        vecFromOrigin: core.Vector3D = root.originConstructionPoint.geometry.vectorTo(point)
        matFromOrigin: core.Matrix3D = core.Matrix3D.create()
        matFromOrigin.translation = vecFromOrigin

        # 変換
        tmpMgr.transform(clone, matToOrigin)
        tmpMgr.transform(clone, matRotate)
        tmpMgr.transform(clone, matFromOrigin)

        # NG
        # matToOrigin.transformBy(matRotate)
        # matToOrigin.transformBy(matFromOrigin)

        # ダンプ
        dump_bodies([clone])


    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))


def dump_bodies(bodyLst: list):
    app: core.Application = core.Application.get()
    des: fusion.Design = app.activeProduct
    root: fusion.Component = des.rootComponent

    baseFeat: fusion.BaseFeature = None
    if des.designType == fusion.DesignTypes.ParametricDesignType:
        baseFeat = root.features.baseFeatures.add()

    bodies: fusion.BRepBodies = root.bRepBodies
    if baseFeat:
        try:
            baseFeat.startEdit()
            [bodies.add(body, baseFeat) for body in bodyLst]
        except:
            pass
        finally:
            baseFeat.finishEdit()
    else:
        [bodies.add(body) for body in bodyLst]

説明書きの通り、3個のマトリックスを作成してボディを3回変換しています。
年末に上手く行かなかった原因の一つは、コメント化している部分のやり方です。

        # NG
        # matToOrigin.transformBy(matRotate)
        # matToOrigin.transformBy(matFromOrigin)

3個のマトリックスを先に変換してしまい、ボディの変換自体は1回に
してしまっていた事が原因でした。

もう一つの原因が、Matrix3D.translationプロパティです。
"transformBy”はメソッドで破壊的です。 しかしtranslationは
プロパティなので扱いの違いに気が付きませんでした。

と、これが解決しても、完成には程遠い。(正直、諦めたい)