|
|
@ -14,10 +14,12 @@ |
|
|
|
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
|
|
|
|
|
|
|
|
import bpy |
|
|
|
import bpy |
|
|
|
from bpy.app.handlers import persistent |
|
|
|
|
|
|
|
|
|
|
|
from contextlib import ExitStack |
|
|
|
|
|
|
|
import itertools |
|
|
|
|
|
|
|
|
|
|
|
from .explosions import * |
|
|
|
from .explosions import * |
|
|
|
from .logger import ExportProgressLogger |
|
|
|
from .logger import ExportProgressLogger, ExportVerboseLogger |
|
|
|
from .mesh import _MeshManager, _VERTEX_COLOR_LAYERS |
|
|
|
from .mesh import _MeshManager, _VERTEX_COLOR_LAYERS |
|
|
|
from ..helpers import * |
|
|
|
from ..helpers import * |
|
|
|
|
|
|
|
|
|
|
@ -26,17 +28,25 @@ _NUM_RENDER_LAYERS = 20 |
|
|
|
class LightBaker(_MeshManager): |
|
|
|
class LightBaker(_MeshManager): |
|
|
|
"""ExportTime Lighting""" |
|
|
|
"""ExportTime Lighting""" |
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, report=None): |
|
|
|
def __init__(self, report=None, stack=None, *, verbose=False): |
|
|
|
self._lightgroups = {} |
|
|
|
self._lightgroups = {} |
|
|
|
if report is None: |
|
|
|
if report is None: |
|
|
|
self._report = ExportProgressLogger() |
|
|
|
self._report = ExportVerboseLogger() if verbose else ExportProgressLogger() |
|
|
|
self.add_progress_steps(self._report, True) |
|
|
|
self.add_progress_steps(self._report, True) |
|
|
|
self._report.progress_start("PREVIEWING LIGHTING") |
|
|
|
self._report.progress_start("BAKING LIGHTING") |
|
|
|
self._own_report = True |
|
|
|
self._own_report = True |
|
|
|
else: |
|
|
|
else: |
|
|
|
self._report = report |
|
|
|
self._report = report |
|
|
|
self._own_report = False |
|
|
|
self._own_report = False |
|
|
|
super().__init__(self._report) |
|
|
|
super().__init__(self._report) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self._context_stack = stack |
|
|
|
|
|
|
|
self.vcol_layer_name = "autocolor" |
|
|
|
|
|
|
|
self.lightmap_name = "{}_LIGHTMAPGEN.png" |
|
|
|
|
|
|
|
self.lightmap_uvtex_name = "LIGHTMAPGEN" |
|
|
|
|
|
|
|
self.retain_lightmap_uvtex = True |
|
|
|
|
|
|
|
self.force = False |
|
|
|
|
|
|
|
self._lightmap_images = {} |
|
|
|
self._uvtexs = {} |
|
|
|
self._uvtexs = {} |
|
|
|
|
|
|
|
|
|
|
|
def __del__(self): |
|
|
|
def __del__(self): |
|
|
@ -77,6 +87,7 @@ class LightBaker(_MeshManager): |
|
|
|
self._apply_render_settings(toggle, False) |
|
|
|
self._apply_render_settings(toggle, False) |
|
|
|
self._select_only(objs, toggle) |
|
|
|
self._select_only(objs, toggle) |
|
|
|
bpy.ops.object.bake_image() |
|
|
|
bpy.ops.object.bake_image() |
|
|
|
|
|
|
|
self._pack_lightmaps(objs) |
|
|
|
|
|
|
|
|
|
|
|
def _bake_vcols(self, objs, layers): |
|
|
|
def _bake_vcols(self, objs, layers): |
|
|
|
with GoodNeighbor() as toggle: |
|
|
|
with GoodNeighbor() as toggle: |
|
|
@ -99,6 +110,8 @@ class LightBaker(_MeshManager): |
|
|
|
# this stuff has been observed to be problematic with GoodNeighbor |
|
|
|
# this stuff has been observed to be problematic with GoodNeighbor |
|
|
|
self._pop_lightgroups() |
|
|
|
self._pop_lightgroups() |
|
|
|
self._restore_uvtexs() |
|
|
|
self._restore_uvtexs() |
|
|
|
|
|
|
|
if not self.retain_lightmap_uvtex: |
|
|
|
|
|
|
|
self._remove_stale_uvtexes(bake) |
|
|
|
return result |
|
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
def _bake_static_lighting(self, bake, toggle): |
|
|
|
def _bake_static_lighting(self, bake, toggle): |
|
|
@ -203,11 +216,17 @@ class LightBaker(_MeshManager): |
|
|
|
material.light_group = dest |
|
|
|
material.light_group = dest |
|
|
|
return shouldibake |
|
|
|
return shouldibake |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_lightmap(self, bo): |
|
|
|
|
|
|
|
return self._lightmap_images.get(bo.name) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_lightmap_name(self, bo): |
|
|
|
|
|
|
|
return self.lightmap_name.format(bo.name) |
|
|
|
|
|
|
|
|
|
|
|
def _get_lightmap_uvtex(self, mesh, modifier): |
|
|
|
def _get_lightmap_uvtex(self, mesh, modifier): |
|
|
|
if modifier.uv_map: |
|
|
|
if modifier.uv_map: |
|
|
|
return mesh.uv_textures[modifier.uv_map] |
|
|
|
return mesh.uv_textures[modifier.uv_map] |
|
|
|
for i in mesh.uv_textures: |
|
|
|
for i in mesh.uv_textures: |
|
|
|
if i.name != "LIGHTMAPGEN": |
|
|
|
if i.name not in {"LIGHTMAPGEN", self.lightmap_uvtex_name}: |
|
|
|
return i |
|
|
|
return i |
|
|
|
return None |
|
|
|
return None |
|
|
|
|
|
|
|
|
|
|
@ -224,12 +243,37 @@ class LightBaker(_MeshManager): |
|
|
|
bake, bake_passes = {}, bpy.context.scene.plasma_scene.bake_passes |
|
|
|
bake, bake_passes = {}, bpy.context.scene.plasma_scene.bake_passes |
|
|
|
bake_vcol = bake.setdefault(("vcol",) + default_layers, []) |
|
|
|
bake_vcol = bake.setdefault(("vcol",) + default_layers, []) |
|
|
|
|
|
|
|
|
|
|
|
for i in objs: |
|
|
|
def lightmap_bake_required(obj) -> bool: |
|
|
|
if i.type != "MESH": |
|
|
|
mod = obj.plasma_modifiers.lightmap |
|
|
|
continue |
|
|
|
if mod.bake_lightmap: |
|
|
|
if bool(i.data.materials) is False: |
|
|
|
if self.force: |
|
|
|
continue |
|
|
|
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 |
|
|
|
mods = i.plasma_modifiers |
|
|
|
lightmap_mod = mods.lightmap |
|
|
|
lightmap_mod = mods.lightmap |
|
|
|
if lightmap_mod.enabled: |
|
|
|
if lightmap_mod.enabled: |
|
|
@ -249,19 +293,25 @@ class LightBaker(_MeshManager): |
|
|
|
if not lm_active_layers & obj_active_layers: |
|
|
|
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)) |
|
|
|
raise ExportError("Bake Lighting '{}': At least one layer the object is on must be selected".format(i.name)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if lightmap_bake_required(i) is False and vcol_bake_required(i) is False: |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
|
|
method = "lightmap" if lightmap_mod.bake_lightmap else "vcol" |
|
|
|
method = "lightmap" if lightmap_mod.bake_lightmap else "vcol" |
|
|
|
key = (method,) + lm_layers |
|
|
|
key = (method,) + lm_layers |
|
|
|
bake_pass = bake.setdefault(key, []) |
|
|
|
bake_pass = bake.setdefault(key, []) |
|
|
|
bake_pass.append(i) |
|
|
|
bake_pass.append(i) |
|
|
|
elif mods.lighting.preshade: |
|
|
|
self._report.msg("'{}': Bake to {}", i.name, method, indent=1) |
|
|
|
vcols = i.data.vertex_colors |
|
|
|
elif mods.lighting.preshade and vcol_bake_required(i): |
|
|
|
for j in _VERTEX_COLOR_LAYERS: |
|
|
|
self._report.msg("'{}': Bake to vcol (crappy)", i.name, indent=1) |
|
|
|
if j in vcols: |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
bake_vcol.append(i) |
|
|
|
bake_vcol.append(i) |
|
|
|
return bake |
|
|
|
return bake |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _pack_lightmaps(self, objs): |
|
|
|
|
|
|
|
for bo in objs: |
|
|
|
|
|
|
|
im = self.get_lightmap(bo) |
|
|
|
|
|
|
|
if im is not None and im.is_dirty: |
|
|
|
|
|
|
|
im.pack(as_png=True) |
|
|
|
|
|
|
|
|
|
|
|
def _pop_lightgroups(self): |
|
|
|
def _pop_lightgroups(self): |
|
|
|
materials = bpy.data.materials |
|
|
|
materials = bpy.data.materials |
|
|
|
for mat_name, lg in self._lightgroups.items(): |
|
|
|
for mat_name, lg in self._lightgroups.items(): |
|
|
@ -296,7 +346,7 @@ class LightBaker(_MeshManager): |
|
|
|
|
|
|
|
|
|
|
|
# We need to ensure that we bake onto the "BlahObject_LIGHTMAPGEN" image |
|
|
|
# We need to ensure that we bake onto the "BlahObject_LIGHTMAPGEN" image |
|
|
|
data_images = bpy.data.images |
|
|
|
data_images = bpy.data.images |
|
|
|
im_name = "{}_LIGHTMAPGEN.png".format(bo.name) |
|
|
|
im_name = self.get_lightmap_name(bo) |
|
|
|
size = modifier.resolution |
|
|
|
size = modifier.resolution |
|
|
|
|
|
|
|
|
|
|
|
im = data_images.get(im_name) |
|
|
|
im = data_images.get(im_name) |
|
|
@ -306,9 +356,10 @@ class LightBaker(_MeshManager): |
|
|
|
# Force delete and recreate the image because the size is out of date |
|
|
|
# Force delete and recreate the image because the size is out of date |
|
|
|
data_images.remove(im) |
|
|
|
data_images.remove(im) |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
# If there is a cached LIGHTMAPGEN uvtexture, nuke it |
|
|
|
# If there is a cached LIGHTMAPGEN uvtexture, nuke it |
|
|
|
uvtex = uv_textures.get("LIGHTMAPGEN", None) |
|
|
|
uvtex = uv_textures.get(self.lightmap_uvtex_name, None) |
|
|
|
if uvtex is not None: |
|
|
|
if uvtex is not None: |
|
|
|
uv_textures.remove(uvtex) |
|
|
|
uv_textures.remove(uvtex) |
|
|
|
|
|
|
|
|
|
|
@ -330,7 +381,7 @@ class LightBaker(_MeshManager): |
|
|
|
uv_textures.active = uv_base |
|
|
|
uv_textures.active = uv_base |
|
|
|
|
|
|
|
|
|
|
|
# this will copy the UVs to the new UV texture |
|
|
|
# this will copy the UVs to the new UV texture |
|
|
|
uvtex = uv_textures.new("LIGHTMAPGEN") |
|
|
|
uvtex = uv_textures.new(self.lightmap_uvtex_name) |
|
|
|
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 |
|
|
@ -347,7 +398,7 @@ class LightBaker(_MeshManager): |
|
|
|
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("LIGHTMAPGEN") |
|
|
|
uvtex = uv_textures.new(self.lightmap_uvtex_name) |
|
|
|
self._associate_image_with_uvtex(uvtex, im) |
|
|
|
self._associate_image_with_uvtex(uvtex, im) |
|
|
|
bpy.ops.object.mode_set(mode="EDIT") |
|
|
|
bpy.ops.object.mode_set(mode="EDIT") |
|
|
|
bpy.ops.mesh.select_all(action="SELECT") |
|
|
|
bpy.ops.mesh.select_all(action="SELECT") |
|
|
@ -358,7 +409,7 @@ class LightBaker(_MeshManager): |
|
|
|
# NOTE that this will need to be reset by us to what the user had previously |
|
|
|
# NOTE that this will need to be reset by us to what the user had previously |
|
|
|
# Not using toggle.track due to observed oddities |
|
|
|
# Not using toggle.track due to observed oddities |
|
|
|
for i in uv_textures: |
|
|
|
for i in uv_textures: |
|
|
|
value = i.name == "LIGHTMAPGEN" |
|
|
|
value = i.name == self.lightmap_uvtex_name |
|
|
|
i.active = value |
|
|
|
i.active = value |
|
|
|
i.active_render = value |
|
|
|
i.active_render = value |
|
|
|
|
|
|
|
|
|
|
@ -375,21 +426,39 @@ class LightBaker(_MeshManager): |
|
|
|
if not self._generate_lightgroup(bo, user_lg): |
|
|
|
if not self._generate_lightgroup(bo, user_lg): |
|
|
|
return False |
|
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
autocolor = vcols.get("autocolor") |
|
|
|
vcol_layer_name = self.vcol_layer_name |
|
|
|
if autocolor is None: |
|
|
|
autocolor = vcols.get(vcol_layer_name) |
|
|
|
autocolor = vcols.new("autocolor") |
|
|
|
needs_vcol_layer = autocolor is None |
|
|
|
|
|
|
|
if needs_vcol_layer: |
|
|
|
|
|
|
|
autocolor = vcols.new(vcol_layer_name) |
|
|
|
toggle.track(vcols, "active", autocolor) |
|
|
|
toggle.track(vcols, "active", autocolor) |
|
|
|
|
|
|
|
|
|
|
|
# Mark "autocolor" as our active render layer |
|
|
|
# Mark "autocolor" as our active render layer |
|
|
|
for vcol_layer in mesh.vertex_colors: |
|
|
|
for vcol_layer in mesh.vertex_colors: |
|
|
|
autocol = vcol_layer.name == "autocolor" |
|
|
|
autocol = vcol_layer.name == vcol_layer_name |
|
|
|
toggle.track(vcol_layer, "active_render", autocol) |
|
|
|
toggle.track(vcol_layer, "active_render", autocol) |
|
|
|
toggle.track(vcol_layer, "active", autocol) |
|
|
|
toggle.track(vcol_layer, "active", autocol) |
|
|
|
mesh.update() |
|
|
|
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 |
|
|
|
# Indicate we should bake |
|
|
|
return True |
|
|
|
return True |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _remove_stale_uvtexes(self, bake): |
|
|
|
|
|
|
|
lightmap_iter = itertools.chain.from_iterable((value for key, value in bake.items() if key[0] == "lightmap")) |
|
|
|
|
|
|
|
for bo in lightmap_iter: |
|
|
|
|
|
|
|
uv_textures = bo.data.uv_textures |
|
|
|
|
|
|
|
uvtex = uv_textures.get(self.lightmap_uvtex_name, None) |
|
|
|
|
|
|
|
if uvtex is not None: |
|
|
|
|
|
|
|
uv_textures.remove(uvtex) |
|
|
|
|
|
|
|
|
|
|
|
def _restore_uvtexs(self): |
|
|
|
def _restore_uvtexs(self): |
|
|
|
for mesh_name, uvtex_name in self._uvtexs.items(): |
|
|
|
for mesh_name, uvtex_name in self._uvtexs.items(): |
|
|
|
mesh = bpy.data.meshes[mesh_name] |
|
|
|
mesh = bpy.data.meshes[mesh_name] |
|
|
@ -422,20 +491,3 @@ 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 |
|
|
|
|
|
|
|
|
|
|
|
@persistent |
|
|
|
|
|
|
|
def _toss_garbage(scene): |
|
|
|
|
|
|
|
"""Removes all LIGHTMAPGEN and autocolor garbage before saving""" |
|
|
|
|
|
|
|
for i in bpy.data.images: |
|
|
|
|
|
|
|
if i.name.endswith("_LIGHTMAPGEN.png"): |
|
|
|
|
|
|
|
bpy.data.images.remove(i) |
|
|
|
|
|
|
|
for i in bpy.data.meshes: |
|
|
|
|
|
|
|
for uv_tex in i.uv_textures: |
|
|
|
|
|
|
|
if uv_tex.name == "LIGHTMAPGEN": |
|
|
|
|
|
|
|
i.uv_textures.remove(uv_tex) |
|
|
|
|
|
|
|
for vcol in i.vertex_colors: |
|
|
|
|
|
|
|
if vcol.name == "autocolor": |
|
|
|
|
|
|
|
i.vertex_colors.remove(vcol) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# collects light baking garbage |
|
|
|
|
|
|
|
bpy.app.handlers.save_pre.append(_toss_garbage) |
|
|
|
|
|
|
|