0xAA55 发表于 2015-4-8 23:27:34

读取stl格式的3D模型的VB的类(By:嗷嗷叫的老马)

这个类用于读取stl格式的3D模型(一种后缀是.stl的3D模型文件,这个和C++的stl库无关)
stl格式的3D模型可以被几乎所有的3D软件加载,比如3dsmax或maya。
不保证有无BUG。可以用于研究stl的文件格式。
原作者:嗷嗷叫的老马
这是一个VB6的类,不是VB.NET的东西。
据说……这种格式不存储纹理坐标。VERSION 1.0 CLASS
BEGIN
MultiUse = -1'True
Persistable = 0'NotPersistable
DataBindingBehavior = 0'vbNone
DataSourceBehavior= 0'vbNone
MTSTransactionMode= 0'NotAnMTSObject
END
Attribute VB_Name = "cLoadSTL"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Option Compare Text

'加载STL格式文件
'
'判断STL文件的格式(ASCII与BINARY),并读入内存
'
'By 嗷嗷叫的老马
'
Private Type MyFacet
    PNX As Single       '法向量
    PNY As Single       '法向量
    PNZ As Single       '法向量
   
    P1X As Single       '第一个顶点
    P1Y As Single       '第一个顶点
    P1Z As Single       '第一个顶点
   
    P2X As Single       '第一个顶点
    P2Y As Single       '第一个顶点
    P2Z As Single       '第一个顶点
   
    P3X As Single       '第一个顶点
    P3Y As Single       '第一个顶点
    P3Z As Single       '第一个顶点
   
    FacetProperties As Integer'面片属性
End Type

Dim mFileName As String         '文件名
Dim mDescriptionAs String   '文件信息
Dim mData() As MyFacet          '三角面片数据
Dim mFacetIndex As Long         '当前索引
Dim mXMax As Single, mYMax As Single, mXMin As Single, mYMin As Single, mZMin As Single, mZMax As Single

Public Function LoadSTLFile(ByVal sFileName As String) As Long
    Dim sBuff As String, sLBuff() As String, byteBuff() As Byte, mFacetCount As Long
    Dim IsAscii As Boolean, DataIndex As Long, VIndex As Long
    Dim Fn As Long, I As Long, J As Long, sTmp() As String
   
'    On Error GoTo errH
   
    LoadSTLFile = -1
   
    If FileLen(sFileName) < 0 Then Exit Function
   
    mFileName = sFileName
   
    mXMax = -999
    mXMin = 999
    mYMax = -999
    mYMin = 999
    mZMax = -999
    mZMin = 999
   
    Fn = FreeFile
    Open mFileName For Binary As #Fn
      ReDim byteBuff(79)
      Get #Fn, , byteBuff()
      
      IsAscii = False
      For I = 0 To 79
            If byteBuff(I) = &HA Then
                IsAscii = True
                Exit For
            End If
      Next
      
'      MsgBox IsAscii
      
      If IsAscii = True Then      'ASCII格式包含换行符
            '以ASCII格式读取数据
            '
'            solid OpenSCAD_Model                                           '文件信息
'                facet normal -1.000000 0.000000 0.000000                     '一个三角面片开始,法向量坐标
'                  outer loop                                                       '开始描述三角面片顶点坐标
'                  vertex -25.000000 -29.250000 8.000000                              '第一个点
'                  vertex -25.000000 -27.586706 7.825181                              '第二个点
'                  vertex -25.000000 -30.913294 7.825181                              '第三个点
'                  endloop                                                          '结束顶点描述
'                endfacet                                                       '一个三角面片结束
'            endsolid OpenSCAD_Model
            
            sBuff = Space(LOF(1))
            Get #Fn, 1, sBuff
            sLBuff() = Split(Replace(sBuff, Chr(0), " "), vbLf)
            
'            MsgBox "00"
            
            mFacetCount = -1
            
            mDescription = Replace(sLBuff(0), "solid ", "")         '得到文件信息
            
'            MsgBox "11"
            
            For I = 1 To UBound(sLBuff)
                sTmp() = Split(Trim(sLBuff(I)), " ")
                For J = 0 To UBound(sTmp)
                  sTmp(J) = Trim(sTmp(J))
                Next
                If J > 0 Then
                  Select Case sTmp(0)
                        Case "facet"            '一个三角面片开始,得到法向量坐标
                            mFacetCount = mFacetCount + 1
                            ReDim Preserve mData(mFacetCount)
                           
                            With mData(mFacetCount)
                              .PNX = sTmp(2)
                              .PNY = sTmp(3)
                              .PNZ = sTmp(4)
                            End With
                        Case "outer"            '描述三角面片顶点坐标
                            VIndex = 1
                        Case "vertex"         '顶点坐标值
                            With mData(mFacetCount)
                              Select Case VIndex
                                    Case 1
                                        .P1X = sTmp(1)
                                        .P1Y = sTmp(2)
                                        .P1Z = sTmp(3)
                                    Case 2
                                        .P2X = sTmp(1)
                                        .P2Y = sTmp(2)
                                        .P2Z = sTmp(3)
                                    Case 3
                                        .P3X = sTmp(1)
                                        .P3Y = sTmp(2)
                                        .P3Z = sTmp(3)
                                    Case Else
                                        GoTo errH
                              End Select
                              VIndex = VIndex + 1
                            End With
                        Case "end"            '结束顶点描述
                        Case "endfacet"         '一个三角面片结束
                        Case "endsolid"         '文件结束
                  End Select
                End If
            Next
            
'            MsgBox "22"
      Else
            '以BINARY格式读取数据
            mDescription = StrConv(byteBuff(), vbUnicode)                              '得到文件信息
            mDescription = Trim(Replace(mDescription, Chr(0), " "))
            
            Get #Fn, , mFacetCount
            
            ReDim mData(mFacetCount - 1)
            Get #Fn, , mData()
      End If
      
      For I = 0 To mFacetCount - 1
            With mData(I)
                If mXMax < .PNX Then mXMax = .PNX
                If mXMin > .PNX Then mXMin = .PNX
               
                If mYMax < .PNY Then mYMax = .PNY
                If mYMin > .PNY Then mYMin = .PNY
               
                If mZMax > .PNZ Then mZMax = .PNZ
                If mZMin > .PNZ Then mZMin = .PNZ
            
                If mXMax < .P1X Then mXMax = .P1X
                If mXMin > .P1X Then mXMin = .P1X
               
                If mYMax < .P1Y Then mYMax = .P1Y
                If mYMin > .P1Y Then mYMin = .P1Y
               
                If mZMax > .P1Z Then mZMax = .P1Z
                If mZMin > .P1Z Then mZMin = .P1Z
            
                If mXMax < .P2X Then mXMax = .P2X
                If mXMin > .P2X Then mXMin = .P2X
               
                If mYMax < .P2Y Then mYMax = .P2Y
                If mYMin > .P2Y Then mYMin = .P2Y
               
                If mZMax > .P2Z Then mZMax = .P2Z
                If mZMin > .P2Z Then mZMin = .P2Z
            
                If mXMax < .P3X Then mXMax = .P1X
                If mXMin > .P3X Then mXMin = .P1X
               
                If mYMax < .P3Y Then mYMax = .P3Y
                If mYMin > .P3Y Then mYMin = .P3Y
               
                If mZMax > .P3Z Then mZMax = .P3Z
                If mZMin > .P3Z Then mZMin = .P3Z
            End With
      Next
      
      LoadSTLFile = UBound(mData) + 1
errH:
    Close #Fn
End Function

Public Function SaveSTLFile(ByVal FileName As String, ByVal IsAscii As Boolean) As Long
    '保存当前数据为STL文件
    '
    Dim I As Long, J As Long, K As Long, sBuff() As String, LineUbound As Long, sTmp As String * 80
    Dim Fn As Long
   
    If IsAscii = True Then
      LineUbound = (UBound(mData) + 1) * 7 + 2 - 1
      ReDim sBuff(LineUbound)
      
      sBuff(0) = "solid " & mDescription
      sBuff(LineUbound) = "endsolid " & mDescription
      
      For I = 1 To LineUbound - 1
            With mData(J)
                Select Case K
                  Case 0
                        sBuff(I) = "facet normal " & .PNX & " " & .PNY & " " & .PNZ
                  Case 1
                        sBuff(I) = "    outer loop"
                  Case 2
                        sBuff(I) = "      vertex " & .P1X & " " & .P1Y & " " & .P1Z
                  Case 3
                        sBuff(I) = "      vertex " & .P2X & " " & .P2Y & " " & .P2Z
                  Case 4
                        sBuff(I) = "      vertex " & .P3X & " " & .P3Y & " " & .P3Z
                  Case 5
                        sBuff(I) = "    endloop"
                  Case 6
                        sBuff(I) = "endfacet"
                        K = -1
                        J = J + 1
                End Select
                K = K + 1
            End With
      Next
      
      On Error Resume Next
            Kill FileName
      On Error GoTo 0
      
      Fn = FreeFile
      Open FileName For Binary As #Fn
            Put #Fn, , Join(sBuff(), vbLf)
      Close #Fn
    Else
      On Error Resume Next
            Kill FileName
      On Error GoTo 0
      
      Fn = FreeFile
      Open FileName For Binary As #Fn
            sTmp = mDescription
            I = UBound(mData) + 1
            
            Put #Fn, , sTmp
            Put #Fn, , I
            Put #Fn, , mData()
      Close #Fn
    End If
   
    SaveSTLFile = FileLen(FileName)
End Function

Public Sub MoveToPoint(ByVal oX As Single, ByVal oY As Single, ByVal oZ As Single)
    '整体平移到指定的点
    '
    Dim I As Long
    Dim XOffset As Single, YOffset As Single, ZOffset As Single                           '最终偏移量
   
    XOffset = oX + mXMin + (mXMax - mXMin) / 2
    YOffset = oY + mYMin + (mYMax - mYMin) / 2
    ZOffset = 0 - mZMin
   
    For I = 0 To FacetCount - 1
      With mData(I)
            .PNX = .PNX + XOffset
            .PNY = .PNY + YOffset
            .PNZ = .PNZ + ZOffset
            
            .P1X = .P1X + XOffset
            .P1Y = .P1Y + YOffset
            .P1Z = .P1Z + ZOffset
            
            .P2X = .P2X + XOffset
            .P2Y = .P2Y + YOffset
            .P2Z = .P2Z + ZOffset
            
            .P3X = .P3X + XOffset
            .P3Y = .P3Y + YOffset
            .P3Z = .P3Z + ZOffset
      End With
    Next
End Sub

Public Sub Rotate(ByVal BaseAxis As Long, ByVal lDir As Long)
    '整体90度翻转
    '
    'BaseAxis       - 基于哪个轴翻转.0=X,1=Y,2=Z
    'lDir         - 翻转方向,正数=顺时针,否则逆时针
    '
    Dim I As Long, tmpVar As Single
    Dim oX As Single, oY As Single, oZ As Single
    Dim maxX As Single, minX As Single, maxY As Single, minY As Single, maxZ As Single, minZ As Single
   
    For I = 0 To UBound(mData)            '得到XYZ的范围
      With mData(I)
            If maxX < .P1X Then maxX = .P1X
            If maxY < .P1Y Then maxY = .P1Y
            If maxZ < .P1Z Then maxZ = .P1Z
            
            If maxX < .P2X Then maxX = .P2X
            If maxY < .P2Y Then maxY = .P2Y
            If maxZ < .P2Z Then maxZ = .P2Z
            
            If maxX < .P3X Then maxX = .P3X
            If maxY < .P3Y Then maxY = .P3Y
            If maxZ < .P3Z Then maxZ = .P3Z
            
            If minX > .P1X Then minX = .P1X
            If minY > .P1Y Then minY = .P1Y
            If minZ > .P1Z Then minZ = .P1Z
            
            If minX > .P2X Then minX = .P2X
            If minY > .P2Y Then minY = .P2Y
            If minZ > .P2Z Then minZ = .P2Z
            
            If minX > .P3X Then minX = .P3X
            If minY > .P3Y Then minY = .P3Y
            If minZ > .P3Z Then minZ = .P3Z
      End With
    Next
   
    oX = minX + (maxX - minX) / 2         '得到对象的中心点
    oY = minY + (maxY - minY) / 2
    oZ = minZ + (maxZ - minZ) / 2
   
    For I = 0 To UBound(mData)
      With mData(I)
            Select Case BaseAxis
                Case 0      '基于X,则翻转Z,Y,圆心为oZ,oY
                  Call RotatePoint(.P1Z, .P1Y, oZ, oY, lDir)
                  Call RotatePoint(.P2Z, .P2Y, oZ, oY, lDir)
                  Call RotatePoint(.P3Z, .P3Y, oZ, oY, lDir)
                  Call RotatePoint(.PNZ, .PNY, oZ, oY, lDir)
                Case 1      '基于Y,则翻转X,Z,圆心为oX,oZ
                  Call RotatePoint(.P1X, .P1Z, oX, oZ, lDir)
                  Call RotatePoint(.P2X, .P2Z, oX, oZ, lDir)
                  Call RotatePoint(.P3X, .P3Z, oX, oZ, lDir)
                  Call RotatePoint(.PNX, .PNZ, oX, oZ, lDir)
                Case 2      '基于Z,则翻转Y,X,圆心为oY,oX
                  Call RotatePoint(.P1Y, .P1X, oY, oX, lDir)
                  Call RotatePoint(.P2Y, .P2X, oY, oX, lDir)
                  Call RotatePoint(.P3Y, .P3X, oY, oX, lDir)
                  Call RotatePoint(.PNY, .PNX, oY, oX, lDir)
            End Select
      End With
    Next
End Sub

Private Sub RotatePoint(ByRef X As Single, ByRef Y As Single, ByVal oX As Single, ByVal oY As Single, ByVal lDir As Long)
    '对一个点进行90度旋转
    '
    Dim tX As Single, tY As Single
    Dim tXBase As Long, tYBase As Long, PPos As Long, destPPos As Long
   
    tX = X
    tY = Y
   
    tXBase = Abs(tX - oX)         '得到绝对距离
    tYBase = Abs(tY - oY)         '得到绝对距离
   
    If tX > oX And tY > oY Then         '得到旋转前所在象限
      PPos = 1
    ElseIf tX > oX And tY < oY Then
      PPos = 4
    ElseIf tX < oX And tY > oY Then
      PPos = 2
    Else
      PPos = 3
    End If
   
                                        '得到旋转后所在象限
    If lDir > 0 Then    '顺时针旋转
      destPPos = PPos - 1
      If destPPos = 0 Then destPPos = 4
    Else                '逆时针旋转
      destPPos = PPos + 1
      If destPPos = 5 Then destPPos = 1
    End If
   
    Select Case destPPos                '得到绝对距离在这个象限中的运算方式
      Case 1
      Case 2
            tXBase = -tXBase
      Case 3
            tXBase = -tXBase
            tYBase = -tYBase
      Case 4
            tYBase = -tYBase
    End Select
   
    X = oX + tXBase                     '得出结果
    Y = oY + tYBase
End Sub

Public Property Get FileName() As String
    FileName = mFileName
End Property

Public Property Get FacetCount() As Long
    On Error Resume Next
   
    FacetCount = UBound(mData) + 1
End Property

Public Property Get FacetIndex() As Long
    FacetIndex = mFacetIndex
End Property

Public Property Let FacetIndex(ByVal vNewValue As Long)
    mFacetIndex = vNewValue
End Property

Public Property Get PN_X() As Single
    PN_X = mData(mFacetIndex).PNX
End Property

Public Property Let PN_X(ByVal vNewValue As Single)
    mData(mFacetIndex).PNX = vNewValue
End Property

Public Property Get PN_Y() As Single
    PN_Y = mData(mFacetIndex).PNY
End Property

Public Property Let PN_Y(ByVal vNewValue As Single)
    mData(mFacetIndex).PNY = vNewValue
End Property

Public Property Get PN_Z() As Single
    PN_Z = mData(mFacetIndex).PNZ
End Property

Public Property Let PN_Z(ByVal vNewValue As Single)
    mData(mFacetIndex).PNZ = vNewValue
End Property

Public Property Get P1_X() As Single
    P1_X = mData(mFacetIndex).P1X
End Property

Public Property Let P1_X(ByVal vNewValue As Single)
    mData(mFacetIndex).P1X = vNewValue
End Property

Public Property Get P1_Y() As Single
    P1_Y = mData(mFacetIndex).P1Y
End Property

Public Property Let P1_Y(ByVal vNewValue As Single)
    mData(mFacetIndex).P1Y = vNewValue
End Property

Public Property Get P1_Z() As Single
    P1_Z = mData(mFacetIndex).P1Z
End Property

Public Property Let P1_Z(ByVal vNewValue As Single)
    mData(mFacetIndex).P1Z = vNewValue
End Property

Public Property Get P2_X() As Single
    P2_X = mData(mFacetIndex).P2X
End Property

Public Property Let P2_X(ByVal vNewValue As Single)
    mData(mFacetIndex).P2X = vNewValue
End Property

Public Property Get P2_Y() As Single
    P2_Y = mData(mFacetIndex).P2Y
End Property

Public Property Let P2_Y(ByVal vNewValue As Single)
    mData(mFacetIndex).P2Y = vNewValue
End Property

Public Property Get P2_Z() As Single
    P2_Z = mData(mFacetIndex).P2Z
End Property

Public Property Let P2_Z(ByVal vNewValue As Single)
    mData(mFacetIndex).P2Z = vNewValue
End Property

Public Property Get P3_X() As Single
    P3_X = mData(mFacetIndex).P3X
End Property

Public Property Let P3_X(ByVal vNewValue As Single)
    mData(mFacetIndex).P3X = vNewValue
End Property

Public Property Get P3_Y() As Single
    P3_Y = mData(mFacetIndex).P3Y
End Property

Public Property Let P3_Y(ByVal vNewValue As Single)
    mData(mFacetIndex).P3Y = vNewValue
End Property

Public Property Get P3_Z() As Single
    P3_Z = mData(mFacetIndex).P3Z
End Property

Public Property Let P3_Z(ByVal vNewValue As Single)
    mData(mFacetIndex).P3Z = vNewValue
End Property

0xAA55 发表于 2015-4-8 23:38:34

总结一下老马写的代码描述的stl的格式。
首先读取80个字节,如果这80个字节中有一个字节的值是0xa,说明是ascii编码的,否则是二进制。
如果是二进制的话,再往后读取4个字节,这是三角形面片的数量。
然后是三角形面片的数据。每个三角形面片包含了1个法向量,3个顶点坐标,都是3个float一组,可以看老马代码中的结构体表达的每个三角形面片数据的格式——就是那个结构体组成的数组。
然后就没有了。
此外如果是ascii编码的话就更简单了,用notepad++打开一目了然。

0xAA55 发表于 2015-4-10 01:38:57

经过我自己的分析,我注意到以下:
1、如果是二进制格式,STL文件的开头应该是“STLEXP 物体名”。其后用零补足80个字节。
2、如果是ASCII模式,文件开头是“solid 物体名(换行)”因此“嗷嗷叫的老马”使用的判断方式是检查文件前80个字节是否有换行符('\x0A'),如果有,说明是ASCII模式。

3、ASCII模式的STL文件,它的结构如下:

solid 第一个物体名
三角形1
三角形2
三角形3
三角形4
...
三角形N
endsolid 第一个物体名
solid 第二个物体名
三角形1
三角形2
...
三角形N
endsolid 第二个物体名
...
solid 第N个物体名
...
endsolid 第N个物体名

其中三角形表的结构如下:
facet normal 法线X 法线Y 法线Z
outer loop
    vertex 顶点1X 顶点1Y 顶点1Z
    vertex 顶点2X 顶点2Y 顶点2Z
    vertex 顶点3X 顶点3Y 顶点3Z
endloop
endfacet

也就是说stl不包含纹理坐标。经过3dsmax测试它确实不能导出纹理坐标。

为了更好地描述stl文件,我用3dsmax建立了一个四棱锥,导出了一个ascii的stl文件和一个binary的stl文件。
ascii:solid Pyramid
facet normal 0.000000e+000 -8.944272e-001 4.472136e-001
    outer loop
      vertex 0.000000e+000 0.000000e+000 2.000000e+001
      vertex -1.000000e+001 -1.000000e+001 0.000000e+000
      vertex 1.000000e+001 -1.000000e+001 0.000000e+000
    endloop
endfacet
facet normal 8.944272e-001 0.000000e+000 4.472136e-001
    outer loop
      vertex 0.000000e+000 0.000000e+000 2.000000e+001
      vertex 1.000000e+001 -1.000000e+001 0.000000e+000
      vertex 1.000000e+001 1.000000e+001 0.000000e+000
    endloop
endfacet
facet normal 0.000000e+000 8.944272e-001 4.472136e-001
    outer loop
      vertex 0.000000e+000 0.000000e+000 2.000000e+001
      vertex 1.000000e+001 1.000000e+001 0.000000e+000
      vertex -1.000000e+001 1.000000e+001 0.000000e+000
    endloop
endfacet
facet normal -8.944272e-001 0.000000e+000 4.472136e-001
    outer loop
      vertex 0.000000e+000 0.000000e+000 2.000000e+001
      vertex -1.000000e+001 1.000000e+001 0.000000e+000
      vertex -1.000000e+001 -1.000000e+001 0.000000e+000
    endloop
endfacet
facet normal -0.000000e+000 0.000000e+000 -1.000000e+000
    outer loop
      vertex -1.000000e+001 -1.000000e+001 0.000000e+000
      vertex 0.000000e+000 0.000000e+000 0.000000e+000
      vertex 1.000000e+001 -1.000000e+001 0.000000e+000
    endloop
endfacet
facet normal 0.000000e+000 0.000000e+000 -1.000000e+000
    outer loop
      vertex 1.000000e+001 -1.000000e+001 0.000000e+000
      vertex 0.000000e+000 0.000000e+000 0.000000e+000
      vertex 1.000000e+001 1.000000e+001 0.000000e+000
    endloop
endfacet
facet normal 0.000000e+000 0.000000e+000 -1.000000e+000
    outer loop
      vertex 1.000000e+001 1.000000e+001 0.000000e+000
      vertex 0.000000e+000 0.000000e+000 0.000000e+000
      vertex -1.000000e+001 1.000000e+001 0.000000e+000
    endloop
endfacet
facet normal 0.000000e+000 0.000000e+000 -1.000000e+000
    outer loop
      vertex -1.000000e+001 1.000000e+001 0.000000e+000
      vertex 0.000000e+000 0.000000e+000 0.000000e+000
      vertex -1.000000e+001 -1.000000e+001 0.000000e+000
    endloop
endfacet
endsolid Pyramid
binary:
页: [1]
查看完整版本: 读取stl格式的3D模型的VB的类(By:嗷嗷叫的老马)