From 907a2aced4b7d70af6be0625632ba373c458f197 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Thu, 16 May 2019 14:38:27 +0300 Subject: [PATCH] Make resource handling in the Blender exporter more flexible All resources are now collected into a dict and referenced or inlined as necessary. This makes it possible for SceneExporter to see the resources used by objects and export them to the resource collection as separate items. --- blender/io_mspgl/datafile.py | 44 ++++++++ blender/io_mspgl/export_mesh.py | 12 +- blender/io_mspgl/export_object.py | 180 +++++++++++++----------------- blender/io_mspgl/export_scene.py | 60 ++++++---- blender/io_mspgl/mesh.py | 3 + 5 files changed, 170 insertions(+), 129 deletions(-) diff --git a/blender/io_mspgl/datafile.py b/blender/io_mspgl/datafile.py index 09fb86c5..42ff454b 100644 --- a/blender/io_mspgl/datafile.py +++ b/blender/io_mspgl/datafile.py @@ -14,6 +14,8 @@ class Statement: s += " {:#.6g}".format(a) elif type(a)==str: s += ' "{}"'.format(a) + elif type(a)==Resource: + s += ' "{}"'.format(a.name) else: s += " {}".format(a) @@ -27,9 +29,51 @@ class Statement: else: f.write(";\n") + class Token: def __init__(self, text): self.text = text def __str__(self): return self.text + + +class Resource: + def __init__(self, name): + self.name = name + self.statements = [] + self.references = [] + + def create_reference_statement(self, keyword, *args): + if len(args)<1: + raise TypeError("create_reference_statement expected at least 2 arguments, got {}".format(1+len(args))) + + resources = [] + for a in args: + if type(a)==Resource: + resources.append(a) + + if not resources: + raise TypeError("create_reference_statement expected a Resource argument, found none") + + self.references += [r for r in resources if r not in self.references] + return Statement(keyword, *args) + + def create_embed_statement(self, keyword, *args): + if len(args)<1: + raise TypeError("create_embed_statement expected at least 2 arguments, got {}".format(1+len(args))) + res = args[-1] + if type(res)!=Resource: + raise TypeError("create_embed_statement expected a Resource as last argument, got {}".format(type(args[-1]).__name__)) + + self.references += [r for r in res.references if r not in self.references] + st = Statement(keyword, *args[:-1]) + st.sub = res.statements + return st + + def collect_references(self): + refs = [] + for r in self.references: + refs += [e for e in r.collect_references() if e not in refs] + refs += [e for e in self.references if e not in refs] + return refs diff --git a/blender/io_mspgl/export_mesh.py b/blender/io_mspgl/export_mesh.py index bb116bda..7b8fd771 100644 --- a/blender/io_mspgl/export_mesh.py +++ b/blender/io_mspgl/export_mesh.py @@ -32,10 +32,10 @@ class MeshExporter: progress = Progress(self.show_progress and context) progress.push_task("", 0.0, 0.95) - statements = self.export_mesh(context, obj, progress) + resource = self.export_mesh(context, obj, progress) with open(out_fn, "w") as out_file: - for s in statements: + for s in resource.statements: s.write_to_file(out_file) def export_mesh(self, context, mesh_or_obj, progress): @@ -48,9 +48,9 @@ class MeshExporter: mesh = create_mesh_from_object(context, mesh_or_obj, progress) progress.pop_task() - from .datafile import Statement, Token - - statements = [] + from .datafile import Resource, Statement, Token + resource = Resource(mesh.name+".mesh") + statements = resource.statements st = Statement("vertices", Token("NORMAL3")) if mesh.uv_layers: @@ -129,4 +129,4 @@ class MeshExporter: progress.set_progress(1.0) - return statements + return resource diff --git a/blender/io_mspgl/export_object.py b/blender/io_mspgl/export_object.py index 4bc6f450..34724221 100644 --- a/blender/io_mspgl/export_object.py +++ b/blender/io_mspgl/export_object.py @@ -45,13 +45,15 @@ class ObjectExporter: return center, radius - def make_external_name(self, base_name, resource_name, ext, index): - if self.shared_resources: - return resource_name+ext - elif lod_index>0: - return "{}_lod{}{}".format(base_name, lod_index, ext) - else: - return base_name+ext + def collect_object_lods(self, obj): + lods = [obj] + if self.export_lods: + lods += sorted([c for c in obj.children if c.lod_for_parent], key=(lambda l: l.lod_index)) + for i, l in enumerate(lods): + if i>0 and l.lod_index!=i: + raise Exception("Inconsistent LOD indices") + + return lods def create_mesh_exporter(self): from .export_mesh import MeshExporter @@ -69,85 +71,70 @@ class ObjectExporter: path, base = os.path.split(out_fn) base = os.path.splitext(base)[0] - meshes = self.export_object_meshes(context, obj, progress, base_name=base) - if self.separate_mesh: - for name, st in meshes.items(): - with open(os.path.join(path, name), "w") as out_file: - for s in st: - s.write_to_file(out_file) - - if self.separate_tech: - lods = [obj] - if self.export_lods: - lods += [c for c in obj.children if c.lod_for_parent] - - for l in lods: - lod_index = l.lod_index if l.lod_for_parent else 0 - - material = None - if obj.material_slots: - material = obj.material_slots[0].material - - if not l.technique: - tech_name = self.make_external_name(base, material.name, ".tech", lod_index) - st = self.export_object_technique(l, base_name=base) - with open(os.path.join(path, tech_name), "w") as out_file: - for s in st: - s.write_to_file(out_file) - elif material and l.override_material: - mat_name = self.make_external_name(base, material.name, ".mat", lod_index) - st = self.export_material(material) - with open(os.path.join(path, mat_name), "w") as out_file: - for s in st: - s.write_to_file(out_file) - - statements = self.export_object(context, obj, progress, meshes=meshes, base_name=base) + resources = {} + self.export_object_resources(context, obj, resources, progress) + + obj_res = self.export_object(context, obj, progress, resources=resources) + refs = obj_res.collect_references() + if not self.shared_resources: + numbers = {} + for r in refs: + ext = os.path.splitext(r.name)[1] + n = numbers.get(ext, 0) + if n>0: + r.name = "{}_{}{}".format(base, n, ext) + else: + r.name = base+ext + numbers[ext] = n+1 + + for r in refs: + with open(os.path.join(path, r.name), "w") as out_file: + for s in r.statements: + s.write_to_file(out_file) with open(out_fn, "w") as out_file: - for s in statements: + for s in obj_res.statements: s.write_to_file(out_file) - def export_object_meshes(self, context, obj, progress, *, base_name=None): - if base_name is None: - base_name = obj.name - - lods = [obj] - if self.export_lods: - lods += [c for c in obj.children if c.lod_for_parent] + def export_object_resources(self, context, obj, resources, progress): + lods = self.collect_object_lods(obj) from .mesh import create_mesh_from_object mesh_export = self.create_mesh_exporter() - meshes = {} for i, l in enumerate(lods): lod_index = l.lod_index if l.lod_for_parent else 0 progress.push_task_slice("LOD {}".format(lod_index), i, len(lods)) - mesh_name = self.make_external_name(base_name, l.data.name, ".mesh", lod_index) - if mesh_name not in meshes: + mesh_name = l.data.name+".mesh" + if mesh_name not in resources: mesh = create_mesh_from_object(context, l, progress) - meshes[mesh_name] = mesh_export.export_mesh(context, mesh, progress) + mesh_res = mesh_export.export_mesh(context, mesh, progress) + resources[mesh_name] = mesh_res - progress.pop_task() + material = None + if l.material_slots and l.material_slots[0].material: + material = l.material_slots[0].material + mat_name = material.name+".mat" + if mat_name not in resources: + resources[mat_name] = self.export_material(material) - return meshes + tech_name = (material.name if material else l.name)+".tech" + if tech_name not in resources: + resources[tech_name] = self.export_object_technique(l, resources=resources) - def export_object(self, context, obj, progress, *, meshes=None, base_name=None): - if base_name is None: - base_name = obj.name + progress.pop_task() - if meshes is None: - meshes = self.export_object_meshes(context, obj, progress, base_name=base_name) + def export_object(self, context, obj, progress, *, resources=None): + if resources is None: + resources = {} + self.export_object_resources(context, obj, resources, progress) - lods = [obj] - for c in obj.children: - if c.lod_for_parent: - if c.lod_index>=len(lods): - lods += [None]*(c.lod_index+1-len(lods)) - lods[c.lod_index] = c + lods = self.collect_object_lods(obj) - from .datafile import Statement - statements = [] + from .datafile import Resource, Statement + obj_res = Resource(obj.name+".object") + statements = obj_res.statements center, radius = self.compute_bounding_sphere(obj) statements.append(Statement("bounding_sphere_hint", *center, radius)) @@ -158,13 +145,11 @@ class ObjectExporter: lod_st = [] if l.data.name!=prev_mesh: - mesh_name = self.make_external_name(base_name, l.data.name, ".mesh", i) + mesh_res = resources[l.data.name+".mesh"] if self.separate_mesh: - lod_st.append(Statement("mesh", mesh_name)) + lod_st.append(obj_res.create_reference_statement("mesh", mesh_res)) else: - st = Statement("mesh") - st.sub = meshes[mesh_name] - lod_st.append(st) + lod_st.append(obj_res.create_embed_statement("mesh", mesh_res)) prev_mesh = l.data.name @@ -174,20 +159,16 @@ class ObjectExporter: tech = (l.technique, mat_name) if tech!=prev_tech: - tech_name = self.make_external_name(base_name, mat_name or l.name, ".tech", i) + tech_res = resources[(mat_name or l.name)+".tech"] if l.technique: if l.inherit_tech: - st = Statement("technique") - st.sub = self.export_object_technique(l, base_name=base_name) - lod_st.append(st) + lod_st.append(obj_res.create_embed_statement("technique", tech_res)) else: lod_st.append(Statement("technique", l.technique)) elif self.separate_tech: - lod_st.append(Statement("technique", tech_name)) + lod_st.append(obj_res.create_reference_statement("technique", tech_res)) else: - st = Statement("technique") - st.sub = self.export_object_technique(l, base_name=base_name) - lod_st.append(st) + lod_st.append(obj_res.create_embed_statement("technique", tech_res)) prev_tech = tech @@ -200,22 +181,23 @@ class ObjectExporter: progress.set_progress(1.0) - return statements - - def export_object_technique(self, obj, *, base_name=None): - if base_name is None: - base_name = obj.name + return obj_res + def export_object_technique(self, obj, *, resources): material = None if obj.material_slots: material = obj.material_slots[0].material - from .datafile import Statement, Token - statements = [] + from .datafile import Resource, Statement, Token + tech_res = Resource((material.name if material else obj.name)+".tech") + + mat_res = None + if material: + mat_res = resources[material.name+".mat"] if obj.technique: if not obj.inherit_tech: - return [] + return tech_res st = Statement("inherit", obj.technique) if material: @@ -227,17 +209,14 @@ class ObjectExporter: elif slot.use_map_normal: st.sub.append(Statement("texture", "normal_map", name)) if obj.override_material: - mat_name = self.make_external_name(base_name, material.name, ".mat", obj.lod_index) - st.sub.append(Statement("material", "surface", mat_name)) - statements.append(st) + st.sub.append(tech_res.create_reference_statement("material", "surface", mat_res)) + tech_res.statements.append(st) - return statements + return tech_res pass_st = Statement("pass", "") if material: - st = Statement("material") - st.sub = self.export_material(material) - pass_st.sub.append(st) + pass_st.sub.append(tech_res.create_embed_statement("material", mat_res)) if self.textures!="NONE": diffuse_tex = None @@ -260,13 +239,14 @@ class ObjectExporter: elif tex.image: st.sub.append(Statement("texture", image_name(tex.image))) pass_st.sub.append(st) - statements.append(pass_st) + tech_res.statements.append(pass_st) - return statements + return tech_res def export_material(self, material): - from .datafile import Statement - statements = [] + from .datafile import Resource, Statement + mat_res = Resource(material.name+".mat") + statements = mat_res.statements cm = get_colormap(material.srgb_colors) if any(s.use_map_color_diffuse for s in material.texture_slots if s): @@ -282,4 +262,4 @@ class ObjectExporter: statements.append(Statement("specular", cm(spec.r), cm(spec.g), cm(spec.g), 1.0)) statements.append(Statement("shininess", material.specular_hardness)) - return statements + return mat_res diff --git a/blender/io_mspgl/export_scene.py b/blender/io_mspgl/export_scene.py index f6776107..b4b9c236 100644 --- a/blender/io_mspgl/export_scene.py +++ b/blender/io_mspgl/export_scene.py @@ -43,42 +43,56 @@ class SceneExporter: from .util import Progress progress = Progress(self.show_progress and context) - from .export_object import ObjectExporter - object_export = ObjectExporter() + resources = {} + self.export_scene_resources(context, unique_objects, resources, progress) + + scene_res = self.export_scene(context, objs, progress, prototypes=object_prototypes, resources=resources) + refs = scene_res.collect_references() from .datafile import Statement if self.resource_collection: + keywords = { ".mat": "material", + ".mesh": "mesh", + ".object": "object", + ".tech": "technique" } with open(os.path.join(path, base+"_resources.mdc"), "w") as res_out: - for i, o in enumerate(unique_objects): - progress.push_task_slice(o.name, i, len(unique_objects)) - st = Statement("object", "{}.object".format(o.name)) - st.sub = object_export.export_object(context, o, progress) + for r in refs: + st = Statement(keywords[os.path.splitext(r.name)[1]], r.name) + st.sub = r.statements st.write_to_file(res_out) - progress.pop_task() else: res_dir = os.path.join(path, base+"_resources") if not os.path.exists(res_dir): os.makedirs(res_dir) - for i, o in enumerate(unique_objects): - progress.push_task_slice(o.name, i, len(unique_objects)) - st = object_export.export_object(context, o, progress) - with open(os.path.join(res_dir, o.name+".object"), "w") as obj_out: - for s in st: - s.write_to_file(obj_out) - progress.pop_task() - - statements = self.export_scene(context, objs, progress, prototypes=object_prototypes) + for r in refs: + with open(os.path.join(res_dir, r.name), "w") as res_out: + for s in r.statements: + s.write_to_file(res_out) with open(out_fn, "w") as out_file: - for s in statements: + for s in scene_res.statements: s.write_to_file(out_file) - def export_scene(self, context, objs, progress, *, prototypes=None): - from .datafile import Statement - statements = [] + def export_scene_resources(self, context, objs, resources, progress): + from .export_object import ObjectExporter + object_export = ObjectExporter() + object_export.separate_mesh = True + object_export.separate_tech = True + + for i, o in enumerate(objs): + progress.push_task_slice(o.name, i, len(objs)) + object_export.export_object_resources(context, o, resources, progress) + obj_name = o.name+".object" + resources[obj_name] = object_export.export_object(context, o, progress, resources=resources) + progress.pop_task() + + def export_scene(self, context, objs, progress, *, prototypes, resources): + from .datafile import Resource, Statement + scene_res = Resource("scene.scene") for o in objs: - st = Statement("object", "{}.object".format(prototypes[o.name].name)) + obj_res = resources[prototypes[o.name].name+".object"] + st = scene_res.create_reference_statement("object", obj_res) # XXX Parent relationships screw up the location and rotation st.sub.append(Statement("position", o.location[0], o.location[1], o.location[2])) if o.rotation_mode=="AXIS_ANGLE": @@ -93,8 +107,8 @@ class SceneExporter: axis = q.axis st.sub.append(Statement("rotation", angle*180/math.pi, axis[0], axis[1], axis[2])) st.sub.append(Statement("scale", o.scale[0], o.scale[1], o.scale[2])) - statements.append(st) + scene_res.statements.append(st) progress.set_progress(1.0) - return statements + return scene_res diff --git a/blender/io_mspgl/mesh.py b/blender/io_mspgl/mesh.py index 78cae28b..609f89a1 100644 --- a/blender/io_mspgl/mesh.py +++ b/blender/io_mspgl/mesh.py @@ -167,6 +167,7 @@ class UvLayer: class Mesh: def __init__(self, mesh): self._mesh = mesh + self.name = mesh.name self.winding_test = mesh.winding_test self.tbn_vecs = mesh.tbn_vecs @@ -773,6 +774,8 @@ def create_mesh_from_object(context, obj, progress): else: mesh = me + mesh.name = obj.data.name + progress.set_task("Triangulating", 0.2, 0.3) mesh.prepare_triangles(progress) progress.set_task("Smoothing", 0.3, 0.5) -- 2.45.2