From: Mikko Rasa Date: Sat, 17 Apr 2021 19:06:07 +0000 (+0300) Subject: Add a Blender operator to export the entire project at once X-Git-Url: http://git.tdb.fi/?p=libs%2Fgl.git;a=commitdiff_plain;h=7ae4af705535271ad84dbfe2b5a24bc9c546ae01 Add a Blender operator to export the entire project at once This also creates sequence templates including lights defined in the scene(s). --- diff --git a/blender/io_mspgl/__init__.py b/blender/io_mspgl/__init__.py index 79575f7d..cba8178f 100644 --- a/blender/io_mspgl/__init__.py +++ b/blender/io_mspgl/__init__.py @@ -132,6 +132,26 @@ class ExportMspGLScene(bpy.types.Operator, ExportHelper): 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" @@ -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(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: diff --git a/blender/io_mspgl/export.py b/blender/io_mspgl/export.py index f36cc4df..37813d08 100644 --- a/blender/io_mspgl/export.py +++ b/blender/io_mspgl/export.py @@ -1,4 +1,5 @@ import os +import itertools class DataExporter: def __init__(self): @@ -44,6 +45,7 @@ class DataExporter: object_exporter = None camera_exporter = None armature_exporter = None + light_exporter = None 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) + 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 @@ -82,3 +91,59 @@ class DataExporter: 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 index 00000000..cfa4a19d --- /dev/null +++ b/blender/io_mspgl/export_light.py @@ -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 diff --git a/blender/io_mspgl/export_scene.py b/blender/io_mspgl/export_scene.py index 3dc3236b..fa7ca5e5 100644 --- a/blender/io_mspgl/export_scene.py +++ b/blender/io_mspgl/export_scene.py @@ -68,3 +68,62 @@ class SceneExporter: 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 diff --git a/blender/io_mspgl/properties.py b/blender/io_mspgl/properties.py index c1c89804..469bc268 100644 --- a/blender/io_mspgl/properties.py +++ b/blender/io_mspgl/properties.py @@ -11,6 +11,7 @@ class MspGLSceneProperties(bpy.types.Panel): 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" @@ -130,6 +131,23 @@ class MspGLTextureNodeProperties(bpy.types.Panel): 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) @@ -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]))) -classes = [MspGLSceneProperties, MspGLMeshProperties, MspGLObjectProperties, MspGLMaterialProperties, MspGLTextureNodeProperties, MspGLUniform, MspGLUniformList] +classes = [MspGLSceneProperties, MspGLMeshProperties, MspGLObjectProperties, MspGLMaterialProperties, MspGLTextureNodeProperties, MspGLLightProperties, MspGLUniform, MspGLUniformList] 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"))) + 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", diff --git a/blender/io_mspgl/scene.py b/blender/io_mspgl/scene.py index 079647ce..53d338cb 100644 --- a/blender/io_mspgl/scene.py +++ b/blender/io_mspgl/scene.py @@ -17,10 +17,14 @@ class Scene: 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.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)) @@ -30,11 +34,21 @@ class Scene: 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] @@ -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) + +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 diff --git a/blender/io_mspgl/util.py b/blender/io_mspgl/util.py index ca13875b..3b2f718b 100644 --- a/blender/io_mspgl/util.py +++ b/blender/io_mspgl/util.py @@ -68,3 +68,12 @@ def 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