Python+OpenGL绘制3D模型(九)完善插件功能: 矩阵,材质,法线

系列文章

一、逆向工程
Sketchup 逆向工程(一)破解.skp文件数据结构
Sketchup 逆向工程(二)分析三维模型数据结构
Sketchup 逆向工程(三)软件逆向工程从何处入手
Sketchup 逆向工程(四)破解的乐趣 钩子 外挂 代码注入

二、OpenGL渲染模型
Python+OpenGL绘制3D模型(一)Python 和 PyQt环境搭建
Python+OpenGL绘制3D模型(二)程序框架PyQt5
Python+OpenGL绘制3D模型(三)程序框架PyQt6
Python+OpenGL绘制3D模型(四)绘制线段
Python+OpenGL绘制3D模型(五)绘制三角型
Python+OpenGL绘制3D模型(六)材质文件载入和贴图映射
Python+OpenGL绘制3D模型(七)制作3dsmax导出插件
Python+OpenGL绘制3D模型(八)绘制插件导出的插件
Python+OpenGL绘制3D模型(九)完善插件功能: 矩阵,材质,法线
Python+OpenGL 杂谈(一)

三、成果
疫情期间关在家里实在没事干,破解了Sketchup,成功做出可以读取并显示.skp文件的程序SuViewer

前言

Sketchup作为目前设计院最为流行的设计软件(非工程制图软件),深受设计师的喜爱,软件小巧,而功能强大,有不少为之开发的插件应运而生,不过呢,关于底层数据结构和工作原理相关的文章少之又少,本文意在填补一下这方面的空缺,通过逆向软件分析,展示软件内部奥秘。本文用到的工具:IDA Pro,Immunity Debugger,Visual Studio (逆向工程三件套)数据结构属于知识产权的核心机密:


文章目录

一、模型的变换矩阵

TODO

二、导出材质、贴图

TODO

三、导出法线

TODO

四、 源代码

1、maxplus_export_sel_model.py

python 复制代码
import MaxPlus
import pickle
import base64
from CModel import CModel, CMaterial, CMesh, CTriangle, CVector3


################################
#                   FILE DESCRIPTION
#  文件描述:CModelExport
#  对应文章:Python+OpenGL绘制3D模型(九) 完善插件功能: 矩阵,材质,法线
#  作者:李航 Lihang
#  使用方法:
#   1、选择要导出的模型
#   2、在命令行窗口中输入
#          python.ExecuteFile "C:/_proj/SuViewer/articles/step3/maxplus_export_sel_model.py"
#   3、把命令行拖入工具栏可以生成快捷方式,方便下次使用
#   4、测试可使用的版本:3dsmax2016
#   ELSE..
################################



# 注意: 保证c:/temp目录存在,否则无法导出
EXPORT_PATH_FILE = "c:/temp/CModel.pickle"

############
# CModelExport
############  
class CModelExport:
    def __init__(self):
        self.list_mats = []
        
    ############
    # export
    #   插件主入口
    ############  
    def export(self):
        model = CModel()

        for n, obj, triObj in self.EnumSeletciontGeometry():
            print ("find %s, class:%s"%(n, obj.GetClassName()) )
            mesh = triObj.GetMesh()

            out_mesh = self.ProcMesh(n, mesh)
            model.list_mesh.append(out_mesh)
        
        self.printMats()
        
        model.list_mats = [mo for mi, mo in self.list_mats]
        
        hf = open("c:/temp/CModel.pickle",  "wb")
        pickle.dump(model,  hf,  2)
        hf.close()

    ############
    # EnumSeletciontGeometry
    #   1、遍历选择的物体
    #   2、生成列表sel_list并返回
    ############  
    def EnumSeletciontGeometry(self):
        sel_list = []
        for n in MaxPlus.SelectionManager.Nodes:        
            # object
            obj = n.EvalWorldState().Getobj()
            if obj.CanConvertToType(MaxPlus.ClassIds.TriMeshGeometry):
                triObj = obj.ConvertToType(MaxPlus.ClassIds.TriMeshGeometry)
                item = (n, obj, MaxPlus.TriObject._CastFrom(triObj))
                sel_list.append(item)
                #print ("find %s, class:%s"%(n, obj.GetClassName()) )
            else:
                print("can't conv triMesh, Type is:%s" % obj.GetClassName())
         
        return sel_list

    ############
    # ProcMesh
    #   1、处理max中的模型
    #   2、转成需要的CModel数据
    ############
    def ProcMesh(self, node, mesh):
        print("---ProcMesh()---")

        #=====================
        # Collect Infomation
        #=====================
        tm = node.GetWorldTM()

        # color
        new_mesh = CMesh()
        color1 = node.GetWireColor()
        new_mesh.color = (color1.GetR(), color1.GetG(), color1.GetB())
        
        # material
        m = node.GetMaterial()
        new_mesh.mat = self.ProcMeshMaterial(m, mesh) if m else None

        #=====================
        # Vertices
        #=====================
        num_verts = mesh.GetNumVertices()
        print ("numVertics:%d"%num_verts)
        for i in range(num_verts):
            loc_pos = mesh.GetVertex(i)
            point = tm.PointTransform(loc_pos)
            nv = CVector3(point.X, point.Y, point.Z)
            new_mesh.list_vertices.append(nv)

        #=====================
        # Normals
        #=====================
        normalBuilder = NormalBuilder()
        normalBuilder.BuildNormals(mesh)
        num_normals = len(normalBuilder.list_normals)
        print("numNormals:%d"%num_normals)
        for i in range(num_normals):
            ln = normalBuilder.list_normals[i]
            nm = tm.VectorTransform(ln).Normalize()
            new_mesh.list_normals.append(CVector3(nm.X,  nm.Y, nm.Z))

        #=====================
        # UV
        #=====================
        num_tverts = mesh.GetNumTVerts()
        print ("numTVerts:%d"%num_tverts)
        for i in range(num_tverts):
            uv = mesh.GetTVert(i)
            nv = CVector3(uv.X, uv.Y, uv.Z)
            new_mesh.list_uvs.append(nv)

        #=====================
        # Triangles
        #=====================
        num_faces = mesh.GetNumFaces()
        print ("numFaces:%d"%num_faces)
        for i in range(num_faces):
            tri = mesh.GetFace(i)
            smGroup = tri.GetSmGroup()
            vi1 = tri.GetVert(0)
            vi2= tri.GetVert(1)
            vi3 = tri.GetVert(2)
            ni1 = normalBuilder.GetNormal(vi1, smGroup )
            ni2 = normalBuilder.GetNormal(vi2, smGroup )
            ni3 = normalBuilder.GetNormal(vi3, smGroup )
            tface = mesh.GetTVFace(i)
            ui1 = tface.GetA()
            ui2 = tface.GetB()
            ui3 = tface.GetC()
            ev1 = True if tri.GetEdgeVis(0) else False
            ev2 = True if tri.GetEdgeVis(1) else False
            ev3 = True if tri.GetEdgeVis(2) else False
            nt = CTriangle()
            nt.a = (vi1, ni1, ui1)
            nt.b = (vi2, ni2, ui2)
            nt.c = (vi3, ni3, ui3)
            norm = mesh.FaceNormal(i)
            nt.matId = tri.GetMatID()
            nt.faceNormal = CVector3(norm.X, norm.Y, norm.Z)
            nt.smGroup = tri.GetSmGroup()
            nt.edgevis = (ev1, ev2, ev3)
            new_mesh.list_tris.append(nt)
            #print(" Tri: %d %d %d"%(tri.GetVert(0), tri.GetVert(1), tri.GetVert(2) ))
            
        return new_mesh

    ############
    # Material
    ############
    def ProcMeshMaterial(self, m, mesh):
        print("---CollectMeshMaterials()---")

        if m.IsMultiMtl():
            num_submat = m.GetNumSubMtls()
            refcount = [0] * num_submat
            
            num_faces = mesh.GetNumFaces()
            for i in range(num_faces):
                f = mesh.GetFace(i)
                mid = f.GetMatID()
                refcount[mid] += 1
            print("refcount:"+str(refcount))
            
            list_submats = []
            for i in range(num_submat):
                if refcount[i]:
                    mMat = m.GetSubMtl(i)
                    if mMat:
                        cm = self.addMaterial(mMat)
                        list_submats.append(cm)
                    else:
                        list_submats.append(None)
                else:
                    list_submats.append(None)
            return list_submats
        else:
            return self.addMaterial(m)

    def addMaterial(self, mi):
        #==============
        #  Find Exist
        #==============
        for min, mout in self.list_mats:
            if min == mi:
                return mout
        
        #==============
        #  Create Out Material
        #==============
        mo = CMaterial()
        
        # Diffuse
        color1 = mi.GetDiffuse()
        mo.diffuse = (color1.GetR(), color1.GetG(), color1.GetB())

        # Texture
        isubmap = MaxPlus.ISubMap._CastFrom(mi)
        #assert(isubmap, "can't cast to ISubMap")
        tex = isubmap.GetSubTexmap(1)
        if tex:
            bitmap = MaxPlus.BitmapTex._CastFrom(tex)
            texmap = bitmap.GetMapName()
            mo.tex_filepath = texmap
            mo.tex_bindata = self.loadFileData(texmap)
        
        # Add to list
        item = (mi, mo)
        self.list_mats.append(item)

        return mo

    def loadFileData(self, filename):
        
        file1 = open(filename,  mode='rb')
        imgdata = bytes(file1.read())
        file1.close()
        
        return base64.b64encode(imgdata)

    def printMats(self):
        print("---printMats()---")
        print("Number of Materials: %d"% len(self.list_mats))
        print("----------------")
        for matId, m in enumerate(self.list_mats):
            mi, mo = m
            print("ID: %d"% matId)
            print("source:" + str(mi))
            print("diffuse: " + str(mo.diffuse))
            print("texture: " + mo.tex_filepath)

############
# NormalBuilder
############
class NormalBuilder:
    def __init__(self):
        self.list_verts = []
        self.list_normals = []

    class Vertex:
        def __init__(self, p):
            self.v = p
            self.an = None
            
        def normals(self):
            if self.an is None:
                return ()
            elif type(self.an) is list:
                return self.an
            else:
                return (self.an, )

        def addNormal(self, n, sg):
            for vn in self.normals():
                if vn.sg & sg:
                    vn.n = vn.n + n
                    return
            
            #not find
            vn = NormalBuilder.VNormal(n, sg)
            if self.an is None:
                self.an = vn
            elif type(self.an) is list:
                self.an.append(vn)
            else:
                self.an = [self.an, vn]
      
    class VNormal:
        def __init__(self, n, sg):
            self.n = n
            self.sg = sg
            self.idx = -1
            
        def add(self, n):
            self.n =  self.n + n
        
    def BuildNormals(self, mesh):
        print("-----BuildNormals------")
        #=====================
        # Vertices
        #=====================
        num_verts = mesh.GetNumVertices()
        for i in range(num_verts):
            point = mesh.GetVertex(i)
            v = NormalBuilder.Vertex(point)
            self.list_verts.append(v)

        num_faces = mesh.GetNumFaces()
        for i in range(num_faces):
            f = mesh.GetFace(i)
            faceNormal = mesh.FaceNormal(i)
            smGroup = f.GetSmGroup()
            v1 = f.GetVert(0)
            self.list_verts[v1].addNormal(faceNormal, smGroup)
            v2 = f.GetVert(1)
            self.list_verts[v2].addNormal(faceNormal, smGroup)
            v3 = f.GetVert(2)
            self.list_verts[v3].addNormal(faceNormal, smGroup)
        
        idx = 0
        for v in self.list_verts:
            for vn in v.normals():
                vn.n = vn.n.Normalize()
                self.list_normals.append(vn.n)
                vn.idx = idx
                idx += 1
    
    def GetNormal(self, vi, sg):
        v = self.list_verts[vi]
        for vn in v.normals():
            if vn.sg & sg:
                return vn.idx
        
        assert(False)

############
# Main
#   1、创建插件导出对象CModelExport
#   2、执行export
############
modelExport = CModelExport()
modelExport.export()

2、CModel.py

python 复制代码
import math

class CModel:
    def __init__(self):
        self.list_mats = []
        self.list_mesh = []
        
class CMaterial:
    def __init__(self):
        self.diffuse = (0, 0, 0)
        self.tex_filepath = ""
        self.tex_bindata = None

class CMesh:
    def __init__(self):
        self.name = ""
        self.color = (0, 0, 0)
        self.list_vertices = []
        self.list_normals = []
        self.list_uvs = []
        self.list_tris = []

class CTriangle:
    def __init__(self):
        self.a=(0, 0, 0)
        self.b=(0, 0, 0)
        self.c=(0, 0, 0)
        self.faceNormal = CVector3.zero()
        self.matId = 0
        self.smGroup = 0
        self.edgevis=(True, True, True)

class CVector3:
    def __init__(self, x, y, z):
        self.x=x
        self.y=y
        self.z=z
    
    @classmethod
    def zero2(cls):
        return CVector3(0.0, 0.0, 0.0)

    @staticmethod
    def zero():
        return CVector3(0.0, 0.0, 0.0)

    def normalize(self):
        s = 1.0 / math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z)
        self.x *= s
        self.y *= s
        self.z *= s
        
    def __add__(self,  R):
        return CVector3(self.x + R.x, self.y + R.y, self.z + R.z)

系列文章预告

目标是一个完善的Viewer,能够显示Sketchup的.skp文件中的3D模型

Corona渲染器照片级渲染效果

相关推荐
Captain823Jack29 分钟前
nlp新词发现——浅析 TF·IDF
人工智能·python·深度学习·神经网络·算法·自然语言处理
资源补给站1 小时前
大恒相机开发(2)—Python软触发调用采集图像
开发语言·python·数码相机
Captain823Jack1 小时前
w04_nlp大模型训练·中文分词
人工智能·python·深度学习·神经网络·算法·自然语言处理·中文分词
PieroPc2 小时前
Python 自动化 打开网站 填表登陆 例子
运维·python·自动化
VinciYan2 小时前
基于Jenkins+Docker的自动化部署实践——整合Git与Python脚本实现远程部署
python·ubuntu·docker·自动化·jenkins·.net·运维开发
测试老哥3 小时前
外包干了两年,技术退步明显。。。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
終不似少年遊*3 小时前
美国加州房价数据分析01
人工智能·python·机器学习·数据挖掘·数据分析·回归算法
如若1233 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python
西猫雷婶3 小时前
python学opencv|读取图像(二十一)使用cv2.circle()绘制圆形进阶
开发语言·python·opencv
supermapsupport4 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap