]> git.tdb.fi Git - libs/gl.git/commitdiff
Make resource handling in the Blender exporter more flexible
authorMikko Rasa <tdb@tdb.fi>
Thu, 16 May 2019 11:38:27 +0000 (14:38 +0300)
committerMikko Rasa <tdb@tdb.fi>
Thu, 16 May 2019 19:34:55 +0000 (22:34 +0300)
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
blender/io_mspgl/export_mesh.py
blender/io_mspgl/export_object.py
blender/io_mspgl/export_scene.py
blender/io_mspgl/mesh.py

index 09fb86c5210e2dcba3349aac31072414bc20ec3e..42ff454b4ccff69ab8d433629d973ddced83e744 100644 (file)
@@ -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
index bb116bda9133f3f8192e5ebfa0f42bc10ad046f1..7b8fd771cd0ceab2c926a1be0db9cc3207745ab1 100644 (file)
@@ -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
index 4bc6f450b330a3ca17159ddea6fa9a2ba1ab836e..34724221184e600b8c40e9a53974275e0a34ffd8 100644 (file)
@@ -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
index f6776107413955964d0e3e757bc5594e3a69d03f..b4b9c2365bc22ea14b7f11fec5c06120c74fb870 100644 (file)
@@ -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
index 78cae28b22f662d3e0eb2ca9fdbb8073536f7893..609f89a1566ae3848efa9404cb0358e537528e09 100644 (file)
@@ -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)