From b98aec88404c190d33e15a6841802573eba9ae0a Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 4 Oct 2017 21:33:07 -0400 Subject: [PATCH 1/3] Fix #63 --- korman/exporter/convert.py | 63 +++++++++++++------------ korman/exporter/mesh.py | 95 ++++++++++++++++++++++++++------------ 2 files changed, 98 insertions(+), 60 deletions(-) diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index 842f0a7..5a807f9 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -53,7 +53,8 @@ class Exporter: self.animation = animation.AnimationConverter(self) self.sumfile = sumfile.SumFile() - # Step 0.9: Init the progress mgr + # Step 0.8: Init the progress mgr + self.report.progress_add_step("Applying Blender Mods") self.report.progress_add_step("Collecting Objects") self.report.progress_add_step("Harvesting Actors") if self._op.bake_lighting: @@ -65,44 +66,46 @@ class Exporter: self.report.progress_add_step("Composing Geometry") self.report.progress_start("EXPORTING AGE") - # Step 1: Create the age info and the pages - self._export_age_info() + # Step 0.9: Apply modifiers to all meshes temporarily. + with self.mesh: + # Step 1: Create the age info and the pages + self._export_age_info() - # Step 2: Gather a list of objects that we need to export, given what the user has told - # us to export (both in the Age and Object Properties)... fun - self._collect_objects() + # Step 2: Gather a list of objects that we need to export, given what the user has told + # us to export (both in the Age and Object Properties)... fun + self._collect_objects() - # Step 2.5: Run through all the objects we collected in Step 2 and see if any relationships - # that the artist made requires something to have a CoordinateInterface - self._harvest_actors() + # Step 2.5: Run through all the objects we collected in Step 2 and see if any relationships + # that the artist made requires something to have a CoordinateInterface + self._harvest_actors() - # Step 2.9: It is assumed that static lighting is available for the mesh exporter. - # Indeed, in PyPRP it was a manual step. So... BAKE NAO! - if self._op.bake_lighting: - self._bake_static_lighting() + # Step 2.9: It is assumed that static lighting is available for the mesh exporter. + # Indeed, in PyPRP it was a manual step. So... BAKE NAO! + if self._op.bake_lighting: + self._bake_static_lighting() - # Step 3: Export all the things! - self._export_scene_objects() + # Step 3: Export all the things! + self._export_scene_objects() - # Step 3.1: Ensure referenced logic node trees are exported - self._export_referenced_node_trees() + # Step 3.1: Ensure referenced logic node trees are exported + self._export_referenced_node_trees() - # Step 3.2: Now that all Plasma Objects (save Mipmaps) are exported, we do any post - # processing that needs to inspect those objects - self._post_process_scene_objects() + # Step 3.2: Now that all Plasma Objects (save Mipmaps) are exported, we do any post + # processing that needs to inspect those objects + self._post_process_scene_objects() - # Step 4: Finalize... - self.mesh.material.finalize() - self.mesh.finalize() + # Step 4: Finalize... + self.mesh.material.finalize() + self.mesh.finalize() - # Step 5: FINALLY. Let's write the PRPs and crap. - self.mgr.save_age(Path(self._op.filepath)) + # Step 5: FINALLY. Let's write the PRPs and crap. + self.mgr.save_age(Path(self._op.filepath)) - # Step 5.1: Save out the export report. - # If the export fails and this doesn't save, we have bigger problems than - # these little warnings and notices. - self.report.progress_end() - self.report.save() + # Step 5.1: Save out the export report. + # If the export fails and this doesn't save, we have bigger problems than + # these little warnings and notices. + self.report.progress_end() + self.report.save() def _bake_static_lighting(self): oven = etlight.LightBaker(self.report) diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 711243c..f8526fc 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -111,9 +111,42 @@ class _GeoData: self.vertices = [] -class MeshConverter: + +class _MeshManager: def __init__(self, exporter): self._exporter = weakref.ref(exporter) + self._mesh_overrides = {} + + def __enter__(self): + report = self._exporter().report + report.progress_advance() + report.progress_range = len(bpy.data.objects) + + # Some modifiers like "Array" will procedurally generate new geometry that will impact + # lightmap generation. The Blender Internal renderer does not seem to be smart enough to + # take this into account. Thus, we temporarily apply modifiers to ALL meshes (even ones that + # are not exported) such that we can generate proper lighting. + scene = bpy.context.scene + for i in bpy.data.objects: + if i.type == "MESH" and i.is_modified(scene, "RENDER"): + # Remember, storing actual pointers to the Blender objects can cause bad things to + # happen because Blender's memory management SUCKS! + self._mesh_overrides[i.name] = i.data.name + i.data = i.to_mesh(scene, True, "RENDER", calc_tessface=False) + report.progress_increment() + return self + + def __exit__(self, type, value, traceback): + data_bos, data_meshes = bpy.data.objects, bpy.data.meshes + for obj_name, mesh_name in self._mesh_overrides.items(): + bo = data_bos.get(obj_name) + trash_mesh, bo.data = bo.data, data_meshes.get(mesh_name) + data_meshes.remove(trash_mesh) + + +class MeshConverter(_MeshManager): + def __init__(self, exporter): + super().__init__(exporter) self.material = material.MaterialConverter(exporter) self._dspans = {} @@ -403,8 +436,11 @@ class MeshConverter: diface.addDrawable(dspan_key, idx) def _export_mesh(self, bo): - # Step 0.7: Update the mesh such that we can do things and schtuff... - mesh = bo.to_mesh(bpy.context.scene, True, "RENDER", calc_tessface=True) + # Previously, this called bo.to_mesh to apply modifiers. However, due to limitations in the + # lightmap generation, this is now done for all modified mesh objects before any Plasma data + # is exported. + mesh = bo.data + mesh.calc_tessface() # Step 0.8: Figure out which materials are attached to this object. Because Blender is backwards, # we can actually have materials that are None. gotdawgit!!! @@ -412,33 +448,32 @@ class MeshConverter: if not materials: return None - with helpers.TemporaryObject(mesh, bpy.data.meshes.remove): - # Step 1: Export all of the doggone materials. - geospans = self._export_material_spans(bo, mesh, materials) - - # Step 2: Export Blender mesh data to Plasma GeometrySpans - self._export_geometry(bo, mesh, materials, geospans) - - # Step 3: Add plGeometrySpans to the appropriate DSpan and create indices - _diindices = {} - for geospan, pass_index in geospans: - dspan = self._find_create_dspan(bo, geospan.material.object, pass_index) - self._report.msg("Exported hsGMaterial '{}' geometry into '{}'", - geospan.material.name, dspan.key.name, indent=1) - idx = dspan.addSourceSpan(geospan) - if dspan not in _diindices: - _diindices[dspan] = [idx,] - else: - _diindices[dspan].append(idx) - - # Step 3.1: Harvest Span indices and create the DIIndices - drawables = [] - for dspan, indices in _diindices.items(): - dii = plDISpanIndex() - dii.indices = indices - idx = dspan.addDIIndex(dii) - drawables.append((dspan.key, idx)) - return drawables + # Step 1: Export all of the doggone materials. + geospans = self._export_material_spans(bo, mesh, materials) + + # Step 2: Export Blender mesh data to Plasma GeometrySpans + self._export_geometry(bo, mesh, materials, geospans) + + # Step 3: Add plGeometrySpans to the appropriate DSpan and create indices + _diindices = {} + for geospan, pass_index in geospans: + dspan = self._find_create_dspan(bo, geospan.material.object, pass_index) + self._report.msg("Exported hsGMaterial '{}' geometry into '{}'", + geospan.material.name, dspan.key.name, indent=1) + idx = dspan.addSourceSpan(geospan) + if dspan not in _diindices: + _diindices[dspan] = [idx,] + else: + _diindices[dspan].append(idx) + + # Step 3.1: Harvest Span indices and create the DIIndices + drawables = [] + for dspan, indices in _diindices.items(): + dii = plDISpanIndex() + dii.indices = indices + idx = dspan.addDIIndex(dii) + drawables.append((dspan.key, idx)) + return drawables def _export_material_spans(self, bo, mesh, materials): """Exports all Materials and creates plGeometrySpans""" From f6182b05b540d352b8e2c7b0dcdb0873390eaf2a Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sat, 7 Oct 2017 23:27:00 -0400 Subject: [PATCH 2/3] Update LM preview to apply modifiers as well --- korman/exporter/convert.py | 2 +- korman/exporter/etlight.py | 11 +++++++---- korman/exporter/mesh.py | 22 +++++++++++++++------- korman/operators/op_lightmap.py | 8 ++++---- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index 5a807f9..edf378e 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -54,7 +54,7 @@ class Exporter: self.sumfile = sumfile.SumFile() # Step 0.8: Init the progress mgr - self.report.progress_add_step("Applying Blender Mods") + self.mesh.add_progress_presteps(self.report) self.report.progress_add_step("Collecting Objects") self.report.progress_add_step("Harvesting Actors") if self._op.bake_lighting: diff --git a/korman/exporter/etlight.py b/korman/exporter/etlight.py index d55a8ae..cc20749 100644 --- a/korman/exporter/etlight.py +++ b/korman/exporter/etlight.py @@ -17,24 +17,25 @@ import bpy from bpy.app.handlers import persistent from .logger import ExportProgressLogger -from .mesh import _VERTEX_COLOR_LAYERS +from .mesh import _MeshManager, _VERTEX_COLOR_LAYERS from ..helpers import * _NUM_RENDER_LAYERS = 20 -class LightBaker: +class LightBaker(_MeshManager): """ExportTime Lighting""" def __init__(self, report=None): self._lightgroups = {} if report is None: self._report = ExportProgressLogger() - self.add_progress_steps(self._report) + self.add_progress_steps(self._report, True) self._report.progress_start("PREVIEWING LIGHTING") self._own_report = True else: self._report = report self._own_report = False + super().__init__(self._report) self._uvtexs = {} def __del__(self): @@ -42,7 +43,9 @@ class LightBaker: self._report.progress_end() @staticmethod - def add_progress_steps(report): + def add_progress_steps(report, add_base=False): + if add_base: + _MeshManager.add_progress_presteps(report) report.progress_add_step("Searching for Bahro") report.progress_add_step("Baking Static Lighting") diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index f8526fc..3226f69 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -18,6 +18,7 @@ from PyHSPlasma import * from math import fabs import weakref +from ..exporter.logger import ExportProgressLogger from . import explosions from .. import helpers from . import material @@ -113,14 +114,18 @@ class _GeoData: class _MeshManager: - def __init__(self, exporter): - self._exporter = weakref.ref(exporter) + def __init__(self, report=None): + if report is not None: + self._report = report self._mesh_overrides = {} + @staticmethod + def add_progress_presteps(report): + report.progress_add_step("Applying Blender Mods") + def __enter__(self): - report = self._exporter().report - report.progress_advance() - report.progress_range = len(bpy.data.objects) + self._report.progress_advance() + self._report.progress_range = len(bpy.data.objects) # Some modifiers like "Array" will procedurally generate new geometry that will impact # lightmap generation. The Blender Internal renderer does not seem to be smart enough to @@ -133,7 +138,7 @@ class _MeshManager: # happen because Blender's memory management SUCKS! self._mesh_overrides[i.name] = i.data.name i.data = i.to_mesh(scene, True, "RENDER", calc_tessface=False) - report.progress_increment() + self._report.progress_increment() return self def __exit__(self, type, value, traceback): @@ -146,12 +151,15 @@ class _MeshManager: class MeshConverter(_MeshManager): def __init__(self, exporter): - super().__init__(exporter) + self._exporter = weakref.ref(exporter) self.material = material.MaterialConverter(exporter) self._dspans = {} self._mesh_geospans = {} + # _report is a property on this subclass + super().__init__() + def _calc_num_uvchans(self, bo, mesh): max_user_texs = plGeometrySpan.kUVCountMask num_user_texs = len(mesh.tessface_uv_textures) diff --git a/korman/operators/op_lightmap.py b/korman/operators/op_lightmap.py index e67e241..4051f9f 100644 --- a/korman/operators/op_lightmap.py +++ b/korman/operators/op_lightmap.py @@ -39,10 +39,10 @@ class LightmapAutobakePreviewOperator(_LightingOperator, bpy.types.Operator): with GoodNeighbor() as toggle: toggle.track(context.scene, "layers", tuple(context.scene.layers)) - bake = LightBaker() - if not bake.bake_static_lighting([context.active_object,]): - self.report({"INFO"}, "No valid lights found to bake.") - return {"FINISHED"} + with LightBaker() as bake: + if not bake.bake_static_lighting([context.active_object,]): + self.report({"INFO"}, "No valid lights found to bake.") + return {"FINISHED"} tex = bpy.data.textures.get("LIGHTMAPGEN_PREVIEW") if tex is None: From 170eb694d26a89546aa5c0e2b6be1ef8049aff37 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 9 Oct 2017 16:34:05 -0400 Subject: [PATCH 3/3] Remove modifiers from objects for baking If certain modifiers are applied during the bake process, the resulting lightmap is fullbright. To solve this, we cache the modifier data, clear them away, then bake. When the export is completed, we then restore the modifiers. --- korman/exporter/mesh.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 3226f69..7161220 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -117,12 +117,21 @@ class _MeshManager: def __init__(self, report=None): if report is not None: self._report = report - self._mesh_overrides = {} + self._overrides = {} @staticmethod def add_progress_presteps(report): report.progress_add_step("Applying Blender Mods") + def _build_prop_dict(self, bstruct): + props = {} + for i in bstruct.bl_rna.properties: + ident = i.identifier + if ident == "rna_type": + continue + props[ident] = getattr(bstruct, ident) if getattr(i, "array_length", 0) == 0 else tuple(getattr(bstruct, ident)) + return props + def __enter__(self): self._report.progress_advance() self._report.progress_range = len(bpy.data.objects) @@ -136,18 +145,36 @@ class _MeshManager: if i.type == "MESH" and i.is_modified(scene, "RENDER"): # Remember, storing actual pointers to the Blender objects can cause bad things to # happen because Blender's memory management SUCKS! - self._mesh_overrides[i.name] = i.data.name + self._overrides[i.name] = { "mesh": i.data.name, "modifiers": [] } i.data = i.to_mesh(scene, True, "RENDER", calc_tessface=False) + + # If the modifiers are left on the object, the lightmap bake can break under some + # situations. Therefore, we now cache the modifiers and clear them away... + if i.plasma_object.enabled: + cache_mods = self._overrides[i.name]["modifiers"] + for mod in i.modifiers: + cache_mods.append(self._build_prop_dict(mod)) + i.modifiers.clear() self._report.progress_increment() return self def __exit__(self, type, value, traceback): data_bos, data_meshes = bpy.data.objects, bpy.data.meshes - for obj_name, mesh_name in self._mesh_overrides.items(): + for obj_name, override in self._overrides.items(): bo = data_bos.get(obj_name) - trash_mesh, bo.data = bo.data, data_meshes.get(mesh_name) + + # Reapply the old mesh + trash_mesh, bo.data = bo.data, data_meshes.get(override["mesh"]) data_meshes.remove(trash_mesh) + # If modifiers were removed, reapply them now. + for cached_mod in override["modifiers"]: + mod = bo.modifiers.new(cached_mod["name"], cached_mod["type"]) + for key, value in cached_mod.items(): + if key in {"name", "type"}: + continue + setattr(mod, key, value) + class MeshConverter(_MeshManager): def __init__(self, exporter):