Browse Source

Refactor export logging

The export logger and export reporter have been merged together to form
an eventually much more powerful export analysis feature. For now, the
benefit is that general log messages don't have to be so fiddly with
print statements and string formatting. You're welcome.
pull/58/head
Adam Johnson 8 years ago
parent
commit
6a3b09b747
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 23
      korlib/texture.cpp
  2. 31
      korman/exporter/convert.py
  3. 20
      korman/exporter/etlight.py
  4. 95
      korman/exporter/logger.py
  5. 45
      korman/exporter/material.py
  6. 13
      korman/exporter/mesh.py
  7. 37
      korman/exporter/rtlight.py
  8. 8
      korman/korlib/texture.py
  9. 6
      korman/nodes/node_conditions.py
  10. 4
      korman/properties/modifiers/render.py
  11. 2
      korman/properties/modifiers/sound.py

23
korlib/texture.cpp

@ -266,14 +266,19 @@ static int _generate_detail_map(pyGLTexture* self, uint8_t* buf, size_t bufsz, G
return 0; return 0;
} }
static _LevelData _get_level_data(pyGLTexture* self, GLint level, bool bgra, bool quiet) { static _LevelData _get_level_data(pyGLTexture* self, GLint level, bool bgra, PyObject* report) {
GLint width, height; GLint width, height;
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width); glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &height); glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &height);
GLenum fmt = bgra ? GL_BGRA_EXT : GL_RGBA; GLenum fmt = bgra ? GL_BGRA_EXT : GL_RGBA;
if (!quiet) // Print out the debug message
PySys_WriteStdout(" Level #%i: %ix%i\n", level, width, height); if (report && report != Py_None) {
PyObjectRef msg_func = PyObject_GetAttrString(report, "msg");
PyObjectRef args = Py_BuildValue("siii", "Level #{}: {}x{}", level, width, height);
PyObjectRef kwargs = Py_BuildValue("{s:i}", "indent", 2);
PyObjectRef result = PyObject_Call(msg_func, args, kwargs);
}
size_t bufsz; size_t bufsz;
bufsz = (width * height * 4); bufsz = (width * height * 4);
@ -284,18 +289,18 @@ static _LevelData _get_level_data(pyGLTexture* self, GLint level, bool bgra, boo
static PyObject* pyGLTexture_get_level_data(pyGLTexture* self, PyObject* args, PyObject* kwargs) { static PyObject* pyGLTexture_get_level_data(pyGLTexture* self, PyObject* args, PyObject* kwargs) {
static char* kwlist[] = { _pycs("level"), _pycs("calc_alpha"), _pycs("bgra"), static char* kwlist[] = { _pycs("level"), _pycs("calc_alpha"), _pycs("bgra"),
_pycs("quiet"), _pycs("fast"), NULL }; _pycs("report"), _pycs("fast"), NULL };
GLint level = 0; GLint level = 0;
bool calc_alpha = false; bool calc_alpha = false;
bool bgra = false; bool bgra = false;
bool quiet = false; PyObject* report = nullptr;
bool fast = false; bool fast = false;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ibbbb", kwlist, &level, &calc_alpha, &bgra, &quiet, &fast)) { if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ibbOb", kwlist, &level, &calc_alpha, &bgra, &report, &fast)) {
PyErr_SetString(PyExc_TypeError, "get_level_data expects an optional int, bool, bool, bool, bool"); PyErr_SetString(PyExc_TypeError, "get_level_data expects an optional int, bool, bool, obejct, bool");
return NULL; return NULL;
} }
_LevelData data = _get_level_data(self, level, bgra, quiet); _LevelData data = _get_level_data(self, level, bgra, report);
if (fast) if (fast)
return pyBuffer_Steal(data.m_data, data.m_dataSize); return pyBuffer_Steal(data.m_data, data.m_dataSize);
@ -372,7 +377,7 @@ static PyMethodDef pyGLTexture_Methods[] = {
}; };
static PyObject* pyGLTexture_get_has_alpha(pyGLTexture* self, void*) { static PyObject* pyGLTexture_get_has_alpha(pyGLTexture* self, void*) {
_LevelData data = _get_level_data(self, 0, false, true); _LevelData data = _get_level_data(self, 0, false, nullptr);
for (size_t i = 3; i < data.m_dataSize; i += 4) { for (size_t i = 3; i < data.m_dataSize; i += 4) {
if (data.m_data[i] != 255) { if (data.m_data[i] != 255) {
delete[] data.m_data; delete[] data.m_data;

31
korman/exporter/convert.py

@ -42,14 +42,13 @@ class Exporter:
return Path(self._op.filepath).stem return Path(self._op.filepath).stem
def run(self): def run(self):
with logger.ExportLogger(self._op.filepath) as _log: with logger.ExportLogger(self._op.filepath) as self.report:
print("Exporting '{}.age'".format(self.age_name)) self.report.msg("Exporting '{}.age'", self.age_name)
start = time.perf_counter() start = time.perf_counter()
# Step 0: Init export resmgr and stuff # Step 0: Init export resmgr and stuff
self.mgr = manager.ExportManager(self) self.mgr = manager.ExportManager(self)
self.mesh = mesh.MeshConverter(self) self.mesh = mesh.MeshConverter(self)
self.report = logger.ExportAnalysis()
self.physics = physics.PhysicsConverter(self) self.physics = physics.PhysicsConverter(self)
self.light = rtlight.LightConverter(self) self.light = rtlight.LightConverter(self)
self.animation = animation.AnimationConverter(self) self.animation = animation.AnimationConverter(self)
@ -95,10 +94,10 @@ class Exporter:
# And finally we crow about how awesomely fast we are... # And finally we crow about how awesomely fast we are...
end = time.perf_counter() end = time.perf_counter()
print("\nExported {}.age in {:.2f} seconds".format(self.age_name, end-start)) self.report.msg("\nExported {}.age in {:.2f} seconds", self.age_name, end-start)
def _bake_static_lighting(self): def _bake_static_lighting(self):
oven = etlight.LightBaker() oven = etlight.LightBaker(self.report)
oven.bake_static_lighting(self._objects) oven.bake_static_lighting(self._objects)
def _collect_objects(self): def _collect_objects(self):
@ -163,7 +162,7 @@ class Exporter:
parent = bo.parent parent = bo.parent
if parent is not None: if parent is not None:
if parent.plasma_object.enabled: if parent.plasma_object.enabled:
print(" Attaching to parent SceneObject '{}'".format(parent.name)) self.report.msg("Attaching to parent SceneObject '{}'", parent.name, indent=1)
parent_ci = self._export_coordinate_interface(None, parent) parent_ci = self._export_coordinate_interface(None, parent)
parent_ci.addChild(so.key) parent_ci.addChild(so.key)
else: else:
@ -187,8 +186,9 @@ class Exporter:
return so.coord.object return so.coord.object
def _export_scene_objects(self): def _export_scene_objects(self):
log_msg = self.report.msg
for bl_obj in self._objects: for bl_obj in self._objects:
print("\n[SceneObject '{}']".format(bl_obj.name)) log_msg("\n[SceneObject '{}']".format(bl_obj.name))
# First pass: do things specific to this object type. # First pass: do things specific to this object type.
# note the function calls: to export a MESH, it's _export_mesh_blobj # note the function calls: to export a MESH, it's _export_mesh_blobj
@ -196,10 +196,10 @@ class Exporter:
try: try:
export_fn = getattr(self, export_fn) export_fn = getattr(self, export_fn)
except AttributeError: except AttributeError:
print("WARNING: '{}' is a Plasma Object of Blender type '{}'".format(bl_obj.name, bl_obj.type)) self.report.warn("""'{}' is a Plasma Object of Blender type '{}'
print("... And I have NO IDEA what to do with that! Tossing.") ... And I have NO IDEA what to do with that! Tossing.""".format(bl_obj.name, bl_obj.type))
continue continue
print(" Blender Object '{}' of type '{}'".format(bl_obj.name, bl_obj.type)) log_msg("Blender Object '{}' of type '{}'".format(bl_obj.name, bl_obj.type), indent=1)
# Create a sceneobject if one does not exist. # Create a sceneobject if one does not exist.
# Before we call the export_fn, we need to determine if this object is an actor of any # Before we call the export_fn, we need to determine if this object is an actor of any
@ -211,7 +211,7 @@ class Exporter:
# And now we puke out the modifiers... # And now we puke out the modifiers...
for mod in bl_obj.plasma_modifiers.modifiers: for mod in bl_obj.plasma_modifiers.modifiers:
print(" Exporting '{}' modifier as '{}'".format(mod.bl_label, mod.key_name)) log_msg("Exporting '{}' modifier".format(mod.bl_label), indent=1)
mod.export(self, bl_obj, sceneobject) mod.export(self, bl_obj, sceneobject)
def _export_empty_blobj(self, so, bo): def _export_empty_blobj(self, so, bo):
@ -227,14 +227,14 @@ class Exporter:
if bo.data.materials: if bo.data.materials:
self.mesh.export_object(bo) self.mesh.export_object(bo)
else: else:
print(" No material(s) on the ObData, so no drawables") self.report.msg("No material(s) on the ObData, so no drawables", indent=1)
def _export_referenced_node_trees(self): def _export_referenced_node_trees(self):
print("\nChecking Logic Trees...") self.report.msg("\nChecking Logic Trees...")
need_to_export = ((name, bo, so) for name, (bo, so) in self.want_node_trees.items() need_to_export = ((name, bo, so) for name, (bo, so) in self.want_node_trees.items()
if name not in self.node_trees_exported) if name not in self.node_trees_exported)
for tree, bo, so in need_to_export: for tree, bo, so in need_to_export:
print(" NodeTree '{}'".format(tree)) self.report.msg("NodeTree '{}'", tree, indent=1)
bpy.data.node_groups[tree].export(self, bo, so) bpy.data.node_groups[tree].export(self, bo, so)
def _harvest_actors(self): def _harvest_actors(self):
@ -269,8 +269,6 @@ class Exporter:
return False return False
def _post_process_scene_objects(self): def _post_process_scene_objects(self):
print("\nPostprocessing SceneObjects...")
mat_mgr = self.mesh.material mat_mgr = self.mesh.material
for bl_obj in self._objects: for bl_obj in self._objects:
sceneobject = self.mgr.find_object(plSceneObject, bl=bl_obj) sceneobject = self.mgr.find_object(plSceneObject, bl=bl_obj)
@ -292,5 +290,4 @@ class Exporter:
for mod in bl_obj.plasma_modifiers.modifiers: for mod in bl_obj.plasma_modifiers.modifiers:
proc = getattr(mod, "post_export", None) proc = getattr(mod, "post_export", None)
if proc is not None: if proc is not None:
print(" '{}' modifier '{}'".format(bl_obj.name, mod.key_name))
proc(self, bl_obj, sceneobject) proc(self, bl_obj, sceneobject)

20
korman/exporter/etlight.py

@ -16,6 +16,7 @@
import bpy import bpy
from bpy.app.handlers import persistent from bpy.app.handlers import persistent
from .logger import ExportLogger
from .mesh import _VERTEX_COLOR_LAYERS from .mesh import _VERTEX_COLOR_LAYERS
from ..helpers import * from ..helpers import *
@ -24,8 +25,9 @@ _NUM_RENDER_LAYERS = 20
class LightBaker: class LightBaker:
"""ExportTime Lighting""" """ExportTime Lighting"""
def __init__(self): def __init__(self, report=None):
self._lightgroups = {} self._lightgroups = {}
self._report = report if report is not None else ExportLogger()
self._uvtexs = {} self._uvtexs = {}
def _apply_render_settings(self, toggle, vcols): def _apply_render_settings(self, toggle, vcols):
@ -63,7 +65,7 @@ class LightBaker:
def bake_static_lighting(self, objs): def bake_static_lighting(self, objs):
"""Bakes all static lighting for Plasma geometry""" """Bakes all static lighting for Plasma geometry"""
print("\nBaking Static Lighting...") self._report.msg("\nBaking Static Lighting...")
bake = self._harvest_bakable_objects(objs) bake = self._harvest_bakable_objects(objs)
with GoodNeighbor() as toggle: with GoodNeighbor() as toggle:
@ -82,24 +84,26 @@ class LightBaker:
bpy.context.scene.layers = (True,) * _NUM_RENDER_LAYERS bpy.context.scene.layers = (True,) * _NUM_RENDER_LAYERS
# Step 1: Prepare... Apply UVs, etc, etc, etc # Step 1: Prepare... Apply UVs, etc, etc, etc
print(" Preparing to bake...") self._report.msg("Preparing to bake...", indent=1)
for key in bake.keys(): for key in bake.keys():
if key[0] == "lightmap": if key[0] == "lightmap":
for i in range(len(bake[key])-1, -1, -1): for i in range(len(bake[key])-1, -1, -1):
obj = bake[key][i] obj = bake[key][i]
if not self._prep_for_lightmap(obj, toggle): if not self._prep_for_lightmap(obj, toggle):
print(" Lightmap '{}' will not be baked -- no applicable lights".format(obj.name)) self._report.msg("Lightmap '{}' will not be baked -- no applicable lights",
obj.name, indent=2)
bake[key].pop(i) bake[key].pop(i)
elif key[0] == "vcol": elif key[0] == "vcol":
for i in range(len(bake[key])-1, -1, -1): for i in range(len(bake[key])-1, -1, -1):
obj = bake[key][i] obj = bake[key][i]
if not self._prep_for_vcols(obj, toggle): if not self._prep_for_vcols(obj, toggle):
if self._has_valid_material(obj): if self._has_valid_material(obj):
print(" VCols '{}' will not be baked -- no applicable lights".format(obj.name)) self._report.msg("VCols '{}' will not be baked -- no applicable lights",
obj.name, indent=2)
bake[key].pop(i) bake[key].pop(i)
else: else:
raise RuntimeError(key[0]) raise RuntimeError(key[0])
print(" ...") self._report.msg(" ...")
# Step 2: BAKE! # Step 2: BAKE!
for key, value in bake.items(): for key, value in bake.items():
@ -107,10 +111,10 @@ class LightBaker:
continue continue
if key[0] == "lightmap": if key[0] == "lightmap":
print(" {} Lightmap(s) [H:{:X}]".format(len(value), hash(key))) self._report.msg("{} Lightmap(s) [H:{:X}]", len(value), hash(key), indent=1)
self._bake_lightmaps(value, key[1:]) self._bake_lightmaps(value, key[1:])
elif key[0] == "vcol": elif key[0] == "vcol":
print(" {} Crap Light(s)".format(len(value))) self._report.msg("{} Crap Light(s)", len(value), indent=1)
self._bake_vcols(value) self._bake_vcols(value)
else: else:
raise RuntimeError(key[0]) raise RuntimeError(key[0])

95
korman/exporter/logger.py

@ -16,61 +16,62 @@
from pathlib import Path from pathlib import Path
import sys import sys
class ExportAnalysis:
"""This is used to collect artist action items from the export process. You can warn about
portability issues, possible oversights, etc. The benefit here is that the user doesn't have
to look through all of the gobbledygook in the export log.
"""
_porting = []
_warnings = []
def save(self):
# TODO
pass
def port(self, message, indent=0):
self._porting.append(message)
print(" " * indent, end="")
print("PORTING: {}".format(message))
def warn(self, message, indent=0):
self._warnings.append(message)
print(" " * indent, end="")
print("WARNING: {}".format(message))
class ExportLogger: class ExportLogger:
"""Yet Another Logger(TM)""" def __init__(self, age_path=None):
self._porting = []
self._warnings = []
self._age_path = age_path
self._file = None
def __enter__(self):
assert self._age_path is not None
def __init__(self, ageFile):
# Make the log file name from the age file path -- this ensures we're not trying to write # Make the log file name from the age file path -- this ensures we're not trying to write
# the log file to the same directory Blender.exe is in, which might be a permission error # the log file to the same directory Blender.exe is in, which might be a permission error
my_path = Path(ageFile) my_path = Path(self._age_path)
my_path = my_path.with_name("{}_export".format(my_path.stem)).with_suffix(".log") my_path = my_path.with_name("{}_export".format(my_path.stem)).with_suffix(".log")
self._file = open(str(my_path), "w") self._file = open(str(my_path), "w")
return self
for i in dir(self._file):
if not hasattr(self, i):
setattr(self, i, getattr(self._file, i))
def __enter__(self):
self._stdout, sys.stdout = sys.stdout, self._file
self._stderr, sys.stderr = sys.stderr, self._file
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
sys.stdout = self._stdout self._file.close()
sys.stderr = self._stderr return False
def flush(self): def msg(self, *args, **kwargs):
self._file.flush() assert args
self._stdout.flush() indent = kwargs.get("indent", 0)
self._stderr.flush() msg = "{}{}".format(" " * indent, args[0])
if len(args) > 1:
msg = msg.format(*args[1:], **kwargs)
if self._file is None:
print(msg)
else:
self._file.writelines((msg, "\n"))
def write(self, str): def port(self, *args, **kwargs):
self._file.write(str) assert args
self._stdout.write(str) indent = kwargs.get("indent", 0)
msg = "{}PORTING: {}".format(" " * indent, args[0])
if len(args) > 1:
msg = msg.format(*args[1:], **kwargs)
if self._file is None:
print(msg)
else:
self._file.writelines((msg, "\n"))
self._porting.append(args[0])
def save(self):
# TODO
pass
def writelines(self, seq): def warn(self, *args, **kwargs):
self._file.writelines(seq) assert args
self._stdout.writelines(seq) indent = kwargs.get("indent", 0)
msg = "{}WARNING: {}".format(" " * indent, args[0])
if len(args) > 1:
msg = msg.format(*args[1:], **kwargs)
if self._file is None:
print(msg)
else:
self._file.writelines((msg, "\n"))
self._warnings.append(args[0])

45
korman/exporter/material.py

@ -125,7 +125,7 @@ class MaterialConverter:
def export_material(self, bo, bm): def export_material(self, bo, bm):
"""Exports a Blender Material as an hsGMaterial""" """Exports a Blender Material as an hsGMaterial"""
print(" Exporting Material '{}'".format(bm.name)) self._report.msg("Exporting Material '{}'", bm.name, indent=1)
hsgmat = self._mgr.add_object(hsGMaterial, name=bm.name, bl=bo) hsgmat = self._mgr.add_object(hsGMaterial, name=bm.name, bl=bo)
slots = [(idx, slot) for idx, slot in enumerate(bm.texture_slots) if slot is not None and slot.use \ slots = [(idx, slot) for idx, slot in enumerate(bm.texture_slots) if slot is not None and slot.use \
@ -198,7 +198,7 @@ class MaterialConverter:
return hsgmat.key return hsgmat.key
def export_waveset_material(self, bo, bm): def export_waveset_material(self, bo, bm):
print(" Exporting WaveSet Material '{}'".format(bm.name)) self._report.msg("Exporting WaveSet Material '{}'", bm.name, indent=1)
# WaveSets MUST have their own material # WaveSets MUST have their own material
unique_name = "{}_WaveSet7".format(bm.name) unique_name = "{}_WaveSet7".format(bm.name)
@ -215,7 +215,7 @@ class MaterialConverter:
def export_bumpmap_slot(self, bo, bm, hsgmat, slot, idx): def export_bumpmap_slot(self, bo, bm, hsgmat, slot, idx):
name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name) name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name)
print(" Exporting Plasma Bumpmap Layers for '{}'".format(name)) self._report.msg("Exporting Plasma Bumpmap Layers for '{}'", name, indent=2)
# Okay, now we need to make 3 layers for the Du, Dw, and Dv # Okay, now we need to make 3 layers for the Du, Dw, and Dv
du_layer = self._mgr.add_object(plLayer, name="{}_DU_BumpLut".format(name), bl=bo) du_layer = self._mgr.add_object(plLayer, name="{}_DU_BumpLut".format(name), bl=bo)
@ -264,7 +264,7 @@ class MaterialConverter:
def export_texture_slot(self, bo, bm, hsgmat, slot, idx, name=None, blend_flags=True): def export_texture_slot(self, bo, bm, hsgmat, slot, idx, name=None, blend_flags=True):
if name is None: if name is None:
name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name) name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name)
print(" Exporting Plasma Layer '{}'".format(name)) self._report.msg("Exporting Plasma Layer '{}'", name, indent=2)
layer = self._mgr.add_object(plLayer, name=name, bl=bo) layer = self._mgr.add_object(plLayer, name=name, bl=bo)
if bm is not None and not slot.use_map_normal: if bm is not None and not slot.use_map_normal:
self._propagate_material_settings(bm, layer) self._propagate_material_settings(bm, layer)
@ -274,10 +274,10 @@ class MaterialConverter:
for i, uvchan in enumerate(bo.data.uv_layers): for i, uvchan in enumerate(bo.data.uv_layers):
if uvchan.name == slot.uv_layer: if uvchan.name == slot.uv_layer:
layer.UVWSrc = i layer.UVWSrc = i
print(" Using UV Map #{} '{}'".format(i, name)) self._report.msg("Using UV Map #{} '{}'", i, name, indent=3)
break break
else: else:
print(" No UVMap specified... Blindly using the first one, maybe it exists :|") self._report.msg("No UVMap specified... Blindly using the first one, maybe it exists :|", indent=3)
# Transform # Transform
xform = hsMatrix44() xform = hsMatrix44()
@ -447,7 +447,8 @@ class MaterialConverter:
name = "{}_DynEnvMap".format(viewpt.name) name = "{}_DynEnvMap".format(viewpt.name)
pl_env = self._mgr.find_object(pl_class, bl=bo, name=name) pl_env = self._mgr.find_object(pl_class, bl=bo, name=name)
if pl_env is not None: if pl_env is not None:
print(" EnvMap for viewpoint {} already exported... NOTE: Your settings here will be overridden by the previous object!".format(viewpt.name)) self._report.msg("EnvMap for viewpoint {} already exported... NOTE: Your settings here will be overridden by the previous object!",
viewpt.name, indent=3)
if isinstance(pl_env, plDynamicCamMap): if isinstance(pl_env, plDynamicCamMap):
pl_env.addTargetNode(self._mgr.find_key(plSceneObject, bl=bo)) pl_env.addTargetNode(self._mgr.find_key(plSceneObject, bl=bo))
pl_env.addMatLayer(layer.key) pl_env.addMatLayer(layer.key)
@ -457,7 +458,7 @@ class MaterialConverter:
oRes = bl_env.resolution oRes = bl_env.resolution
eRes = helpers.ensure_power_of_two(oRes) eRes = helpers.ensure_power_of_two(oRes)
if oRes != eRes: if oRes != eRes:
print(" Overriding EnvMap size to ({}x{}) -- POT".format(eRes, eRes)) self._report.msg("Overriding EnvMap size to ({}x{}) -- POT", eRes, eRes, indent=3)
# And now for the general ho'hum-ness # And now for the general ho'hum-ness
pl_env = self._mgr.add_object(pl_class, bl=bo, name=name) pl_env = self._mgr.add_object(pl_class, bl=bo, name=name)
@ -595,10 +596,11 @@ class MaterialConverter:
detail_fade_start=layer_props.detail_fade_start, detail_fade_stop=layer_props.detail_fade_stop, detail_fade_start=layer_props.detail_fade_start, detail_fade_stop=layer_props.detail_fade_stop,
detail_opacity_start=layer_props.detail_opacity_start, detail_opacity_stop=layer_props.detail_opacity_stop) detail_opacity_start=layer_props.detail_opacity_start, detail_opacity_stop=layer_props.detail_opacity_stop)
if key not in self._pending: if key not in self._pending:
print(" Stashing '{}' for conversion as '{}'".format(texture.image.name, str(key))) self._report.msg("Stashing '{}' for conversion as '{}'",
texture.image.name, str(key), indent=3)
self._pending[key] = [layer.key,] self._pending[key] = [layer.key,]
else: else:
print(" Found another user of '{}'".format(texture.image.name)) self._report.msg("Found another user of '{}'", texture.image.name, indent=3)
self._pending[key].append(layer.key) self._pending[key].append(layer.key)
def _export_texture_type_none(self, bo, layer, texture): def _export_texture_type_none(self, bo, layer, texture):
@ -609,16 +611,16 @@ class MaterialConverter:
"""This exports an externally prepared layer and image""" """This exports an externally prepared layer and image"""
key = _Texture(image=image) key = _Texture(image=image)
if key not in self._pending: if key not in self._pending:
print(" Stashing '{}' for conversion as '{}'".format(image.name, str(key))) self._report.msg("Stashing '{}' for conversion as '{}'", image.name, key, indent=2)
self._pending[key] = [layer.key,] self._pending[key] = [layer.key,]
else: else:
print(" Found another user of '{}'".format(key)) self._report.msg("Found another user of '{}'", key, indent=2)
self._pending[key].append(layer.key) self._pending[key].append(layer.key)
def finalize(self): def finalize(self):
for key, layers in self._pending.items(): for key, layers in self._pending.items():
name = str(key) name = str(key)
print("\n[Mipmap '{}']".format(name)) self._report.msg("\n[Mipmap '{}']", name)
image = key.image image = key.image
oWidth, oHeight = image.size oWidth, oHeight = image.size
@ -628,7 +630,8 @@ class MaterialConverter:
eWidth = helpers.ensure_power_of_two(oWidth) eWidth = helpers.ensure_power_of_two(oWidth)
eHeight = helpers.ensure_power_of_two(oHeight) eHeight = helpers.ensure_power_of_two(oHeight)
if (eWidth != oWidth) or (eHeight != oHeight): if (eWidth != oWidth) or (eHeight != oHeight):
print(" Image is not a POT ({}x{}) resizing to {}x{}".format(oWidth, oHeight, eWidth, eHeight)) self._report.msg("Image is not a POT ({}x{}) resizing to {}x{}",
oWidth, oHeight, eWidth, eHeight, indent=1)
self._resize_image(image, eWidth, eHeight) self._resize_image(image, eWidth, eHeight)
# Some basic mipmap settings. # Some basic mipmap settings.
@ -640,11 +643,11 @@ class MaterialConverter:
with helper as glimage: with helper as glimage:
if key.mipmap: if key.mipmap:
numLevels = glimage.num_levels numLevels = glimage.num_levels
print(" Generating mip levels") self._report.msg("Generating mip levels", indent=1)
glimage.generate_mipmap() glimage.generate_mipmap()
else: else:
numLevels = 1 numLevels = 1
print(" Stuffing image data") self._report.msg("Stuffing image data", indent=1)
# Uncompressed bitmaps are BGRA # Uncompressed bitmaps are BGRA
fmt = compression == plBitmap.kUncompressed fmt = compression == plBitmap.kUncompressed
@ -653,7 +656,7 @@ class MaterialConverter:
# this mipmap for per-page textures :( # this mipmap for per-page textures :(
data = [] data = []
for i in range(numLevels): for i in range(numLevels):
data.append(glimage.get_level_data(i, key.calc_alpha, fmt)) data.append(glimage.get_level_data(i, key.calc_alpha, fmt, report=self._report))
# Be a good citizen and reset the Blender Image to pre-futzing state # Be a good citizen and reset the Blender Image to pre-futzing state
image.reload() image.reload()
@ -663,9 +666,9 @@ class MaterialConverter:
mgr = self._mgr mgr = self._mgr
pages = {} pages = {}
print(" Adding to Layer(s)") self._report.msg("Adding to Layer(s)", indent=1)
for layer in layers: for layer in layers:
print(" {}".format(layer.name)) self._report.msg(layer.name, indent=2)
page = mgr.get_textures_page(layer) # Layer's page or Textures.prp page = mgr.get_textures_page(layer) # Layer's page or Textures.prp
# If we haven't created this plMipmap in the page (either layer's page or Textures.prp), # If we haven't created this plMipmap in the page (either layer's page or Textures.prp),
@ -722,6 +725,10 @@ class MaterialConverter:
layer.runtime = utils.color(bm.diffuse_color) layer.runtime = utils.color(bm.diffuse_color)
layer.specular = utils.color(bm.specular_color) layer.specular = utils.color(bm.specular_color)
@property
def _report(self):
return self._exporter().report
def _resize_image(self, image, width, height): def _resize_image(self, image, width, height):
image.scale(width, height) image.scale(width, height)
image.update() image.update()

13
korman/exporter/mesh.py

@ -177,8 +177,8 @@ class MeshConverter:
for loc in self._dspans.values(): for loc in self._dspans.values():
for dspan in loc.values(): for dspan in loc.values():
print("\n[DrawableSpans '{}']".format(dspan.key.name)) self._report.msg("\n[DrawableSpans '{}']", dspan.key.name)
print(" Composing geometry data") self._report.msg("Composing geometry data", indent=1)
# This mega-function does a lot: # This mega-function does a lot:
# 1. Converts SourceSpans (geospans) to Icicles and bakes geometry into plGBuffers # 1. Converts SourceSpans (geospans) to Icicles and bakes geometry into plGBuffers
@ -189,7 +189,7 @@ class MeshConverter:
# Might as well say something else just to fascinate anyone who is playing along # Might as well say something else just to fascinate anyone who is playing along
# at home (and actually enjoys reading these lawgs) # at home (and actually enjoys reading these lawgs)
print(" Bounds and SpaceTree in the saddle") self._report.msg("Bounds and SpaceTree in the saddle", indent=1)
def _export_geometry(self, bo, mesh, materials, geospans): def _export_geometry(self, bo, mesh, materials, geospans):
geodata = [_GeoData(len(mesh.vertices)) for i in materials] geodata = [_GeoData(len(mesh.vertices)) for i in materials]
@ -422,7 +422,8 @@ class MeshConverter:
_diindices = {} _diindices = {}
for geospan, pass_index in geospans: for geospan, pass_index in geospans:
dspan = self._find_create_dspan(bo, geospan.material.object, pass_index) dspan = self._find_create_dspan(bo, geospan.material.object, pass_index)
print(" Exported hsGMaterial '{}' geometry into '{}'".format(geospan.material.name, dspan.key.name)) self._report.msg("Exported hsGMaterial '{}' geometry into '{}'",
geospan.material.name, dspan.key.name, indent=1)
idx = dspan.addSourceSpan(geospan) idx = dspan.addSourceSpan(geospan)
if dspan not in _diindices: if dspan not in _diindices:
_diindices[dspan] = [idx,] _diindices[dspan] = [idx,]
@ -497,3 +498,7 @@ class MeshConverter:
@property @property
def _mgr(self): def _mgr(self):
return self._exporter().mgr return self._exporter().mgr
@property
def _report(self):
return self._exporter().report

37
korman/exporter/rtlight.py

@ -43,19 +43,19 @@ class LightConverter:
# If you change these calculations, be sure to update the AnimationConverter! # If you change these calculations, be sure to update the AnimationConverter!
intens, attenEnd = self.convert_attenuation(bl) intens, attenEnd = self.convert_attenuation(bl)
if bl.falloff_type == "CONSTANT": if bl.falloff_type == "CONSTANT":
print(" Attenuation: No Falloff") self._report.msg("Attenuation: No Falloff", indent=2)
pl.attenConst = intens pl.attenConst = intens
pl.attenLinear = 0.0 pl.attenLinear = 0.0
pl.attenQuadratic = 0.0 pl.attenQuadratic = 0.0
pl.attenCutoff = attenEnd pl.attenCutoff = attenEnd
elif bl.falloff_type == "INVERSE_LINEAR": elif bl.falloff_type == "INVERSE_LINEAR":
print(" Attenuation: Inverse Linear") self._report.msg("Attenuation: Inverse Linear", indent=2)
pl.attenConst = 1.0 pl.attenConst = 1.0
pl.attenLinear = self.convert_attenuation_linear(intens, attenEnd) pl.attenLinear = self.convert_attenuation_linear(intens, attenEnd)
pl.attenQuadratic = 0.0 pl.attenQuadratic = 0.0
pl.attenCutoff = attenEnd pl.attenCutoff = attenEnd
elif bl.falloff_type == "INVERSE_SQUARE": elif bl.falloff_type == "INVERSE_SQUARE":
print(" Attenuation: Inverse Square") self._report.msg("Attenuation: Inverse Square", indent=2)
pl.attenConst = 1.0 pl.attenConst = 1.0
pl.attenLinear = 0.0 pl.attenLinear = 0.0
pl.attenQuadratic = self.convert_attenuation_quadratic(intens, attenEnd) pl.attenQuadratic = self.convert_attenuation_quadratic(intens, attenEnd)
@ -75,18 +75,18 @@ class LightConverter:
return max(0.0, (intensity * _FAR_POWER - 1.0) / pow(end, 2)) return max(0.0, (intensity * _FAR_POWER - 1.0) / pow(end, 2))
def _convert_area_lamp(self, bl, pl): def _convert_area_lamp(self, bl, pl):
print(" [LimitedDirLightInfo '{}']".format(bl.name)) self._report.msg("[LimitedDirLightInfo '{}']", bl.name, indent=1)
pl.width = bl.size pl.width = bl.size
pl.depth = bl.size if bl.shape == "SQUARE" else bl.size_y pl.depth = bl.size if bl.shape == "SQUARE" else bl.size_y
pl.height = bl.plasma_lamp.size_height pl.height = bl.plasma_lamp.size_height
def _convert_point_lamp(self, bl, pl): def _convert_point_lamp(self, bl, pl):
print(" [OmniLightInfo '{}']".format(bl.name)) self._report.msg("[OmniLightInfo '{}']", bl.name, indent=1)
self._convert_attenuation(bl, pl) self._convert_attenuation(bl, pl)
def _convert_spot_lamp(self, bl, pl): def _convert_spot_lamp(self, bl, pl):
print(" [SpotLightInfo '{}']".format(bl.name)) self._report.msg("[SpotLightInfo '{}']", bl.name, indent=1)
self._convert_attenuation(bl, pl) self._convert_attenuation(bl, pl)
# Spot lights have a few more things... # Spot lights have a few more things...
@ -102,7 +102,7 @@ class LightConverter:
pl.falloff = 1.0 pl.falloff = 1.0
def _convert_sun_lamp(self, bl, pl): def _convert_sun_lamp(self, bl, pl):
print(" [DirectionalLightInfo '{}']".format(bl.name)) self._report.msg("[DirectionalLightInfo '{}']", bl.name, indent=1)
def export_rtlight(self, so, bo): def export_rtlight(self, so, bo):
bl_light = bo.data bl_light = bo.data
@ -132,18 +132,18 @@ class LightConverter:
# Apply the colors # Apply the colors
if bl_light.use_diffuse and not shadow_only: if bl_light.use_diffuse and not shadow_only:
print(" Diffuse: {}".format(diff_str)) self._report.msg("Diffuse: {}", diff_str, indent=2)
pl_light.diffuse = hsColorRGBA(*diff_color) pl_light.diffuse = hsColorRGBA(*diff_color)
else: else:
print(" Diffuse: OFF") self._report.msg("Diffuse: OFF", indent=2)
pl_light.diffuse = hsColorRGBA(0.0, 0.0, 0.0, energy) pl_light.diffuse = hsColorRGBA(0.0, 0.0, 0.0, energy)
if bl_light.use_specular and not shadow_only: if bl_light.use_specular and not shadow_only:
print(" Specular: {}".format(spec_str)) self._report.msg("Specular: {}", spec_str, indent=2)
pl_light.setProperty(plLightInfo.kLPHasSpecular, True) pl_light.setProperty(plLightInfo.kLPHasSpecular, True)
pl_light.specular = hsColorRGBA(*spec_color) pl_light.specular = hsColorRGBA(*spec_color)
else: else:
print(" Specular: OFF") self._report.msg("Specular: OFF", indent=2)
pl_light.specular = hsColorRGBA(0.0, 0.0, 0.0, energy) pl_light.specular = hsColorRGBA(0.0, 0.0, 0.0, energy)
rtlamp = bl_light.plasma_lamp rtlamp = bl_light.plasma_lamp
@ -202,7 +202,7 @@ class LightConverter:
# projection Lamp with our own faux Material. Unfortunately, Plasma only supports projecting # projection Lamp with our own faux Material. Unfortunately, Plasma only supports projecting
# one layer. We could exploit the fUnderLay and fOverLay system to export everything, but meh. # one layer. We could exploit the fUnderLay and fOverLay system to export everything, but meh.
if len(tex_slots) > 1: if len(tex_slots) > 1:
self._exporter().warn("Only one texture slot can be exported per Lamp. Picking the first one: '{}'".format(slot.name), indent=3) self._report.warn("Only one texture slot can be exported per Lamp. Picking the first one: '{}'".format(slot.name), indent=3)
layer = mat.export_texture_slot(bo, None, None, slot, 0, blend_flags=False) layer = mat.export_texture_slot(bo, None, None, slot, 0, blend_flags=False)
state = layer.state state = layer.state
@ -243,7 +243,7 @@ class LightConverter:
def find_material_light_keys(self, bo, bm): def find_material_light_keys(self, bo, bm):
"""Given a blender material, we find the keys of all matching Plasma RT Lights. """Given a blender material, we find the keys of all matching Plasma RT Lights.
NOTE: We return a tuple of lists: ([permaLights], [permaProjs])""" NOTE: We return a tuple of lists: ([permaLights], [permaProjs])"""
print(" Searching for runtime lights...") self._report.msg("Searching for runtime lights...", indent=1)
permaLights = [] permaLights = []
permaProjs = [] permaProjs = []
@ -272,16 +272,17 @@ class LightConverter:
break break
else: else:
# didn't find a layer where both lamp and object were, skip it. # didn't find a layer where both lamp and object were, skip it.
print(" [{}] '{}': not in same layer, skipping...".format(lamp.type, obj.name)) self._report.msg("[{}] '{}': not in same layer, skipping...",
lamp.type, obj.name, indent=2)
continue continue
# This is probably where PermaLight vs PermaProj should be sorted out... # This is probably where PermaLight vs PermaProj should be sorted out...
pl_light = self.get_light_key(obj, lamp, None) pl_light = self.get_light_key(obj, lamp, None)
if self._is_projection_lamp(lamp): if self._is_projection_lamp(lamp):
print(" [{}] PermaProj '{}'".format(lamp.type, obj.name)) self._report.msg("[{}] PermaProj '{}'", lamp.type, obj.name, indent=2)
permaProjs.append(pl_light) permaProjs.append(pl_light)
else: else:
print(" [{}] PermaLight '{}'".format(lamp.type, obj.name)) self._report.msg("[{}] PermaLight '{}'", lamp.type, obj.name, indent=2)
permaLights.append(pl_light) permaLights.append(pl_light)
return (permaLights, permaProjs) return (permaLights, permaProjs)
@ -308,3 +309,7 @@ class LightConverter:
@property @property
def mgr(self): def mgr(self):
return self._exporter().mgr return self._exporter().mgr
@property
def _report(self):
return self._exporter().report

8
korman/korlib/texture.py

@ -76,14 +76,14 @@ class GLTexture:
# It will simplify our state tracking a bit. # It will simplify our state tracking a bit.
bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_GENERATE_MIPMAP, 1) bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_GENERATE_MIPMAP, 1)
def get_level_data(self, level=0, calc_alpha=False, bgra=False, quiet=False, fast=False): def get_level_data(self, level=0, calc_alpha=False, bgra=False, report=None, fast=False):
"""Gets the uncompressed pixel data for a requested mip level, optionally calculating the alpha """Gets the uncompressed pixel data for a requested mip level, optionally calculating the alpha
channel from the image color data channel from the image color data
""" """
width = self._get_tex_param(bgl.GL_TEXTURE_WIDTH, level) width = self._get_tex_param(bgl.GL_TEXTURE_WIDTH, level)
height = self._get_tex_param(bgl.GL_TEXTURE_HEIGHT, level) height = self._get_tex_param(bgl.GL_TEXTURE_HEIGHT, level)
if not quiet: if report is not None:
print(" Level #{}: {}x{}".format(level, width, height)) report.msg("Level #{}: {}x{}", level, width, height, indent=2)
# Grab the image data # Grab the image data
size = width * height * 4 size = width * height * 4
@ -138,7 +138,7 @@ class GLTexture:
@property @property
def has_alpha(self): def has_alpha(self):
data = self.get_level_data(quiet=True, fast=True) data = self.get_level_data(report=None, fast=True)
for i in range(3, len(data), 4): for i in range(3, len(data), 4):
if data[i] != 255: if data[i] != 255:
return True return True

6
korman/nodes/node_conditions.py

@ -427,18 +427,18 @@ class PlasmaVolumeSensorNode(PlasmaNodeBase, bpy.types.Node):
suffix = "Exit" suffix = "Exit"
theName = "{}_{}_{}".format(self.id_data.name, self.name, suffix) theName = "{}_{}_{}".format(self.id_data.name, self.name, suffix)
print(" [LogicModifier '{}']".format(theName)) exporter.report.msg("[LogicModifier '{}']", theName, indent=2)
logicKey = exporter.mgr.find_create_key(plLogicModifier, name=theName, so=so) logicKey = exporter.mgr.find_create_key(plLogicModifier, name=theName, so=so)
logicmod = logicKey.object logicmod = logicKey.object
logicmod.setLogicFlag(plLogicModifier.kMultiTrigger, True) logicmod.setLogicFlag(plLogicModifier.kMultiTrigger, True)
logicmod.notify = self.generate_notify_msg(exporter, so, "satisfies") logicmod.notify = self.generate_notify_msg(exporter, so, "satisfies")
# Now, the detector objects # Now, the detector objects
print(" [ObjectInVolumeDetector '{}']".format(theName)) exporter.report.msg("[ObjectInVolumeDetector '{}']", theName, indent=2)
detKey = exporter.mgr.find_create_key(plObjectInVolumeDetector, name=theName, so=so) detKey = exporter.mgr.find_create_key(plObjectInVolumeDetector, name=theName, so=so)
det = detKey.object det = detKey.object
print(" [VolumeSensorConditionalObject '{}']".format(theName)) exporter.report.msg("[VolumeSensorConditionalObject '{}']", theName, indent=2)
volKey = exporter.mgr.find_create_key(plVolumeSensorConditionalObject, name=theName, so=so) volKey = exporter.mgr.find_create_key(plVolumeSensorConditionalObject, name=theName, so=so)
volsens = volKey.object volsens = volKey.object

4
korman/properties/modifiers/render.py

@ -427,10 +427,10 @@ class PlasmaVisControl(PlasmaModifierProperties):
else: else:
this_sv = bo.plasma_modifiers.softvolume this_sv = bo.plasma_modifiers.softvolume
if this_sv.enabled: if this_sv.enabled:
print(" [VisRegion] I'm a SoftVolume myself :)") exporter.report.msg("[VisRegion] I'm a SoftVolume myself :)", indent=1)
rgn.region = this_sv.get_key(exporter, so) rgn.region = this_sv.get_key(exporter, so)
else: else:
print(" [VisRegion] SoftVolume '{}'".format(self.softvolume)) exporter.report.msg("[VisRegion] SoftVolume '{}'", self.softvolume, indent=1)
sv_bo = bpy.data.objects.get(self.softvolume, None) sv_bo = bpy.data.objects.get(self.softvolume, None)
if sv_bo is None: if sv_bo is None:
raise ExportError("'{}': Invalid object '{}' for VisControl soft volume".format(bo.name, self.softvolume)) raise ExportError("'{}': Invalid object '{}' for VisControl soft volume".format(bo.name, self.softvolume))

2
korman/properties/modifiers/sound.py

@ -173,7 +173,7 @@ class PlasmaSound(bpy.types.PropertyGroup):
name = "Sfx-{}_{}".format(so.key.name, self.sound_data) name = "Sfx-{}_{}".format(so.key.name, self.sound_data)
else: else:
name = "Sfx-{}_{}:{}".format(so.key.name, self.sound_data, channel) name = "Sfx-{}_{}:{}".format(so.key.name, self.sound_data, channel)
print(" [{}] {}".format(pClass.__name__[2:], name)) exporter.report.msg("[{}] {}", pClass.__name__[2:], name, indent=1)
sound = exporter.mgr.find_create_object(pClass, so=so, name=name) sound = exporter.mgr.find_create_object(pClass, so=so, name=name)
# If this object is a soft volume itself, we will use our own soft region. # If this object is a soft volume itself, we will use our own soft region.

Loading…
Cancel
Save