一、逆向工程
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
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()
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渲染器照片级渲染效果