From db9fa2e2aba82aadc8da3fb8f4f4839419a06d90 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Thu, 15 Apr 2021 20:04:26 +0300 Subject: [PATCH] Redesign object exporting To combat operator bloat, all object-based data is now exported with a common front-end class. SceneExporter also uses this to export its resources. Meshes can't be exported on their own anymore, only as part of objects. But loose meshes aren't that useful anymore with the improvements in defining materials and techniques in Blender. --- blender/io_mspgl/__init__.py | 95 +++++++++-------------------- blender/io_mspgl/export.py | 78 +++++++++++++++++++++++ blender/io_mspgl/export_armature.py | 11 +--- blender/io_mspgl/export_camera.py | 9 +-- blender/io_mspgl/export_mesh.py | 26 -------- blender/io_mspgl/export_object.py | 55 +---------------- blender/io_mspgl/export_scene.py | 24 ++------ 7 files changed, 117 insertions(+), 181 deletions(-) create mode 100644 blender/io_mspgl/export.py diff --git a/blender/io_mspgl/__init__.py b/blender/io_mspgl/__init__.py index 473750a5..8de91d25 100644 --- a/blender/io_mspgl/__init__.py +++ b/blender/io_mspgl/__init__.py @@ -8,10 +8,11 @@ bl_info = { if "bpy" in locals(): import imp - for sub in "animation", "armature", "datafile", "export_animation", "export_armature", "export_camera", "export_material", "export_mesh", "export_object", "export_scene", "export_texture", "material", "mesh", "properties", "util": + for sub in "animation", "armature", "datafile", "export", "export_animation", "export_armature", "export_camera", "export_material", "export_mesh", "export_object", "export_scene", "export_texture", "material", "mesh", "properties", "util": if sub in locals(): imp.reload(locals()[sub]) +import os import bpy from bpy_extras.io_utils import ExportHelper @@ -39,66 +40,42 @@ class ExportMspGLBase(ExportHelper): for k, v in self.as_keywords().items(): setattr(exporter, k, v) -class ExportMspGLMeshBase(ExportMspGLBase): - export_all: bpy.props.BoolProperty(name="Export all selected", description="Export all selected objects (use generated filenames)", default=False) +class ExportMspGLData(bpy.types.Operator): + bl_idname = "export.mspgl_data" + bl_label = "Export Msp GL data" + bl_description = "Export object data in Msp GL format" - def draw(self, context): - self.general_col = self.layout.column() - - col = self.layout.column() - if len(context.selected_objects)>1: - col.label(text="Object selection") - col.prop(self, "export_all") - -class ExportMspGLMesh(bpy.types.Operator, ExportMspGLMeshBase): - bl_idname = "export_mesh.mspgl_mesh" - bl_label = "Export Msp GL mesh" - bl_description = "Export one or more meshes in Msp GL format" - - filename_ext = ".mesh" - - def create_exporter(self): - from .export_mesh import MeshExporter - return MeshExporter() - -class ExportMspGLObject(bpy.types.Operator, ExportMspGLMeshBase): - bl_idname = "export_mesh.mspgl_object" - bl_label = "Export Msp GL object" - bl_description = "Export one or more objects in Msp GL format" - - filename_ext = ".object" - - collection: bpy.props.BoolProperty(name="As a collection", description="Write all data into a single collection file", default=False) + filepath: bpy.props.StringProperty(name="File path", description="File path for exporting the data", subtype='FILE_PATH') + collection: bpy.props.BoolProperty(name="As a collection", description="Export all data as a single collection file", default=False) shared_resources: bpy.props.BoolProperty(name="Shared resources", description="Use global names for resource files to enable sharing", default=True) - def check(self, context): - ext_changed = self.set_extension(".mdc" if self.collection else ".object") - super_result = super().check(context) - return ext_changed or super_result + @classmethod + def poll(cls, context): + return len(context.selected_objects)>0 - def create_exporter(self): - from .export_object import ObjectExporter - return ObjectExporter() + def invoke(self, context, event): + blend_filepath = context.blend_data.filepath + if blend_filepath: + self.filepath = os.path.splitext(blend_filepath)[0]+".mdc" + else: + self.filepath = "data.mdc" + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} - def draw(self, context): - super().draw(context) + def execute(self, context): + from .export import DataExporter + exporter = DataExporter() + exporter.collection = self.collection + exporter.shared_resources = self.shared_resources + exporter.export_to_file(context, self.filepath) + return {'FINISHED'} + def draw(self, context): col = self.layout.column() col.label(text="Files") col.prop(self, "collection") col.prop(self, "shared_resources") -class ExportMspGLArmature(bpy.types.Operator, ExportMspGLBase): - bl_idname = "export.mspgl_armature" - bl_label = "Export Msp GL armature" - bl_description = "Export an armature in Msp GL format" - - filename_ext = ".arma" - - def create_exporter(self): - from .export_armature import ArmatureExporter - return ArmatureExporter() - class ExportMspGLAnimation(bpy.types.Operator, ExportMspGLBase): bl_idname = "export.mspgl_animation" bl_label = "Export Msp GL animation" @@ -150,17 +127,6 @@ class ExportMspGLScene(bpy.types.Operator, ExportMspGLBase): if self.resource_collection: col.prop(self, "skip_existing") -class ExportMspGLCamera(bpy.types.Operator, ExportMspGLBase): - bl_idname = "export.mspgl_camera" - bl_label = "Export Msp GL camera" - bl_description = "Export a camera in Msp GL format" - - filename_ext = ".camera" - - def create_exporter(self): - from .export_camera import CameraExporter - return CameraExporter() - class AddUniform(bpy.types.Operator): bl_idname = "material.add_uniform" bl_label = "Add Uniform" @@ -186,14 +152,11 @@ class RemoveUniform(bpy.types.Operator): return {"FINISHED"} def menu_func_export(self, context): - self.layout.operator(ExportMspGLMesh.bl_idname, text="Msp GL mesh") - self.layout.operator(ExportMspGLObject.bl_idname, text="Msp GL object") - self.layout.operator(ExportMspGLArmature.bl_idname, text="Msp GL armature") + 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(ExportMspGLCamera.bl_idname, text="Msp GL camera") -classes = [ExportMspGLMesh, ExportMspGLObject, ExportMspGLArmature, ExportMspGLAnimation, ExportMspGLScene, ExportMspGLCamera, AddUniform, RemoveUniform] +classes = [ExportMspGLData, ExportMspGLAnimation, ExportMspGLScene, AddUniform, RemoveUniform] def register(): for c in classes: diff --git a/blender/io_mspgl/export.py b/blender/io_mspgl/export.py new file mode 100644 index 00000000..96f39320 --- /dev/null +++ b/blender/io_mspgl/export.py @@ -0,0 +1,78 @@ +import os + +class DataExporter: + def __init__(self): + self.show_progress = True + self.collection = False + self.shared_resources = True + + def export_to_file(self, context, out_fn): + from .util import Progress + progress = Progress(self.show_progress and context) + + objects = context.selected_objects + + resources = {} + material_atlases = {} + + dummy_res = self.export_resources(context, objects, resources, material_atlases, progress) + + path, base = os.path.split(out_fn) + base, ext = os.path.splitext(base) + + refs = dummy_res.collect_references() + if not self.shared_resources: + numbers = {} + for r in refs: + res_ext = os.path.splitext(r.name)[1] + n = numbers.get(res_ext, 0) + if n>0: + r.name = "{}_{}{}".format(base, n, res_ext) + else: + r.name = base+res_ext + numbers[res_ext] = n+1 + + if self.collection: + dummy_res.write_collection(out_fn, exclude_self=True) + else: + for r in refs: + r.write_to_file(os.path.join(path, r.name)) + + def export_resources(self, context, objects, resources, material_atlases, progress): + if material_atlases is None: + material_atlases = {} + + object_exporter = None + camera_exporter = None + armature_exporter = None + + from .datafile import Resource + dummy_res = Resource("dummy", "dummy") + + for i, obj in enumerate(objects): + progress.push_task_slice(obj.name, i, len(objects)) + res = None + if obj.type=='MESH': + if not object_exporter: + from .export_object import ObjectExporter + object_exporter = ObjectExporter() + object_exporter.export_object_resources(context, obj, resources, material_atlases, progress) + res = object_exporter.export_object(obj, resources, progress) + elif obj.type=='CAMERA': + if not camera_exporter: + from .export_camera import CameraExporter + camera_exporter = CameraExporter() + res = camera_exporter.export_camera(obj) + elif obj.type=='ARMATURE': + if not armature_exporter: + from .export_armature import ArmatureExporter + armature_exporter = ArmatureExporter() + res = armature_exporter.export_armature(context, obj) + + if res: + resources[res.name] = res + dummy_res.create_reference_statement("ref", res) + + progress.pop_task() + + return dummy_res diff --git a/blender/io_mspgl/export_armature.py b/blender/io_mspgl/export_armature.py index c2ec6712..27bf6e0b 100644 --- a/blender/io_mspgl/export_armature.py +++ b/blender/io_mspgl/export_armature.py @@ -1,16 +1,7 @@ class ArmatureExporter: - def export_to_file(self, context, out_fn): - obj = context.active_object - - statements = self.export_armature(context, obj) - - with open(out_fn, "w") as out_file: - for s in statements: - s.write_to_file(out_file) - def export_armature(self, obj): if obj.type!="ARMATURE": - raise Exception("Object is not an armature") + raise ValueError("Object is not an armature") from .armature import Armature armature = Armature(obj.data) diff --git a/blender/io_mspgl/export_camera.py b/blender/io_mspgl/export_camera.py index fb45535b..02fce103 100644 --- a/blender/io_mspgl/export_camera.py +++ b/blender/io_mspgl/export_camera.py @@ -2,16 +2,9 @@ import math import mathutils class CameraExporter: - def export_to_file(self, context, out_fn): - obj = context.active_object - - resource = self.export_camera(obj) - - resource.write_to_file(out_fn) - def export_camera(self, obj): if obj.type!='CAMERA': - raise Exception("Object is not a camera") + raise ValueError("Object is not a camera") from .datafile import Resource, Statement resource = Resource(obj.name+".camera", "camera") diff --git a/blender/io_mspgl/export_mesh.py b/blender/io_mspgl/export_mesh.py index 24226f29..2a8dbe00 100644 --- a/blender/io_mspgl/export_mesh.py +++ b/blender/io_mspgl/export_mesh.py @@ -4,32 +4,6 @@ import bpy import mathutils class MeshExporter: - def __init__(self): - self.show_progress = True - self.export_all = False - - def export_to_file(self, context, out_fn): - if self.export_all: - objs = [o for o in context.selected_objects if o.type=="MESH"] - else: - objs = [context.active_object] - - from .util import Progress - - path, base = os.path.split(out_fn) - base, ext = os.path.splitext(base) - - progress = Progress(self.show_progress and context) - for i, obj in enumerate(objs): - if self.export_all: - out_fn = os.path.join(path, obj.data.name+ext) - - progress.push_task_slice(obj.data.name, i, len(objs)) - resource = self.export_mesh(context, obj, progress) - - resource.write_to_file(out_fn) - progress.pop_task() - def export_mesh(self, context, mesh_or_obj, progress): from .mesh import Mesh, create_mesh_from_object diff --git a/blender/io_mspgl/export_object.py b/blender/io_mspgl/export_object.py index 54bde96a..3ee06fc2 100644 --- a/blender/io_mspgl/export_object.py +++ b/blender/io_mspgl/export_object.py @@ -2,12 +2,6 @@ import os import mathutils class ObjectExporter: - def __init__(self): - self.show_progress = True - self.export_all = False - self.collection = False - self.shared_resources = True - def compute_bounding_sphere(self, obj): p1 = max(((v.co, v.co.length) for v in obj.data.vertices), key=lambda x:x[1])[0] p2 = max(((v.co, (v.co-p1).length) for v in obj.data.vertices), key=lambda x:x[1])[0] @@ -45,48 +39,6 @@ class ObjectExporter: material_atlas_export = MaterialAtlasExporter() return material_atlas_export - def export_to_file(self, context, out_fn): - if self.export_all: - objs = [o for o in context.selected_objects if o.type=="MESH"] - else: - objs = [context.active_object] - - from .util import Progress - progress = Progress(self.show_progress and context) - - path, base = os.path.split(out_fn) - base, ext = os.path.splitext(base) - - resources = {} - for i, obj in enumerate(objs): - if self.export_all: - out_fn = os.path.join(path, obj.name+ext) - - progress.push_task_slice(obj.name, i, len(objs)) - self.export_object_resources(context, obj, resources, None, progress) - - obj_res = self.export_object(context, obj, resources, progress) - refs = obj_res.collect_references() - if not self.shared_resources: - numbers = {} - for r in refs: - res_ext = os.path.splitext(r.name)[1] - n = numbers.get(res_ext, 0) - if n>0: - r.name = "{}_{}{}".format(base, n, res_ext) - else: - r.name = base+res_ext - numbers[res_ext] = n+1 - - if self.collection: - obj_res.write_collection(out_fn) - else: - for r in refs: - r.write_to_file(os.path.join(path, r.name)) - obj_res.write_to_file(out_fn) - - progress.pop_task() - def export_object_resources(self, context, obj, resources, material_atlases, progress): if material_atlases is None: material_atlases = {} @@ -140,10 +92,9 @@ class ObjectExporter: progress.pop_task() - def export_object(self, context, obj, resources, progress): - if resources is None: - resources = {} - self.export_object_resources(context, obj, resources, None, progress) + def export_object(self, obj, resources, progress): + if obj.type!='MESH': + raise ValueError("Object is not a mesh") lods = self.collect_object_lods(obj) diff --git a/blender/io_mspgl/export_scene.py b/blender/io_mspgl/export_scene.py index b2c3a2e6..1dea4b24 100644 --- a/blender/io_mspgl/export_scene.py +++ b/blender/io_mspgl/export_scene.py @@ -17,16 +17,13 @@ class SceneExporter: if self.visible_collections: collections = [c.collection for c in context.view_layer.layer_collection.children if not (c.hide_viewport or c.collection.hide_viewport)] objs = [o for o in objs if any((o.name in c.all_objects) for c in collections)] - objs = [o for o in objs if o.type=="MESH" and not o.lod_for_parent] + objs = [o for o in objs if o.type=="MESH" and not o.lod_for_parent and o.data.vertices] objs = [o for o in objs if (not o.compound or o.parent not in objs)] objs.sort(key=lambda x:x.name) path, base = os.path.split(out_fn) base, ext = os.path.splitext(base) - from .export_object import ObjectExporter - object_export = ObjectExporter() - object_prototypes = {} unique_objects = [] export_names = {} @@ -75,8 +72,11 @@ class SceneExporter: from .util import Progress progress = Progress(self.show_progress and context) + from .export import DataExporter + data_exporter = DataExporter() + resources = {} - self.export_scene_resources(context, unique_objects, resources, progress) + data_exporter.export_resources(context, unique_objects, resources, None, progress) for n, r in resources.items(): if r.name in export_names: r.name = export_names[r.name] @@ -98,20 +98,6 @@ class SceneExporter: scene_res.write_to_file(out_fn) - def export_scene_resources(self, context, objs, resources, progress): - from .export_object import ObjectExporter - object_export = ObjectExporter() - object_export.single_file = False - - material_atlases = {} - - for i, o in enumerate(objs): - progress.push_task_slice(o.name, i, len(objs)) - object_export.export_object_resources(context, o, resources, material_atlases, progress) - obj_name = o.name+".object" - resources[obj_name] = object_export.export_object(context, o, resources, progress) - progress.pop_task() - def export_scene(self, context, objs, resources, prototypes, progress): from .datafile import Resource, Statement, Token scene_res = Resource("scene.scene", "scene") -- 2.43.0