]> git.tdb.fi Git - libs/gl.git/commitdiff
Redesign progress and error reporting in the Blender exporter
authorMikko Rasa <tdb@tdb.fi>
Sat, 5 Mar 2022 08:50:04 +0000 (10:50 +0200)
committerMikko Rasa <tdb@tdb.fi>
Sat, 5 Mar 2022 17:42:34 +0000 (19:42 +0200)
Progress is now tracked in a context object, which also holds the Blender
context.  Tasks are represented by independent objects instead of a stack
structure.

The context/task objects also track which data object that task is about,
so the object path can be reported in errors.

blender/io_mspgl/__init__.py
blender/io_mspgl/context.py [new file with mode: 0644]
blender/io_mspgl/export.py
blender/io_mspgl/export_material.py
blender/io_mspgl/export_mesh.py
blender/io_mspgl/export_object.py
blender/io_mspgl/export_scene.py
blender/io_mspgl/mesh.py
blender/io_mspgl/operators.py
blender/io_mspgl/scene.py
blender/io_mspgl/util.py

index dec550da78f450e2bb354d014b48eb0739d7c421..4b6e2ff4dfc3e9063db926c44cc50621c641b596 100644 (file)
@@ -6,10 +6,11 @@ bl_info = {
        "description": "Export Msp GL data",
        "category": "Import-Export" }
 
-files = ("animation", "armature", "datafile", "export", "export_animation",
-       "export_armature", "export_camera", "export_light", "export_material",
-       "export_mesh", "export_object", "export_scene", "export_texture",
-       "material", "mesh", "operators", "properties", "scene", "util")
+files = ("animation", "armature", "context", "datafile", "export",
+       "export_animation", "export_armature", "export_camera", "export_light",
+       "export_material", "export_mesh", "export_object", "export_scene",
+       "export_texture", "material", "mesh", "operators", "properties", "scene",
+       "util")
 
 if "bpy" in locals():
        import imp
diff --git a/blender/io_mspgl/context.py b/blender/io_mspgl/context.py
new file mode 100644 (file)
index 0000000..eb215cb
--- /dev/null
@@ -0,0 +1,74 @@
+class ExportError(Exception):
+       def __init__(self, objs):
+               super().__init__(" -> ".join(o.name for o in objs))
+               self.objs = objs
+
+class ExportContext:
+       def __init__(self, ctx, *args, verbose=False):
+               self.obj = None
+
+               if type(ctx)==ExportContext:
+                       self.level = ctx.level+1
+                       self.parent = ctx
+                       self.context = ctx.context
+                       self.verbose = ctx.verbose
+                       self.descr, self.start, self.delta = args
+                       if type(self.descr)!=str:
+                               self.obj = self.descr
+                               self.descr = self.descr.name
+               else:
+                       self.level = 0
+                       self.parent = None
+                       self.context = ctx
+                       self.verbose = verbose
+                       self.descr = None
+                       self.start = 0.0
+                       self.delta = 1.0
+                       self.context.window_manager.progress_begin(self.start, self.delta)
+
+               if self.verbose and self.descr:
+                       d = self.descr
+                       if self.obj:
+                               d += " ({})".format(type(self.obj).__name__)
+                       print("{} {}".format("ยท"*self.level, d))
+
+               self.slice_delta = 0.0
+               self.progress = self.start
+               self.child = None
+               self.context.window_manager.progress_update(self.progress)
+               self.last_progress_update = self.progress
+
+       def export(self, func, *args, **kwargs):
+               try:
+                       func(self, *args, **kwargs)
+               except Exception as e:
+                       tasks = []
+                       c = self
+                       while c:
+                               tasks.append(c)
+                               c = c.child
+                       objs = [t.obj for t in tasks if t.obj]
+                       raise ExportError(objs) from e
+
+       def task(self, task, end):
+               self.child = ExportContext(self, task, self.progress, self.start+end*self.delta-self.progress)
+               self.progress += self.child.delta
+               return self.child
+
+       def set_progress(self, progr):
+               self.progress = self.start+progr*self.delta
+               self.child = None
+               if self.progress>self.last_progress_update+0.001:
+                       self.context.window_manager.progress_update(self.progress)
+                       self.last_progress_update = self.progress
+
+       def set_slices(self, count):
+               if count>0:
+                       self.slice_delta = self.delta/count
+               else:
+                       self.slice_delta = 0.0
+
+       def next_slice(self, task):
+               self.child = ExportContext(self, task, self.progress, self.slice_delta)
+               self.progress += self.slice_delta
+               return self.child
index 6db2c81c6cbb9f718ebec222227182e9e8d7e96b..190069c4f3caa961ed3512ec191b9366f7238be4 100644 (file)
@@ -2,20 +2,19 @@ import os
 import itertools
 
 class DataExporter:
-       def export_to_file(self, context, out_fn, *, collection=False, shared_resources=False):
-               from .util import Progress
-               progress = Progress(context)
-
-               objects = context.selected_objects
+       def export_to_file(self, ctx, out_fn, *, collection=False, shared_resources=False):
+               objects = context.context.selected_objects
 
                resources = {}
                material_atlases = {}
 
-               dummy_res = self.export_resources(context, objects, resources, material_atlases, progress)
+               task = ctx.task("Exporting resources", 1.0)
+               dummy_res = self.export_resources(task, objects, resources, material_atlases)
 
                path, base = os.path.split(out_fn)
                base, ext = os.path.splitext(base)
 
+               task = ctx.task("Writing files", 1.0)
                refs = dummy_res.collect_references()
                if not shared_resources:
                        numbers = {}
@@ -34,7 +33,7 @@ class DataExporter:
                        for r in refs:
                                r.write_to_file(os.path.join(path, r.name))
 
-       def export_resources(self, context, objects, resources, material_atlases, progress):
+       def export_resources(self, ctx, objects, resources, material_atlases):
                if material_atlases is None:
                        material_atlases = {}
 
@@ -46,8 +45,9 @@ class DataExporter:
                from .datafile import Resource
                dummy_res = Resource("dummy", "dummy")
 
-               for i, obj in enumerate(objects):
-                       progress.push_task_slice(obj.name, i, len(objects))
+               ctx.set_slices(len(objects))
+               for obj in objects:
+                       task = ctx.next_slice(obj)
                        res_name = None
                        res = None
                        if obj.type=='MESH':
@@ -56,8 +56,8 @@ class DataExporter:
                                        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)
+                                       object_exporter.export_object_resources(task, obj, resources, material_atlases)
+                                       res = object_exporter.export_object(obj, resources)
                        elif obj.type=='CAMERA':
                                res_name = obj.name+".camera"
                                if res_name not in resources:
@@ -71,7 +71,7 @@ class DataExporter:
                                        if not armature_exporter:
                                                from .export_armature import ArmatureExporter
                                                armature_exporter = ArmatureExporter()
-                                       res = armature_exporter.export_armature(context, obj)
+                                       res = armature_exporter.export_armature(obj)
                        elif obj.type=='LIGHT':
                                res_name = obj.name+".light"
                                if res_name not in resources:
@@ -84,27 +84,26 @@ class DataExporter:
                                resources[res_name] = res
                                dummy_res.create_reference_statement("ref", res)
 
-                       progress.pop_task()
-
                return dummy_res
 
 class ProjectExporter:
-       def export_to_directory(self, context, out_dir):
-               from .util import Progress
-               progress = Progress(context)
-
+       def export_to_directory(self, ctx, out_dir):
                from .scene import create_scene_chain
 
+               task = ctx.task("Preparing scenes", 0.0)
+               task.set_slices(len(ctx.context.blend_data.scenes))
+
                scenes = {}
                sequences = []
-               for s in context.blend_data.scenes:
+               for s in ctx.context.blend_data.scenes:
+                       subtask = task.next_slice(s)
                        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:
+                       elif s.export_disposition!='IGNORE' and s.name not in scenes:
                                scene = create_scene(s)
                                if s.export_disposition=='SCENE':
                                        scenes[scene.name] = scene
@@ -132,9 +131,13 @@ class ProjectExporter:
                scene_exporter = SceneExporter()
                data_exporter = DataExporter()
 
+               task = ctx.task("Exporting resources", 1.0)
                resources = {}
-               dummy_res = data_exporter.export_resources(context, all_objects, resources, None, progress)
+               dummy_res = data_exporter.export_resources(task, all_objects, resources, None)
+
+               task = ctx.task("Exporting scenes", 1.0)
                for s in ordered_scenes:
+                       subtask = task.task(s, 0.5)
                        scene_name = s.name+".scene"
                        if scene_name not in resources:
                                scene_res = scene_exporter.export_scene(s, resources)
@@ -142,6 +145,7 @@ class ProjectExporter:
                                dummy_res.create_reference_statement("ref", scene_res)
 
                for s in sequences:
+                       subtask = task.task(s, 0.5)
                        seq_name = s.name+".seq"
                        if seq_name not in resources:
                                scene_exporter.export_sequence_resources(s, resources)
@@ -149,6 +153,7 @@ class ProjectExporter:
                                resources[seq_name] = seq_res
                                dummy_res.create_reference_statement("ref", seq_res)
 
+               task = ctx.task("Writing files", 1.0)
                refs = dummy_res.collect_references()
                for r in refs:
                        r.write_to_file(os.path.join(out_dir, r.name))
index 826ab92688429e69cf5b735c06227674fa8fbd64..a109ed24351f2782093cf70ab792654f69ba53eb 100644 (file)
@@ -62,7 +62,7 @@ def create_technique_resource(material, resources):
        return tech_res
 
 class MaterialExporter:
-       def export_technique_resources(self, material, resources):
+       def export_technique_resources(self, ctx, material, resources):
                from .export_texture import SamplerExporter, TextureExporter
                texture_export = TextureExporter()
                sampler_export = SamplerExporter()
@@ -71,16 +71,21 @@ class MaterialExporter:
                if type(material)!=Material:
                        material = Material(material)
 
-               for p in material.properties:
-                       if p.texture:
-                               tex_name = p.texture.image.name+".tex2d"
-                               if tex_name not in resources:
-                                       resources[tex_name] = texture_export.export_texture(p.texture, p.tex_usage, invert_green=p.invert_green)
+               textured_props = [p for p in material.properties if p.texture]
+
+               ctx.set_slices(len(textured_props)+1)
+               for p in textured_props:
+                       ctx.next_slice(p.texture.image)
+
+                       tex_name = p.texture.image.name+".tex2d"
+                       if tex_name not in resources:
+                               resources[tex_name] = texture_export.export_texture(p.texture, p.tex_usage, invert_green=p.invert_green)
 
-                               samp_name = sampler_export.get_sampler_name(p.texture)
-                               if samp_name not in resources:
-                                       resources[samp_name] = sampler_export.export_sampler(p.texture)
+                       samp_name = sampler_export.get_sampler_name(p.texture)
+                       if samp_name not in resources:
+                               resources[samp_name] = sampler_export.export_sampler(p.texture)
 
+               ctx.next_slice(material)
                mat_name = material.name+".mat"
                if mat_name not in resources:
                        if material.type:
index f54a05fd2a6c0b3f39844f16d294a0a57e6d0981..b85fd2f8c57c9906290548daaf7882aa962b1837 100644 (file)
@@ -4,20 +4,21 @@ import bpy
 import mathutils
 
 class MeshExporter:
-       def export_mesh(self, context, mesh_or_obj, progress):
+       def export_mesh(self, ctx, mesh_or_obj):
                from .mesh import Mesh, create_mesh_from_object
 
                if type(mesh_or_obj)==Mesh:
                        mesh = mesh_or_obj
                else:
-                       progress.push_task("", 0.0, 0.9)
-                       mesh = create_mesh_from_object(context, mesh_or_obj, progress)
-                       progress.pop_task()
+                       task = ctx.task("Preparing mesh", 0.9)
+                       mesh = create_mesh_from_object(task, mesh_or_obj)
 
                from .datafile import Resource, Statement, Token
                resource = Resource(mesh.name+".mesh", "mesh")
                statements = resource.statements
 
+               task = ctx.task("Creating statements", 1.0)
+
                st = Statement("vertices", Token("VERTEX3_FLOAT"))
                stride = 12
                if mesh.vertices[0].color:
@@ -113,6 +114,6 @@ class MeshExporter:
                if mesh.winding_test:
                        statements.append(Statement("winding", Token('COUNTERCLOCKWISE')))
 
-               progress.set_progress(1.0)
+               task.set_progress(1.0)
 
                return resource
index b39307c8704fd78a74864a4e012ddda990ca44c3..d78f5fcd27fd1d266e705e286ed1b8ec3c805bc1 100644 (file)
@@ -11,7 +11,7 @@ class ObjectExporter:
 
                return lods
 
-       def export_object_resources(self, context, obj, resources, material_atlases, progress):
+       def export_object_resources(self, ctx, obj, resources, material_atlases):
                if material_atlases is None:
                        material_atlases = {}
 
@@ -25,9 +25,10 @@ class ObjectExporter:
                material_export = MaterialExporter()
                material_atlas_export = MaterialAtlasExporter()
 
-               for i, l in enumerate(lods):
+               ctx.set_slices(len(lods))
+               for l in lods:
                        lod_index = l.lod_index if l.lod_for_parent else 0
-                       progress.push_task_slice("LOD {}".format(lod_index), i, len(lods))
+                       task = ctx.next_slice("LOD {}".format(lod_index))
 
                        material_atlas = None
                        atlas_flags = [m.render_mode!='EXTERNAL' and m.material_atlas for m in l.data.materials if m]
@@ -41,7 +42,7 @@ class ObjectExporter:
                                if material_atlas_key in material_atlases:
                                        material_atlas = material_atlases[material_atlas_key]
                                else:
-                                       material_atlas = create_material_atlas(context, l.data.materials[0])
+                                       material_atlas = create_material_atlas(task.context, l.data.materials[0])
                                        material_atlases[material_atlas_key] = material_atlas
 
                                tech_name = "{}.tech".format(material_atlas.name)
@@ -50,24 +51,24 @@ class ObjectExporter:
                                        resources[tech_name] = material_atlas_export.export_technique(material_atlas, resources)
                        elif l.material_slots and l.material_slots[0].material:
                                material = l.material_slots[0].material
+                               subtask = task.task(material, 0.1)
                                if material.render_mode!='EXTERNAL':
                                        tech_name = material.name+".tech"
                                        if tech_name not in resources:
                                                material = Material(material)
-                                               material_export.export_technique_resources(material, resources)
+                                               material_export.export_technique_resources(subtask, material, resources)
                                                resources[tech_name] = material_export.export_technique(material, resources)
                        elif "stub.tech" not in resources:
                                resources["stub.tech"] = self.export_stub_technique()
 
                        mesh_name = l.data.name+".mesh"
                        if mesh_name not in resources:
-                               mesh = create_mesh_from_object(context, l, material_atlas, progress)
-                               mesh_res = mesh_export.export_mesh(context, mesh, progress)
+                               subtask = task.task(l, 1.0)
+                               mesh = create_mesh_from_object(subtask, l, material_atlas)
+                               mesh_res = mesh_export.export_mesh(subtask, mesh)
                                resources[mesh_name] = mesh_res
 
-                       progress.pop_task()
-
-       def export_object(self, obj, resources, progress):
+       def export_object(self, obj, resources):
                if obj.type!='MESH':
                        raise ValueError("Object {} is not a mesh".format(obj.name))
 
@@ -119,8 +120,6 @@ class ObjectExporter:
                        else:
                                statements += lod_st
 
-               progress.set_progress(1.0)
-
                return obj_res
 
        def export_stub_technique(self):
index a069af2a60d6fafa88313705e6b743a43267320d..78ce2cbd5a69b2329337e72e5ae499b8c9dc513e 100644 (file)
@@ -4,21 +4,21 @@ import itertools
 import mathutils
 
 class SceneExporter:
-       def export_to_file(self, context, out_fn, *, selected_only=False, visible_only=True, collection=True, skip_existing=True):
-               from .util import Progress
-               progress = Progress(context)
-
+       def export_to_file(self, ctx, out_fn, *, selected_only=False, visible_only=True, collection=True, skip_existing=True):
                from .scene import create_scene_from_current
-               scene = create_scene_from_current(context, selected_only=selected_only, visible_only=visible_only)
+               task = ctx.task("Preparing scene", 0.1)
+               scene = create_scene_from_current(task, selected_only=selected_only, visible_only=visible_only)
 
                resources = {}
-               self.export_scene_resources(context, scene, resources, progress)
+               task = ctx.task("Exporting resources", 0.9)
+               self.export_scene_resources(task, scene, resources)
+               task = ctx.task(scene, 1.0)
                scene_res = self.export_scene(scene, resources)
-               progress.set_progress(1.0)
 
                path, base = os.path.split(out_fn)
                base, ext = os.path.splitext(base)
 
+               task = ctx.task("Writing files", 1.0)
                if collection:
                        existing = None
                        if skip_existing:
@@ -29,11 +29,11 @@ class SceneExporter:
                        for r in scene_res.collect_references():
                                r.write_to_file(os.path.join(path, r.name))
 
-       def export_scene_resources(self, context, scene, resources, progress):
+       def export_scene_resources(self, ctx, scene, resources):
                from .export import DataExporter
                data_exporter = DataExporter()
 
-               data_exporter.export_resources(context, scene.prototypes, resources, None, progress)
+               data_exporter.export_resources(ctx, scene.prototypes, resources, None)
 
        def export_scene(self, scene, resources):
                from .datafile import Resource, Statement, Token
index 8009191f080a045468c36908cee738a988e562e4..fb568ec416fb04739ef7b49c8b2bbd0a1f9f972e 100644 (file)
@@ -305,7 +305,7 @@ class Mesh:
 
                self.lines += other.lines
 
-       def prepare_triangles(self, progress):
+       def prepare_triangles(self, task):
                face_count = len(self.faces)
                for i in range(face_count):
                        f = self.faces[i]
@@ -365,9 +365,9 @@ class Mesh:
                        f.normal = normals[1-cut_index]
                        nf.normal = normals[3-cut_index]
 
-                       progress.set_progress(i/face_count)
+                       task.set_progress(i/face_count)
 
-       def prepare_smoothing(self, progress):
+       def prepare_smoothing(self, task):
                smooth_limit = -1
                if self.smoothing=='NONE':
                        for f in self.faces:
@@ -380,14 +380,12 @@ class Mesh:
                for e in self.edges:
                        e.check_smooth(smooth_limit)
 
-               progress.push_task("Sharp edges", 0.0, 0.7)
-               self.split_vertices(self.find_smooth_group, progress)
+               subtask = task.task("Sharp edges", 0.7)
+               self.split_vertices(self.find_smooth_group, subtask)
 
                if self.smoothing!='BLENDER':
-                       progress.set_task("Updating normals", 0.7, 1.0)
-                       self.compute_normals(progress)
-
-               progress.pop_task()
+                       subtask = task.task("Updating normals", 1.0)
+                       self.compute_normals(subtask)
 
        def prepare_vertex_groups(self, obj):
                if not self.vertex_groups:
@@ -439,7 +437,7 @@ class Mesh:
                        for i in f.loop_indices:
                                layer.uvs[i] = uv
 
-       def prepare_uv(self, progress):
+       def prepare_uv(self, task):
                # Form a list of UV layers referenced by materials with the array atlas
                # property set
                array_uv_layers = [] #[t.uv_layer for m in self.materials if m.array_atlas for t in m.texture_slots if t and t.texture_coords=='UV']
@@ -457,9 +455,6 @@ class Mesh:
                                        for i in f.loop_indices:
                                                l.uvs[i] = mathutils.Vector((*l.uvs[i], layer))
 
-               prog_count = len(self.uv_layers)
-               prog_step = 0
-
                # Split by the UV layer used for tangent vectors first so connectivity
                # remains intact for tangent vector computation
                tangent_layer_index = -1
@@ -471,26 +466,27 @@ class Mesh:
                        elif self.uv_layers[0].unit==0:
                                tangent_layer_index = 0
 
-                       if tangent_layer_index>=0:
-                               prog_count += 1
-                               progress.push_task_slice("Computing tangents", 0, prog_count)
-                               self.split_vertices(self.find_uv_group, progress, tangent_layer_index)
-                               progress.set_task_slice(self.tangent_uvtex, 1, prog_count)
-                               self.compute_tangents(tangent_layer_index, progress)
-                               progress.pop_task()
-                               prog_step = 2
-                       else:
+                       if tangent_layer_index<0:
                                raise Exception("Invalid configuration on mesh {}: No tangent UV layer".format(self.name))
 
+               prog_count = len(self.uv_layers)
+               if tangent_layer_index>=0:
+                       prog_count += 1
+               task.set_slices(prog_count)
+
+               if tangent_layer_index>=0:
+                       subtask = task.next_slice("Computing tangents")
+                       self.split_vertices(self.find_uv_group, subtask, tangent_layer_index)
+                       subtask = task.next_slice(self.tangent_uvtex)
+                       self.compute_tangents(tangent_layer_index, subtask)
+
                # Split by the remaining UV layers
                for i, u in enumerate(self.uv_layers):
                        if i==tangent_layer_index:
                                continue
 
-                       progress.push_task_slice(u.name, prog_step, prog_count)
-                       self.split_vertices(self.find_uv_group, progress, i)
-                       progress.pop_task()
-                       prog_step += 1
+                       subtask = task.next_slice(u.name)
+                       self.split_vertices(self.find_uv_group, subtask, i)
 
                # Copy UVs from layers to vertices
                for v in self.vertices:
@@ -502,11 +498,11 @@ class Mesh:
                        else:
                                v.uvs = [(0.0, 0.0)]*len(self.uv_layers)
 
-       def prepare_colors(self, progress):
+       def prepare_colors(self, task):
                if not self.colors:
                        return
 
-               self.split_vertices(self.find_color_group, progress)
+               self.split_vertices(self.find_color_group, task)
 
                for v in self.vertices:
                        if v.faces:
@@ -515,7 +511,7 @@ class Mesh:
                        else:
                                v.color = (1.0, 1.0, 1.0, 1.0)
 
-       def split_vertices(self, find_group_func, progress, *args):
+       def split_vertices(self, find_group_func, task, *args):
                vertex_count = len(self.vertices)
                for i in range(vertex_count):
                        v = self.vertices[i]
@@ -567,7 +563,7 @@ class Mesh:
                                        f.vertices[f.vertices.index(v)] = nv
                                        nv.faces.append(f)
 
-                       progress.set_progress(i/vertex_count)
+                       task.set_progress(i/vertex_count)
 
        def find_smooth_group(self, vertex, face):
                face.flag = True
@@ -613,7 +609,7 @@ class Mesh:
 
                return group
 
-       def compute_normals(self, progress):
+       def compute_normals(self, task):
                for i, v in enumerate(self.vertices):
                        v.normal = mathutils.Vector()
                        for f in v.faces:
@@ -631,9 +627,9 @@ class Mesh:
                        else:
                                v.normal = mathutils.Vector((0, 0, 1))
 
-                       progress.set_progress(i/len(self.vertices))
+                       task.set_progress(i/len(self.vertices))
 
-       def compute_tangents(self, index, progress):
+       def compute_tangents(self, index, task):
                layer_uvs = self.uv_layers[index].uvs
 
                for i, v in enumerate(self.vertices):
@@ -657,13 +653,13 @@ class Mesh:
                        if v.tan.length:
                                v.tan.normalize()
 
-                       progress.set_progress(i/len(self.vertices))
+                       task.set_progress(i/len(self.vertices))
 
-       def prepare_sequence(self, progress):
-               progress.push_task("Reordering faces", 0.0, 0.5)
-               self.reorder_faces(progress)
+       def prepare_sequence(self, task):
+               subtask = task.task("Reordering faces", 0.5)
+               self.reorder_faces(subtask)
 
-               progress.set_task("Building sequence", 0.5, 1.0)
+               subtask = task.task("Building sequence", 1.0)
                sequence = None
                for i, f in enumerate(self.faces):
                        if sequence:
@@ -690,13 +686,11 @@ class Mesh:
                                sequence = f.vertices[:]
                                self.vertex_sequence.append(sequence)
 
-                       progress.set_progress(i/len(self.faces))
-
-               progress.pop_task()
+                       subtask.set_progress(i/len(self.faces))
 
                self.reorder_vertices()
 
-       def reorder_faces(self, progress):
+       def reorder_faces(self, task):
                # Tom Forsyth's vertex cache optimization algorithm
                # http://eelpi.gotdns.org/papers/fast_vert_cache_opt.html
 
@@ -772,7 +766,7 @@ class Mesh:
                        del cached_vertices[max_cache_size:]
 
                        n_processed += 1
-                       progress.set_progress(n_processed/len(self.faces))
+                       task.set_progress(n_processed/len(self.faces))
 
                self.faces = reordered_faces
                for i, f in enumerate(self.faces):
@@ -795,11 +789,11 @@ class Mesh:
                        e.key = make_edge_key(e.vertices[0].index, e.vertices[1].index)
 
 
-def create_mesh_from_object(context, obj, material_atlas, progress):
+def create_mesh_from_object(ctx, obj, material_atlas):
        if obj.type!="MESH":
                raise Exception("Object {} is not a mesh".format(obj.name))
 
-       progress.push_task("Preparing mesh", 0.0, 0.2)
+       task = ctx.task("Collecting mesh data", 0.2)
 
        objs = [(obj, mathutils.Matrix())]
        i = 0
@@ -810,7 +804,7 @@ def create_mesh_from_object(context, obj, material_atlas, progress):
                        if c.type=="MESH" and c.compound:
                                objs.append((c, m*c.matrix_local))
 
-       dg = context.evaluated_depsgraph_get()
+       dg = ctx.context.evaluated_depsgraph_get()
 
        mesh = None
        for o, m in objs:
@@ -844,19 +838,17 @@ def create_mesh_from_object(context, obj, material_atlas, progress):
        if material_atlas:
                mesh.apply_material_atlas(material_atlas)
 
-       progress.set_task("Triangulating", 0.2, 0.3)
-       mesh.prepare_triangles(progress)
-       progress.set_task("Smoothing", 0.3, 0.5)
-       mesh.prepare_smoothing(progress)
-       progress.set_task("Vertex groups", 0.5, 0.6)
+       task = ctx.task("Triangulating", 0.3)
+       mesh.prepare_triangles(task)
+       task = ctx.task("Smoothing", 0.5)
+       mesh.prepare_smoothing(task)
+       task = ctx.task("Vertex groups", 0.6)
        mesh.prepare_vertex_groups(obj)
-       progress.set_task("Preparing UVs", 0.6, 0.75)
-       mesh.prepare_uv(progress)
-       progress.set_task("Preparing vertex colors", 0.75, 0.85)
-       mesh.prepare_colors(progress)
-       progress.set_task("Render sequence", 0.85, 1.0)
-       mesh.prepare_sequence(progress)
-
-       progress.pop_task()
+       task = ctx.task("Preparing UVs", 0.75)
+       mesh.prepare_uv(task)
+       task = ctx.task("Preparing vertex colors", 0.85)
+       mesh.prepare_colors(task)
+       task = ctx.task("Render sequence", 1.0)
+       mesh.prepare_sequence(task)
 
        return mesh
index 920f025141cde05add6a40631a902b6777de511e..3741358406bad0cbf67ac12f4825220bbc5d9d82 100644 (file)
@@ -19,6 +19,7 @@ class ExportMspGLData(bpy.types.Operator):
        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)
+       debug_mode: bpy.props.BoolProperty(name="Debug mode", description="Enable debugging output to console")
 
        @classmethod
        def poll(cls, context):
@@ -34,9 +35,12 @@ class ExportMspGLData(bpy.types.Operator):
                return {'RUNNING_MODAL'}
 
        def execute(self, context):
+               from .context import ExportContext
                from .export import DataExporter
+
+               ex_ctx = ExportContext(context, verbose=self.debug_mode)
                exporter = DataExporter()
-               exporter.export_to_file(context, self.filepath,
+               ex_ctx.export(exporter.export_to_file, self.filepath,
                        collection=self.collection,
                        shared_resources=self.shared_resources)
                return {'FINISHED'}
@@ -47,6 +51,11 @@ class ExportMspGLData(bpy.types.Operator):
                col.prop(self, "collection")
                col.prop(self, "shared_resources")
 
+               self.layout.separator()
+
+               col = self.layout.column()
+               col.prop(self, "debug_mode")
+
 class ExportMspGLAnimation(bpy.types.Operator, ExportHelper):
        bl_idname = "export.mspgl_animation"
        bl_label = "Export Msp GL animation"
@@ -57,6 +66,7 @@ class ExportMspGLAnimation(bpy.types.Operator, ExportHelper):
        export_all: bpy.props.BoolProperty(name="Export all animations", description="Export all animations present on the selected objects' NLA tracks")
        collection: bpy.props.BoolProperty(name="As a collection", description="Export the animations as a single collection file", default=True)
        looping_threshold: bpy.props.FloatProperty(name="Looping threshold", description="Tolerance for curve beginning and end values for looping", min=0.0, soft_max=1.0, precision=4, default=0.001)
+       debug_mode: bpy.props.BoolProperty(name="Debug mode", description="Enable debugging output to console")
 
        def check(self, context):
                ext_changed = self.set_extension(".mdc" if self.export_all and self.collection else ".anim")
@@ -64,9 +74,12 @@ class ExportMspGLAnimation(bpy.types.Operator, ExportHelper):
                return ext_changed or super_result
 
        def execute(self, context):
+               from .context import ExportContext
                from .export_animation import AnimationExporter
+
+               ex_ctx = ExportContext(context, verbose=self.debug_mode)
                exporter = AnimationExporter()
-               exporter.export_to_file(context, self.filepath,
+               ex_ctx.export(exporter.export_to_file, self.filepath,
                        export_all=self.export_all,
                        collection=self.collection,
                        looping_threshold=looping_threshold)
@@ -79,6 +92,11 @@ class ExportMspGLAnimation(bpy.types.Operator, ExportHelper):
                        col.prop(self, "collection")
                col.prop(self, "looping_threshold")
 
+               self.layout.separator()
+
+               col = self.layout.column()
+               col.prop(self, "debug_mode")
+
 class ExportMspGLScene(bpy.types.Operator, ExportHelper):
        bl_idname = "export_scene.mspgl_scene"
        bl_label = "Export Msp GL scene"
@@ -90,6 +108,7 @@ class ExportMspGLScene(bpy.types.Operator, ExportHelper):
        visible_only: bpy.props.BoolProperty(name="Visible only", description="Only export objects in visible collections", default=True)
        collection: bpy.props.BoolProperty(name="As a collection", description="Export the scene and all resources as a single collection file", default=False)
        skip_existing: bpy.props.BoolProperty(name="Skip existing files", description="Skip resources that already exist as files", default=True)
+       debug_mode: bpy.props.BoolProperty(name="Debug mode", description="Enable debugging output to console")
 
        def invoke(self, context, event):
                self.filepath = context.scene.name+".scene"
@@ -101,9 +120,12 @@ class ExportMspGLScene(bpy.types.Operator, ExportHelper):
                return ext_changed or super_result
 
        def execute(self, context):
+               from .context import ExportContext
                from .export_scene import SceneExporter
+
+               ex_ctx = ExportContext(context, verbose=self.debug_mode)
                exporter = SceneExporter()
-               exporter.export_to_file(context, self.filepath,
+               ex_ctx.export(exporter.export_to_file, self.filepath,
                        selected_only=self.selected_only,
                        visible_only=self.visible_only,
                        collection=self.collection,
@@ -118,12 +140,18 @@ class ExportMspGLScene(bpy.types.Operator, ExportHelper):
                if self.collection:
                        col.prop(self, "skip_existing")
 
+               self.layout.separator()
+
+               col = self.layout.column()
+               col.prop(self, "debug_mode")
+
 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')
+       debug_mode: bpy.props.BoolProperty(name="Debug mode", description="Enable debugging output to console")
 
        def invoke(self, context, event):
                blend_filepath = context.blend_data.filepath
@@ -133,11 +161,18 @@ class ExportMspGLProject(bpy.types.Operator):
                return {'RUNNING_MODAL'}
 
        def execute(self, context):
+               from .context import ExportContext
                from .export import ProjectExporter
+
+               ex_ctx = ExportContext(context, verbose=self.debug_mode)
                exporter = ProjectExporter()
-               exporter.export_to_directory(context, self.directory)
+               ex_ctx.export(exporter.export_to_directory, self.directory)
                return {'FINISHED'}
 
+       def draw(self, context):
+               col = self.layout.column()
+               col.prop(self, "debug_mode")
+
 class AddRenderMethod(bpy.types.Operator):
        bl_idname = "material.add_render_method"
        bl_label = "Add Render Method"
index e4f31067cd8a910e48b842e6b66d04cde1f0259f..70dbc8c9a7178d19470c5ba3d0ae5e1c2e142250 100644 (file)
@@ -92,7 +92,7 @@ def get_all_collections(collection):
                result += get_all_collections(c)
        return result
 
-def create_scene_from_current(context, *, selected_only=False, visible_only=True):
+def create_scene_from_current(ctx, *, selected_only=False, visible_only=True):
        obj_filters = []
 
        if selected_only:
@@ -100,7 +100,7 @@ def create_scene_from_current(context, *, selected_only=False, visible_only=True
 
        if visible_only:
                visible_names = set()
-               for c in get_all_collections(context.view_layer.layer_collection):
+               for c in get_all_collections(ctx.context.view_layer.layer_collection):
                        if not c.hide_viewport and not c.collection.hide_viewport:
                                visible_names.update(o.name for o in c.collection.objects)
                obj_filters.append(lambda o: o.name in visible_names)
@@ -111,7 +111,7 @@ def create_scene_from_current(context, *, selected_only=False, visible_only=True
        if obj_filters:
                obj_filter = lambda o: all(f(o) for f in obj_filters)
 
-       return Scene(context.scene, obj_filter)
+       return Scene(ctx.context.scene, obj_filter)
 
 def create_scene(scene, *, visible_only=True):
        obj_filter = None
index 4141e9f77600891eb7ef405ecf2cc6bb3d642944..8ca41bd8e845657edf33463dd643e259c80afc3d 100644 (file)
@@ -1,57 +1,5 @@
 import os
 
-class Progress:
-       def __init__(self, context):
-               self.task = ""
-               self.start = 0.0
-               self.delta = 1.0
-               self.last = 0.0
-               self.stack = []
-               if context:
-                       self.window_manager = context.window_manager
-                       self.window_manager.progress_begin(0.0, 1.0)
-               else:
-                       self.window_manager = None
-
-       def push_task(self, task, low, high):
-               self.stack.append((self.task, self.start, self.delta))
-               self.set_task(task, low, high)
-
-       def push_task_slice(self, task, index, count):
-               self.push_task(task, index/count, (index+1)/count)
-
-       def pop_task(self):
-               if not self.stack:
-                       return
-
-               self.set_progress(1.0)
-               self.task, self.start, self.delta = self.stack.pop()
-
-       def set_task(self, task, low, high):
-               if self.stack:
-                       outer = self.stack[-1]
-                       if outer[0]:
-                               task = "{}: {}".format(outer[0], task)
-                       low = outer[1]+low*outer[2]
-                       high = outer[1]+high*outer[2]
-
-               self.task = task
-               self.start = low
-               self.delta = high-low
-
-               self.set_progress(0.0)
-
-       def set_task_slice(self, task, index, count):
-               self.set_task(task, index/count, (index+1)/count)
-
-       def set_progress(self, value):
-               value = self.start+self.delta*value
-               if value>self.last+0.001:
-                       if self.window_manager:
-                               self.window_manager.progress_update(value)
-                       self.last = value
-
-
 def linear_to_srgb(l):
        if l<0.0031308:
                return 12.92*l