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渲染器照片级渲染效果

相关推荐
Candice_jy1 分钟前
vscode运行ipynb文件:使用docker中的虚拟环境
服务器·ide·vscode·python·docker·容器·编辑器
流烟默25 分钟前
基于Optuna 贝叶斯优化的自动化XGBoost 超参数调优器
人工智能·python·机器学习·超参数优化
海琴烟Sunshine28 分钟前
leetcode 263. 丑数 python
python·算法·leetcode
AI视觉网奇1 小时前
yolo 获取异常样本 yolo 异常
开发语言·python·yolo
程序员爱钓鱼1 小时前
Python编程实战 面向对象与进阶语法 迭代器与生成器
后端·python·ipython
程序员爱钓鱼1 小时前
Python编程实战 面向对象与进阶语法 JSON数据读写
后端·python·ipython
TH88861 小时前
一体化负氧离子监测站:实时、精准监测空气中负氧离子浓度及其他环境参数
python
苏打水com2 小时前
0基础学前端:100天拿offer实战课(第3天)—— CSS基础美化:给网页“精装修”的5大核心技巧
人工智能·python·tensorflow
老黄编程2 小时前
pcl 3DSC特征描述符、对应关系可视化以及ICP配准
3d·pcl·3dsc·icp
顾安r2 小时前
11.5 脚本 本地网站收藏(解封归来)
linux·服务器·c语言·python·bash