こちらの続きです。
曲線と戦ってみる8 - C#ATIA
曲線の長さとチャレンジする円弧の長さの関係を
掴みたい為、こちらのコードを修正しました。
曲線と戦ってみる4 - C#ATIA
何処を修正したのか忘れてしまった為、全てコードを
記載しておきます。(KCLは必要です)
'vba Arc_Approximated2 Option Explicit Private Const mPntCount = 301& '曲線上のポイント数 Private Const mIndexMax = mPntCount - 1 '配列用のインデックス最大値 Private Const mTole = 0.02 '近似トレランス Private Const PI = 3.141592 '円周率 4 * Math.Atn(1) Private mPnts As Variant '曲線上の点 Private mPoss As Variant '曲線上の点の座標値 Private mArcs() As Variant '近似化成功した際の3点のインデックス Private mArcsCount& '近似化成功した数 Private mCurveLen# '選択した曲線長さ Sub CATMain() 'Documentチェック If KCL.IsType_Of_T(CATIA.ActiveDocument, "DrawingDocument") Then MsgBox "Part又はProductファイルを開いてください" Exit Sub End If '曲線選択 Dim Msg$: Msg = "曲線を選択して下さい : ESCキー 終了" Dim SelElem As SelectedElement Set SelElem = KCL.SelectElement(Msg, Array("HybridShape")) If KCL.IsNothing(SelElem) Then Exit Sub Dim CurveRef As Reference: Set CurveRef = SelElem.Reference '各種設定 Dim Doc As PartDocument: Set Doc = KCL.GetParent_Of_T(CurveRef, "PartDocument") '点作成、座標値取得 mPnts = InitPntsOnCurve(Doc.Part, CurveRef) mPoss = GetCoordinatesAry(mPnts) '近似化 mCurveLen = GetCurveLength(Doc.Part, CurveRef) ReDim mArcs(mIndexMax) mArcsCount = 0 Debug.Print "曲線全長,点数,トレランス" + vbNewLine + _ CStr(mCurveLen) + "," + CStr(mPntCount) + "," + CStr(mTole) + vbNewLine + _ "曲線長さ,円弧長さ,近似化" '検証 Call ChallengeArc(0, mIndexMax) If mArcsCount < 1 Then MsgBox "近似化できませんでした" '本当は点を削除する必要性アリ Exit Sub End If ReDim Preserve mArcs(mArcsCount - 1) '点群出力 Call AddTreePnts(mPnts, Doc.Part, "Points") '近似生成 Dim ArcCount&: ArcCount = Init3PntArc(mArcs, Doc.Part, "ConvertArcs") 'end MsgBox "円弧を" + CStr(ArcCount) + "個生成" End Sub '----- サポート関数 ----- '近似化の生成 'return-形状セット内の要素数 Private Function Init3PntArc&(ByVal Ary As Variant, ByVal Pt As Part, ByVal HBName As String) Dim Fact As HybridShapeFactory: Set Fact = Pt.HybridShapeFactory Dim Ref(2) As Reference Dim Arc() As HybridShapeCircle3Points: ReDim Arc(UBound(Ary)) Dim i& On Error Resume Next For i = 0 To UBound(Ary) Set Arc(i) = Fact.AddNewCircle3Points(mPnts(Ary(i)(0)), _ mPnts(Ary(i)(1)), _ mPnts(Ary(i)(2))) Call Pt.UpdateObject(Arc(i)) If Not Err.Number = 0 Then '演算上OKでもCATIAのトレランス上NGとなるデータを除去 Set Arc(i) = Nothing Err.Clear End If Next On Error GoTo 0 Init3PntArc = AddTreePnts(Arc, Pt, HBName) End Function '形状セットに挿入 'return-形状セット内の要素数 Private Function AddTreePnts&(ByVal Ary As Variant, ByVal Pt As Part, ByVal HBName As String) Dim HB As HybridBody: Set HB = Pt.HybridBodies.Add HB.Name = HBName Dim i& For i = 0 To UBound(Ary) If Not KCL.IsNothing(Ary(i)) Then Call HB.AppendHybridShape(Ary(i)) End If Next AddTreePnts = HB.HybridShapes.Count End Function 'トレランス以内に近似化可能な3点のインデックス郡を取得 - 再帰 Private Sub ChallengeArc(ByVal StIdx&, ByVal EndIdx&) If 2 > (EndIdx - StIdx) Then Exit Sub End If Dim MidIdx&: MidIdx = Int((StIdx + EndIdx) * 0.5) Dim Arc As Variant Arc = GetCircumCircleOfTriangle(mPoss(StIdx), mPoss(MidIdx), mPoss(EndIdx)) Dim CLen$: CLen = CStr(mCurveLen * ((EndIdx - StIdx) / mIndexMax)) '検証 If Not IsEmpty(Arc) Then If CheckTolerance(KCL.GetRangeAry(mPoss, StIdx + 1, EndIdx - 1), Arc) Then 'OK Debug.Print CLen + " , " + CStr(Arc(2)) + " , OK" '検証 mArcs(mArcsCount) = Array(StIdx, MidIdx, EndIdx) mArcsCount = mArcsCount + 1 Exit Sub Else Debug.Print CLen + " , " + CStr(Arc(2)) + " , NG" '検証 End If Else Debug.Print CLen + " , , NG" '検証 End If 'NG Call ChallengeArc(StIdx, MidIdx) Call ChallengeArc(MidIdx, EndIdx) End Sub '座標値郡が円弧のトレランス以内に有るかチェック 'GetCircumCircleOfTriangle関数の戻り値を使用すること Private Function CheckTolerance(ByVal Ary As Variant, ByVal Arc As Variant) As Boolean Dim i& For i = 0 To UBound(Ary) If mTole < Math.Abs(Arc(1) - GetDist(Ary(i), Arc(0))) Then CheckTolerance = False Exit Function End If Next CheckTolerance = True End Function '点群より座標値郡取得 Private Function GetCoordinatesAry(ByVal Pnts As Variant) As Variant Dim Poss() As Variant: ReDim Poss(mIndexMax) Dim P(2) As Variant, i& For i = 0 To mIndexMax Call Pnts(i).GetCoordinates(P) Poss(i) = P Next GetCoordinatesAry = Poss End Function '曲線上の点群作成 Private Function InitPntsOnCurve(ByVal Pt As Part, ByVal CurveRef As Reference) As Variant Dim Fact As HybridShapeFactory: Set Fact = Pt.HybridShapeFactory Dim Pnts() As Variant: ReDim Pnts(mIndexMax) Dim i&, Ratio# For i = 0 To mIndexMax Ratio = i / mIndexMax Set Pnts(i) = Fact.AddNewPointOnCurveFromPercent(CurveRef, Ratio, False) Call Pt.UpdateObject(Pnts(i)) Next InitPntsOnCurve = Pnts End Function '曲線長さの取得 Private Function GetCurveLength#(ByVal Pt As Part, ByVal Ref As Reference) GetCurveLength = Pt.Parent.GetWorkbench("SPAWorkbench").GetMeasurable(Ref).Length End Function '----- 演算関連 ----- '三角形の外接円の取得(3点通過円) 'return:array(0)-外心(array(2)) ' array(1)-半径 ' array(2)-円弧長さ Private Function GetCircumCircleOfTriangle(ByVal P1 As Variant, _ ByVal P2 As Variant, _ ByVal P3 As Variant) As Variant Dim M1 As Variant: M1 = GetMidPnt(P1, P2) Dim M2 As Variant: M2 = GetMidPnt(P2, P3) Dim V1 As Variant: V1 = Normalize(ToVecter(P1, P2)) Dim V2 As Variant: V2 = Normalize(ToVecter(P2, P3)) Dim PlaneVec As Variant '3P平面のベクトル PlaneVec = Cross(V1, V2) Dim EulerLineVec As Variant 'V2の直交ベクトル EulerLineVec = Normalize(Cross(V2, PlaneVec)) '3点が直線上に存在している場合、除去 Dim nv#: nv = Dot(V1, EulerLineVec) If GetDistSquare(ZeroVec, EulerLineVec) < 0.001 Then Exit Function If Math.Abs(nv) < 0.001 Then Exit Function 'オリジナルは過剰な精度な為値を修正 'これが何に該当するのか不明 M2から外心までの距離? Dim t#: t = (Dot(V1, M1) - Dot(V1, M2)) / nv '外接円 Dim CCofT(2) As Variant CCofT(0) = GetCircumCenter(M2, EulerLineVec, t) '外心取得 CCofT(1) = GetDist(CCofT(0), P1) '半径取得 Dim Over180 As Boolean '円弧の開き180°を超えているか? Over180 = IIf(Dot(Normalize(Cross(Normalize(ToVecter(P1, P3)), PlaneVec)), _ Normalize(ToVecter(P1, CCofT(0)))) > 0, True, False) CCofT(2) = GetArcLength(CCofT(1), GetDist(P1, P3), Over180) '円弧長さ GetCircumCircleOfTriangle = CCofT End Function '2点の中間点 Private Function GetMidPnt(ByVal P1 As Variant, ByVal P2 As Variant) As Variant Dim Pnt(2) As Double, i& For i = 0 To UBound(Pnt) Pnt(i) = (P1(i) + P2(i)) * 0.5 Next GetMidPnt = Pnt End Function '外心の取得 Private Function GetCircumCenter(ByVal P As Variant, _ ByVal V As Variant, _ ByVal L As Double) As Variant Dim cc(2) As Double, i& For i = 0 To UBound(cc) cc(i) = P(i) + V(i) * L Next GetCircumCenter = cc End Function '2点間距離-平方数 Private Function GetDistSquare#(ByVal P1 As Variant, ByVal P2 As Variant) GetDistSquare = (P2(0) - P1(0)) * (P2(0) - P1(0)) + _ (P2(1) - P1(1)) * (P2(1) - P1(1)) + _ (P2(2) - P1(2)) * (P2(2) - P1(2)) End Function '2点間距離 Private Function GetDist#(ByVal P1 As Variant, ByVal P2 As Variant) GetDist = Math.Sqr(GetDistSquare(P1, P2)) End Function '半径と弦の長さより円弧の長さを取得 'param:Radius-半径,Chord-弦の長さ,Over-180°越え? 'return:円弧長さ Private Function GetArcLength#(ByVal Radius As Double, ByVal Chord As Double, _ ByVal Over As Boolean) Dim Theta#: Theta = ASin((Chord / 2) / Radius) * 2 GetArcLength = Radius * IIf(Over, 2 * PI - Theta, Theta) End Function 'アークサイン Private Function ASin#(Number As Double) Dim X#, Y#, Z# '辺Zに仮定値を与えて辺Xと辺Yの値を算出 Z = 1# Y = Z * Number X = Math.Sqr(Z * Z - Y * Y) 'ラジアンを取得 Select Case Number Case -1 '正弦=-1の場合は-90度 ASin = (PI / 2) * -1 Case 1 '正弦= 1の場合は 90度 ASin = (PI / 2) * 1 Case Else 'YとXの正接(tanθ=Y / X)からラジアンを取得 ASin = Math.Atn(Y / X) End Select End Function '----- ベクトル関連 ----- '内積3D Private Function Dot(ByVal V1 As Variant, ByVal V2 As Variant) As Double Dot = V1(0) * V2(0) + V1(1) * V2(1) + V1(2) * V2(2) End Function '外積3D Private Function Cross(ByVal V1 As Variant, ByVal V2 As Variant) As Variant Dim Vec(2) As Double Vec(0) = V1(1) * V2(2) - V1(2) * V2(1) Vec(1) = V1(2) * V2(0) - V1(0) * V2(2) Vec(2) = V1(0) * V2(1) - V1(1) * V2(0) Cross = Vec End Function '単位ベクトル化 Private Function Normalize(ByVal V1 As Variant) As Variant Dim L As Double: L = GetDist(ZeroVec, V1) If L < 0.00001 Then Normalize = ZeroVec Exit Function End If Dim Nml(2) As Double, i& For i = 0 To UBound(Nml) Nml(i) = V1(i) / L Next Normalize = Nml End Function '2点間ベクトル Private Function ToVecter(ByVal P1 As Variant, ByVal P2 As Variant) As Variant Dim Vec(2) As Double, i& For i = 0 To UBound(Vec) Vec(i) = P2(i) - P1(i) Next ToVecter = Vec End Function 'ゼロベクトル Private Function ZeroVec() As Variant ZeroVec = Array(0#, 0#, 0#) End Function
主にChallengeArc関数でイミディエイトウィンドウに曲線、円弧の長さ
成功失敗の結果を出力させています。
実はこれちょっと前に出来上がっていたのですが、円弧長さの
計算が正しいものか自信が無く・・・。
検索すればズバッと式が見つかると思ったのですが、見つからず。
結局、裏紙に丸と三角書いて考えました。
通過する3点をピンクの三角形とみなし外接円は既にわかっています。
P1-外心-P3の角度が求まればよいわけです。
外接円半径とP1-P3距離の半分を利用し、アークサインでθを算出し
円弧長さを求めました。(もっとすんなり計算する方法有りますか?)
このような場合は良いんですよ。問題はこちら。
円弧の開きが180°を超えると言いますか、外心が三角形の内側に
なった場合、θの角度が求まってしまい赤の円弧長になってしま
います。まぁ当然です。
で結局、P1-P3のラインと外心の位置関係を内外積で判断して円弧を
算出しているのが、GetArcLength関数です。
もっと簡単に求まるものなのでしょうか?
「正弦定理で求まるのでは?」とチラッと言われたのですが・・・。
調べてみても、外接円の半径は求まる(=全周)と思うのですが
弧の長さはこれでは求まらないような・・・。