Python+OpenGL绘制3D模型(八)绘制插件导出的模型

发布时间:2023年12月29日

系列文章

一、逆向工程
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 杂谈(一)

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

前言

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


在这里插入图片描述

一、载入模型文件

由于文件用pickle直接dump出来,用的时候一键载入,非常简单

import pickle

with open("c:/temp/CModel.pickle",  "rb") as hf:
    model = pickle.Unpickler(hf).load()

二、绘制

3dsmax中有2种模型的数据,triMesh polyMesh,
读3dsmax的API就是读3dsmax软件的整个发展史,这里面有很多历史遗留问题,最早建模是三角形建模技术,后来才发展为更先进的四边形建模技术(也叫细分面建模),一个4边型可以分成4个,再分成16个。3dsmax在使用了新的建模架构,又不得不保留老的数据结构,导致api和源代码无比膨胀,难于维护,让人有点莫名其妙,这2种模型数据,可以互相转换,最终我们导出的是triMesh数据
在这里插入图片描述

三、1个主要问题

3dsmax中模型的大部分是4边型,但是转成了triMesh的模型,为什么在3dsmax中显示的还能保持是四边面,这是因为转成的三角形数据,edge是有影藏属性的,可以任然显示层4边型,如果打开显示影藏,就能看到3dsmax中的模型是三角型了

解决的办法,在导出插件的时候,我们导出了edgevis的信息,这就可以辅助显示四边面了

在构建线框的时候,加入如下代码

        for tri in mesh.list_tris:
            vi1 = tri.a[0]
            vi2 = tri.b[0]
            vi3 = tri.c[0]
            if tri.edgevis[0]:
                add_edge(vi1, vi2)
            if tri.edgevis[1]:
                add_edge(vi2, vi3)
            if tri.edgevis[2]:
                add_edge(vi3, vi1)

修改后的线框绘制如下

在这里插入图片描述
这个效果就很Nice了

四、源代码

Draw1.py

import pickle

################################
#                   FILE DESCRIPTION
#  文件描述:Draw Model()
#  对应文章:Python+OpenGL绘制3D模型(八)绘制插件导出的模型
#  作者:李航 Lihang
#
################################
MODEL_FILE = "c:/temp/CModel.pickle"

class Draw1:
    def __init__(self):
        self.model=None
    
    ############
    # load
    #   载入模型文件
    ############
    def load(self, filepath=MODEL_FILE):
        with open(filepath,  "rb") as hf:
            self.model = pickle.Unpickler(hf).load()

            for mesh in self.model.list_mesh:
                self.build_edges(mesh)
    
    ############
    # draw
    #   主入口
    ############
    def draw(self, gl):
        
        if self.model is None:
            return
            
        for mesh in self.model.list_mesh:
            # 设置z-buff偏移
            gl.glEnable(gl.GL_POLYGON_OFFSET_FILL)
            gl.glPolygonOffset(1, 1)
            
            # 绘制填充面
            self.drawMesh(mesh, gl)
        
            # 关闭z-buff偏移
            gl.glDisable(gl.GL_POLYGON_OFFSET_FILL)
            
            # 绘制线框
            gl.glColor3f(0.0, 0.0, 0.0)
            self.drawMesh_linemode(mesh, gl)
    
    ############
    # build_edges
    #   1、创建edges列表
    #   2、公用的线不重复绘制
    #   3、3dsmax中的隐藏线不绘制(edgevis)
    ############
    def build_edges(self, mesh):
        list1 = []
        def add_edge(a, b):
            for c, d in list1:
                if a == c and b == d or a == d and b == c:
                    return
            item = (a, b)
            list1.append(item)
        mesh.list_edges = list1

        for tri in mesh.list_tris:
            vi1 = tri.a[0]
            vi2 = tri.b[0]
            vi3 = tri.c[0]
            if tri.edgevis[0]:
                add_edge(vi1, vi2)
            if tri.edgevis[1]:
                add_edge(vi2, vi3)
            if tri.edgevis[2]:
                add_edge(vi3, vi1)
                
    ############
    # drawMesh_linemode
    #   绘制线框
    ############
    def drawMesh_linemode(self, mesh, gl):
        for a, b in mesh.list_edges:
            v1 = mesh.list_vertices[a]
            v2 = mesh.list_vertices[b]
            gl.glBegin(gl.GL_LINES)
            gl.glVertex3f(v1.x, v1.y, v1.z)
            gl.glVertex3f(v2.x, v2.y, v2.z)
            gl.glEnd()
    
    def drawMesh(self, mesh, gl):
        # 设置颜色
        (r, g, b)=mesh.color
        gl.glColor3f(r, g, b)
        
        # 绘制
        self.drawSubMesh(mesh, gl)

    ############
    # drawSubMesh
    #   绘制三角型
    ############
    def drawSubMesh(self, mesh, gl):
        for tri in mesh.list_tris:
            (vi1, ni1, uvi1) = tri.a
            (vi2, ni2, uvi2) = tri.b
            (vi3, ni3, uvi3) = tri.c
            v1 = mesh.list_vertices[vi1]
            v2 = mesh.list_vertices[vi2]
            v3 = mesh.list_vertices[vi3]
            
            gl.glBegin(gl.GL_TRIANGLES)
            gl.glVertex3f(v1.x, v1.y, v1.z)
            gl.glVertex3f(v2.x, v2.y, v2.z)
            gl.glVertex3f(v3.x, v3.y, v3.z)
            gl.glEnd()


tOpenGLqt5.py

import sys
from PyQt5.QtCore import (QPoint)
from PyQt5.QtGui import (QMatrix4x4, QVector3D, QOpenGLVersionProfile)
from PyQt5.QtWidgets import QApplication, QOpenGLWidget

from Draw1 import Draw1

############
# GLWidget
# OpenGL 窗口通用程序框架
#   1、创建OpenGL环境
#   2、设置矩阵
#   3、控制窗口视角
#   4、调用 draw 绘图主函数
############
class GLWidget(QOpenGLWidget):
    def __init__(self, parent):
        super(GLWidget, self).__init__( parent)
        self.dragPressPos = QPoint()
        self.rotX=45
        self.rotZ=0
        self.ps_button = 0
        self.ps_rotX = 0
        self.ps_rotZ = 0
        self.zoom=10
        
        self.draw1 = None

    ############
    # 创建OpenGL环境
    # Qt6 和 Qt5的主要区别在这里
    ############
    def initializeGL(self):
        version_profile = QOpenGLVersionProfile()
        version_profile.setVersion(2, 0)
        self.gl = self.context().versionFunctions(version_profile)
        self.gl.initializeOpenGLFunctions()
        
    ############
    # paintEvent
    ############
    def paintEvent(self,  event):
        # Step 0
        self.makeCurrent()

        # Step 1
        self.gl.glClearColor(0.85, 0.85, 0.85, 1.0)
        self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT | self.gl.GL_DEPTH_BUFFER_BIT)
        self.gl.glEnable(self.gl.GL_DEPTH_TEST)
        
        # Step 2
        self.SetupMatrix()
        
        # Step 3
        if self.draw1 is not None:
            draw1.draw(self.gl)
        #self.drawTarget(self.gl)

    ############
    # 绘图
    # 这里是个绘图的简单测试代码
    ############
    def drawTarget(self, gl):
        p = QVector3D(0, 0, 0)
        
        gl.glColor3f(1.0, 0.0, 0.0);
        gl.glBegin(gl.GL_LINES)
        gl.glVertex3d(p.x()-1,  p.y(),  p.z()  )
        gl.glVertex3d(p.x()+1,  p.y(),  p.z() )
        gl.glEnd()
        
        gl.glColor3f(0.0, 1.0, 0.0);
        gl.glBegin(gl.GL_LINES)
        gl.glVertex3d(p.x(),  p.y()-1,  p.z()  )
        gl.glVertex3d(p.x(),  p.y()+1,  p.z() )
        gl.glEnd()
        
        gl.glColor3f(0.0, 0.0, 1.0);
        gl.glBegin(gl.GL_LINES)
        gl.glVertex3d(p.x(),  p.y(),  p.z()  )
        gl.glVertex3d(p.x(),  p.y(),  p.z() +4 )
        gl.glEnd()

    ############
    # 设置矩阵
    # 透视矩阵和Camera矩阵
    ############
    def SetupMatrix(self):
        # ViewPort
        w = self.width()
        h = self.height()
        self.gl.glViewport(0, 0, w, h)  
        
        # Projection
        self.gl.glMatrixMode(self.gl.GL_PROJECTION)
        pm = QMatrix4x4()
        aspectRatio = w/h
        fov = 45 / aspectRatio if w < h else 45
        pm.perspective(fov,  w/h,  0.1,  100)
        self.gl.glLoadMatrixf(pm.data())
        
        # Camera
        self.gl.glMatrixMode(self.gl.GL_MODELVIEW)
        self.gl.glLoadIdentity()
        
        self.gl.glTranslatef(0.0,0.0,-self.zoom)
        self.gl.glRotatef(self.rotX-90,1.0,0.0,0.0)
        self.gl.glRotatef(self.rotZ,0.0,0.0,1.0)
        self.gl.glTranslatef(0.0,0.0,-1.0)
        
    ############
    # 视角控制
    # 1、左键旋转
    # 2、中间缩放
    # 3、平移 **TODO**
    ############
    def mousePressEvent(self,event):
        self.dragPressPos = event.pos()
        
        self.ps_button = event.button()
        self.ps_rotX = self.rotX
        self.ps_rotZ = self.rotZ
        
    def mouseMoveEvent(self, event):
        diff = event.pos() - self.dragPressPos
        if self.ps_button == 1:
            self.rotX = self.ps_rotX + diff.y()*0.5
            if self.rotX > 90:
                self.rotX = 90
            if self.rotX < -90:
                self.rotX = -90;            
            # rotZ
            self.rotZ = self.ps_rotZ + diff.x()*0.5

        self.repaint()

    def wheelEvent(self, event):
        delta = event.angleDelta().y()

        if delta < 0 :
            self.zoom += self.zoom * 0.2
        else:
            self.zoom -= self.zoom * 0.2
        
        self.repaint()

############
# App
# 创建主窗口应用程序
# 并且进入消息循环
############
if __name__ == '__main__':
    app = QApplication(sys.argv)
    widget = GLWidget(None)
    widget.resize(640, 480)
    widget.show()
    
    draw1 = Draw1()
    draw1.load()
    widget.draw1 = draw1
    
    sys.exit(app.exec())

系列文章预告

目标是一个完善的Viewer,能够显示Sketchup的.skp文件中的3D模型
在这里插入图片描述

Corona渲染器照片级渲染效果
在这里插入图片描述

文章来源:https://blog.csdn.net/weixin_42377388/article/details/135297487
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。