Browse Source

Merge pull request #269 from Hoikas/lighting_no_uvtouching

Don't touch my UV maps!
pull/273/head
Adam Johnson 3 years ago committed by GitHub
parent
commit
4adbeb7cc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      korman/exporter/convert.py
  2. 106
      korman/exporter/etlight.py
  3. 8
      korman/exporter/mesh.py
  4. 2
      korman/operators/op_lightmap.py
  5. 2
      korman/properties/modifiers/render.py
  6. 2
      korman/ui/modifiers/render.py

2
korman/exporter/convert.py

@ -58,7 +58,7 @@ class Exporter:
self.image = image.ImageCache(self) self.image = image.ImageCache(self)
self.locman = locman.LocalizationConverter(self) self.locman = locman.LocalizationConverter(self)
self.decal = decal.DecalConverter(self) self.decal = decal.DecalConverter(self)
self.oven = etlight.LightBaker(self.report) self.oven = etlight.LightBaker(mesh=self.mesh, report=self.report)
# Step 0.8: Init the progress mgr # Step 0.8: Init the progress mgr
self.mesh.add_progress_presteps(self.report) self.mesh.add_progress_presteps(self.report)

106
korman/exporter/etlight.py

@ -15,7 +15,7 @@
import bpy import bpy
from contextlib import ExitStack from contextlib import contextmanager
import itertools import itertools
from .explosions import * from .explosions import *
@ -25,10 +25,10 @@ from ..helpers import *
_NUM_RENDER_LAYERS = 20 _NUM_RENDER_LAYERS = 20
class LightBaker(_MeshManager): class LightBaker:
"""ExportTime Lighting""" """ExportTime Lighting"""
def __init__(self, report=None, *, verbose=False): def __init__(self, *, mesh=None, report=None, verbose=False):
self._lightgroups = {} self._lightgroups = {}
if report is None: if report is None:
self._report = ExportVerboseLogger() if verbose else ExportProgressLogger() self._report = ExportVerboseLogger() if verbose else ExportProgressLogger()
@ -38,7 +38,11 @@ class LightBaker(_MeshManager):
else: else:
self._report = report self._report = report
self._own_report = False self._own_report = False
super().__init__(self._report)
# This used to be the base class, but due to the need to access the export state
# which may be stored in the exporter's mesh manager, we've changed from is-a to has-a
# semantics. Sorry for this confusion!
self._mesh = _MeshManager(self._report) if mesh is None else mesh
self.vcol_layer_name = "autocolor" self.vcol_layer_name = "autocolor"
self.lightmap_name = "{}_LIGHTMAPGEN.png" self.lightmap_name = "{}_LIGHTMAPGEN.png"
@ -52,6 +56,13 @@ class LightBaker(_MeshManager):
if self._own_report: if self._own_report:
self._report.progress_end() self._report.progress_end()
def __enter__(self):
self._mesh.__enter__()
return self
def __exit__(self, *exc_info):
self._mesh.__exit__(*exc_info)
@staticmethod @staticmethod
def add_progress_steps(report, add_base=False): def add_progress_steps(report, add_base=False):
if add_base: if add_base:
@ -99,11 +110,11 @@ class LightBaker(_MeshManager):
"""Bakes all static lighting for Plasma geometry""" """Bakes all static lighting for Plasma geometry"""
self._report.msg("\nBaking Static Lighting...") self._report.msg("\nBaking Static Lighting...")
bake = self._harvest_bakable_objects(objs)
with GoodNeighbor() as toggle: with GoodNeighbor() as toggle:
try: try:
# reduce the amount of indentation # reduce the amount of indentation
bake = self._harvest_bakable_objects(objs, toggle)
result = self._bake_static_lighting(bake, toggle) result = self._bake_static_lighting(bake, toggle)
finally: finally:
# this stuff has been observed to be problematic with GoodNeighbor # this stuff has been observed to be problematic with GoodNeighbor
@ -221,21 +232,13 @@ class LightBaker(_MeshManager):
def get_lightmap_name(self, bo): def get_lightmap_name(self, bo):
return self.lightmap_name.format(bo.name) return self.lightmap_name.format(bo.name)
def _get_lightmap_uvtex(self, mesh, modifier):
if modifier.uv_map:
return mesh.uv_textures[modifier.uv_map]
for i in mesh.uv_textures:
if i.name not in {"LIGHTMAPGEN", self.lightmap_uvtex_name}:
return i
return None
def _has_valid_material(self, bo): def _has_valid_material(self, bo):
for material in bo.data.materials: for material in bo.data.materials:
if material is not None: if material is not None:
return True return True
return False return False
def _harvest_bakable_objects(self, objs): def _harvest_bakable_objects(self, objs, toggle):
# The goal here is to minimize the calls to bake_image, so we are going to collect everything # The goal here is to minimize the calls to bake_image, so we are going to collect everything
# that needs to be baked and sort it out by configuration. # that needs to be baked and sort it out by configuration.
default_layers = tuple((True,) * _NUM_RENDER_LAYERS) default_layers = tuple((True,) * _NUM_RENDER_LAYERS)
@ -252,7 +255,8 @@ class LightBaker(_MeshManager):
if self.lightmap_uvtex_name in uv_texture_names: if self.lightmap_uvtex_name in uv_texture_names:
self._report.msg("'{}': Skipping due to valid lightmap override", obj.name, indent=1) self._report.msg("'{}': Skipping due to valid lightmap override", obj.name, indent=1)
else: else:
self._report.msg("'{}': Have lightmap but UVs are missing???", obj.name, indent=1) self._report.warn("'{}': Have lightmap, but regenerating UVs", obj.name, indent=1)
self._prep_for_lightmap_uvs(obj, mod.image, toggle)
return False return False
return True return True
return False return False
@ -357,6 +361,24 @@ class LightBaker(_MeshManager):
im = data_images.new(im_name, width=size, height=size) im = data_images.new(im_name, width=size, height=size)
self._lightmap_images[bo.name] = im self._lightmap_images[bo.name] = im
self._prep_for_lightmap_uvs(bo, im, toggle)
# Now, set the new LIGHTMAPGEN uv layer as what we want to render to...
# NOTE that this will need to be reset by us to what the user had previously
# Not using toggle.track due to observed oddities
for i in uv_textures:
value = i.name == self.lightmap_uvtex_name
i.active = value
i.active_render = value
# Indicate we should bake
return True
def _prep_for_lightmap_uvs(self, bo, image, toggle):
mesh = bo.data
modifier = bo.plasma_modifiers.lightmap
uv_textures = mesh.uv_textures
# If there is a cached LIGHTMAPGEN uvtexture, nuke it # If there is a cached LIGHTMAPGEN uvtexture, nuke it
uvtex = uv_textures.get(self.lightmap_uvtex_name, None) uvtex = uv_textures.get(self.lightmap_uvtex_name, None)
if uvtex is not None: if uvtex is not None:
@ -366,6 +388,7 @@ class LightBaker(_MeshManager):
toggle.track(bo, "hide", False) toggle.track(bo, "hide", False)
# Because the way Blender tracks active UV layers is massively stupid... # Because the way Blender tracks active UV layers is massively stupid...
if uv_textures.active is not None:
self._uvtexs[mesh.name] = uv_textures.active.name self._uvtexs[mesh.name] = uv_textures.active.name
# We must make this the active object before touching any operators # We must make this the active object before touching any operators
@ -375,7 +398,9 @@ class LightBaker(_MeshManager):
# this tended to create sharp edges. There was already a discussion about this on the # this tended to create sharp edges. There was already a discussion about this on the
# Guild of Writers forum, so I'm implementing a code version of dendwaler's process, # Guild of Writers forum, so I'm implementing a code version of dendwaler's process,
# as detailed here: https://forum.guildofwriters.org/viewtopic.php?p=62572#p62572 # as detailed here: https://forum.guildofwriters.org/viewtopic.php?p=62572#p62572
uv_base = self._get_lightmap_uvtex(mesh, modifier) # This has been amended with Sirius's observations in GH-265 about forced uv map
# packing. Namely, don't do it unless modifiers make us.
uv_base = uv_textures.get(modifier.uv_map) if modifier.uv_map else None
if uv_base is not None: if uv_base is not None:
uv_textures.active = uv_base uv_textures.active = uv_base
@ -384,36 +409,29 @@ class LightBaker(_MeshManager):
uv_textures.active = uvtex uv_textures.active = uvtex
# if the artist hid any UVs, they will not be baked to... fix this now # if the artist hid any UVs, they will not be baked to... fix this now
bpy.ops.object.mode_set(mode="EDIT") with self._set_mode("EDIT"):
bpy.ops.uv.reveal() bpy.ops.uv.reveal()
bpy.ops.object.mode_set(mode="OBJECT") self._associate_image_with_uvtex(uv_textures.active, image)
self._associate_image_with_uvtex(uv_textures.active, im)
bpy.ops.object.mode_set(mode="EDIT") # Meshes with modifiers need to have islands packed to prevent generated vertices
# from sharing UVs. Sigh.
# prep the uvtex for lightmapping if self._mesh.is_collapsed(bo):
# Danger: uv_base.name -> UnicodeDecodeError (wtf? another blender bug?)
self._report.warn("'{}': packing islands in UV Texture '{}' due to modifier collapse",
bo.name, modifier.uv_map, indent=2)
with self._set_mode("EDIT"):
bpy.ops.mesh.select_all(action="SELECT") bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.uv.average_islands_scale() bpy.ops.uv.select_all(action="SELECT")
bpy.ops.uv.pack_islands() bpy.ops.uv.pack_islands(margin=0.01)
else: else:
# same thread, see Sirius's suggestion RE smart unwrap. this seems to yield good # same thread, see Sirius's suggestion RE smart unwrap. this seems to yield good
# results in my tests. it will be good enough for quick exports. # results in my tests. it will be good enough for quick exports.
uvtex = uv_textures.new(self.lightmap_uvtex_name) uvtex = uv_textures.new(self.lightmap_uvtex_name)
self._associate_image_with_uvtex(uvtex, im) uv_textures.active = uvtex
bpy.ops.object.mode_set(mode="EDIT") self._associate_image_with_uvtex(uvtex, image)
with self._set_mode("EDIT"):
bpy.ops.mesh.select_all(action="SELECT") bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.uv.smart_project() bpy.ops.uv.smart_project(island_margin=0.05)
bpy.ops.object.mode_set(mode="OBJECT")
# Now, set the new LIGHTMAPGEN uv layer as what we want to render to...
# NOTE that this will need to be reset by us to what the user had previously
# Not using toggle.track due to observed oddities
for i in uv_textures:
value = i.name == self.lightmap_uvtex_name
i.active = value
i.active_render = value
# Indicate we should bake
return True
def _prep_for_vcols(self, bo, toggle): def _prep_for_vcols(self, bo, toggle):
mesh = bo.data mesh = bo.data
@ -445,7 +463,7 @@ class LightBaker(_MeshManager):
# future exports as an optimization. We won't reach this point if there is already an # future exports as an optimization. We won't reach this point if there is already an
# autocolor layer (gulp). # autocolor layer (gulp).
if not self.force and needs_vcol_layer: if not self.force and needs_vcol_layer:
self.context_stack.enter_context(TemporaryObject(vcol_layer, vcols.remove)) self._mesh.context_stack.enter_context(TemporaryObject(vcol_layer, vcols.remove))
# Indicate we should bake # Indicate we should bake
return True return True
@ -490,3 +508,11 @@ class LightBaker(_MeshManager):
elif isinstance(i.data, bpy.types.Mesh) and not self._has_valid_material(i): elif isinstance(i.data, bpy.types.Mesh) and not self._has_valid_material(i):
toggle.track(i, "hide_render", True) toggle.track(i, "hide_render", True)
i.select = value i.select = value
@contextmanager
def _set_mode(self, mode):
bpy.ops.object.mode_set(mode=mode)
try:
yield
finally:
bpy.ops.object.mode_set(mode="OBJECT")

8
korman/exporter/mesh.py

@ -167,6 +167,7 @@ class _MeshManager:
self.context_stack = ExitStack() self.context_stack = ExitStack()
if report is not None: if report is not None:
self._report = report self._report = report
self._entered = False
self._overrides = {} self._overrides = {}
@staticmethod @staticmethod
@ -183,6 +184,9 @@ class _MeshManager:
return props return props
def __enter__(self): def __enter__(self):
assert self._entered is False, "_MeshManager is not reentrant"
self._entered = True
self.context_stack.__enter__() self.context_stack.__enter__()
scene = bpy.context.scene scene = bpy.context.scene
@ -231,6 +235,10 @@ class _MeshManager:
if key in {"name", "type"} or (cached_mod["type"], key) in readonly_attributes: if key in {"name", "type"} or (cached_mod["type"], key) in readonly_attributes:
continue continue
setattr(mod, key, value) setattr(mod, key, value)
self._entered = False
def is_collapsed(self, bo) -> bool:
return bo.name in self._overrides
class MeshConverter(_MeshManager): class MeshConverter(_MeshManager):

2
korman/operators/op_lightmap.py

@ -61,7 +61,7 @@ class LightmapAutobakePreviewOperator(_LightingOperator, bpy.types.Operator):
if not self.final: if not self.final:
bake.lightmap_name = "{}_LIGHTMAPGEN_PREVIEW.png" bake.lightmap_name = "{}_LIGHTMAPGEN_PREVIEW.png"
bake.lightmap_uvtex_name = "LIGHTMAPGEN_PREVIEW" bake.lightmap_uvtex_name = "LIGHTMAPGEN_PREVIEW"
bake.force = self.final bake.force = True
bake.retain_lightmap_uvtex = self.final bake.retain_lightmap_uvtex = self.final
if not bake.bake_static_lighting([context.object,]): if not bake.bake_static_lighting([context.object,]):
self.report({"WARNING"}, "No valid lights found to bake.") self.report({"WARNING"}, "No valid lights found to bake.")

2
korman/properties/modifiers/render.py

@ -472,7 +472,7 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
materials = mat_mgr.get_materials(bo) materials = mat_mgr.get_materials(bo)
# Find the stupid UVTex # Find the stupid UVTex
uvtex_name = "LIGHTMAPGEN" uvtex_name = exporter.oven.lightmap_uvtex_name
uvw_src = next((i for i, uvtex in enumerate(bo.data.uv_textures) if uvtex.name == uvtex_name), None) uvw_src = next((i for i, uvtex in enumerate(bo.data.uv_textures) if uvtex.name == uvtex_name), None)
if uvw_src is None: if uvw_src is None:
raise ExportError("'{}': Lightmap UV Texture '{}' seems to be missing. Did you delete it?", bo.name, uvtex_name) raise ExportError("'{}': Lightmap UV Texture '{}' seems to be missing. Did you delete it?", bo.name, uvtex_name)

2
korman/ui/modifiers/render.py

@ -196,6 +196,8 @@ def lightmap(modifier, layout, context):
col = layout.column() col = layout.column()
col.active = is_texture col.active = is_texture
col.prop_search(modifier, "uv_map", context.active_object.data, "uv_textures") col.prop_search(modifier, "uv_map", context.active_object.data, "uv_textures")
if bool(modifier.id_data.modifiers) and modifier.uv_map:
col.label("UV Map islands will be packed on export.", icon="ERROR")
col = layout.column() col = layout.column()
col.active = is_texture col.active = is_texture
col.prop(modifier, "image", icon="IMAGE_RGB") col.prop(modifier, "image", icon="IMAGE_RGB")

Loading…
Cancel
Save