]> git.tdb.fi Git - libs/gl.git/commitdiff
Add a Blender operator to export the entire project at once
authorMikko Rasa <tdb@tdb.fi>
Sat, 17 Apr 2021 19:06:07 +0000 (22:06 +0300)
committerMikko Rasa <tdb@tdb.fi>
Sat, 17 Apr 2021 22:10:56 +0000 (01:10 +0300)
This also creates sequence templates including lights defined in the
scene(s).

blender/io_mspgl/__init__.py
blender/io_mspgl/export.py
blender/io_mspgl/export_light.py [new file with mode: 0644]
blender/io_mspgl/export_scene.py
blender/io_mspgl/properties.py
blender/io_mspgl/scene.py
blender/io_mspgl/util.py

index 79575f7df23b2c80761e24c09f499f68b7f45ee6..cba8178f66b06c2b36d775a5c37a125cf534d3cd 100644 (file)
@@ -132,6 +132,26 @@ class ExportMspGLScene(bpy.types.Operator, ExportHelper):
                if self.collection:
                        col.prop(self, "skip_existing")
 
                if self.collection:
                        col.prop(self, "skip_existing")
 
+class ExportMspGLProject(bpy.types.Operator):
+       bl_idname = "export.mspgl_project"
+       bl_label = "Export Msp GL project"
+       bl_description = "Export the entire project in Msp GL format"
+
+       directory: bpy.props.StringProperty(name="Directory", description="Directory for exporting the data", subtype='FILE_PATH')
+
+       def invoke(self, context, event):
+               blend_filepath = context.blend_data.filepath
+               if blend_filepath:
+                       self.directory = os.path.split(blend_filepath)[0]
+               context.window_manager.fileselect_add(self)
+               return {'RUNNING_MODAL'}
+
+       def execute(self, context):
+               from .export import ProjectExporter
+               exporter = ProjectExporter()
+               exporter.export_to_directory(context, self.directory)
+               return {'FINISHED'}
+
 class AddUniform(bpy.types.Operator):
        bl_idname = "material.add_uniform"
        bl_label = "Add Uniform"
 class AddUniform(bpy.types.Operator):
        bl_idname = "material.add_uniform"
        bl_label = "Add Uniform"
@@ -160,8 +180,9 @@ def menu_func_export(self, context):
        self.layout.operator(ExportMspGLData.bl_idname, text="Msp GL data")
        self.layout.operator(ExportMspGLAnimation.bl_idname, text="Msp GL animation")
        self.layout.operator(ExportMspGLScene.bl_idname, text="Msp GL scene")
        self.layout.operator(ExportMspGLData.bl_idname, text="Msp GL data")
        self.layout.operator(ExportMspGLAnimation.bl_idname, text="Msp GL animation")
        self.layout.operator(ExportMspGLScene.bl_idname, text="Msp GL scene")
+       self.layout.operator(ExportMspGLProject.bl_idname, text="Msp GL project")
 
 
-classes = [ExportMspGLData, ExportMspGLAnimation, ExportMspGLScene, AddUniform, RemoveUniform]
+classes = [ExportMspGLData, ExportMspGLAnimation, ExportMspGLScene, ExportMspGLProject, AddUniform, RemoveUniform]
 
 def register():
        for c in classes:
 
 def register():
        for c in classes:
index f36cc4df6c1d774d8b66d6604f3ab2ac9440ca3e..37813d081a0d1bebf2cf5407431b59dce6de4589 100644 (file)
@@ -1,4 +1,5 @@
 import os
 import os
+import itertools
 
 class DataExporter:
        def __init__(self):
 
 class DataExporter:
        def __init__(self):
@@ -44,6 +45,7 @@ class DataExporter:
                object_exporter = None
                camera_exporter = None
                armature_exporter = None
                object_exporter = None
                camera_exporter = None
                armature_exporter = None
+               light_exporter = None
 
                from .datafile import Resource
                dummy_res = Resource("dummy", "dummy")
 
                from .datafile import Resource
                dummy_res = Resource("dummy", "dummy")
@@ -74,6 +76,13 @@ class DataExporter:
                                                from .export_armature import ArmatureExporter
                                                armature_exporter = ArmatureExporter()
                                        res = armature_exporter.export_armature(context, obj)
                                                from .export_armature import ArmatureExporter
                                                armature_exporter = ArmatureExporter()
                                        res = armature_exporter.export_armature(context, obj)
+                       elif obj.type=='LIGHT':
+                               res_name = obj.name+".light"
+                               if res_name not in resources:
+                                       if not light_exporter:
+                                               from .export_light import LightExporter
+                                               light_exporter = LightExporter()
+                                       res = light_exporter.export_light(obj)
 
                        if res:
                                resources[res_name] = res
 
                        if res:
                                resources[res_name] = res
@@ -82,3 +91,59 @@ class DataExporter:
                        progress.pop_task()
 
                return dummy_res
                        progress.pop_task()
 
                return dummy_res
+
+class ProjectExporter:
+       def export_to_directory(self, context, out_dir):
+               from .util import Progress
+               progress = Progress(context)
+
+               from .scene import create_scene_chain
+
+               scenes = {}
+               sequences = []
+               for s in context.blend_data.scenes:
+                       if s.export_disposition=='IGNORE':
+                               continue
+
+                       if s.export_disposition=='SEQUENCE':
+                               scene = create_scene_chain(s, scenes)
+                               sequences.append(scene)
+                       elif s.name not in scenes:
+                               scene = create_scene(s)
+                               if s.export_disposition=='SCENE':
+                                       scenes[scene.name] = scene
+
+               all_objects = []
+               for s in scenes.values():
+                       all_objects += s.prototypes
+                       all_objects += s.lights
+                       if s.camera:
+                               all_objects.append(s.camera)
+
+               from .util import make_unique
+               all_objects = make_unique(all_objects)
+
+               from .export_scene import SceneExporter
+               scene_exporter = SceneExporter()
+               data_exporter = DataExporter()
+
+               resources = {}
+               dummy_res = data_exporter.export_resources(context, all_objects, resources, None, progress)
+               for s in scenes.values():
+                       scene_name = s.name+".scene"
+                       if scene_name not in resources:
+                               scene_res = scene_exporter.export_scene(s, resources)
+                               resources[scene_name] = scene_res
+                               dummy_res.create_reference_statement("ref", scene_res)
+
+               for s in sequences:
+                       seq_name = s.name+".seq"
+                       if seq_name not in resources:
+                               scene_exporter.export_sequence_resources(s, resources)
+                               seq_res = scene_exporter.export_sequence(s, resources)
+                               resources[seq_name] = seq_res
+                               dummy_res.create_reference_statement("ref", seq_res)
+
+               refs = dummy_res.collect_references()
+               for r in refs:
+                       r.write_to_file(os.path.join(out_dir, r.name))
diff --git a/blender/io_mspgl/export_light.py b/blender/io_mspgl/export_light.py
new file mode 100644 (file)
index 0000000..cfa4a19
--- /dev/null
@@ -0,0 +1,22 @@
+import mathutils
+
+class LightExporter:
+       def export_light(self, obj):
+               if obj.type!='LIGHT':
+                       raise ValueError("Object is not a light")
+               light = obj.data
+
+               from .datafile import Resource, Statement
+               light_res = Resource(light.name+".light", "light")
+
+               if light.type=='SUN':
+                       pos = obj.matrix_world@mathutils.Vector((0.0, 0.0, 1.0, 0.0))
+               else:
+                       pos = obj.matrix_world@mathutils.Vector((0.0, 0.0, 0.0, 1.0))
+
+               light_res.statements.append(Statement("position", *tuple(pos)))
+               c = light.color
+               e = light.energy
+               light_res.statements.append(Statement("color", c.r*e, c.g*e, c.b*e))
+
+               return light_res
index 3dc3236b04b992c67ced8a583e991f50a9011410..fa7ca5e538150f3e1df98f2054ddcf8880609385 100644 (file)
@@ -68,3 +68,62 @@ class SceneExporter:
                        scene_res.statements.append(st)
 
                return scene_res
                        scene_res.statements.append(st)
 
                return scene_res
+
+       def export_sequence_resources(self, scene, resources):
+               from .datafile import Resource, Statement, Token
+
+               if scene.background_set:
+                       wrapper_name = scene.name+".wrapper.scene"
+                       if wrapper_name not in resources:
+                               wrapper_res = Resource(wrapper_name, "scene")
+                               wrapper_res.statements.append(Statement("type", Token("ordered")))
+                               for s in scene.get_chain():
+                                       wrapper_res.statements.append(wrapper_res.create_reference_statement("scene", resources[s.name+".scene"]))
+
+                               resources[wrapper_name] = wrapper_res
+
+               lights = []
+               s = scene
+               while s:
+                       lights += s.lights
+                       s = s.background_set
+
+               from .util import make_unique
+               lights = make_unique(lights)
+
+               from .export_light import LightExporter
+               light_exporter = LightExporter()
+               for l in lights:
+                       light_name = l.name+".light"
+                       if light_name not in resources:
+                               resources[light_name] = light_exporter.export_light(l)
+
+               lighting_name = scene.name+".lightn"
+               if lighting_name not in resources:
+                       lighting_res = Resource(lighting_name, "lighting")
+                       for l in lights:
+                               lighting_res.statements.append(lighting_res.create_reference_statement("light", resources[l.name+".light"]))
+
+                       resources[lighting_name] = lighting_res
+
+       def export_sequence(self, scene, resources):
+               from .datafile import Resource, Statement
+               seq_res = Resource(scene.name+".seq", "sequence")
+
+               content = scene
+               if scene.background_set:
+                       content = resources[scene.name+".wrapper.scene"]
+
+               ss = Statement("pass", "", "content")
+               ss.sub.append(Statement("depth_test", "lequal"))
+               ss.sub.append(seq_res.create_reference_statement("lighting", resources[scene.name+".lightn"]))
+               ss.sub.append(seq_res.create_reference_statement("scene", content))
+               seq_res.statements.append(ss)
+
+               # Add a colorcurve with linear response to convert into sRGB color space
+               ss = Statement("colorcurve")
+               ss.sub.append(Statement("brightness_response", 1.0))
+               ss.sub.append(Statement("srgb"))
+               seq_res.statements.append(ss)
+
+               return seq_res
index c1c89804f3791a8268882fd8f997ec1338bc2f10..469bc26844f7bea0f8424df45ca21e6f86ab4af1 100644 (file)
@@ -11,6 +11,7 @@ class MspGLSceneProperties(bpy.types.Panel):
                scene = context.scene
 
                self.layout.prop(scene, "scene_type")
                scene = context.scene
 
                self.layout.prop(scene, "scene_type")
+               self.layout.prop(scene, "export_disposition")
 
 class MspGLMeshProperties(bpy.types.Panel):
        bl_idname = "MESH_PT_mspgl_properties"
 
 class MspGLMeshProperties(bpy.types.Panel):
        bl_idname = "MESH_PT_mspgl_properties"
@@ -130,6 +131,23 @@ class MspGLTextureNodeProperties(bpy.types.Panel):
                        self.layout.prop(node, "use_mipmap")
                        self.layout.prop(node, "max_anisotropy")
 
                        self.layout.prop(node, "use_mipmap")
                        self.layout.prop(node, "max_anisotropy")
 
+class MspGLLightProperties(bpy.types.Panel):
+       bl_idname = "LIGHT_PT_mspgl_properties"
+       bl_label = "MspGL properties"
+       bl_space_type = "PROPERTIES"
+       bl_region_type = "WINDOW"
+       bl_context = "data"
+
+       @classmethod
+       def poll(cls, context):
+               return context.active_object.type=="LIGHT"
+
+       def draw(self, context):
+               light = context.active_object.data
+
+               if light.use_shadow:
+                       self.layout.prop(light, "shadow_map_size")
+
 class MspGLUniform(bpy.types.PropertyGroup):
        name: bpy.props.StringProperty(name="Name", description="Name of the uniform variable")
        size: bpy.props.IntProperty(name="Size", description="Number of elements in the uniform", min=1, max=4, default=4)
 class MspGLUniform(bpy.types.PropertyGroup):
        name: bpy.props.StringProperty(name="Name", description="Name of the uniform variable")
        size: bpy.props.IntProperty(name="Size", description="Number of elements in the uniform", min=1, max=4, default=4)
@@ -146,7 +164,7 @@ class MspGLUniformList(bpy.types.UIList):
                        layout.prop(uniform, "name", text="", emboss=False, icon_value=icon)
                        layout.label(text="({})".format(", ".join("{:.3f}".format(v) for v in uniform.values[:uniform.size])))
 
                        layout.prop(uniform, "name", text="", emboss=False, icon_value=icon)
                        layout.label(text="({})".format(", ".join("{:.3f}".format(v) for v in uniform.values[:uniform.size])))
 
-classes = [MspGLSceneProperties, MspGLMeshProperties, MspGLObjectProperties, MspGLMaterialProperties, MspGLTextureNodeProperties, MspGLUniform, MspGLUniformList]
+classes = [MspGLSceneProperties, MspGLMeshProperties, MspGLObjectProperties, MspGLMaterialProperties, MspGLTextureNodeProperties, MspGLLightProperties, MspGLUniform, MspGLUniformList]
 
 def register_properties():
        for c in classes:
 
 def register_properties():
        for c in classes:
@@ -156,6 +174,11 @@ def register_properties():
                items=(("SIMPLE", "Simple", "Objects are rendered in no specific order"),
                        ("ORDERED", "Ordered", "Objects are rendered in order by their name"),
                        ("ZSORTED", "Z-sorted", "Objects are rendered in order by their distance from the camera")))
                items=(("SIMPLE", "Simple", "Objects are rendered in no specific order"),
                        ("ORDERED", "Ordered", "Objects are rendered in order by their name"),
                        ("ZSORTED", "Z-sorted", "Objects are rendered in order by their distance from the camera")))
+       bpy.types.Scene.export_disposition = bpy.props.EnumProperty(name="Export disposition", description="What to do with this scene during project export", default="IGNORE",
+               items=(("IGNORE", "Ignore", "The scene won't be exported"),
+                       ("CONTENTS", "Contents only", "Objects in the scene will be exported, but not the scene itself"),
+                       ("SCENE", "Scene", "The scene will be exported"),
+                       ("SEQUENCE", "Sequence", "The scene will be exported along with a rendering sequence")))
 
        bpy.types.Mesh.winding_test = bpy.props.BoolProperty(name="Winding test", description="Perform winding test to skip back faces")
        bpy.types.Mesh.smoothing = bpy.props.EnumProperty(name="Smoothing", description="Smoothing method to use", default="MSPGL",
 
        bpy.types.Mesh.winding_test = bpy.props.BoolProperty(name="Winding test", description="Perform winding test to skip back faces")
        bpy.types.Mesh.smoothing = bpy.props.EnumProperty(name="Smoothing", description="Smoothing method to use", default="MSPGL",
index 079647ce00985a41252084cdfd6a3ca435b06626..53d338cb88b05ec625e6f5b044ae99d59df19019 100644 (file)
@@ -17,10 +17,14 @@ class Scene:
        def __init__(self, scene, obj_filter=None):
                self.name = scene.name
                self.scene_type = scene.scene_type
        def __init__(self, scene, obj_filter=None):
                self.name = scene.name
                self.scene_type = scene.scene_type
+               self.export_disposition = scene.export_disposition
+               self.background_set = None
+               self.camera = scene.camera
                self.prototypes = []
                self.instances = []
                self.prototypes = []
                self.instances = []
+               self.lights = []
 
 
-               objects = [o for o in scene.objects if o.type=='MESH']
+               objects = scene.objects[:]
                objects.sort(key=lambda o:o.name)
                if obj_filter:
                        objects = list(filter(obj_filter, objects))
                objects.sort(key=lambda o:o.name)
                if obj_filter:
                        objects = list(filter(obj_filter, objects))
@@ -30,11 +34,21 @@ class Scene:
                        if o.name in processed:
                                continue
 
                        if o.name in processed:
                                continue
 
-                       clones = [c for c in objects if is_same_object(o, c)]
-                       self.prototypes.append(o)
-                       for c in clones:
-                               self.instances.append(Instance(c, o))
-                               processed.add(c.name)
+                       if o.type=='MESH':
+                               clones = [c for c in objects if is_same_object(o, c)]
+                               self.prototypes.append(o)
+                               for c in clones:
+                                       self.instances.append(Instance(c, o))
+                                       processed.add(c.name)
+                       elif o.type=='LIGHT':
+                               self.lights.append(o)
+
+       def get_chain(self):
+               result = []
+               if self.background_set:
+                       result = self.background_set.get_chain()
+               result.append(self)
+               return result
 
 def get_all_collections(collection):
        result = [collection]
 
 def get_all_collections(collection):
        result = [collection]
@@ -62,3 +76,39 @@ def create_scene_from_current(context, *, selected_only=False, visible_only=True
                obj_filter = lambda o: all(f(o) for f in obj_filters)
 
        return Scene(context.scene, obj_filter)
                obj_filter = lambda o: all(f(o) for f in obj_filters)
 
        return Scene(context.scene, obj_filter)
+
+def create_scene(scene, *, visible_only=True):
+       obj_filter = None
+
+       if visible_only:
+               visible_names = set()
+               for c in get_all_collections(scene.collection):
+                       if not c.hide_viewport:
+                               visible_names.update(o.name for o in c.objects)
+               obj_filter = lambda o: o.name in visible_names
+
+       return Scene(scene, obj_filter)
+
+def create_scene_chain(scene, cache, *, visible_only=True):
+       if cache is None:
+               cache = {}
+
+       top = None
+       prev = None
+       while scene:
+               converted = None
+               if scene.name in cache:
+                       converted = cache[scene.name]
+               else:
+                       converted = create_scene(scene, visible_only=visible_only)
+                       cache[scene.name] = converted
+
+               if not top:
+                       top = converted
+               if prev:
+                       prev.background_set = converted
+
+               prev = converted
+               scene = scene.background_set
+
+       return top
index ca13875befe31aeae05433d84ebc04ae073b5342..3b2f718bdb4aebf4399870ad6227b4554e78c347 100644 (file)
@@ -68,3 +68,12 @@ def basename(path):
        if path.startswith("//"):
                path = path[2:]
        return os.path.basename(path)
        if path.startswith("//"):
                path = path[2:]
        return os.path.basename(path)
+
+def make_unique(values):
+       seen = set()
+       result = []
+       for i in values:
+               if i not in seen:
+                       result.append(i)
+                       seen.add(i)
+       return result