From: Mikko Rasa Date: Sat, 5 Mar 2022 08:50:04 +0000 (+0200) Subject: Redesign progress and error reporting in the Blender exporter X-Git-Url: http://git.tdb.fi/?a=commitdiff_plain;h=308dc6b8f5ee1aa3bb8f205e2ed6464749eebbe5;p=libs%2Fgl.git Redesign progress and error reporting in the Blender exporter 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. --- diff --git a/blender/io_mspgl/__init__.py b/blender/io_mspgl/__init__.py index dec550da..4b6e2ff4 100644 --- a/blender/io_mspgl/__init__.py +++ b/blender/io_mspgl/__init__.py @@ -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 index 00000000..eb215cb0 --- /dev/null +++ b/blender/io_mspgl/context.py @@ -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 diff --git a/blender/io_mspgl/export.py b/blender/io_mspgl/export.py index 6db2c81c..190069c4 100644 --- a/blender/io_mspgl/export.py +++ b/blender/io_mspgl/export.py @@ -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)) diff --git a/blender/io_mspgl/export_material.py b/blender/io_mspgl/export_material.py index 826ab926..a109ed24 100644 --- a/blender/io_mspgl/export_material.py +++ b/blender/io_mspgl/export_material.py @@ -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: diff --git a/blender/io_mspgl/export_mesh.py b/blender/io_mspgl/export_mesh.py index f54a05fd..b85fd2f8 100644 --- a/blender/io_mspgl/export_mesh.py +++ b/blender/io_mspgl/export_mesh.py @@ -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 diff --git a/blender/io_mspgl/export_object.py b/blender/io_mspgl/export_object.py index b39307c8..d78f5fcd 100644 --- a/blender/io_mspgl/export_object.py +++ b/blender/io_mspgl/export_object.py @@ -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): diff --git a/blender/io_mspgl/export_scene.py b/blender/io_mspgl/export_scene.py index a069af2a..78ce2cbd 100644 --- a/blender/io_mspgl/export_scene.py +++ b/blender/io_mspgl/export_scene.py @@ -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 diff --git a/blender/io_mspgl/mesh.py b/blender/io_mspgl/mesh.py index 8009191f..fb568ec4 100644 --- a/blender/io_mspgl/mesh.py +++ b/blender/io_mspgl/mesh.py @@ -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 diff --git a/blender/io_mspgl/operators.py b/blender/io_mspgl/operators.py index 920f0251..37413584 100644 --- a/blender/io_mspgl/operators.py +++ b/blender/io_mspgl/operators.py @@ -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" diff --git a/blender/io_mspgl/scene.py b/blender/io_mspgl/scene.py index e4f31067..70dbc8c9 100644 --- a/blender/io_mspgl/scene.py +++ b/blender/io_mspgl/scene.py @@ -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 diff --git a/blender/io_mspgl/util.py b/blender/io_mspgl/util.py index 4141e9f7..8ca41bd8 100644 --- a/blender/io_mspgl/util.py +++ b/blender/io_mspgl/util.py @@ -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