C#ATIA

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

VBAのリスト問題1

"VBAのリスト"としましたが、使うなら配列か?コレクションか?
と言う事です。
多くの先人の方々が、実測し答えも出ているような気もしますが、
githubで公開されているものや自作も含め、そろそろ自分の中での
答えを出したい気持ちがあります。
(配列とコレクションであれば、一時的にコレクションを使って
最終的には動的配列を使う が僕の中の答えです)

まず、処理時間を測定する為のStopWatchクラスです。

'vba StopWatch.cls

Option Explicit

Private startTime As Double
Private splitTime As Double


Private Sub Class_Initialize()
    startTime = -1
    splitTime = -1
End Sub


Public Sub start()
    startTime = Timer
    splitTime = startTime
End Sub


Public Function total( _
    Optional ByVal msg As String = "") As Double
    
    Dim t As Double
    t = Timer - startTime
    total = t
    
    Call dump_time(t, msg)
End Function


Public Function split(Optional ByVal msg As String = "")
    Dim t As Double
    t = Timer - splitTime
    split = t
    
    Call dump_time(t, msg)
    splitTime = Timer
End Function


Private Function is_started() As Boolean
    is_started = IIf(startTime > 0, True, False)
End Function


Private Sub dump_time( _
    ByVal timeVal As Double, _
    Optional ByVal msg As String = "")

    If Not is_started Then
        dump "---"
        Exit Sub
    End If
    
    dump msg & timeVal & "s"
End Sub


Private Sub dump(ByVal msg As String)
    Debug.Print msg
End Sub

startメソッドで測定開始し、totalでstartからの時間を出力します。
splitは前回にsplitからの時間を出力し、最初のsplitの時はstartからの
時間の出力です。
resetを用意すべきかどうか悩みましたが、もう一度インスタンス
すれば済む話なので、ありません。

念の為Long型とObject型の両方を試しておきたい為、処理で
利用するだけの無意味なクラスです。

'vba myObj.cls

Option Explicit

Public x As Long

何も無いです。ほんの気持ちです。

続いてエントリーポイントとなるメインの関数(プロシージャ?)です。

'vba main.bas

Option Explicit

Dim sw_ As StopWatch

Sub test_list()

    Dim Count As Long
    Count = 50000
    
    Set sw_ = New StopWatch

    Debug.Print vbCrLf & "**コレクション**"
    sw_.start
    Call test1(Count)
    sw_.total "**終了** :"

End Sub

"Call test1(Count)"を書き換える、又は追加しながら実行します。


それでは、テストする関数です。
まずは無難に普通のコレクションにします。

Private Sub test1( _
    ByVal Count As Long)
    
    Dim lst As Collection
    Set lst = New Collection
    
    Dim i As Long
    For i = 0 To Count
        lst.Add i
    Next
    
    sw_.split "Long 代入:"
    
    Dim x As Long
    For i = 1 To lst.Count
        x = lst(i)
    Next
    sw_.split "呼出:"

    
    sw_.split "--:"
    Set lst = New Collection
    Dim obj As myObj
    Set obj = New myObj
    
    For i = 0 To Count
        lst.Add obj
    Next
    
    sw_.split "Object 代入:"
    
    For i = 1 To lst.Count
        Set obj = lst.Item(i)
    Next
    
    sw_.split "呼出:"

End Sub

"for eachの方が早いじゃん"と言われるのは十分承知していますが、
統一してforで処理します。
処理的には、Long型の代入と取り出しを行い、続いてオブジェクト型の
代入と取り出しを行います。実際に実行した結果の一例です。

**コレクション**
Long 代入:0.0390625s
呼出:24.4453125s
--:0s
Object 代入:0.0703125s
呼出:24.390625s
**終了** :48.96875s

LongとObjectに大きな差はありませんね。
代入に比べ呼び出しは多くの時間がかかる事がわかりました。
"コレクションは遅い"と言われる理由は、呼び出しが遅い事が
原因の様に感じます。

続いてキー付きのコレクションです。実はこの機能はかなり
後になって知りました。要はdictionary型のような扱いが
出来るようですが、代入のaddメソッドのkeyとvalue
他の多くの言語と逆です・・・。(気持ちはわかる)
癖強めですが、処理が早いと言う事は聞いたことがあります。

Private Sub test2( _
    ByVal Count As Long)

    Dim lst As Collection
    Set lst = New Collection
    
    Dim i As Long
    For i = 0 To Count
        lst.Add i, CStr(i)
    Next
    
    sw_.split "Long 代入:"
    
    Dim x As Variant
    For i = 0 To lst.Count - 1
        x = lst.Item(CStr(i))
    Next

    sw_.split "呼出:"

    
    sw_.split "--:"
    Set lst = New Collection
    Dim obj As myObj
    Set obj = New myObj
    
    For i = 0 To Count
        lst.Add obj, CStr(i)
    Next
    
    sw_.split "Object 代入:"
    
    For i = 0 To lst.Count - 1
        Set obj = lst(CStr(i))
    Next
    
    sw_.split "呼出:"

End Sub

実行結果の1例がこちら

**コレクション-キー付き**
Long 代入:0.890625s
呼出:0.3671875s
--:0s
Object 代入:0.984375s
呼出:0.359375s
**終了** :2.65625s

通常のコレクションに比べ、若干代入は遅いのですが、
呼び出しは桁違いに速いです。(恐らくForだからだと思います)

ダメだ、眠い。 続きは後日に。

リファクタリング

昨年末の予想通りすっかり余裕が無く、更新していませんでした。

あまり書ける事が無いものの、Bingチャット(Copilot)は
チョロチョロ試しています。
ある処理でもうちょっと良い方法が無いものかと思い、
試した所、ソコソコの長さのものが一つの関数で出力されました。

"いくら何でも、おかしい・・・"と思い、追加の注文(背景紫が僕)

逆切れ・・・とまでは言いませんが、まさか逆に"教えろ"と言われるとは
思っていませんでした。
(具体的に何処をリファクタリングすべきか指摘したら、確かに行いましたが
指摘箇所以外はしてくれませんでした)

リファクタリングのメリット・デメリットは、先人の方々がいっぱい記載
してくれているので割愛しますが、個人的な意味合いとタイミングです。

一つの関数に大量の処理を行うと、デバッグ時のブレークポイント
止めた際、変数がドバっと大量に表示されチェックすべき項目が増えて為
憂鬱になりませんかね?
そもそも変数が多くなると、変数名に悩みませんか?僕の場合、
ボキャブラリー不足で名前を決めるだけで悩んじゃいます。

逆に小さな処理を行うシンプルな関数であれば、変数が数個だけの
場合が多いのでシンプルな変数名でも構わないと思ってます。
その様な場合でも、関数名や引数名は若干長くても分かりやすい
名前にすべきだと思ってます。

例えば

function get_points() as variant

より

function get_points_by_sketch() as variant

の方が、明確でしょう。

コレクション内のオブジェクトのプロパティを利用したソート2

こちらの続きです。
C#ATIA

Bingチャットも良いのですが、こちらのコメント欄に記載した
phindと言うサービスの存在を知りました。
Bingチャットでコードレビュー - C#ATIA

コーディングに関してはGPT-4を上回ると言う事らしいですが、
それ以上にBingチャットとは比較にならない程、返答が速いです。

同様の質問をした際の返答の回答部分はこの様な感じでした。

Function DistanceFromOrigin() As Double
   DistanceFromOrigin = Sqr(Me.x ^ 2 + Me.y ^ 2)
End Function


Function ComparePoints(point1 As Point2D, point2 As Point2D) As Long
   If point1.DistanceFromOrigin < point2.DistanceFromOrigin Then
       ComparePoints = -1
   ElseIf point1.DistanceFromOrigin > point2.DistanceFromOrigin Then
       ComparePoints = 1
   Else
       ComparePoints = 0
   End If
End Function


Sub SortPoints(points As Collection)
   Dim i As Long
   Dim j As Long
   Dim temp As Variant
   Dim hasSwapped As Boolean

   Do
       hasSwapped = False
       For i = 1 To points.Count - 1
           For j = i + 1 To points.Count
               If ComparePoints(points(i), points(j)) > 0 Then
                  Set temp = points(i)
                  Set points(i) = points(j)
                  Set points(j) = temp
                  hasSwapped = True
               End If
           Next j
       Next i
   Loop Until Not hasSwapped
End Sub

肝心のソート部分が"Function"では無く"Sub"だったので、
望んでいるものでは無かったのですが、Bingチャット同様に
スワップ処理をコレクション要素の再代入になっている為、
NGです。

その後何度かエラーを指摘し得られた回答のコード部分が
こちらです。

Public Function ComparePoints(point1 As Point2D, point2 As Point2D) As Long
 Dim distance1 As Double
 Dim distance2 As Double

 distance1 = Sqr((point1.x ^ 2) + (point1.y ^ 2))
 distance2 = Sqr((point2.x ^ 2) + (point2.y ^ 2))

 If distance1 < distance2 Then
     ComparePoints = -1
 ElseIf distance1 > distance2 Then
     ComparePoints = 1
 Else
     ComparePoints = 0
 End If
End Function


Public Function SortPoints(points As Collection) As Collection
  Dim arr() As Point2D
  Dim sortedPoints As New Collection
  Dim i As Long
  Dim j As Long
  Dim temp As Point2D

  ' Step 1: Copy all elements to temporary array
  ReDim arr(1 To points.Count)
  For i = 1 To points.Count
      Set arr(i) = points(i)
  Next i

  ' Step 2: Sort the array using bubble sort
  For i = 1 To points.Count - 1
      For j = i + 1 To points.Count
          If ComparePoints(arr(i), arr(j)) > 0 Then
              ' Swap arr(i) and arr(j)
              Set temp = arr(i)
              Set arr(i) = arr(j)
              Set arr(j) = temp
          End If
      Next j
  Next i

  ' Step 3: Create new sorted collection
  For i = LBound(arr) To UBound(arr)
      sortedPoints.Add arr(i)
  Next i

  Set SortPoints = sortedPoints
End Function

Bingチャット同様で、

コレクション→配列→ソート→コレクション

で処理されています。もうVBAでは定石な手段なのでしょう。


実は、年末に試した際に一回目の回答で得られたものに驚きました。
(控えていない上、試しに実行もしませんでした。スイマセン)
ソート関数の引数で、ソート条件をaddressOfで渡しているサンプルを
書き出したのですが、良く調べたらVBAではアドレスから関数の
呼び出しは基本的には出来ない仕様のようなので、恐らく
動かなかったかな・・・。

addressOfを利用する方法を解説しているサイトは、ここぐらいでしょう。
いげ太の日記


phindはVSCodeの拡張があります。VBAVSCodeで開発出来ないかな・・・。

コレクション内のオブジェクトのプロパティを利用したソート1

あけましておめでとうございます。
本年もマイペースでやっていきます。

年明け早々、あの様な震災があったり、それに伴った人災(航空機の接触)と
心痛む出来事の連続で唖然としつつ、普通に正月を迎えられることが
非常に幸せな事なのだと感じております・・・。
(ご冥福をお祈りいたします)

あぁ今年は目標を立てない事にします。昨年一つも達成しなかったので。


昨年の11月に、こちらをチラッと書きました。
ボディの体積をダンプする5 - C#ATIA
ここではCATIAのオブジェクトを利用したソートになっていますが、
興味があるのはそういう事ではなく、
"コレクション内のオブジェクトのプロパティを利用したソート"
についてがテーマでした。
これは僕にとってVBAの大きな疑問です。

昨年末頃にこちらを試しました。
Bingチャットでコードレビュー - C#ATIA
その際、気づかなかったのですが、後で"AIはどんな処理を書くの?"と
言う疑問が湧き、試した所これが中々面白かったです。

実は年末にBingチャットで試した際、最初はJavaのコードを答えられ、
"VBAで教えて下さい”と追加質問したところ、
"他の言語は学習していますがVBAは学習していない為、答えられません"
のような回答となりました。
(思わず、BingはVBAを答えないとダメだろ と突っ込みました)


今日、(恐らく質問文が異なっていると思います)再度質問したところ
答えてくれました。
念の為、こちらの文章で質問してみました。

VBAで次のPoint2Dクラスモジュールがあります。
'vba Point2D.cls

Option Explicit

public x As Double
public y As Double

このPoint2Dクラスのインスタンスを複数保持しているPointsコレクションがあります。

このコレクションを原点から近い順にソートされたコレクションが得られるVBAの関数を教えて下さい。

Bingチェットが答えてくれたコードのみです。

'Point2Dクラスのインスタンスの距離を計算する関数
Function Distance(p As Point2D) As Double
    Distance = Sqr(p.x ^ 2 + p.y ^ 2)
End Function


'Pointsコレクションを原点から近い順にソートする関数
Function SortByDistance(points As Collection) As Collection
    Dim i As Long, j As Long
    Dim n As Long
    Dim temp As Point2D
    n = points.Count 'コレクションの要素数
    'バブルソートのアルゴリズム
    For i = 1 To n - 1
        For j = i + 1 To n
            '距離が小さいものを前に移動する
            If Distance(points(j)) < Distance(points(i)) Then
                Set temp = points(i)
                Set points(i) = points(j)
                Set points(j) = temp
            End If
        Next j
    Next i
    'ソートされたコレクションを返す
    Set SortByDistance = points
End Function

これだけでは試す事が出来ない為、幾つかの関数を用意しました。
(これをBingチャットに書かせる自信が無いので・・・)

'エントリーポイント
Sub main()
    Dim coordinates As Variant
    coordinates = Array( _
        Array(0, 1), _
        Array(2, 12), _
        Array(5, 5), _
        Array(20, 3), _
        Array(8, 2), _
        Array(10, 5), _
        Array(0, 2) _
    )
    
    Dim points As Collection
    Set points = New Collection
    
    Dim pnt As Point2D
    Dim i As Long
    For i = 0 To UBound(coordinates)
        Set pnt = New Point2D
        pnt.x = coordinates(i)(0)
        pnt.y = coordinates(i)(1)
        points.Add pnt
    Next
    
    dump_points points, "** before **"

    Dim sortedPoints As Collection
    Set sortedPoints = SortByDistance(points)

    dump_points sortedPoints, "** after **"
End Sub


Private Sub dump_points( _
    ByVal points As Collection, _
    Optional ByVal msg As String = "")
    
    If Len(msg) > 0 Then
        Debug.Print msg
    End If
    
    Dim pnt As Point2D
    For Each pnt In points
        dump_point pnt
    Next
End Sub


Private Sub dump_point( _
    ByVal point As Point2D)
    
    Debug.Print "X:" & point.x & " Y:" & point.y
End Sub

又、質問時に定義している"Point2D.cls"クラスモジュールも念の為
記載しておきます。

'vba Point2D.cls

Option Explicit

Public x As Double
Public y As Double

用意が整ったので、実行したところエラーとなりました。
この部分でのエラーです。

                Set points(i) = points(j)

・・・実行する前から気が付いていましたが、念の為実行してみました。

これはコレクション内の要素を書き換えようとした際の部分ですが、
そこでエラーとなります。

そうなんです、VBAのコレクションは内部の要素の上書きが出来ないんです。
ソート処理には幾つかアルゴリズムがありますが(僕はあまり詳しくないです)
何れもスワップ処理が必要となりますが、VBAのコレクションは要素の
上書きが出来ない為、この部分が非常にネックとなるはずです。
(コレクションのRemoveメソッドと挿入位置指定のAddメソッドで
出来ない事は無いはずですが、面倒です)

続いてBingチャットにこちらを質問しました。

VBAのコレクションは要素の再代入が出来ない為エラーになります。

得られた回答のコード部分です。

'Point2Dクラスのインスタンスの距離を計算する関数
Function Distance(p As Point2D) As Double
    Distance = Sqr(p.x ^ 2 + p.y ^ 2)
End Function


'Pointsコレクションを原点から近い順にソートする関数
Function SortByDistance(points As Collection) As Collection
    Dim i As Long, j As Long
    Dim n As Long
    Dim temp As Point2D
    Dim arr() As Point2D '配列を宣言
    n = points.Count 'コレクションの要素数
    ReDim arr(1 To n) As Point2D '配列のサイズを指定
    'コレクションの要素を配列にコピー
    For i = 1 To n
        Set arr(i) = points(i)
    Next i
    'バブルソートのアルゴリズム
    For i = 1 To n - 1
        For j = i + 1 To n
            '距離が小さいものを前に移動する
            If Distance(arr(j)) < Distance(arr(i)) Then
                Set temp = arr(i)
                Set arr(i) = arr(j)
                Set arr(j) = temp
            End If
        Next j
    Next i
    'コレクションを空にする
    For i = n To 1 Step -1
        points.Remove i
    Next i
    '配列の要素をコレクションに追加
    For i = 1 To n
        points.Add arr(i)
    Next i
    'ソートされたコレクションを返す
    Set SortByDistance = points
End Function

結論からすると正しく処理出来るものになっていました。

結局、

コレクション→配列→ソート→コレクション

の処理を行っています。正直な所、予想通りです。

VBAの悩ましい部分がここなんです。
・コレクション:代入する際の要素数は自動拡張するが、再代入が出来ない。
・配列:再代入可能だが、要素数は自動拡張しない。
細かな事を言えば、メモリの確保の仕方の違いなどもあるとは思いますが、
何時も"どっち使う?"になってしまいます。(個人的には配列)

もうちょっと試しているのですが、長くなったので次回に。

Bingチャットでコードレビュー

正式な名称は”Bingチャット”では無く、"ウェブ版Copilot"かも知れない・・・
※Copilotが付くサービスが多すぎてわからない

ChatGPTでは無くBingチャットを利用したのは、無料のChatGPTは
GPT-3.5なのに対し、BingチャットはGPT-4だと言う単調な理由です。
GPT-4をあまり制限なく無料で利用出来るのはBingチャットだけ
じゃないかな?

”AIでコードレビュー”は今更感のある鉄板ネタですね。
ユーチューバーがメントスコーラやるようなものです・・・。

では本題ですが、こちらのVector_2Dクラスを試してみます。
自作2Dの点とベクトル - C#ATIA

チャットに

次のVBAコードのコードレビューをお願いします。
(コードベタ張り)

してみました。理由が定かではないのですが、2回連続で回答頂きました。

一回目

こんにちは、このはBingです。VBAコードのレビューをお手伝いします。😊

コードを拝見しましたが、全体的にはきれいに書かれていると思います。ただ、いくつか改善できる点がありますので、以下にコメントします。

- **プロパティ**の部分では、`x`と`y`のゲッターとセッターを定義していますが、これはVBAでは不要です。VBAでは、`Public x As Double`と宣言するだけで、自動的にゲッターとセッターが生成されます¹。このようにすると、コードがシンプルになります。
- **メソッド**の部分では、`clone`と`as_point`のメソッドがほとんど同じ処理をしています。これは冗長ですので、`clone`メソッドは削除しても良いと思います。`as_point`メソッドは、`Vector_2D`クラスのインスタンスを`Point_2D`クラスのインスタンスに変換する役割がありますが、`clone`メソッドは`Vector_2D`クラスのインスタンスをそのまま複製するだけです。これは、`Set vec = Me`と書けば同じことができます²。
- `as_array`メソッドは、`x`と`y`の値を配列にして返すだけですが、これはあまり意味がありません。配列にする必要がある場合は、呼び出し側で`Array(vec.x, vec.y)`と書けば同じことができます³。このメソッドも削除しても良いと思います。
- `cross`メソッドと`dot`メソッドは、ベクトルの外積と内積を計算するメソッドですが、これらは`Vector_2D`クラスのメソッドとして定義するよりも、別のモジュールに`Public Function`として定義する方が良いと思います。これは、ベクトルの外積と内積は、2つのベクトルの間の演算であり、特定のベクトルのプロパティやメソッドではないからです⁴。このようにすると、`cross(vec1, vec2)`や`dot(vec1, vec2)`と書けば、どのベクトルに対しても外積や内積を計算できます。
- `subtract`メソッドと`add`メソッドは、ベクトルの減算と加算を行うメソッドですが、これらは`Vector_2D`クラスのメソッドとして定義するのではなく、`Point_2D`クラスのオペレーターとして定義する方が良いと思います。これは、ベクトルの減算と加算は、2つの点の間の演算であり、特定のベクトルのプロパティやメソッドではないからです⁵。このようにすると、`vec1 = pnt1 - pnt2`や`vec2 = pnt1 + pnt2`と書けば、どの点に対してもベクトルを計算できます。
- `scale_by`メソッドは、ベクトルのスカラー倍を行うメソッドですが、これは`Vector_2D`クラスのメソッドとして定義するのではなく、`Point_2D`クラスのオペレーターとして定義する方が良いと思います。これは、ベクトルのスカラー倍は、点と数値の間の演算であり、特定のベクトルのプロパティやメソッドではないからです。このようにすると、`vec = pnt * ratio`や`vec = ratio * pnt`と書けば、どの点に対してもベクトルを計算できます。
- `normalize`メソッドは、ベクトルの単位化を行うメソッドですが、これは`Vector_2D`クラスのメソッドとして定義するのではなく、別のモジュールに`Public Function`として定義する方が良いと思います。これは、ベクトルの単位化は、ベクトルの長さを1にする演算であり、特定のベクトルのプロパティやメソッドではないからです。このようにすると、`vec = normalize(pnt)`と書けば、どの点に対してもベクトルを単位化できます。
- `with_array`メソッドは、配列の値を`x`と`y`に設定するメソッドですが、これはあまり意味がありません。配列の値を設定する必要がある場合は、呼び出し側で`vec.x = ary(0)`と`vec.y = ary(1)`と書けば同じことができます。このメソッドも削除しても良いと思います。
- `str`メソッドは、`x`と`y`の値を文字列にして返すメソッドですが、これは`Point_2D`クラスの`ToString`メソッドとして定義する方が良いと思います。これは、`ToString`メソッドは、オブジェクトの文字列表現を返すメソッドであり、標準的なメソッド名です。このようにすると、`str = pnt.ToString`と書けば、どの点に対しても文字列を取得できます。
- `is_equal`メソッドは、ベクトルの一致を判定するメソッドですが、これは`Vector_2D`クラスのメソッドとして定義するのではなく、`Point_2D`クラスのオペレーターとして定義する方が良いと思います。これは、ベクトルの一致は、2つの点の間の演算であり、特定のベクトルのプロパティやメソッドではない

いきなりお褒めの言葉を頂きました。3.5ではそこまでの気遣いは
出来ないでしょう?
細かく見てもキリが無いので、ざっくりとした印象です。
・読み書き可のプロパティは、もうセッターゲッター無しが主流なのでしょうか?
・色々とメソッドに関しては、"外部の関数にすべき"と忠告頂きましたが、
 これらの多くは、Fusion360のオブジェクトをリスペクトして作成した為、
 メソッドとして実装したい。(このスタイルには非常に共感したため)
・全般的に"点"と"ベクトル"をほぼ同一レベルの扱いをした提案としているように
 受け取れましたが、"点"と"ベクトル"は明確な別クラスとしたい気持ちが
 強いです。特に以下の"点"同士の演算の戻り値が"ベクトル"となる事は
 非常に受け入れがたい。

vec1 = pnt1 - pnt2
vec2 = pnt1 + pnt2


2個目の回答

こんにちは、このはBingです。VBAコードのコードレビューをお手伝いします。😊

あなたのコードは2次元ベクトルを表すクラスモジュールであると理解しました。コードの構造や機能は基本的に良いと思いますが、いくつか改善できる点があります。以下に指摘事項と提案をまとめました。

- コードの冒頭には、クラスモジュールの説明やプロパティやメソッドの一覧をコメントとして記述すると良いでしょう。これは、コードの可読性や保守性を高めるために重要です¹²。
- プロパティやメソッドの名前は、できるだけわかりやすくすることが望ましいです。たとえば、`as_point`や`as_array`は、`to_point`や`to_array`の方がより直感的に理解できると思います³。また、`str`は`to_string`や`format`などの方が一般的です⁴。
- メソッドの中で、引数や戻り値の型を明示的に指定すると、コードの安全性や信頼性が向上します⁵。たとえば、`Public Function clone() As Point_2D`は、`Public Function clone() As Vector_2D`とするべきです。また、`Public Function as_array() As Variant`は、`Public Function as_array() As Double()`とすることで、配列の型をDoubleに限定できます。
- メソッドの中で、変数の宣言を忘れないようにしましょう。たとえば、`Public Function is_equal( ... ) As Boolean`の中で、`distance`という変数が宣言されていません。これは、`Option Explicit`を指定しているとエラーになります。また、変数のスコープを適切に設定することも重要です⁶。たとえば、`Public Function normalize() As Boolean`の中で、`length`という変数はローカル変数なので、`Dim`ではなく`Private`で宣言するべきです。

以上が私のコードレビューの結果です。コードの改善に役立てていただければ幸いです。😊

こちらもザックリとした印象です。
・メソッド名については・・・はい、おっしゃる通りです。
・cloneメソッドの戻り値の型指定が間違っている事を指摘してくれました。
・Point_2Dクラスを未提示な為、多くの相応しくない指摘をされました。


1個目の回答は細かく見てくれましたが、2回目はキチっと間違いを
指摘してくれました。


おまけで、このクラスのユニットテスト用のコードを聞いてみた際の
回答です。

VBAでユニットテストコードを書くには、いくつかの方法があります。¹²³ その中で、一つの例として、[VBAUnittest](^4^)というテストフレームワークを使ってみましょう。このフレームワークは、以下のような手順で使用できます。

- [VBAUnittest.bas](^4^)をVBAのエディタにインポートします。
- 「開発」タブの「マクロのセキュリティ」から「VBAプロジェクトオブジェクトモデルへのアクセスを信頼する」にチェックを付けます。
- テストケースを以下のような形で作成します。テストモジュール名は、Testから開始。テストプロシージャ名は、testから開始します。
- テストを実行するには、`runTest`というマクロを実行します。テスト結果は、`testResult`というシートに出力されます。

例えば、あなたのクラスモジュールの名前が`Vector_2D`だとすると、以下のようなテストコードを書くことができます。

```vba
'テストモジュール名はTestから始める
TestVector_2D

Option Explicit

'テストプロシージャ名はtestから始める
Sub testLength()
    'テスト対象のクラスをインスタンス化
    Dim vec As Vector_2D
    Set vec = New Vector_2D
    
    'テスト用のデータを設定
    vec.x = 3
    vec.y = 4
    
    '期待される結果
    Dim expected As Double
    expected = 5
    
    '実際の結果
    Dim actual As Double
    actual = vec.length
    
    '結果の比較
    assert expected, actual
End Sub
```

このようにして、あなたのクラスモジュールの機能をテストすることができます。⁵⁶ ユニットテストの書き方や利点については、[こちら](^1^)や[こちら](^2^)などの記事を参考にしてください。😊


※追記です
ついでにドキュメントを追記するように頼んだ結果です。

このクラスのドキュメンテーションを追記するには、クラスモジュールの冒頭にコメントとして説明を記述する必要があります。¹² 例えば、以下のような形式で書くことができます。

```vba
'クラス名: Vector_2D
'説明: 2次元ベクトルを表すクラス
'プロパティ:
'  x: x座標を取得または設定する
'  y: y座標を取得または設定する
'  length: ベクトルの長さを取得する
'メソッド:
'  clone: ベクトルのクローンを作成する
'  to_point: ベクトルをPoint_2Dオブジェクトに変換する
'  to_array: ベクトルを配列に変換する
'  cross: ベクトルとの外積を計算する
'  dot: ベクトルとの内積を計算する
'  subtract: ベクトルを減算する
'  add: ベクトルを加算する
'  scale_by: ベクトルをスカラー倍する
'  normalize: ベクトルを単位化する
'  with_array: 配列からベクトルを設定する
'  to_string: ベクトルを文字列に変換する
'  is_equal: ベクトルが等しいか判定する
```

このようにドキュメンテーションを追記することで、コードの可読性や保守性を高めることができます。¹² また、ドキュメンテーションを自動生成するツールもありますので、参考にしてみてください。³⁴ 😊

良く理解しています。これは楽!

ソリッドをサーフェス化

ソリッドをサーフェス化したい場面はそれ程多くないとは
思いますが、忘れつつあるのでテストしました。

'vba
Option Explicit

Sub main()

    Dim pDoc As partDocument
    Set pDoc = CATIA.ActiveDocument

    Dim pt As Part
    Set pt = pDoc.Part
    
    Dim bdy As Body
    Set bdy = pt.Bodies.Item(1)
    
    solid_to_surface bdy

    MsgBox "Done"

End Sub


Private Function solid_to_surface( _
    ByVal bdy As Body)

    Dim pt As Part
    Set pt = bdy.parent.parent

    Dim shapes As shapes
    Set shapes = bdy.shapes
    
    Dim ref As Reference
    Set ref = pt.CreateReferenceFromObject( _
        shapes.Item(shapes.count) _
    )
    
    Dim hsFact As HybridShapeFactory
    Set hsFact = pt.HybridShapeFactory

    Dim direct As HybridShapeDirection
    Set direct = hsFact.AddNewDirectionByCoord(1#, 0#, 0#)

    Dim trans As HybridShapeTranslate
    Set trans = hsFact.AddNewEmptyTranslate()
    With trans
        .ElemToTranslate = ref
        .VectorType = 0
        .Direction = direct
        .DistanceValue = 0#
        .VolumeResult = False
    End With

    Dim hBody As HybridBody
    Set hBody = pt.HybridBodies.add()

    hBody.AppendHybridShape trans
    pt.UpdateObject trans

    Set ref = pt.CreateReferenceFromObject(trans)
    
    Dim surfExp As HybridShapeSurfaceExplicit
    Set surfExp = hsFact.AddNewSurfaceDatum(ref)
    
    hBody.AppendHybridShape surfExp
    pt.UpdateObject surfExp
    
    hsFact.DeleteObjectForDatum trans

End Function

Fusion360の場合は、サーフェスが閉じた状態になると勝手に
ソリッドになりますが、CATIAの場合は意図的にソリッド化する
必要がありますよね。

逆に言えば、意図的に閉じたサーフェスFusion360で作り出す事が
難しいです。

サーフェス化するには単純にGSDの機能を利用すれば良いだけ
なのですが、今回は移動量0の移動を行いました。
他の機能でも出来ますね。

但し、過去に抽出でやった時にデータの状態によっては意図しない
場合がありました。原因は分かっていますが、内緒です。
移動なら大丈夫でしょう。

VBAで.gitignoreを読み込み、判断する

VBAで".gitignore"ファイルを読み込み、判断したいんです。
".gitignore"ファイルはgitで監視対象から無視するためのファイルです。
(わかりにくい表現ですね。調べて下さい・・・)

".gitignore"ファイルでは、フォルダーの指定も出来るのですが、
VBAのプロジェクトではフォルダーの観念が無いため無視。
取りあえずファイルだけを対象として考えます。

で、こんなものを試しに作りました。

'vba

Option Explicit

Private Const TARGET_FOLDER = "C:\temp\test\"

Sub main()
    Dim gitignorePath As String
    gitignorePath = TARGET_FOLDER & ".gitignore"

    'http://officetanaka.net/excel/vba/filesystemobject/filesystemobject.htm
    Dim fso As Object
    Set fso = get_fso
    
    If Not fso.FileExists(gitignorePath) Then Exit Sub

    Dim aryFilter As Variant
    aryFilter = read_file(gitignorePath)

    Dim folder As Object
    Set folder = fso.GetFolder(TARGET_FOLDER)
    
    Dim file As Object
    Dim res As Boolean
    For Each file In folder.Files
        res = is_valid(file.name, aryFilter)
        dump file.name & " : " & res
    Next

End Sub


Private Function is_valid( _
    ByVal name As String, _
    ByVal aryFilter As Variant) As Boolean

    Dim i As Long
    Dim filter As String
    On Error Resume Next
    For i = 0 To UBound(aryFilter)
        filter = aryFilter(i)
        If Len(filter) < 1 Then GoTo continue

        If name Like filter Then
            is_valid = False
            Exit Function
        End If
continue:
    Next
    
    is_valid = True
    
End Function


Private Function get_fso() As Object
    Set get_fso = CreateObject("Scripting.FileSystemObject")
End Function


Private Function read_file( _
    ByVal path As String) As Variant

    On Error Resume Next
    
    Dim buf As Variant
    With get_fso.GetFile(path).OpenAsTextStream
        buf = .ReadAll
        .Close
    End With
    
    Dim newLine As String
    newLine = get_newline_character(buf)
    
    read_file = Split(buf, newLine)
    
    On Error GoTo 0

End Function


Private Function get_newline_character( _
    ByVal txt As String) As String

    Dim res As String

    Select Case True
        Case InStr(txt, vbCrLf) > 0
            res = vbCrLf
        Case InStr(txt, vbCr) > 0
            res = vbCr
        Case InStr(txt, vbLf) > 0
            res = vbLf
        Case Else
            res = ""
    End Select

    get_newline_character = res

End Function


Private Sub dump(ByVal msg As String)
    Debug.Print msg
End Sub

正直の申し上げると、is_valid関数内では Like を使用していますが、
最初は正規表現で挑戦していましたが、上手く行かず挫折しました。

試しに、TARGET_FOLDERとなるフォルダーには、こんなファイルが
入っているとします。

".gitignore"ファイルの中身はこんな感じです。

.gitignore
#*.bas

あぁフォーマットについては何処かで調べて下さい。
自分も分かっていないので・・・。

この状態で実行した結果はこちらです。

.gitignore : False
hoge.bas : True
huga.bas : True
piyo.cls : True

"True"となるものは監視対象を意味します。
取りあえずOKです。

この様に変更して

.gitignore
*.bas

再度実行した結果

.gitignore : False
hoge.bas : False
huga.bas : False
piyo.cls : True

OKです。

続いてこの様に変更して

.gitignore
hoge.bas

再度実行した結果

.gitignore : False
hoge.bas : False
huga.bas : True
piyo.cls : True

OKです。


順調なのですが、これはNGパターンです。

.gitignore
*.bas
!hoge.bas

再度実行した結果

.gitignore : False
hoge.bas : False
huga.bas : False
piyo.cls : True

これはダメなんです。
これになって欲しかった。

.gitignore : False
hoge.bas : True
huga.bas : False
piyo.cls : True

原因はis_valid関数内の処理だと十分理解しているのですが、
どうやって書けば良いのか・・・。

**************

本文と無関係なのですが、Hatena Blogに”AIタイトルアシスタント”と言う
機能が追加されていました。
ブログ本文からAIでタイトル候補を幾つか提案してくれる機能の様です。
凄いですね。今回はこんな感じでした。

無視してやった。