|
|
@ -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,7 +388,8 @@ 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... |
|
|
|
self._uvtexs[mesh.name] = uv_textures.active.name |
|
|
|
if uv_textures.active is not None: |
|
|
|
|
|
|
|
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 |
|
|
|
bpy.context.scene.objects.active = bo |
|
|
|
bpy.context.scene.objects.active = bo |
|
|
@ -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): |
|
|
|
bpy.ops.mesh.select_all(action="SELECT") |
|
|
|
# Danger: uv_base.name -> UnicodeDecodeError (wtf? another blender bug?) |
|
|
|
bpy.ops.uv.average_islands_scale() |
|
|
|
self._report.warn("'{}': packing islands in UV Texture '{}' due to modifier collapse", |
|
|
|
bpy.ops.uv.pack_islands() |
|
|
|
bo.name, modifier.uv_map, indent=2) |
|
|
|
|
|
|
|
with self._set_mode("EDIT"): |
|
|
|
|
|
|
|
bpy.ops.mesh.select_all(action="SELECT") |
|
|
|
|
|
|
|
bpy.ops.uv.select_all(action="SELECT") |
|
|
|
|
|
|
|
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) |
|
|
|
bpy.ops.mesh.select_all(action="SELECT") |
|
|
|
with self._set_mode("EDIT"): |
|
|
|
bpy.ops.uv.smart_project() |
|
|
|
bpy.ops.mesh.select_all(action="SELECT") |
|
|
|
bpy.ops.object.mode_set(mode="OBJECT") |
|
|
|
bpy.ops.uv.smart_project(island_margin=0.05) |
|
|
|
|
|
|
|
|
|
|
|
# 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") |
|
|
|