C#ATIA

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

流用して、4分木る 2

こちらの続きです。
流用して、4分木る1 - C#ATIA


オリジナルのモートン順序の番号とY軸が逆になってしまう部分ですが、
何となくですが、自分なりの考えです。

番号を求める際、オリジナルのコードではこんな式で計算した後に、
関数に投げています。

 // 座標→線形4分木要素番号変換関数
 DWORD GetPointElem( float pos_x, float pos_y )
 {
        return Get2DMortonNumber( (WORD)((pos_x-m_fLeft)/m_fUnit_W),
                                  (WORD)((pos_y-m_fTop)/m_fUnit_H) );
 }

Y軸に関しては

((点のY座標 - 空間の最小位置のY座標)÷ 単位空間のY方向の幅 ) 
 を、少数切捨て

の計算をし、Y方向に最小位置から何番目の空間か? を求めています。
X方向も同じです。つまり、空間の最小位置に近い方にモートン順序の番号の
" 0 " が存在するはずです。 それを考慮するとモートン順序の番号は
f:id:kandennti:20170112175006p:plain
こんな感じで出てくるはずなので、前回の計算で合っているように
感じてます。


では何故、オリジナルの説明とY軸が逆方向になるのか? なのですが
恐らくこんな理由だと思います。

2D・3DCADの場合、画面上に表示する際には通常OpenGLが利用されて
います。OpenGLは基本的に右手座標系となっているはずです。
(数学のグラフ等とイメージが一致します)

一方ゲームの場合は、主にDirectXが利用されていると思います。
DirectXの場合は、基本的に左手座標系となっていた記憶があります。

オリジナルのサイトでは、ゲームのための説明を行っているため、左手座標系
でのモートン順序の番号となっているのではないのかな? と感じて
います。
確かExcelのシェイプも、画面右に進むのが +X で、下に進むのが +Y
だった記憶です。(M社、左手座標系が好きなんだな…)

ので、Y軸逆転してますがこのまま進めることにしました。
オリジナルの説明でも
"空間を区別する為のハッシュとして利用"
のような説明になっており、上下レベルのモートン順序の番号の関係も
問題無いと思ってます。


でも、8分木のモートン順序の番号は一致しているんですよね…。
(空間の最小位置が " 0 " で一致してます)

インプロセス実行時、一部をアウトプロセスで実行

KCLには言語判定の関数 "GetLanguage" 関数を入れているのですが、
最近になって打率が悪いことに気が付きました。
判定できない原因の一つが、マクロ自身をインプロセスで実行した際に
ステータスバーの文字を取得する前に、判定しているような気がしています。

その為、インプロセス実行時にも一部のマクロをアウトプロセスで実行する方法が
無いかな? と思いテストしてみました。


こちらは、ベースとなるコードです。

'vba using-'KCL0.09'
Sub CATMain()
    Dim Hb As HybridBody: Set Hb = KCL.SelectItem("Select", "HybridBody")
    Call SelCrv(Hb)
    MsgBox "Done"
End Sub

Sub SelCrv(Prm)
    Dim Hb As HybridBody
    Set Hb = Prm
    
    Dim Sel As Selection
    Set Sel = CATIA.ActiveDocument.Selection
    Sel.Clear
    
    Dim HS As HybridShape
    For Each HS In Hb.HybridShapes
        Sel.add HS
    Next
End Sub

マクロ実行後、指定した形状セット内の点・線・面を選択した状態で
終了するだけの無意味なコードです。

インプロセスな実行とアウトプロセスな実行の違いは、マクロ実行時にCATIA自身を
操作できるか? 出来ないか? で判断することが出来ます。
・インプロセス - 操作出来ない
・アウトプロセス - 操作出来る(困っちゃう)

その為、形状セット内にソコソコ要素が入っているもので上記のコードを
実行した際、ズーム・ムーブ・スピン等の操作で簡単に判断できます。


インプロセス実行時に、一部のマクロをアウトプロセスで実行する方法は
幾つか有りそうな気がしていますが、外部にファイルを用意し
取り込んだ上で実行するのでは、色々な意味で手間がかかりそうな
気がしましたので没。

こちらで、imihitoさんにイロイロと教えて頂いた方法を利用し、
CATVBAの標準モジュールをマクロで削除したい (希望)2 - C#ATIA

こちらの方法と併用する事にしました。
外部のマクロを実行する3 - C#ATIA

要は、VBAの関数を文字として取得し、外部マクロとして実行する
と言うひねくれた方法です。
但し、これがアウトプロセスとして実行できるものかどうかは、
実際に実行するまではわかりません。


また、VBAプロジェクト内のコードを文字として取得する方法は、
こちらのサンプルが非常に参考になりました。
モジュール内のコードを操作する(CodeModule オブジェクトのプロパティ) | ExcelWork.info

'vba using-'KCL0.09'
'参考 http://excelwork.info/excel/codemoduleproperty/
Sub CATMain()
    '*** VBE準備 ***
    Dim VbPjName$: VbPjName = "Using_KCL_Sample"    'プロジェクト名
    Dim VbCpName$: VbCpName = "Test_Func_Evaluate"  'モジュール名
    Dim VbFcName$: VbFcName = "SelCrv"              '関数名

    'VBE
    Dim Vbe As Object: Set Vbe = GetVBE()
    If KCL.IsNothing(Vbe) Then Exit Sub
    
    'VBProject
    Dim Pj As Object: Set Pj = GetVBProject(VbPjName, Vbe)
    If KCL.IsNothing(Pj) Then Exit Sub
    
    'VBComponent
    Dim Cp As Object: Set Cp = GetVBComponent(VbCpName, Pj)
    If KCL.IsNothing(Cp) Then Exit Sub
    
    'CodeModule
    Dim Cm As Object: Set Cm = Cp.CodeModule
    
    'Component内のコード取得
    Dim Code$: Code = GetCode(VbFcName, Cm)
    If Code = vbNullString Then Exit Sub

    '*** CATIA ***
    '選択
    Dim Hb As HybridBody: Set Hb = KCL.SelectItem("Select", "HybridBody")
    
    '実行言語
    Dim SLang As CATScriptLanguage: SLang = CATVBALanguage
    
    '引数
    Dim Prm(0) As Variant: Set Prm(0) = Hb
    
    'SystemService
    Dim SS As Variant: Set SS = CATIA.SystemService
    
    '呼出し
    Call SS.Evaluate(Code, SLang, VbFcName, Prm)
    
    MsgBox "Done"
End Sub

'アウトプロセス用マクロ
Sub SelCrv(Prm)
    Dim Hb As HybridBody
    Set Hb = Prm
    
    Dim Sel As Selection
    Set Sel = CATIA.ActiveDocument.Selection
    Sel.Clear
    
    Dim HS As HybridShape
    For Each HS In Hb.HybridShapes
        Sel.add HS
    Next
End Sub

'Code取得
Private Function GetCode(ByVal FancName$, ByVal Cm As Object) As String
    GetCode = vbNullString
    On Error Resume Next
        With Cm
            Dim Start&: Start = .ProcStartLine(FancName, 0) '開始行
            Dim Count&: Count = .ProcCountLines(FancName, 0) '文字数
            GetCode = .Lines(Start, Count)
        End With
    On Error GoTo 0
    If GetCode = vbNullString Then
        MsgBox "関数 ' " & FancName & " ' が取得できませんでした"
    End If
End Function

'VBComponent取得
Private Function GetVBComponent(ByVal Name$, ByVal Pj As Object) As Object
    On Error Resume Next
        Set GetVBComponent = Pj.VBComponents.Item(Name)
    On Error GoTo 0
    If KCL.IsNothing(GetVBComponent) Then
        MsgBox "VBComponent ' " & Name & " ' が取得できませんでした"
    End If
End Function

'VBProject取得
Private Function GetVBProject(ByVal Name$, ByVal Vbe As Object) As Object
    On Error Resume Next
        Set GetVBProject = Vbe.VBProjects.Item(Name)
    On Error GoTo 0
    If KCL.IsNothing(GetVBProject) Then
        MsgBox "VBProject ' " & Name & " ' が取得できませんでした"
    End If
End Function

'VBEditor取得
'Special_Thx Mr.imihito
Private Function GetVBE() As Object
    Set GetVBE = Nothing
    
    'VBAのバージョンチェック
    Dim COMObjectName$
    #If VBA7 Then
        COMObjectName = "MSAPC.Apc.7.1"
    #ElseIf VBA6 Then
        COMObjectName = "MSAPC.Apc.6.2"
    #Else
        MsgBox "VBAのバージョンが未対応です"
        Exit Function
    #End If
    
    'APC取得
    Dim oApc As Object: Set oApc = Nothing
    On Error Resume Next
        Set oApc = CreateObject(COMObjectName)
    On Error GoTo 0
    
    'VBE取得
    If KCL.IsNothing(oApc) Then
        MsgBox "MSAPC.Apcが取得できませんでした"
        Exit Function
    End If
    
    Set GetVBE = oApc.Vbe
End Function

もし試される方がいるようでしたら、プロジェクト名・モジュール名については
各々の環境にあわせた名称に変更する必要があります。

これをインプロセスで実行すると…、無事実行中でもCATIAの操作が可能でした。
当時 "こんなの無意味" ぐらいの事を書きましたが、利用方法があるん
ですね。 以前試しておいて良かった。

流用して、4分木る1

こちらで遅かった為の改善策を模索中です。
2D曲線の折れ線化を利用し、重複線の選択1 - C#ATIA

曲線の折れ線化については、後回しにして4分木に挑戦です。
8分木の計算式変更して、次元を一つ落とせば良いのだろうと
思っているのですが。

'vba test_Quadtree Ver0.0.1  using-'KCL0.09'
'モートン順序を利用した4分木空間分割テスト用クズコード

Option Explicit

Private Const MAXLEVEL = 2                      '有効空間分割最大レベル

Private m_Level&                                '分割レベル
Private m_Tolerance#                            '一致トレランス
Private m_MaxCount&                             '同一空間内最大数(目安)
Private m_AxisCount&                            '空間分割時の各軸の最大数
Private m_MinPos                                '空間最小座標
Private m_Unit                                  '空間単位サイズ
Private m_ToleranceRatio                        '空間単位サイズに対してのトレランス比率
Private m_CellCount&()                          'レベル毎の空間数

Sub CATMain()
    'テスト座標
    Dim Pnts As Collection: Set Pnts = New Collection
    With Pnts
        .Add Array(-40#, -40#)
        .Add Array(40#, 40#)
        .Add Array(0#, 0#)
        .Add Array(25#, -25#)
        .Add Array(1#, 1#)
    End With
    
    '初期設定
    Dim Tol#: Tol = 0.001            '端点一致トレランス
    Dim MaxCount&: MaxCount = 2      '空間内最大数
    
    If Not SetStart(Tol, MaxCount) Then
        MsgBox "設定値が不正です"
        Exit Sub
    End If
    
    '座標値郡のIdxList作成
    Dim PntIdxList As Collection: Set PntIdxList = InitRangeList(Pnts.Count)
    Dim Id: Id = SetSpaceInfo(Pnts, PntIdxList)
    
    '確認
    Call DumpQuadIdx(Pnts)
End Sub

'確認用
Private Sub DumpQuadIdx(ByVal PosList)
    Dim Pos
    Debug.Print " **** "
    For Each Pos In PosList
        Debug.Print "Pos:" & Pos(0) & "," & Pos(1), _
                    "MotonNo:" & GetPointElem(Pos)
    Next
End Sub

'線形4分木準備
''' @param :Tolerance-Double-一致トレランス
''' @param :MaxCount-long-同一空間内最大数(目安)
''' @return:Boolean
Private Function SetStart(ByVal Tolerance#, ByVal MaxCount&) As Boolean
    SetStart = False
    If Tolerance <= 0 Then Exit Function
    
    m_Tolerance = Tolerance
    m_MaxCount = MaxCount
    m_ToleranceRatio = InitRangeAry(2, 0)
    
    ReDim m_CellCount(MAXLEVEL + 1)
    m_CellCount(0) = 1
    Dim i&
    For i = 1 To UBound(m_CellCount)
        m_CellCount(i) = m_CellCount(i - 1) * 4
    Next
    SetStart = True
End Function

'空間情報設定
''' @param :Pnts-Collection(array(Double))-座標値郡
''' @param :Idxs-Collection(long)-座標値郡Idx
''' @return:Boolean
Private Function SetSpaceInfo(ByVal Pnts, ByVal Idxs As Collection) As Boolean
    SetSpaceInfo = False
    Dim SpSize: SpSize = GetSpaceSize_Idx(Pnts, Idxs)
    m_MinPos = AryAdd(SpSize(0), m_Tolerance * -1)
    Dim W: W = ArySub(AryAdd(SpSize(1), m_Tolerance), m_MinPos)
    If Not SetLevel(W) Then Exit Function
    m_Unit = AryDiv(W, m_AxisCount)
    
    Dim i&
    For i = 0 To 1
        m_ToleranceRatio(i) = m_ToleranceRatio(i) / m_Unit(i)
    Next
    SetSpaceInfo = True
End Function

'座標値郡から空間サイズ取得
''' @param :EndPnts-Collection(array(Double))-座標値郡
''' @return:array(array(Double))-0:最小値 1:最大値
Private Function GetSpaceSize_Idx(ByVal EndPnts As Collection, ByVal IdxList As Collection) As Variant
    Dim Min: Min = InitRangeAry(2, 1.79769313486231E+308)
    Dim Max: Max = InitRangeAry(2, -1.79769313486231E+308)
    
    Dim Idx, i&
    For Each Idx In IdxList
        For i = 0 To 1
            If Min(i) > EndPnts.Item(Idx)(i) Then Min(i) = EndPnts.Item(Idx)(i)
            If Max(i) < EndPnts.Item(Idx)(i) Then Max(i) = EndPnts.Item(Idx)(i)
        Next
    Next
    GetSpaceSize_Idx = Array(Min, Max)
End Function

'空間サイズとトレランスからレベル算出し設定
Private Function SetLevel(ByVal W) As Boolean
    SetLevel = False
    Dim Min#: Min = 1.79769313486231E+308
    Dim i&
    For i = 0 To 1
         If Min > W(i) Then Min = W(i)
    Next
    Dim TmpLv&: TmpLv = Fix(Log_n((Min / (m_Tolerance * 2 + 0.002)), 2))
    If TmpLv > MAXLEVEL Then
        m_Level = MAXLEVEL
    Else
        m_Level = TmpLv
    End If
    
    If m_Level < 1 Then Exit Function
    m_AxisCount = sl(1, m_Level)
    SetLevel = True
End Function

'ビット分割関数2D
''' @param :n-long
''' @return:long
Private Function BitSeparateFor2D(ByVal n&) As Long
    Dim S&: S = n
    S = (S Or sl(S, 8)) And &HFF00FF
    S = (S Or sl(S, 4)) And &HF0F0F0F
    S = (S Or sl(S, 2)) And &H33333333
    BitSeparateFor2D = (S Or sl(S, 1)) And &H55555555
End Function

'4分木モートン順序算出関数
''' @param :x-long
''' @param :y-long
''' @return:long
Private Function Get2DMortonNumber(ByVal X&, ByVal Y&) As Long
   Get2DMortonNumber = BitSeparateFor2D(X) Or _
                       sl(BitSeparateFor2D(Y), 1)
End Function

'線形4分木インデックス取得関数
''' @param :Pos-array(Double)
''' @return:long
Private Function GetPointElem(ByVal Pos As Variant) As Long
   GetPointElem = Get2DMortonNumber(Fix((Pos(0) - m_MinPos(0)) / m_Unit(0)), _
                                    Fix((Pos(1) - m_MinPos(1)) / m_Unit(1)))
End Function


'配列同士の引き算-細かいチェック無し
Private Function ArySub(ByVal a, ByVal b) As Variant
    ArySub = Array(a(0) - b(0), a(1) - b(1))
End Function

'配列と実数の足し算-細かいチェック無し
Private Function AryAdd(ByVal a, ByVal b#) As Variant
    AryAdd = Array(a(0) + b, a(1) + b)
End Function

'配列と実数の割り算-細かいチェック無し
Private Function AryDiv(ByVal a, ByVal b#) As Variant
    AryDiv = Array(a(0) / b, a(1) / b)
End Function

'nを底とする対数
Private Function Log_n(X, n)
    Log_n = Log(X) / Log(n)
End Function

' 左シフト
Private Function sl(ByVal X&, ByVal n&) As Long
    If n = 0 Then
        sl = X
    Else
        Dim k: k = CLng(2 ^ (32 - n - 1))
        Dim d: d = X And (k - 1)
        Dim C: C = d * CLng(2 ^ n)
        If X And k Then C = C Or &H80000000
        sl = C
    End If
End Function

'初期化済み配列生成 - オブジェクトNG
Private Function InitRangeAry(ByVal Count&, ByVal Value As Variant)
    Dim Ary() As Variant: ReDim Ary(Count)
    Dim i&
        For i = 0 To Count
            Ary(i) = Value
        Next
    InitRangeAry = Ary
End Function

'初期化済みコレクション生成
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

実行してみた感じはこちら。

Pos:-40,-40   MotonNo:0
Pos:40,40     MotonNo:15
Pos:0,0       MotonNo:12
Pos:25,-25    MotonNo:5
Pos:1,1       MotonNo:12

てっきり上手く行くと思ったのに・・・・。

こちらの画像を参考にすると
その8 4分木空間分割を最適化する!
モートン番号は 10,5,6,15,6 の順で出て欲しいのに。
Y方向が逆になってます。 何故? 何処間違えたのだろう?

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

こちらの続きです。
2D曲線の折れ線化 - スプライン - C#ATIA


2DCADであれば比較的、重複線削除の機能を持ったものもあると思います。
AutoCADであればこんな機能です。
OVERKILL[重複オブジェクト削除] (コマンド) | AutoCAD | Autodesk Knowledge Network

僕が使用しているAdvanceCADでも機能は有るのですが、完全に一致して
いなければ "重複線" と判断されず、削除されないんです、AutoCAD
知りませんが。(極端な話 0.00000001mm ズレていても削除されません)

3Dな時代なので、恐らく現実的には寸法指定の無い部分であれば、大まかな
形状のラインさえ図面に存在していれば良いような気がしています。(違いますか?)

完全に一致していないものでも "不要だよ" と思われる線が図面内には
結構な量が存在していませんかね?

それを考えると、やはりトレランスを考慮した重複線削除の機能が欲しいんです。
個人的には。(要は見た目で影響ない範囲で、データを軽くしたい)



2D円弧と2Dスプライン折れ線化を利用し、指定したビュー内の重複線を
探し出し、選択した状態で終了するテストマクロです。

説明不足な上、重複チェックがほぼ総当りに近い状態な為、使い物には
なりませんが、後に修正したものとの比較の為に掲載します。

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

Option Explicit

'*** 設定 ***
Private Const POLY_TOL = 0.1        '折れ線化トレランス
Private Const OVER_TOL = 0.001      '重複判断トレランス
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 ** :" & 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"
    
    '列挙用ソート
    Dim EnumLst As Collection: Set EnumLst = InitRangeList(CrvLst.Count)
    Call Q_ISort_List(EnumLst, LngLst)
    Debug.Print "EnumLst- " & EnumLst.Count & "個 : " & KCL.SW_GetTime & "s"
    
    '重複線Idx取得
    Dim OverLst As Collection: Set OverLst = GetOverlapList(EnumLst, PolyLst)
    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(IdxList As Collection, PolyList As Collection) As Collection
    Set GetOverlapList = Nothing

    Dim i&, j&
    Dim List As Collection: Set List = New Collection
    
    For i = 1 To IdxList.Count '重複線を判断する側(長いほうの線)
        For j = i + 1 To IdxList.Count '重複線を判断される側
            If IsOverlap(PolyList(IdxList(i)), PolyList(IdxList(j))) Then
                List.Add IdxList(j)
            End If
        Next
    Next
    Set GetOverlapList = List
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 '終点座標
    'Dim Lng# '長さ
    
    'スプライン情報
    Call Geo.GetEndPoints(Pos)
    StPos = Array(Pos(0), Pos(1))
    EnPos = Array(Pos(2), Pos(3))
    
    '折れ線化
    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)
        
        '分割点作成 距離チェック
        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
        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_V_P(Normaliz2d(A, B), C) 'ここ前回と変更した
    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))
    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

'長さ順の列挙用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

最近、コードが長すぎる・・・ブログに掲載するには限界を超えている気がします。

折れ線化トレランス と 重複判断トレランス の2つのトレランスを持たせているのは、
以前に作った際、悩んだ末の名残です。

ビューに線が265本と3955本あるデータで、折れ線化するまでの処理を
折れ線化トレランス 0.1 と 0.001 で試した結果がこちらです。

- 265本 -
** Obj Start ** :
POLY_TOL-0.1 : OVER_TOL-0.001
CrvLst- 265個 : 0.143s
RngLst- 265個 : 0.187s
LngLst- 265個 : 0.268s
PolyLst- 265個 : 0.584s
EnumLst- 265個 : 0.598s

** Obj Start ** :
POLY_TOL-0.001 : OVER_TOL-0.001
CrvLst- 265個 : 0.143s
RngLst- 265個 : 0.187s
LngLst- 265個 : 0.272s
PolyLst- 265個 : 3.092s
EnumLst- 265個 : 3.104s


- 3955本 -
** Obj Start ** :
POLY_TOL-0.1 : OVER_TOL-0.001
CrvLst- 3955個 : 2.008s
RngLst- 3955個 : 2.643s
LngLst- 3955個 : 3.953s
PolyLst- 3955個 : 16.375s
EnumLst- 3955個 : 18.51s

** Obj Start ** :
POLY_TOL-0.001 : OVER_TOL-0.001
CrvLst- 3955個 : 2.054s
RngLst- 3955個 : 2.711s
LngLst- 3955個 : 4.029s
PolyLst- 3955個 : 221.812s
EnumLst- 3955個 : 223.93s

折れ線化トレランス(POLY_TOL)を 0.001mmにすると、一気に処理時間が増えます。
原因は曲線の折れ線化のアルゴリズムの悪さです。 が、これ以上の良い方法が
わかりません。


Fusion360だと、こんな関数があるんですよ。
Help
試してはいないのですが、始点パラメータ・終点パラメータ・トレランス を指定してやれば、
トレランス以内で折れ線化するための点群が、恐らく得られる関数だと思います。
最初に見付けたとき、羨ましくてしょうがなかったです。

CATIA V5 Time Saving Toolbar

これ、すごいなぁ

www.youtube.com

全てを把握してはいないのですが、作れないことは無さそうだけど
これだけ集められると圧倒されます。

これ英語だけど(Fernandoさん ルーマニアの方の様です)無料っぽいです。

2D曲線の折れ線化 - スプライン

こちらの続きです。
2D曲線の折れ線化 - 円弧 - C#ATIA


前回のもののスプライン版です。

円弧版同様に、3DからリンクしたものはNGです。

'vba test_Curve2Polyline  using-'KCL0.09'
'2Dスプラインの折れ線化

Option Explicit

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

Sub CATMain()
    'ドキュメントのチェック
    If Not KCL.CanExecute("DrawingDocument") Then Exit Sub
    
    '選択
    Dim Geo As Curve2D: Set Geo = KCL.SelectItem("選択", "Curve2D")
    If KCL.IsNothing(Geo) Then Exit Sub
    
    '折れ線座標郡取得
    Dim PolyList As Collection: Set PolyList = Curve2Polyline(Geo)
    If KCL.IsNothing(PolyList) Then Exit Sub
    
    '点・線作成
    Dim View As DrawingView: Set View = KCL.GetParent_Of_T(Geo, "GeometricElements")
    Call DumpPnt2D(PolyList, View.Factory2D)
    Call DumpPoly2D(PolyList, View.Factory2D)
End Sub

'スプライン折れ線化 閉じた円弧はNG
Private Function Curve2Polyline(Geo As AnyObject) As Collection
    Const CutCount = 4 '分割数
    
    '円弧情報
    Dim Prm(1) '始点終点パラメータ
    Dim Pos(1) '座標
    Dim Lng# '長さ
    
    With Geo
        Call .GetParamExtents(Prm)
        Call .GetPointAtParam(Prm(0), Pos)
        Lng = .GetLengthAtParam(Prm(0), Prm(1))
    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)
        
        '分割点作成 距離チェック
        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) < m_PolyTol 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
        CutMax(0) = -1# '距離初期化
        
        If EQ(LoopSPara, LoopEPara) Then
            '始点と終点がほぼ同一 未対応
            Stop
        End If
    Loop
    Set Curve2Polyline = PntList
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

'単位ベクトルと点の距離
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))
    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


'確認用
'2D点
Private Sub DumpPnt2D(ByVal List As Collection, ByVal Fact As Factory2D)
    Dim Pos, P As Point2D
    For Each Pos In List
        Set P = Fact.CreatePoint(Pos(0), Pos(1))
        P.ReportName = 3
        P.Construction = False
    Next
End Sub

'2D線
Private Sub DumpPoly2D(ByVal List As Collection, ByVal Fact As Factory2D)
    Dim i&, L As Line2D
    For i = 1 To List.Count - 1
        Set L = Fact.CreateLine(List(i)(0), List(i)(1), _
                                List(i + 1)(0), List(i + 1)(1))
    Next
End Sub

CATIAの2Dは、円弧(Circle2D)は曲線(Curve2D)を継承している為、
選択フィルタを "Curve2D" 円弧も選択出来ちゃいます。円弧は
"特別な曲線" と言う扱いのようです。
(このマクロはチェックをしていない為、閉じた円弧はNGです)
円弧の場合は、演算で座標値を求めている為、前回のものの方が
処理が速いです。


実際に試した感じです。
f:id:kandennti:20170106193904p:plain
こんな感じのスプラインです。スプラインであれば閉じていたり自己交差
していても大丈夫です。

f:id:kandennti:20170106193910p:plain
マクロ実行後は、同様にトレランス以内となる点と直線を作ります。

f:id:kandennti:20170106193914p:plain
拡大した感じです。オレンジが元のスプラインで黒がマクロで作成した点と線です。
当然ですがトレランスを満たす為、曲率の大きい部分には多くの点が作成
されます。

f:id:kandennti:20170106193919p:plain
前回同様に、上記のコードはトレランス0.1mmとしていたので、スプラインの
両側0.1mmオフセットしたものが青色です。


当時、かなり悩みましたが思い付いた方法がこれでした。
後に調べたところ、"繰り返し折れ線近似法" と言う名称が一番近い
表現でした。

以前見つけたサイトが見つからなかったですが、こちらのアルゴリズムに
近かったです。(PDFがDLされちゃいます)
https://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwjw_tfJqa3RAhUIFJQKHXBnCVgQFggaMAA&url=https%3A%2F%2Fipsj.ixsq.nii.ac.jp%2Fej%2Findex.php%3Faction%3Dpages_view_main%26active_action%3Drepository_action_common_download%26item_id%3D119245%26item_no%3D1%26attribute_id%3D1%26file_no%3D1%26page_id%3D13%26block_id%3D8&usg=AFQjCNGoFx4jULe-WtjvRZWfs4rk3afBzA&bvm=bv.142059868,d.dGo


当時随分探したのですが 折れ線→スプラインは結構見つかるのですが、
逆はほぼ見つかりませんでした。
リンク先の方法だと最初に大量に点を作成する必要があるのですが、
その方法だと処理時間が長くなりすぎるので、出来るだけ無駄な点を
作成しないで折れ線化したいのですが、未だにベストな方法が見つかって
いません。

過去に、3Dスプラインの円弧近似化をこちらで行いましたが、
曲線と戦ってみる9 - C#ATIA
こちらは二分検索でやったのですが、こっちの方が速いのかな?

2D曲線の折れ線化 - 円弧

過去に作った2Dの重複線削除マクロを修正して、再度Upする意欲が
出て来たので、しばらくはこれに取り組みたいと思っています。

昔のコードに4分木を適用するのが結構面倒な事になりそうな為、
ほぼ作り直しです。


当時、重複線を判断する際、直線・円弧は何となく出来そうな
気がしていたのですが、スプラインについてはどの様に判断すれば
良いのかわかりませんでした。
結局、思いついたのは全て折れ線として変換し、直線と点同士
であれば、重複を判断出来そうだなぁと思い折れ線化の方法で
作りました。

以前の円弧→折れ線のコードを修正しテストしてみました。
選択する円弧は、3DからリンクしたものはNGです。

'vba test_Circle2Polyline  using-'KCL0.09'
'2D円弧の折れ線化

Option Explicit

'*** 設定 ***
Private Const m_PolyTol = 0.1 '折れ線化トレランス
'************

Sub CATMain()
    'ドキュメントのチェック
    If Not KCL.CanExecute("DrawingDocument") Then Exit Sub
    
    '選択
    Dim Geo As Circle2D
    Set Geo = KCL.SelectItem("選択", "Circle2D")
    If KCL.IsNothing(Geo) Then Exit Sub
    
    '折れ線座標郡取得
    Dim PolyList As Collection: Set PolyList = Circle2Poly(Geo)
    If KCL.IsNothing(PolyList) Then Exit Sub
    
    '点・線作成
    Dim View As DrawingView: Set View = KCL.GetParent_Of_T(Geo, "GeometricElements")
    Call DumpPnt2D(PolyList, View.Factory2D)
    Call DumpPoly2D(PolyList, View.Factory2D)
End Sub

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

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

'確認用
'2D点
Private Sub DumpPnt2D(ByVal List As Collection, ByVal Fact As Factory2D)
    Dim Pos, P As Point2D
    For Each Pos In List
        Set P = Fact.CreatePoint(Pos(0), Pos(1))
        P.ReportName = 3
        P.Construction = False
    Next
End Sub

'2D線
Private Sub DumpPoly2D(ByVal List As Collection, ByVal Fact As Factory2D)
    Dim i&, L As Line2D
    For i = 1 To List.Count - 1
        Set L = Fact.CreateLine(List(i)(0), List(i)(1), _
                                List(i + 1)(0), List(i + 1)(1))
    Next
End Sub

CATIAの機能として、曲線上に点を作成する事が可能なのですが
処理が遅い為、円弧情報から演算し指定トレランス以内で折れ線化出来る
座標値を求めています。
又、CATIAで2D円弧を書いた場合、通常は反時計回りになりますが
唯一ミラーした場合だけ時計回りとなります。

実際に試した感じです。
f:id:kandennti:20170106173919p:plain
こんな感じの円弧ですが、閉じた円弧でも大丈夫です。

f:id:kandennti:20170106173924p:plain
マクロ実行後は、トレランス以内となる点と直線を作ります。

f:id:kandennti:20170106173930p:plain
拡大してみると、オレンジが元の円弧で黒がマクロで作成した点と線です。

f:id:kandennti:20170106173934p:plain
上記のコードはトレランス0.1mmとしていたので、円弧を両側0.1mmオフセット
したものが青色です。画像は点と点の中間付近の一番トレランスから
外れやすい部分ですが、トレランス以内で折れ線化出来ています。


改めてコードを見た際、 "何でこんな計算式なのだろう?" と
思ったのですが、Sin Cosの計算回数を減らす為にこんな式にした
ようです。(本人が忘れています…)