From 304f23bd007bcf4b8e8d6da12a97dbbd4b76012d Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sun, 1 Aug 2021 19:38:02 -0400 Subject: [PATCH] Don't touch my Col layer! In user testing, the "Bake All" operator overwriting the "Col" layer was blowing away too much manual shading, reducing the usefulness of the feature severely. This changes us to using the "autocolor" layer and making it a somewhat ephemeral coloring layer. Any "autocolor" layer generated by the exporter should be removed when the export finishes. Otherwise, it should persist. --- korman/exporter/convert.py | 11 ++++-- korman/exporter/etlight.py | 65 +++++++++++++++++++++++++-------- korman/operators/op_lightmap.py | 54 ++------------------------- korman/ui/modifiers/render.py | 7 +++- 4 files changed, 64 insertions(+), 73 deletions(-) diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index d2f3a9a..2f40500 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -14,10 +14,13 @@ # along with Korman. If not, see . import bpy -from ..korlib import ConsoleToggler + from pathlib import Path +from contextlib import ExitStack + +from ..korlib import ConsoleToggler + from PyHSPlasma import * -import time from . import animation from . import camera @@ -44,7 +47,7 @@ class Exporter: def run(self): log = logger.ExportVerboseLogger if self._op.verbose else logger.ExportProgressLogger - with ConsoleToggler(self._op.show_console), log(self._op.filepath) as self.report: + with ConsoleToggler(self._op.show_console), log(self._op.filepath) as self.report, ExitStack() as self.context_stack: # Step 0: Init export resmgr and stuff self.mgr = manager.ExportManager(self) self.mesh = mesh.MeshConverter(self) @@ -56,7 +59,7 @@ class Exporter: self.image = image.ImageCache(self) self.locman = locman.LocalizationConverter(self) self.decal = decal.DecalConverter(self) - self.oven = etlight.LightBaker(self.report) + self.oven = etlight.LightBaker(self.report, stack=self.context_stack) # Step 0.8: Init the progress mgr self.mesh.add_progress_presteps(self.report) diff --git a/korman/exporter/etlight.py b/korman/exporter/etlight.py index fc62bd1..f1ce2ad 100644 --- a/korman/exporter/etlight.py +++ b/korman/exporter/etlight.py @@ -15,6 +15,7 @@ import bpy +from contextlib import ExitStack import itertools from .explosions import * @@ -27,7 +28,7 @@ _NUM_RENDER_LAYERS = 20 class LightBaker(_MeshManager): """ExportTime Lighting""" - def __init__(self, report=None, verbose=False): + def __init__(self, report=None, stack=None, *, verbose=False): self._lightgroups = {} if report is None: self._report = ExportVerboseLogger() if verbose else ExportProgressLogger() @@ -38,6 +39,8 @@ class LightBaker(_MeshManager): self._report = report self._own_report = False super().__init__(self._report) + + self._context_stack = stack self.vcol_layer_name = "autocolor" self.lightmap_name = "{}_LIGHTMAPGEN.png" self.lightmap_uvtex_name = "LIGHTMAPGEN" @@ -240,6 +243,36 @@ class LightBaker(_MeshManager): bake, bake_passes = {}, bpy.context.scene.plasma_scene.bake_passes bake_vcol = bake.setdefault(("vcol",) + default_layers, []) + def lightmap_bake_required(obj) -> bool: + mod = obj.plasma_modifiers.lightmap + if mod.bake_lightmap: + if self.force: + return True + if mod.image is not None: + uv_texture_names = frozenset((i.name for i in obj.data.uv_textures)) + if self.lightmap_uvtex_name in uv_texture_names: + self._report.msg("'{}': Skipping due to valid lightmap override", obj.name, indent=1) + else: + self._report.msg("'{}': Have lightmap but UVs are missing???", obj.name, indent=1) + return False + return True + return False + + def vcol_bake_required(obj) -> bool: + if obj.plasma_modifiers.lightmap.bake_lightmap: + return False + vcol_layer_names = frozenset((vcol_layer.name.lower() for vcol_layer in obj.data.vertex_colors)) + manual_layer_names = _VERTEX_COLOR_LAYERS & vcol_layer_names + if manual_layer_names: + self._report.msg("'{}': Skipping due to valid manual vertex color layer(s): '{}'", obj.name, manual_layer_names.pop(), indent=1) + return False + if self.force: + return True + if self.vcol_layer_name.lower() in vcol_layer_names: + self._report.msg("'{}': Skipping due to valid matching vertex color layer(s): '{}'", obj.name, self.vcol_layer_name, indent=1) + return False + return True + for i in filter(lambda x: x.type == "MESH" and bool(x.data.materials), objs): mods = i.plasma_modifiers lightmap_mod = mods.lightmap @@ -260,26 +293,17 @@ class LightBaker(_MeshManager): if not lm_active_layers & obj_active_layers: raise ExportError("Bake Lighting '{}': At least one layer the object is on must be selected".format(i.name)) - # OK, now that the sanity checking is done, we could opt-out if an image is already - # set and we're not being forced... - if not self.force: - want_image = lightmap_mod.bake_lightmap - if want_image and lightmap_mod.image is not None and self.lightmap_uvtex_name in i.data.uv_textures: - self._report.msg("'{}': Skipping due to valid lightmap override", i.name, indent=1) - continue - elif not want_image and any((vcol_layer.name.lower() in _VERTEX_COLOR_LAYERS for vcol_layer in i.data.vertex_colors)): - self._report.msg("'{}': Skipping due to valid vertex color layer", i.name, indent=1) - continue + if lightmap_bake_required(i) is False and vcol_bake_required(i) is False: + continue method = "lightmap" if lightmap_mod.bake_lightmap else "vcol" key = (method,) + lm_layers bake_pass = bake.setdefault(key, []) bake_pass.append(i) self._report.msg("'{}': Bake to {}", i.name, method, indent=1) - elif mods.lighting.preshade: - if self.force or not any((vcol_layer.name.lower() in _VERTEX_COLOR_LAYERS for vcol_layer in i.data.vertex_colors)): - self._report.msg("'{}': Bake to vcol (crappy)", i.name, indent=1) - bake_vcol.append(i) + elif mods.lighting.preshade and vcol_bake_required(i): + self._report.msg("'{}': Bake to vcol (crappy)", i.name, indent=1) + bake_vcol.append(i) return bake def _pack_lightmaps(self, objs): @@ -404,7 +428,8 @@ class LightBaker(_MeshManager): vcol_layer_name = self.vcol_layer_name autocolor = vcols.get(vcol_layer_name) - if autocolor is None: + needs_vcol_layer = autocolor is None + if needs_vcol_layer: autocolor = vcols.new(vcol_layer_name) toggle.track(vcols, "active", autocolor) @@ -415,6 +440,14 @@ class LightBaker(_MeshManager): toggle.track(vcol_layer, "active", autocol) mesh.update() + # Vertex colors are sort of ephemeral, so if we have an exit stack, we want to + # terminate this layer when the exporter is done. But, this is not an unconditional + # nukage. If we're in the lightmap operators, we clearly want this to persist for + # future exports as an optimization. We won't reach this point if there is already an + # autocolor layer (gulp). + if self._context_stack is not None and needs_vcol_layer: + self._context_stack.enter_context(TemporaryObject(vcol_layer, vcols.remove)) + # Indicate we should bake return True diff --git a/korman/operators/op_lightmap.py b/korman/operators/op_lightmap.py index f95f7ad..b757cf8 100644 --- a/korman/operators/op_lightmap.py +++ b/korman/operators/op_lightmap.py @@ -24,8 +24,6 @@ from ..helpers import UiHelper from ..korlib import ConsoleToggler class _LightingOperator: - _FINAL_VERTEX_COLOR_LAYER = "Col" - @contextmanager def _oven(self, context): if context.scene.world is not None: @@ -56,17 +54,9 @@ class LightmapAutobakePreviewOperator(_LightingOperator, bpy.types.Operator): def __init__(self): super().__init__() - def draw(self, context): - layout = self.layout - - layout.label("This will overwrite the following vertex color layer:") - layout.label(self._FINAL_VERTEX_COLOR_LAYER, icon="GROUP_VCOL") - def execute(self, context): with self._oven(context) as bake: - if self.final: - bake.vcol_layer_name = self._FINAL_VERTEX_COLOR_LAYER - else: + if not self.final: bake.lightmap_name = "{}_LIGHTMAPGEN_PREVIEW.png" bake.lightmap_uvtex_name = "LIGHTMAPGEN_PREVIEW" bake.force = self.final @@ -91,14 +81,6 @@ class LightmapAutobakePreviewOperator(_LightingOperator, bpy.types.Operator): return {"FINISHED"} - def invoke(self, context, event): - # If this is a vertex color bake, we need to be sure that the user really - # wants to blow away any color layer they have. - if self.final and context.object.plasma_modifiers.lightmap.bake_type == "vcol": - if any((i.name == self._FINAL_VERTEX_COLOR_LAYER for i in context.object.data.vertex_colors)): - return context.window_manager.invoke_props_dialog(self) - return self.execute(context) - class LightmapBakeMultiOperator(_LightingOperator, bpy.types.Operator): bl_idname = "object.plasma_lightmap_bake" @@ -118,7 +100,6 @@ class LightmapBakeMultiOperator(_LightingOperator, bpy.types.Operator): with self._oven(context) as bake: bake.force = True - bake.vcol_layer_name = self._FINAL_VERTEX_COLOR_LAYER if not bake.bake_static_lighting(filtered_objects): self.report({"WARNING"}, "Nothing was baked.") return {"FINISHED"} @@ -149,45 +130,19 @@ class LightmapClearMultiOperator(_LightingOperator, bpy.types.Operator): def _iter_vcols(self, objects): yield from filter(lambda x: x.type == "MESH" and not x.plasma_modifiers.lightmap.bake_lightmap, objects) - def _iter_final_vcols(self, objects): - yield from filter(lambda x: x.data.vertex_colors.get(self._FINAL_VERTEX_COLOR_LAYER), self._iter_vcols(objects)) - - def draw(self, context): - layout = self.layout - - layout.label("This will remove the vertex color layer '{}' on:".format(self._FINAL_VERTEX_COLOR_LAYER)) - col = layout.column_flow() - - _MAX_OBJECTS = 50 - vcol_iter = enumerate(self._iter_final_vcols(self._get_objects(context))) - for _, bo in itertools.takewhile(lambda x: x[0] < _MAX_OBJECTS, vcol_iter): - col.label(bo.name, icon="OBJECT_DATA") - remainder = sum((1 for _, _ in vcol_iter)) - if remainder: - layout.label("... and {} other objects.".format(remainder)) - - def _get_objects(self, context): - return context.selected_objects if self.clear_selection else context.scene.objects - def execute(self, context): - all_objects = self._get_objects(context) + all_objects = context.selected_objects if self.clear_selection else context.scene.objects for i in self._iter_lightmaps(all_objects): i.plasma_modifiers.lightmap.image = None for i in self._iter_vcols(all_objects): vcols = i.data.vertex_colors - col_layer = vcols.get(self._FINAL_VERTEX_COLOR_LAYER) + col_layer = vcols.get("autocolor") if col_layer is not None: vcols.remove(col_layer) return {"FINISHED"} - def invoke(self, context, event): - all_objects = self._get_objects(context) - if any(self._iter_final_vcols(all_objects)): - return context.window_manager.invoke_props_dialog(self) - return self.execute(context) - @bpy.app.handlers.persistent def _toss_garbage(scene): @@ -204,9 +159,6 @@ def _toss_garbage(scene): uvtex = i.uv_textures.get("LIGHTMAPGEN_PREVIEW") if uvtex is not None: i.uv_textures.remove(uvtex) - vcol_layer = i.vertex_colors.get("autocolor") - if vcol_layer is not None: - i.vertex_colors.remove(vcol_layer) # collects light baking garbage bpy.app.handlers.save_pre.append(_toss_garbage) diff --git a/korman/ui/modifiers/render.py b/korman/ui/modifiers/render.py index d051e5f..1205105 100644 --- a/korman/ui/modifiers/render.py +++ b/korman/ui/modifiers/render.py @@ -205,8 +205,11 @@ def lightmap(modifier, layout, context): layout.label("Transparent objects cannot be lightmapped.", icon="ERROR") else: row = layout.row(align=True) - row.operator("object.plasma_lightmap_preview", "Preview", icon="RENDER_STILL").final = False - row.operator("object.plasma_lightmap_preview", "Bake for Export", icon="RENDER_STILL").final = True + if modifier.bake_lightmap: + row.operator("object.plasma_lightmap_preview", "Preview", icon="RENDER_STILL").final = False + row.operator("object.plasma_lightmap_preview", "Bake for Export", icon="RENDER_STILL").final = True + else: + row.operator("object.plasma_lightmap_preview", "Bake", icon="RENDER_STILL").final = True # Kind of clever stuff to show the user a preview... # We can't show images, so we make a hidden ImageTexture called LIGHTMAPGEN_PREVIEW. We check