184 lines
6.8 KiB
Python
184 lines
6.8 KiB
Python
|
|
import math
|
|
import os
|
|
import shutil
|
|
from xml.dom.minidom import Document
|
|
|
|
import bpy
|
|
import bpy_extras
|
|
from bpy.props import BoolProperty, IntProperty, StringProperty
|
|
from bpy_extras.io_utils import ExportHelper
|
|
from mathutils import Matrix
|
|
|
|
bl_info = {
|
|
"name": "Export Nori scenes format",
|
|
"author": "Adrien Gruson, Delio Vicini, Tizian Zeltner",
|
|
"version": (0, 1),
|
|
"blender": (2, 80, 0),
|
|
"location": "File > Export > Nori exporter (.xml)",
|
|
"description": "Export Nori scene format (.xml)",
|
|
"warning": "",
|
|
"wiki_url": "",
|
|
"tracker_url": "",
|
|
"category": "Import-Export"}
|
|
|
|
|
|
class NoriWriter:
|
|
|
|
def __init__(self, context, filepath):
|
|
self.context = context
|
|
self.filepath = filepath
|
|
self.working_dir = os.path.dirname(self.filepath)
|
|
|
|
def create_xml_element(self, name, attr):
|
|
el = self.doc.createElement(name)
|
|
for k, v in attr.items():
|
|
el.setAttribute(k, v)
|
|
return el
|
|
|
|
def create_xml_entry(self, t, name, value):
|
|
return self.create_xml_element(t, {"name": name, "value": value})
|
|
|
|
def create_xml_transform(self, mat, el=None):
|
|
transform = self.create_xml_element("transform", {"name": "toWorld"})
|
|
if(el):
|
|
transform.appendChild(el)
|
|
value = ""
|
|
for j in range(4):
|
|
for i in range(4):
|
|
value += str(mat[j][i]) + ","
|
|
transform.appendChild(self.create_xml_element("matrix", {"value": value[:-1]}))
|
|
return transform
|
|
|
|
def create_xml_mesh_entry(self, filename):
|
|
meshElement = self.create_xml_element("mesh", {"type": "obj"})
|
|
meshElement.appendChild(self.create_xml_element("string", {"name": "filename", "value": "meshes/"+filename}))
|
|
return meshElement
|
|
|
|
def write(self):
|
|
"""Main method to write the blender scene into Nori format"""
|
|
|
|
n_samples = 32
|
|
# create xml document
|
|
self.doc = Document()
|
|
self.scene = self.doc.createElement("scene")
|
|
self.doc.appendChild(self.scene)
|
|
|
|
# 1) write integrator configuration
|
|
self.scene.appendChild(self.create_xml_element("integrator", {"type": "normals"}))
|
|
|
|
# 2) write sampler
|
|
sampler = self.create_xml_element("sampler", {"type": "independent"})
|
|
sampler.appendChild(self.create_xml_element("integer", {"name": "sampleCount", "value": str(n_samples)}))
|
|
self.scene.appendChild(sampler)
|
|
|
|
# 3) export one camera
|
|
cameras = [cam for cam in self.context.scene.objects
|
|
if cam.type in {'CAMERA'}]
|
|
if(len(cameras) == 0):
|
|
print("WARN: No camera to export")
|
|
else:
|
|
if(len(cameras) > 1):
|
|
print("WARN: Does not handle multiple camera, only export the active one")
|
|
self.scene.appendChild(self.write_camera(self.context.scene.camera)) # export the active one
|
|
|
|
# 4) export all meshes
|
|
if not os.path.exists(self.working_dir + "/meshes"):
|
|
os.makedirs(self.working_dir + "/meshes")
|
|
|
|
meshes = [obj for obj in self.context.scene.objects
|
|
if obj.type in {'MESH', 'FONT', 'SURFACE', 'META'}]
|
|
print(meshes)
|
|
for mesh in meshes:
|
|
self.write_mesh(mesh)
|
|
|
|
# 6) write the xml file
|
|
self.doc.writexml(open(self.filepath, "w"), "", "\t", "\n")
|
|
|
|
def write_camera(self, cam):
|
|
"""convert the selected camera (cam) into xml format"""
|
|
camera = self.create_xml_element("camera", {"type": "perspective"})
|
|
camera.appendChild(self.create_xml_entry("float", "fov", str(cam.data.angle * 180 / math.pi)))
|
|
camera.appendChild(self.create_xml_entry("float", "nearClip", str(cam.data.clip_start)))
|
|
camera.appendChild(self.create_xml_entry("float", "farClip", str(cam.data.clip_end)))
|
|
percent = self.context.scene.render.resolution_percentage / 100.0
|
|
camera.appendChild(self.create_xml_entry("integer", "width", str(
|
|
int(self.context.scene.render.resolution_x * percent))))
|
|
camera.appendChild(self.create_xml_entry("integer", "height", str(
|
|
int(self.context.scene.render.resolution_y * percent))))
|
|
|
|
mat = cam.matrix_world
|
|
|
|
# Conversion to Y-up coordinate system
|
|
coord_transf = bpy_extras.io_utils.axis_conversion(
|
|
from_forward='Y', from_up='Z', to_forward='-Z', to_up='Y').to_4x4()
|
|
mat = coord_transf @ mat
|
|
pos = mat.translation
|
|
# Nori's camera needs this these coordinates to be flipped
|
|
m = Matrix([[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 0]])
|
|
t = mat.to_3x3() @ m.to_3x3()
|
|
mat = Matrix([[t[0][0], t[0][1], t[0][2], pos[0]],
|
|
[t[1][0], t[1][1], t[1][2], pos[1]],
|
|
[t[2][0], t[2][1], t[2][2], pos[2]],
|
|
[0, 0, 0, 1]])
|
|
trans = self.create_xml_transform(mat)
|
|
camera.appendChild(trans)
|
|
return camera
|
|
|
|
def write_mesh(self, mesh):
|
|
viewport_selection = self.context.selected_objects
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
|
|
obj_name = mesh.name + ".obj"
|
|
obj_path = os.path.join(self.working_dir, 'meshes', obj_name)
|
|
mesh.select_set(True)
|
|
bpy.ops.export_scene.obj(filepath=obj_path, check_existing=False,
|
|
use_selection=True, use_edges=False, use_smooth_groups=False,
|
|
use_materials=False, use_triangles=True, use_mesh_modifiers=True)
|
|
mesh.select_set(False)
|
|
|
|
# Add the corresponding entry to the xml
|
|
mesh_element = self.create_xml_mesh_entry(obj_name)
|
|
# We currently just export a default material, a more complex material conversion
|
|
# could be implemented following: http://tinyurl.com/nnhxwuh
|
|
bsdf_element = self.create_xml_element("bsdf", {"type": "diffuse"})
|
|
bsdf_element.appendChild(self.create_xml_entry("color", "albedo", "0.75,0.75,0.75"))
|
|
mesh_element.appendChild(bsdf_element)
|
|
self.scene.appendChild(mesh_element)
|
|
|
|
for ob in viewport_selection:
|
|
ob.select_set(True)
|
|
|
|
|
|
class NoriExporter(bpy.types.Operator, ExportHelper):
|
|
"""Export a blender scene into Nori scene format"""
|
|
|
|
# add to menu
|
|
bl_idname = "export_scene.nori"
|
|
bl_label = "Export Nori scene"
|
|
|
|
filename_ext = ".xml"
|
|
filter_glob: StringProperty(default="*.xml", options={'HIDDEN'})
|
|
|
|
def execute(self, context):
|
|
nori = NoriWriter(context, self.filepath)
|
|
nori.write()
|
|
return {'FINISHED'}
|
|
|
|
def menu_func_export(self, context):
|
|
self.layout.operator(NoriExporter.bl_idname, text="Export Nori scene...")
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_class(NoriExporter)
|
|
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_class(NoriExporter)
|
|
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register()
|