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 杂谈(一)
三、成果
疫情期间关在家里实在没事干,破解了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渲染器照片级渲染效果
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!