BallanceBlenderHelper/ballance_blender_plugin/bm_import_export.py

365 lines
12 KiB
Python
Raw Normal View History

2020-07-09 11:33:45 +08:00
import bpy,bmesh,bpy_extras,mathutils
import pathlib,zipfile,time,os,tempfile,math
import struct,shutil
2020-07-12 11:42:05 +08:00
from bpy_extras import io_utils,node_shader_utils
2020-07-14 14:55:12 +08:00
from . import utils, config
2020-07-09 11:33:45 +08:00
class ImportBM(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
"""Load a Ballance Map File (BM file spec 1.0)"""
bl_idname = "import_scene.bm"
bl_label = "Import BM "
bl_options = {'PRESET', 'UNDO'}
filename_ext = ".bm"
def execute(self, context):
import_bm(context, self.filepath)
return {'FINISHED'}
class ExportBM(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
"""Save a Ballance Map File (BM file spec 1.0)"""
bl_idname = "export_scene.bm"
bl_label = 'Export BM'
bl_options = {'PRESET'}
filename_ext = ".bm"
export_mode: bpy.props.EnumProperty(
name="Export mode",
items=(('COLLECTION', "Selected collection", "Export the selected collection"),
('OBJECT', "Selected objects", "Export the selected objects"),
),
)
export_target: bpy.props.StringProperty(
name="Export target",
description="Which one will be exported",
)
def execute(self, context):
2020-07-20 16:14:39 +08:00
export_bm(context, self.filepath, self.export_mode, self.export_target, "") #todo: fix no_component_suffix
return {'FINISHED'}
# ========================================== method
2020-07-09 11:33:45 +08:00
bm_current_version = 10
def import_bm(context,filepath):
# todo: finish this
pass
def export_bm(context,filepath,export_mode, export_target, no_component_suffix):
# ============================================ alloc a temp folder
tempFolderObj = tempfile.TemporaryDirectory()
tempFolder = tempFolderObj.name
# debug
# tempFolder = "G:\\ziptest"
tempTextureFolder = os.path.join(tempFolder, "Texture")
os.makedirs(tempTextureFolder)
# ============================================ find export target
if export_mode== "COLLECTION":
objectList = bpy.data.collections[export_target].objects
else:
objectList = [bpy.data.objects[export_target]]
needSuffixChecker = no_component_suffix != ""
componentObj = set()
for obj in objectList:
if needSuffixChecker and obj.name.endwith(no_component_suffix):
pass # meshObjList.add(obj)
else:
if is_component(obj.name):
componentObj.add(obj)
else:
pass # meshObjList.add(obj)
# ============================================ export
finfo = open(os.path.join(tempFolder, "index.bm"), "wb")
finfo.write(struct.pack("I", bm_current_version))
# ====================== export object
fobject = open(os.path.join(tempFolder, "object.bm"), "wb")
meshSet = set()
meshList = []
meshCount = 0
for obj in objectList:
# only export mesh object
if obj.type != 'MESH':
continue
varis_component = obj in componentObj
# clean no mesh object
currentMesh = obj.data
if currentMesh == None:
continue
# triangle first and then group
if not varis_component:
if currentMesh not in meshSet:
mesh_triangulate(currentMesh)
meshSet.add(currentMesh)
meshList.append(currentMesh)
meshId = meshCount
meshCount += 1
else:
meshId = meshList.index(currentMesh)
# write finfo first
write_string(finfo, obj.name)
write_int(finfo, info_bm_type.OBJECT)
write_long(finfo, fobject.tell())
# write fobject
write_int(fobject, 1 if varis_component else 0)
write_worldMatrix(fobject, obj.matrix_world)
if varis_component:
write_int(fobject, get_component_id(obj.name))
else:
write_int(fobject, meshId)
fobject.close()
# ====================== export mesh
fmesh = open(os.path.join(tempFolder, "mesh.bm"), "wb")
materialSet = set()
materialList = []
for mesh in meshList:
mesh.calc_normals_split()
# write finfo first
write_string(finfo, mesh.name)
write_int(finfo, info_bm_type.MESH)
write_long(finfo, fmesh.tell())
# write fmesh
# vertices
vecList = mesh.vertices[:]
write_int(fmesh, len(vecList))
for vec in vecList:
#swap yz
write_3vector(fmesh,vec.co[0],vec.co[2],vec.co[1])
# uv
face_index_pairs = [(face, index) for index, face in enumerate(mesh.polygons)]
uv_layer = mesh.uv_layers.active.data[:]
write_int(fmesh, len(face_index_pairs) * 3)
for f, f_index in face_index_pairs:
# it should be triangle face, otherwise throw a error
if (f.loop_total != 3):
raise Exception("Not a triangle", f.poly.loop_total)
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
uv = uv_layer[loop_index].uv
# reverse v
write_2vector(fmesh, uv[0], -uv[1])
# normals
write_int(fmesh, len(face_index_pairs) * 3)
for f, f_index in face_index_pairs:
# no need to check triangle again
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
nml = mesh.loops[loop_index].normal
# swap yz
write_3vector(fmesh, nml[0], nml[2], nml[1])
# face
# get material first
currentMat = mesh.materials[:]
noMaterial = len(currentMat) == 0
for mat in currentMat:
if mat not in materialSet:
materialSet.add(mat)
materialList.append(mat)
write_int(fmesh, len(face_index_pairs))
vtIndex = []
vnIndex = []
vIndex = []
for f, f_index in face_index_pairs:
# confirm material use
if noMaterial:
usedMat = 0
else:
usedMat = materialList.index(currentMat[f.material_index])
# export face
vtIndex.clear()
vnIndex.clear()
vIndex.clear()
counter = 0
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
vIndex.append(mesh.loops[loop_index].vertex_index)
vnIndex.append(f_index * 3 + counter)
vtIndex.append(f_index * 3 + counter)
counter += 1
# reverse vertices sort
write_face(fmesh,
vIndex[2], vtIndex[2], vnIndex[2],
vIndex[1], vtIndex[1], vnIndex[1],
vIndex[0], vtIndex[0], vnIndex[0])
# set used material
write_int(fmesh, 0 if noMaterial else 1)
write_int(fmesh, usedMat)
mesh.free_normals_split()
fmesh.close()
# ====================== export material
fmaterial = open(os.path.join(tempFolder, "material.bm"), "wb")
textureSet = set()
textureList = []
textureCount = 0
for material in materialList:
# write finfo first
write_string(finfo, material.name)
write_int(finfo, info_bm_type.MATERIAL)
write_long(finfo, fmaterial.tell())
# write basic color
mat_wrap = node_shader_utils.PrincipledBSDFWrapper(material)
if mat_wrap:
use_mirror = mat_wrap.metallic != 0.0
if use_mirror:
write_3vector(fmaterial, mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic)
else:
write_3vector(fmaterial, 1, 1, 1)
write_3vector(fmaterial, mat_wrap.base_color[0], mat_wrap.base_color[1], mat_wrap.base_color[2])
write_3vector(fmaterial, mat_wrap.specular, mat_wrap.specular, mat_wrap.specular)
# confirm texture
tex_wrap = getattr(mat_wrap, "base_color_texture", None)
if tex_wrap:
image = tex_wrap.image
if image:
# add into texture list
if image not in textureSet:
textureSet.add(image)
textureList.append(image)
currentTexture = textureCount
textureCount += 1
else:
currentTexture = textureList.index(image)
write_int(fmaterial, 1)
write_int(fmaterial, currentTexture)
else:
# no texture
write_int(fmaterial, 0)
write_int(fmaterial, 0)
else:
# no texture
write_int(fmaterial, 0)
write_int(fmaterial, 0)
else:
# no Principled BSDF. write garbage
write_3vector(fmaterial, 0.8, 0.8, 0.8)
write_3vector(fmaterial, 0.8, 0.8, 0.8)
write_3vector(fmaterial, 0.8, 0.8, 0.8)
write_int(fmaterial, 0)
write_int(fmaterial, 0)
fmaterial.close()
# ====================== export texture
ftexture = open(os.path.join(tempFolder, "texture.bm"), "wb")
2020-07-12 11:42:05 +08:00
source_dir = os.path.dirname(bpy.data.filepath)
2020-07-14 14:55:12 +08:00
existed_texture = set()
2020-07-12 11:42:05 +08:00
2020-07-09 11:33:45 +08:00
for texture in textureList:
# write finfo first
write_string(finfo, texture.name)
write_int(finfo, info_bm_type.TEXTURE)
write_long(finfo, ftexture.tell())
# confirm internal
2020-07-12 11:42:05 +08:00
texture_filepath = io_utils.path_reference(texture.filepath, source_dir, tempTextureFolder,
'ABSOLUTE', "", None, texture.library)
filename = os.path.basename(texture_filepath)
2020-07-09 11:33:45 +08:00
write_string(ftexture, filename)
if (is_external_texture(filename)):
write_int(ftexture, 1)
else:
2020-07-14 14:55:12 +08:00
# copy internal texture, if this file is copied, do not copy it again
2020-07-09 11:33:45 +08:00
write_int(ftexture, 0)
2020-07-14 14:55:12 +08:00
if filename not in existed_texture:
shutil.copy(texture_filepath, os.path.join(tempTextureFolder, filename))
existed_texture.add(filename)
2020-07-09 11:33:45 +08:00
ftexture.close()
# close info fs
finfo.close()
# ============================================ save zip and clean up folder
if os.path.isfile(filepath):
os.remove(filepath)
with zipfile.ZipFile(filepath, 'w', zipfile.ZIP_DEFLATED, 9) as zipObj:
for folderName, subfolders, filenames in os.walk(tempFolder):
for filename in filenames:
filePath = os.path.join(folderName, filename)
arcname=os.path.relpath(filePath, tempFolder)
zipObj.write(filePath, arcname)
tempFolderObj.cleanup()
# ======================================================================================= export / import assistant
class info_bm_type():
OBJECT = 0
MESH = 1
MATERIAL = 2
TEXTURE = 3
def is_component(name):
return get_component_id(name) != -1
def get_component_id(name):
return -1 # todo: finish this, -1 mean not a component
def is_external_texture(name):
2020-07-14 14:55:12 +08:00
if name in config.external_texture_list:
return True
else:
return False
2020-07-09 11:33:45 +08:00
def mesh_triangulate(me):
bm = bmesh.new()
bm.from_mesh(me)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(me)
bm.free()
# ======================================================================================= file io assistant
def write_string(fs,str):
count=len(str)
write_int(fs,count)
fs.write(str.encode("utf_32_le"))
def write_int(fs,num):
fs.write(struct.pack("I", num))
def write_long(fs,num):
fs.write(struct.pack("Q", num))
def write_worldMatrix(fs, matt):
mat = matt.transposed()
fs.write(struct.pack("ffffffffffffffff",
mat[0][0],mat[0][2], mat[0][1], mat[0][3],
mat[2][0],mat[2][2], mat[2][1], mat[2][3],
mat[1][0],mat[1][2], mat[1][1], mat[1][3],
mat[3][0],mat[3][2], mat[3][1], mat[3][3]))
def write_3vector(fs, x, y ,z):
fs.write(struct.pack("fff", x, y ,z))
def write_2vector(fs, u, v):
fs.write(struct.pack("ff", u, v))
def write_face(fs, v1, vn1, vt1, v2, vn2, vt2, v3, vn3, vt3):
fs.write(struct.pack("IIIIIIIII", v1, vn1, vt1, v2, vn2, vt2, v3, vn3, vt3))