Option Strict Off
Option Explicit On
Imports System.Runtime.InteropServices
Imports SolidEdgePart
Imports SolidEdgeGeometry
Imports SolidEdgeConstants

Module modGandT
    Dim arcEdge As SolidEdgeGeometry.Edge
    Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Integer) '11/30/2005 ccprestr PR5342372

    Private Const Pi As Double = 3.14159265358979

    Private Enum ArcReturnConstants
        ArcSuccess = 1
        ArcInputIsCircle = 2
        ArcInputIsNotCircular = 3
        ArcUnkownFailure = 4
    End Enum

    Public Sub Main()
        ' need to think about taking these back to a generic object
        Dim objApp As SolidEdgeFramework.Application
        Dim objFlatDoc As SolidEdgePart.PartDocument
        Dim objModel As SolidEdgePart.Model
        Dim objFaces As SolidEdgeGeometry.Faces
        Dim objFace As SolidEdgeGeometry.Face
        Dim objTopFaces() As SolidEdgeGeometry.Face
        Dim objEdges As SolidEdgeGeometry.Edges
        Dim objEdge As SolidEdgeGeometry.Edge
        Dim objFoldedDoc As Object

        Dim dblMinRange(2) As Double
        Dim dblMaxRange(2) As Double
        Dim dblNormal(3) As Double
        Dim iTopFaceCount As Short
        Dim iOutlineEdgeCount As Short
        Dim iFoldEdgeCount As Short
        Dim objOutlineEdges() As Object
        Dim objFoldEdges() As Object
        Dim lgFaceCount As Integer
        Dim objSharedFaces(2) As Object
        Dim objArcFaces(2) As Object
        Dim iSharedFaceIndex As Short
        Dim i As Short
        Dim j As Short
        Dim k As Short
        Dim iFilePointer As Short
        Dim dblStartPoint(3) As Double
        Dim dblEndPoint(3) As Double
        Dim dblCenter(3) As Double
        Dim dblRadius As Double
        Dim dblStartAngle As Double
        Dim dblSweepAngle As Double
        Dim dblMajorAxis(3) As Double
        Dim DuplicateEdge As Boolean
        Dim dblOrigin(2) As Double
        Dim StrokeCount As Integer
        Dim StrokePoints() As Double
        Dim StrokeParams() As Double
        Dim Sync As Boolean

        Sync = False
        ReDim StrokePoints(100 * 3)
        ReDim StrokeParams(100 * 3)

        ' Connect to a running instance of Solid Edge.
        On Error Resume Next
        objApp = GetObject(, "SolidEdge.Application")
        If Err.Number Then
            MsgBox("Solid Edge must be running.")
            End
        End If
        On Error GoTo 0

        ' Make sure a sheetmetal document is open.
        If objApp.ActiveEnvironment <> "SheetMetal" And objApp.ActiveEnvironment <> "DMSheetMetal" Then
            MsgBox("A Sheet Metal document must be open.")
            End
        End If

        objFoldedDoc = objApp.ActiveDocument

        'Test to see if this file has been saved by
        'parsing objFoldedDoc.Name for a .psm extension
        If StrComp(Right(objFoldedDoc.Name, 4), ".psm", 1) <> 0 Then
            MsgBox("The Sheet Metal document must be saved before continuing.")
            End
        End If

        'Make sure the document contains a solid model
        If objFoldedDoc.Models.Count = 0 Then
            MsgBox("The Sheet Metal document must contain a part.")
            End
        End If

        ' Create a new Part document to create the flattened part in.
        objFlatDoc = objApp.Documents.Add("SolidEdge.PartDocument", "normal.par")

        ' Place the current sheet metal document into the part document and flatten it.
        objModel = objFlatDoc.Models.AddCopiedPart(FileName:=objFoldedDoc.FullName, XScale:=1, YScale:=1, ZScale:=1, MirrorOrFlattenPlane:=objFlatDoc.RefPlanes.Item(1), Flatten:=True)

        If SolidEdgeConstants.FeatureStatusConstants.igFeatureFailed = objModel.CopiedParts.Item(1).Status Then
            MsgBox("The Sheet Metal part could not be flattened.")
            ' Close the flat sheet metal file without saving.
            objFlatDoc.Close(False)
            End
        End If

        ' Get all of the planar faces in the model.
        objFaces = objModel.Body.Faces(FaceType:=SolidEdgeConstants.FeatureTopologyQueryTypeConstants.igQueryPlane)

        ' Allocate memory to store all of the top faces.
        ReDim objTopFaces(objFaces.Count / 2)

        ' Find and save all faces whose normal is in the positive Z axis.
        iTopFaceCount = -1
        For Each objFace In objFaces
            ' Get the parametric range of the face.
            objFace.GetParamRange(MinParam:=dblMinRange, MaxParam:=dblMaxRange)

            ' Get the normal at the min param point.
            objFace.GetNormal(NumParams:=1, Params:=dblMinRange, Normals:=dblNormal)

            ' Check to see if it's pointed in the positive Z direction.
            If dblNormal(2) > 0.9 Then
                ' Save this face because it's one of the top faces.
                iTopFaceCount = iTopFaceCount + 1
                objTopFaces(iTopFaceCount) = objFace
            End If
        Next objFace

        ' Allocate space for the edges
        ReDim objFoldEdges(objModel.Body.Edges(SolidEdgeConstants.FeatureTopologyQueryTypeConstants.igQueryAll).Count / 2)
        ReDim objOutlineEdges(objModel.Body.Edges(SolidEdgeConstants.FeatureTopologyQueryTypeConstants.igQueryAll).Count / 2)

        ' Initialize the counters.
        iOutlineEdgeCount = -1
        iFoldEdgeCount = -1

        ' Go through the top faces and collect all of the unique edges.  If any edges
        ' are shared by top faces, then they're saved seperately as fold edges.
        For i = 0 To iTopFaceCount
            ' Get all of the edges of the current face.
            objEdges = objTopFaces(i).Edges
            ' Check each edge to see if it is shared with another face.
            For j = 1 To objEdges.Count

                ' Get the other face that shares this edge.

                objEdge = objEdges.Item(j)
                objEdge.GetFaces(NumFaces:=lgFaceCount, Faces:=objSharedFaces)

                ' Get the other face.
                If objSharedFaces(0) Is objTopFaces(i) Then
                    iSharedFaceIndex = 1
                Else
                    iSharedFaceIndex = 0
                End If

                ' Get the parametric range of the face.
                objSharedFaces(iSharedFaceIndex).GetParamRange(MinParam:=dblMinRange, MaxParam:=dblMaxRange)

                ' Get the normal at the min param point.
                objSharedFaces(iSharedFaceIndex).GetNormal(NumParams:=1, Params:=dblMinRange, Normals:=dblNormal)

                ' If the normal is pointing in the positive Z direction then the edge is shared with another top face.
                If dblNormal(2) > 0.9 Then
                    ' Check to see if this edge is already accounted for.
                    DuplicateEdge = False
                    For k = 0 To iFoldEdgeCount
                        If objEdge Is objFoldEdges(k) Then
                            DuplicateEdge = True
                            Exit For
                        End If
                    Next

                    If Not DuplicateEdge Then
                        iFoldEdgeCount = iFoldEdgeCount + 1
                        objFoldEdges(iFoldEdgeCount) = objEdge
                    End If
                Else
                    ' The edge is outside edge.
                    iOutlineEdgeCount = iOutlineEdgeCount + 1
                    objOutlineEdges(iOutlineEdgeCount) = objEdge
                End If
            Next
        Next

        ' Write out the information to a file.
        iFilePointer = FreeFile()
        FileOpen(iFilePointer, System.IO.Path.GetTempPath & "SEDump.txt", OpenMode.Output)
        Dim EdgeID As Integer
        PrintLine(iFilePointer, "Edge curves ------------------------")
        For i = 0 To iOutlineEdgeCount
            EdgeID = objOutlineEdges(i).ID
            Select Case objOutlineEdges(i).Geometry.Type
                Case SolidEdgeConstants.GNTTypePropertyConstants.igLine
                    objOutlineEdges(i).GetEndPoints(StartPoint:=dblStartPoint, EndPoint:=dblEndPoint)

                    PrintLine(iFilePointer, "   Line" & " ID=" & EdgeID)
                    PrintLine(iFilePointer, "      Startpoint: " & FormatNumber(dblStartPoint(0), 8) & ", " & FormatNumber(dblStartPoint(1), 8) & ", " & FormatNumber(dblStartPoint(2), 8))
                    PrintLine(iFilePointer, "      Endpoint: " & FormatNumber(dblEndPoint(0), 8) & ", " & FormatNumber(dblEndPoint(1), 8) & ", " & FormatNumber(dblEndPoint(2), 8))
                Case SolidEdgeConstants.GNTTypePropertyConstants.igCircle
                    ' Determine if the edge is actually a circle or an arc.
                    If objOutlineEdges(i).IsClosed Then
                        objOutlineEdges(i).Geometry.GetCircleData(CenterPoint:=dblCenter, AxisVector:=dblNormal, Radius:=dblRadius)

                        PrintLine(iFilePointer, "   Circle" & " ID=" & EdgeID)
                        PrintLine(iFilePointer, "      Centerpoint: " & FormatNumber(dblCenter(0), 8) & ", " & FormatNumber(dblCenter(1), 8) & ", " & FormatNumber(dblCenter(2), 8))
                        PrintLine(iFilePointer, "      Radius: " & FormatNumber(dblRadius, 8))
                    Else

                        GetArcData(objOutlineEdges(i), True, dblCenter, dblNormal, dblStartPoint, dblEndPoint, dblStartAngle, dblSweepAngle, dblMajorAxis, dblRadius)

                        ' Modify the start angle so it is based on the major axis direction.  Since we're only
                        ' concerned with the arc in 2D space on the X-Y place, the start angle should be relative
                        ' to the x axis instead.
                        dblOrigin(0) = 0
                        dblOrigin(1) = 0
                        dblStartAngle = dblStartAngle + Bearing(dblOrigin, dblMajorAxis)

                        PrintLine(iFilePointer, "   Arc" & " ID=" & EdgeID)
                        PrintLine(iFilePointer, "      Centerpoint: " & FormatNumber(dblCenter(0), 8) & ", " & FormatNumber(dblCenter(1), 8) & ", " & FormatNumber(dblCenter(2), 8))
                        PrintLine(iFilePointer, "      Startpoint: " & FormatNumber(dblStartPoint(0), 8) & ", " & FormatNumber(dblStartPoint(1), 8) & ", " & FormatNumber(dblStartPoint(2), 8))
                        PrintLine(iFilePointer, "      Endpoint: " & FormatNumber(dblEndPoint(0), 8) & ", " & FormatNumber(dblEndPoint(1), 8) & ", " & FormatNumber(dblEndPoint(2), 8))
                        PrintLine(iFilePointer, "      Start Angle: " & FormatNumber(dblStartAngle, 8))
                        PrintLine(iFilePointer, "      Sweep Angle: " & FormatNumber(dblSweepAngle, 8))
                        PrintLine(iFilePointer, "      Radius: " & FormatNumber(dblRadius, 8))
                    End If
                Case SolidEdgeConstants.GNTTypePropertyConstants.igEllipse, SolidEdgeConstants.GNTTypePropertyConstants.igBSplineCurve
                    If objOutlineEdges(i).Geometry.Type = SolidEdgeConstants.GNTTypePropertyConstants.igEllipse Then
                        PrintLine(iFilePointer, "   Ellipse (stroked)" & " ID=" & EdgeID)
                    ElseIf objOutlineEdges(i).Geometry.Type = SolidEdgeConstants.GNTTypePropertyConstants.igBSplineCurve Then
                        PrintLine(iFilePointer, "   BSplineCurve (stroked)" & " ID=" & EdgeID)
                    End If

                    ' Stroke ellipses and bspline curves.
                    objEdge = objOutlineEdges(i)
                    'Dim numPoints(2) As Integer
                    'numPoints = objEdge.StrokeCount
                    ReDim StrokePoints(100 * 3)
                    ReDim StrokeParams(100 * 3)

                    objOutlineEdges(i).GetStrokeData(Tolerance:=0.001, StrokeCount:=StrokeCount, Points:=StrokePoints, Params:=StrokeParams)

                    PrintLine(iFilePointer, "      Points: " & StrokeCount & " ID=" & EdgeID)
                    For j = 1 To StrokeCount
                        PrintLine(iFilePointer, "      " & FormatNumber(StrokePoints((j - 1) * 3), 8) & ", " & FormatNumber(StrokePoints(((j - 1) * 3) + 1), 8) & ", " & FormatNumber(StrokePoints(((j - 1) * 3) + 2), 8))
                    Next
            End Select
        Next

        PrintLine(iFilePointer, "")
        PrintLine(iFilePointer, "Bend line curves ------------------------")
        For i = 0 To iFoldEdgeCount
            EdgeID = objFoldEdges(i).ID
            Select Case objFoldEdges(i).Geometry.Type
                Case SolidEdgeConstants.GNTTypePropertyConstants.igLine
                    objFoldEdges(i).GetEndPoints(StartPoint:=dblStartPoint, EndPoint:=dblEndPoint)

                    PrintLine(iFilePointer, "   Line" & " ID=" & EdgeID)
                    PrintLine(iFilePointer, "      Startpoint: " & FormatNumber(dblStartPoint(0), 8) & ", " & FormatNumber(dblStartPoint(1), 8) & ", " & FormatNumber(dblStartPoint(2), 8))
                    PrintLine(iFilePointer, "      Endpoint: " & FormatNumber(dblEndPoint(0), 8) & ", " & FormatNumber(dblEndPoint(1), 8) & ", " & FormatNumber(dblEndPoint(2), 8))
                Case SolidEdgeConstants.GNTTypePropertyConstants.igCircle
                    ' Determine if the edge is actually a circle or an arc.
                    If objFoldEdges(i).IsClosed Then
                        objFoldEdges(i).Geometry.GetCircleData(CenterPoint:=dblCenter, AxisVector:=dblNormal, Radius:=dblRadius)

                        PrintLine(iFilePointer, "   Circle" & " ID=" & EdgeID)
                        PrintLine(iFilePointer, "      Centerpoint: " & FormatNumber(dblCenter(0), 8) & ", " & FormatNumber(dblCenter(1), 8) & ", " & FormatNumber(dblCenter(2), 8))
                        PrintLine(iFilePointer, "      Radius: " & FormatNumber(dblRadius, 8))
                    Else
                        GetArcData(objFoldEdges(i), True, dblCenter, dblNormal, dblStartPoint, dblEndPoint, dblStartAngle, dblSweepAngle, dblMajorAxis, dblRadius)

                        ' Modify the start angle so it is based on the major axis direction.  Since we're only
                        ' concerned with the arc in 2D space on the X-Y place, the start angle should be relative
                        ' to the x axis instead.
                        dblOrigin(0) = 0
                        dblOrigin(1) = 0
                        dblStartAngle = dblStartAngle + Bearing(dblOrigin, dblMajorAxis)

                        PrintLine(iFilePointer, "   Arc" & " ID=" & EdgeID)
                        PrintLine(iFilePointer, "      Centerpoint: " & FormatNumber(dblCenter(0), 8) & ", " & FormatNumber(dblCenter(1), 8) & ", " & FormatNumber(dblCenter(2), 8))
                        PrintLine(iFilePointer, "      Startpoint: " & FormatNumber(dblStartPoint(0), 8) & ", " & FormatNumber(dblStartPoint(1), 8) & ", " & FormatNumber(dblStartPoint(2), 8))
                        PrintLine(iFilePointer, "      Endpoint: " & FormatNumber(dblEndPoint(0), 8) & ", " & FormatNumber(dblEndPoint(1), 8) & ", " & FormatNumber(dblEndPoint(2), 8))
                        PrintLine(iFilePointer, "      Start Angle: " & FormatNumber(dblStartAngle, 8))
                        PrintLine(iFilePointer, "      Sweep Angle: " & FormatNumber(dblSweepAngle, 8))
                        PrintLine(iFilePointer, "      Radius: " & FormatNumber(dblRadius, 8))
                    End If
                Case SolidEdgeConstants.GNTTypePropertyConstants.igEllipse, SolidEdgeConstants.GNTTypePropertyConstants.igBSplineCurve
                    If objFoldEdges(i).Geometry.Type = SolidEdgeConstants.GNTTypePropertyConstants.igEllipse Then
                        PrintLine(iFilePointer, "   Ellipse (stroked)" & " ID=" & EdgeID)
                    ElseIf objFoldEdges(i).Geometry.Type = SolidEdgeConstants.GNTTypePropertyConstants.igBSplineCurve Then
                        PrintLine(iFilePointer, "   BSplineCurve (stroked)" & " ID=" & EdgeID)
                    End If

                    ' Stroke ellipses and bspline curves.
                    objFoldEdges(i).GetStrokeData(Tolerance:=0.001, StrokeCount:=StrokeCount, Points:=StrokePoints, Params:=StrokeParams)

                    PrintLine(iFilePointer, "      Points: " & StrokeCount & " ID=" & EdgeID)
                    For j = 1 To StrokeCount
                        PrintLine(iFilePointer, "      " & FormatNumber(StrokePoints((j - 1) * 3), 8) & ", " & FormatNumber(StrokePoints(((j - 1) * 3) + 1), 8) & ", " & FormatNumber(StrokePoints(((j - 1) * 3) + 2), 8))
                    Next
            End Select
        Next

        ' Close the flat sheet metal file without saving.
        objFlatDoc.Close(False)
        objFoldedDoc.activate()

        ' Close the log file
        FileClose(iFilePointer)

        MsgBox("Data file written to '" & System.IO.Path.GetTempPath & "\SEDump.txt'", MsgBoxStyle.OkOnly)

        End
    End Sub
	
	'
	' Purpose: Given an edge that represents an arc, this function uses the edge information and the geometry data of
	'          the underlying circle to compute the arc defintion.  The output is a complete defintion of an arc.
	'          (Some of the data is redundent.  For example the start and sweep angles can be computed using the
	'          start and end points.)
	'
	' Arguments: ArcEdge (I) - Edge object.
	'            UseFaceNormal (I) - Boolean flag that indicates wether the arc definition is to be returned with
	'                                respect to the arc geometry normal or the normal of the face the arc is on.
	'            CenterPoint (O) - Array of 3 doubles that define the center point of the arc or circle.
	'            NormalVector (O) - Array of 3 doubles that define the normal vector of the arc.
	'            StartPoint (O) - Array of 3 doubles that define the start point of the arc.
	'            EndPoint (O) - Array of 3 doubles that define the end point of the arc.
	'            StartAngle - Double value that defines the start angle.
	'            SweepAngle - Double value that defines the sweep angle.
	'            MajorAxis - Array of 3 double that define the major axis vector of the arc.  This defines the
	'                        zero angle for the start and sweep angles.
	'            Radius - Double value that defines the radius of the arc.
	'
	' Return: The return value can be one of the following constants from the ArcReturnConstants enum.
	'    ArcSuccess = 1
	'    ArcInputIsCircle = 2
	'    ArcInputIsNotCircular = 3
	'    ArcUnkownFailure = 4
	'
    Private Function GetArcData(ByRef arcEdge As SolidEdgeGeometry.Edge, ByRef UseFaceNormal As Boolean, ByRef CenterPoint() As Double, ByRef NormalVector() As Double, ByRef StartPoint() As Double, ByRef EndPoint() As Double, ByRef StartAngle As Double, ByRef SweepAngle As Double, ByRef MajorAxis() As Double, ByRef Radius As Double) As ArcReturnConstants
        Dim mStartPnt(3) As Double
        Dim mEndPnt(3) As Double
        Dim mTmpPnt(3) As Double
        Dim mParamExtent(2) As Double
        Dim mMidParam(1) As Double
        Dim mMidPnt(3) As Double
        Dim mTmpVector1(3) As Double
        Dim mTmpVector2(3) As Double
        Dim mTmpVector3(3) As Double
        Dim mLength As Double

        ' Make sure the edge is a circle.
        If arcEdge.Geometry.Type <> SolidEdgeConstants.GNTTypePropertyConstants.igCircle Then
            GetArcData = ArcReturnConstants.ArcInputIsNotCircular
            Exit Function
        End If

        ' Check to see if it is a full circle by seeing if
        ' the start and end vertices of the edge are the same.
        If arcEdge.StartVertex Is arcEdge.EndVertex Then
            ' Fill in the information we do know about a circle.
            arcEdge.Geometry.GetCircleData(CenterPoint, NormalVector, Radius)
            If UseFaceNormal Then
                GetParallelFaceNormal(arcEdge, NormalVector)
            End If

            GetArcData = ArcReturnConstants.ArcInputIsCircle
            Exit Function
        End If

        ' Get the circle information.
        arcEdge.Geometry.GetCircleData(CenterPoint, NormalVector, Radius)
        If UseFaceNormal Then
            GetParallelFaceNormal(arcEdge, NormalVector)
        End If

        ' Get the start and end points from the edge.
        arcEdge.GetEndPoints(mStartPnt, mEndPnt)

        ' Get a point on the arc between the start and end points.
        arcEdge.GetParamExtents(mParamExtent(0), mParamExtent(1))
        mMidParam(0) = (mParamExtent(0) + mParamExtent(1)) / 2
        arcEdge.GetPointAtParam(1, mMidParam, mMidPnt)

        ' Check to make sure the flow of the arc is CCW from the start to end point.
        mTmpVector1(0) = mEndPnt(0) - CenterPoint(0)
        mTmpVector1(1) = mEndPnt(1) - CenterPoint(1)
        mTmpVector1(2) = mEndPnt(2) - CenterPoint(2)

        mTmpVector2(0) = mMidPnt(0) - CenterPoint(0)
        mTmpVector2(1) = mMidPnt(1) - CenterPoint(1)
        mTmpVector2(2) = mMidPnt(2) - CenterPoint(2)
        VectorCrossProduct(mTmpVector1, mTmpVector2, mTmpVector3)
        Normalize(mTmpVector3)

        If VectorDotProduct(NormalVector, mTmpVector3) > 0 Then
            ' The arc is Clockwise so reverse the start and end points.
            mTmpPnt(0) = mStartPnt(0)
            mTmpPnt(1) = mStartPnt(1)
            mTmpPnt(2) = mStartPnt(2)

            mStartPnt(0) = mEndPnt(0)
            mStartPnt(1) = mEndPnt(1)
            mStartPnt(2) = mEndPnt(2)

            mEndPnt(0) = mTmpPnt(0)
            mEndPnt(1) = mTmpPnt(1)
            mEndPnt(2) = mTmpPnt(2)
        End If

        ' Calculate the sweep of the arc.
        mTmpVector1(0) = mStartPnt(0) - CenterPoint(0)
        mTmpVector1(1) = mStartPnt(1) - CenterPoint(1)
        mTmpVector1(2) = mStartPnt(2) - CenterPoint(2)
        Normalize(mTmpVector1)

        mTmpVector2(0) = mEndPnt(0) - CenterPoint(0)
        mTmpVector2(1) = mEndPnt(1) - CenterPoint(1)
        mTmpVector2(2) = mEndPnt(2) - CenterPoint(2)
        Normalize(mTmpVector2)
        VectorCrossProduct(mTmpVector1, mTmpVector2, mTmpVector3)
        mLength = VectorLength(mTmpVector3)

        ' Set the sign of the length based on whether it's in the same direction as the normal or not.
        If VectorDotProduct(NormalVector, mTmpVector3) < 0 Then
            mLength = -mLength
        End If

        If VectorDotProduct(mTmpVector1, mTmpVector2) >= 0 Then
            ' The second vector is pointing in the same direction as the first so
            ' the angle is in either the 1st or 4th quadrants.
            SweepAngle = (2 * Pi) + ArcSin(mLength)
        Else
            ' The vectors are pointing in opposite directions so the angle
            ' is in either the 2nd or 3rd quadrants.
            SweepAngle = Pi - ArcSin(mLength)
        End If

        If SweepAngle > (2 * Pi) Then
            SweepAngle = SweepAngle - (2 * Pi)
        End If

        StartAngle = 0

        MajorAxis(0) = mStartPnt(0) - CenterPoint(0)
        MajorAxis(1) = mStartPnt(1) - CenterPoint(1)
        MajorAxis(2) = mStartPnt(2) - CenterPoint(2)

        StartPoint(0) = mStartPnt(0)
        StartPoint(1) = mStartPnt(1)
        StartPoint(2) = mStartPnt(2)

        EndPoint(0) = mEndPnt(0)
        EndPoint(1) = mEndPnt(1)
        EndPoint(2) = mEndPnt(2)

        GetArcData = ArcReturnConstants.ArcSuccess
    End Function
	
    Private Sub GetParallelFaceNormal(ByRef ArcEdge As SolidEdgeGeometry.Edge, ByRef NormalVector() As Double)
        Dim objArcFaces(2) As Object
        Dim dblParamExtent(2) As Double
        Dim iFaceCount As Integer
        Dim dblNormal1(3) As Double
        Dim dblNormal2(3) As Double
        Dim dblCenterParam(2) As Double
        Dim dblArcNormal(3) As Double
        Dim dblMinRange(3) As Double
        Dim dblMaxRange(3) As Double
        ' Get the normal of the arc.
        ArcEdge.Geometry.GetAxisVector(dblArcNormal)

        ' Get the two faces connected to the edge.
        arcEdge.GetFaces(NumFaces:=iFaceCount, Faces:=objArcFaces)

        ' Get the parametric range of the face.
        objArcFaces(0).GetParamRange(MinParam:=dblMinRange, MaxParam:=dblMaxRange)

        ' Compute a center param point.
        dblCenterParam(0) = (dblMinRange(0) + dblMaxRange(0)) / 2
        dblCenterParam(1) = (dblMinRange(1) + dblMaxRange(1)) / 2

        ' Get the normal at the min param point.
        objArcFaces(0).GetNormal(NumParams:=1, Params:=dblCenterParam, Normals:=dblNormal1)

        ' Get the parametric range of the face.
        objArcFaces(1).GetParamRange(MinParam:=dblMinRange, MaxParam:=dblMaxRange)

        ' Compute a center param point.
        dblCenterParam(0) = (dblMinRange(0) + dblMaxRange(0)) / 2
        dblCenterParam(1) = (dblMinRange(1) + dblMaxRange(1)) / 2

        ' Get the normal at the min param point.
        objArcFaces(1).GetNormal(NumParams:=1, Params:=dblCenterParam, Normals:=dblNormal2)

        ' Check to see if it's pointed in the positive Z direction.
        If System.Math.Abs(VectorDotProduct(dblArcNormal, dblNormal1)) > System.Math.Abs(VectorDotProduct(dblArcNormal, dblNormal2)) Then
            NormalVector(0) = dblNormal1(0)
            NormalVector(1) = dblNormal1(1)
            NormalVector(2) = dblNormal1(2)
        Else
            NormalVector(0) = dblNormal2(0)
            NormalVector(1) = dblNormal2(1)
            NormalVector(2) = dblNormal2(2)
        End If
    End Sub
	
	
	Public Function VectorLength(ByRef InVector() As Double) As Double
		Dim dy, dx, dz As Double
		
        dx = InVector(0)
        dy = InVector(1)
        dz = InVector(2)
		
		VectorLength = System.Math.Sqrt((dx * dx) + (dy * dy) + (dz * dz))
	End Function
	
	
	Public Function VectorDotProduct(ByRef Vector1() As Double, ByRef Vector2() As Double) As Double
        VectorDotProduct = (Vector1(0) * Vector2(0) + Vector1(1) * Vector2(1) + Vector1(2) * Vector2(2))
	End Function
	
	
	Public Sub VectorCrossProduct(ByRef Vector1() As Double, ByRef Vector2() As Double, ByRef OutVector() As Double)
        OutVector(0) = (Vector1(1) * Vector2(2)) - (Vector1(2) * Vector2(1))
        OutVector(1) = (Vector1(2) * Vector2(0)) - (Vector1(0) * Vector2(2))
        OutVector(2) = (Vector1(0) * Vector2(1)) - (Vector1(1) * Vector2(0))
	End Sub
	
	' Normalizes a 3D vector.
	Public Function Normalize(ByRef Vector() As Double) As Boolean
		Dim XVal As Double
		Dim YVal As Double
		Dim ZVal As Double
		Dim Dist As Double
		
        XVal = Vector(0)
        YVal = Vector(1)
        ZVal = Vector(2)
		
		Dist = XVal * XVal + YVal * YVal + ZVal * ZVal
		If Dist > 0 Then
			Dist = System.Math.Sqrt(Dist)
			XVal = XVal / Dist
			YVal = YVal / Dist
			ZVal = ZVal / Dist
            Vector(0) = XVal
            Vector(1) = YVal
            Vector(2) = ZVal
			Normalize = True
		Else
			Normalize = False
		End If
	End Function
	
	
	Public Function ArcSin(ByRef x As Double) As Double
		If x = -1 Then
			ArcSin = -Pi / 2
		ElseIf x = 1 Then 
			ArcSin = Pi / 2
		Else
			ArcSin = System.Math.Atan(x / System.Math.Sqrt(-x * x + 1))
		End If
	End Function
	
	
	Public Function Arccos(ByRef x As Double) As Double
		If x = 1 Then
			Arccos = 0
		ElseIf (x + 1) < 0.000001 Then 
			' X is -1
			Arccos = Pi
		Else
			Arccos = System.Math.Atan(-x / System.Math.Sqrt(-x * x + 1)) + 2 * (Pi / 4)
		End If
	End Function
	
	
	Public Function Bearing(ByRef OriginPoint() As Double, ByRef DirPoint() As Double) As Double
		Dim Length As Double
		
		' Compute distance between the points.
        Length = DistPointPoint2D(OriginPoint, DirPoint)
		
		' Compute angle of line assuming in first quadrant.
        Bearing = Arccos(System.Math.Abs(DirPoint(0) - OriginPoint(0)) / Length)

        ' Redefine angle based on which quadrant the point is in.
        If DirPoint(0) > OriginPoint(0) And DirPoint(1) >= OriginPoint(1) Then
            ' Second point is in first quadrant.
            Bearing = Bearing
        ElseIf DirPoint(0) <= OriginPoint(0) And DirPoint(1) >= OriginPoint(1) Then
            ' Second point is in second quadrant.
            Bearing = Pi - Bearing
        ElseIf DirPoint(0) <= OriginPoint(0) And DirPoint(1) < OriginPoint(1) Then
            ' Second point is in third quadrant.
            Bearing = Pi + Bearing
        ElseIf DirPoint(0) > OriginPoint(0) And DirPoint(1) < OriginPoint(1) Then
            ' Second point is in fourth quadrant.
            ' Special case where it is very close to zero, set to zero.
            If System.Math.Abs(Bearing) < 0.0000001 Then
                Bearing = 0
            Else
                Bearing = (2 * Pi) - Bearing
            End If
        End If
	End Function
	
	' Computes the distance between two 2D points.
	Private Function DistPointPoint2D(ByRef Point1() As Double, ByRef Point2() As Double) As Object
        Dim dy, dx As Double
		
        dx = Point1(0) - Point2(0)
        dy = Point1(1) - Point2(1)
		
		DistPointPoint2D = System.Math.Sqrt((dx * dx) + (dy * dy))
	End Function
End Module