diff --git a/korlib/texture.cpp b/korlib/texture.cpp
index af4f0f6..63d3975 100644
--- a/korlib/texture.cpp
+++ b/korlib/texture.cpp
@@ -266,14 +266,19 @@ static int _generate_detail_map(pyGLTexture* self, uint8_t* buf, size_t bufsz, G
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;
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &height);
GLenum fmt = bgra ? GL_BGRA_EXT : GL_RGBA;
- if (!quiet)
- PySys_WriteStdout(" Level #%i: %ix%i\n", level, width, height);
+ // Print out the debug message
+ 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;
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 char* kwlist[] = { _pycs("level"), _pycs("calc_alpha"), _pycs("bgra"),
- _pycs("quiet"), _pycs("fast"), NULL };
+ _pycs("report"), _pycs("fast"), NULL };
GLint level = 0;
bool calc_alpha = false;
bool bgra = false;
- bool quiet = false;
+ PyObject* report = nullptr;
bool fast = false;
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ibbbb", kwlist, &level, &calc_alpha, &bgra, &quiet, &fast)) {
- PyErr_SetString(PyExc_TypeError, "get_level_data expects an optional int, bool, bool, bool, bool");
+ 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, obejct, bool");
return NULL;
}
- _LevelData data = _get_level_data(self, level, bgra, quiet);
+ _LevelData data = _get_level_data(self, level, bgra, report);
if (fast)
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*) {
- _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) {
if (data.m_data[i] != 255) {
delete[] data.m_data;
diff --git a/korman/__init__.py b/korman/__init__.py
index 552a43f..5d516e1 100644
--- a/korman/__init__.py
+++ b/korman/__init__.py
@@ -22,7 +22,7 @@ from . import operators
bl_info = {
"name": "Korman",
"author": "Guild of Writers",
- "blender": (2, 77, 0),
+ "blender": (2, 78, 0),
"location": "File > Import-Export",
"description": "Exporter for Cyan Worlds' Plasma Engine",
"warning": "beta",
diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py
index 4e26fe0..842f0a7 100644
--- a/korman/exporter/convert.py
+++ b/korman/exporter/convert.py
@@ -14,6 +14,7 @@
# along with Korman. If not, see .
import bpy
+from ..korlib import ConsoleToggler
from pathlib import Path
from PyHSPlasma import *
import time
@@ -42,19 +43,28 @@ class Exporter:
return Path(self._op.filepath).stem
def run(self):
- with logger.ExportLogger(self._op.filepath) as _log:
- print("Exporting '{}.age'".format(self.age_name))
- start = time.perf_counter()
-
+ log = logger.ExportVerboseLogger if self._op.verbose else logger.ExportProgressLogger
+ with ConsoleToggler(self._op.show_console), log(self._op.filepath) as self.report:
# Step 0: Init export resmgr and stuff
self.mgr = manager.ExportManager(self)
self.mesh = mesh.MeshConverter(self)
- self.report = logger.ExportAnalysis()
self.physics = physics.PhysicsConverter(self)
self.light = rtlight.LightConverter(self)
self.animation = animation.AnimationConverter(self)
self.sumfile = sumfile.SumFile()
+ # Step 0.9: Init the progress mgr
+ self.report.progress_add_step("Collecting Objects")
+ self.report.progress_add_step("Harvesting Actors")
+ if self._op.bake_lighting:
+ etlight.LightBaker.add_progress_steps(self.report)
+ self.report.progress_add_step("Exporting Scene Objects")
+ self.report.progress_add_step("Exporting Logic Nodes")
+ self.report.progress_add_step("Finalizing Plasma Logic")
+ self.report.progress_add_step("Exporting Textures")
+ self.report.progress_add_step("Composing Geometry")
+ self.report.progress_start("EXPORTING AGE")
+
# Step 1: Create the age info and the pages
self._export_age_info()
@@ -91,17 +101,18 @@ class Exporter:
# Step 5.1: Save out the export report.
# If the export fails and this doesn't save, we have bigger problems than
# these little warnings and notices.
+ self.report.progress_end()
self.report.save()
- # And finally we crow about how awesomely fast we are...
- end = time.perf_counter()
- print("\nExported {}.age in {:.2f} seconds".format(self.age_name, end-start))
-
def _bake_static_lighting(self):
- oven = etlight.LightBaker()
+ oven = etlight.LightBaker(self.report)
oven.bake_static_lighting(self._objects)
def _collect_objects(self):
+ self.report.progress_advance()
+ self.report.progress_range = len(bpy.data.objects)
+ inc_progress = self.report.progress_increment
+
# Grab a naive listing of enabled pages
age = bpy.context.scene.world.plasma_age
pages_enabled = frozenset([page.name for page in age.pages if page.enabled])
@@ -136,6 +147,7 @@ class Exporter:
self._objects.append(obj)
elif page not in all_pages:
error.add(page, obj.name)
+ inc_progress()
error.raise_if_error()
def _export_age_info(self):
@@ -163,7 +175,7 @@ class Exporter:
parent = bo.parent
if parent is not None:
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.addChild(so.key)
else:
@@ -187,8 +199,13 @@ class Exporter:
return so.coord.object
def _export_scene_objects(self):
+ self.report.progress_advance()
+ self.report.progress_range = len(self._objects)
+ inc_progress = self.report.progress_increment
+ log_msg = self.report.msg
+
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.
# note the function calls: to export a MESH, it's _export_mesh_blobj
@@ -196,10 +213,10 @@ class Exporter:
try:
export_fn = getattr(self, export_fn)
except AttributeError:
- print("WARNING: '{}' is a Plasma Object of Blender type '{}'".format(bl_obj.name, bl_obj.type))
- print("... And I have NO IDEA what to do with that! Tossing.")
+ self.report.warn("""'{}' is a Plasma Object of Blender type '{}'
+ ... And I have NO IDEA what to do with that! Tossing.""".format(bl_obj.name, bl_obj.type))
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.
# Before we call the export_fn, we need to determine if this object is an actor of any
@@ -211,8 +228,9 @@ class Exporter:
# And now we puke out the 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)
+ inc_progress()
def _export_empty_blobj(self, so, bo):
# We don't need to do anything here. This function just makes sure we don't error out
@@ -227,21 +245,33 @@ class Exporter:
if bo.data.materials:
self.mesh.export_object(bo)
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):
- print("\nChecking Logic Trees...")
- need_to_export = ((name, bo, so) for name, (bo, so) in self.want_node_trees.items()
- if name not in self.node_trees_exported)
+ self.report.progress_advance()
+ self.report.progress_range = len(self.want_node_trees)
+ inc_progress = self.report.progress_increment
+
+ self.report.msg("\nChecking Logic Trees...")
+ need_to_export = [(name, bo, so) for name, (bo, so) in self.want_node_trees.items()
+ if name not in self.node_trees_exported]
+ self.report.progress_value = len(self.want_node_trees) - len(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)
+ inc_progress()
def _harvest_actors(self):
+ self.report.progress_advance()
+ self.report.progress_range = len(self._objects) + len(bpy.data.textures)
+ inc_progress = self.report.progress_increment
+
for bl_obj in self._objects:
for mod in bl_obj.plasma_modifiers.modifiers:
if mod.enabled:
self.actors.update(mod.harvest_actors())
+ inc_progress()
# This is a little hacky, but it's an edge case... I guess?
# We MUST have CoordinateInterfaces for EnvironmentMaps (DCMs, bah)
@@ -251,6 +281,7 @@ class Exporter:
viewpt = envmap.viewpoint_object
if viewpt is not None:
self.actors.add(viewpt.name)
+ inc_progress()
def has_coordiface(self, bo):
if bo.type in {"CAMERA", "EMPTY", "LAMP"}:
@@ -269,7 +300,9 @@ class Exporter:
return False
def _post_process_scene_objects(self):
- print("\nPostprocessing SceneObjects...")
+ self.report.progress_advance()
+ self.report.progress_range = len(self._objects)
+ inc_progress = self.report.progress_increment
mat_mgr = self.mesh.material
for bl_obj in self._objects:
@@ -292,5 +325,5 @@ class Exporter:
for mod in bl_obj.plasma_modifiers.modifiers:
proc = getattr(mod, "post_export", None)
if proc is not None:
- print(" '{}' modifier '{}'".format(bl_obj.name, mod.key_name))
proc(self, bl_obj, sceneobject)
+ inc_progress()
diff --git a/korman/exporter/etlight.py b/korman/exporter/etlight.py
index ab93089..0f1c428 100644
--- a/korman/exporter/etlight.py
+++ b/korman/exporter/etlight.py
@@ -16,6 +16,7 @@
import bpy
from bpy.app.handlers import persistent
+from .logger import ExportProgressLogger
from .mesh import _VERTEX_COLOR_LAYERS
from ..helpers import *
@@ -24,10 +25,27 @@ _NUM_RENDER_LAYERS = 20
class LightBaker:
"""ExportTime Lighting"""
- def __init__(self):
+ def __init__(self, report=None):
self._lightgroups = {}
+ if report is None:
+ self._report = ExportProgressLogger()
+ self.add_progress_steps(self._report)
+ self._report.progress_start("PREVIEWING LIGHTING")
+ self._own_report = True
+ else:
+ self._report = report
+ self._own_report = False
self._uvtexs = {}
+ def __del__(self):
+ if self._own_report:
+ self._report.progress_end()
+
+ @staticmethod
+ def add_progress_steps(report):
+ report.progress_add_step("Searching for Bahro")
+ report.progress_add_step("Baking Static Lighting")
+
def _apply_render_settings(self, toggle, vcols):
render = bpy.context.scene.render
toggle.track(render, "use_textures", False)
@@ -63,7 +81,7 @@ class LightBaker:
def bake_static_lighting(self, objs):
"""Bakes all static lighting for Plasma geometry"""
- print("\nBaking Static Lighting...")
+ self._report.msg("\nBaking Static Lighting...")
bake = self._harvest_bakable_objects(objs)
with GoodNeighbor() as toggle:
@@ -77,43 +95,51 @@ class LightBaker:
return result
def _bake_static_lighting(self, bake, toggle):
+ inc_progress = self._report.progress_increment
+
# Step 0.9: Make all layers visible.
# This prevents context operators from phailing.
bpy.context.scene.layers = (True,) * _NUM_RENDER_LAYERS
# Step 1: Prepare... Apply UVs, etc, etc, etc
- print(" Preparing to bake...")
+ self._report.progress_advance()
+ self._report.progress_range = len(bake)
+ self._report.msg("Preparing to bake...", indent=1)
for key in bake.keys():
if key[0] == "lightmap":
for i in range(len(bake[key])-1, -1, -1):
obj = bake[key][i]
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)
elif key[0] == "vcol":
for i in range(len(bake[key])-1, -1, -1):
obj = bake[key][i]
if not self._prep_for_vcols(obj, toggle):
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)
else:
raise RuntimeError(key[0])
- print(" ...")
+ inc_progress()
+ self._report.msg(" ...")
# Step 2: BAKE!
+ self._report.progress_advance()
+ self._report.progress_range = len(bake)
for key, value in bake.items():
- if not value:
- continue
-
- if key[0] == "lightmap":
- print(" {} Lightmap(s) [H:{:X}]".format(len(value), hash(key)))
- self._bake_lightmaps(value, key[1:])
- elif key[0] == "vcol":
- print(" {} Crap Light(s)".format(len(value)))
- self._bake_vcols(value)
- else:
- raise RuntimeError(key[0])
+ if value:
+ if key[0] == "lightmap":
+ self._report.msg("{} Lightmap(s) [H:{:X}]", len(value), hash(key), indent=1)
+ self._bake_lightmaps(value, key[1:])
+ elif key[0] == "vcol":
+ self._report.msg("{} Crap Light(s)", len(value), indent=1)
+ self._bake_vcols(value)
+ else:
+ raise RuntimeError(key[0])
+ inc_progress()
# Return how many thingos we baked
return sum(map(len, bake.values()))
diff --git a/korman/exporter/logger.py b/korman/exporter/logger.py
index e8dc566..059e04b 100644
--- a/korman/exporter/logger.py
+++ b/korman/exporter/logger.py
@@ -13,64 +13,282 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see .
+from ..korlib import ConsoleToggler
from pathlib import Path
-import sys
+import threading
+import time
-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.
- """
+_HEADING_SIZE = 50
+_MAX_ELIPSES = 3
+_MAX_TIME_UNTIL_ELIPSES = 2.0
- _porting = []
- _warnings = []
+class _ExportLogger:
+ def __init__(self, print_logs, age_path=None):
+ self._porting = []
+ self._warnings = []
+ self._age_path = Path(age_path) if age_path is not None else None
+ self._file = None
+ self._print_logs = print_logs
+ self._time_start_overall = 0
+
+ def __enter__(self):
+ assert self._age_path is not None
+
+ # 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
+ my_path = self._age_path.with_name("{}_export".format(self._age_path.stem)).with_suffix(".log")
+ self._file = open(str(my_path), "w")
+ return self
+
+ def __exit__(self, type, value, traceback):
+ if value is not None:
+ ConsoleToggler().keep_console = True
+ self._file.close()
+ return False
+
+ def msg(self, *args, **kwargs):
+ assert args
+ if self._file is not None:
+ indent = kwargs.get("indent", 0)
+ msg = "{}{}".format(" " * indent, args[0])
+ if len(args) > 1:
+ msg = msg.format(*args[1:], **kwargs)
+ self._file.writelines((msg, "\n"))
+ if self._print_logs:
+ print(msg)
+
+ def port(self, *args, **kwargs):
+ assert args
+ 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 not None:
+ self._file.writelines((msg, "\n"))
+ if self._print_logs:
+ print(msg)
+ self._porting.append(args[0])
+
+
+ def progress_add_step(self, name):
+ pass
+
+ def progress_advance(self):
+ pass
+
+ def progress_complete_step(self):
+ pass
+
+ def progress_end(self):
+ if self._age_path is not None:
+ export_time = time.perf_counter() - self._time_start_overall
+ self.msg("\nExported '{}' in {:.2f}s", self._age_path.name, export_time)
+
+ # Ensure the got dawg thread goes good-bye
+ self._thread.join(timeout=5.0)
+ assert not self._thread.is_alive()
+
+ def progress_increment(self):
+ pass
+
+ def progress_start(self, action):
+ if self._age_path is not None:
+ self.msg("Exporting '{}'", self._age_path.name)
+ self._time_start_overall = time.perf_counter()
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, *args, **kwargs):
+ assert args
+ 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 not None:
+ self._file.writelines((msg, "\n"))
+ if self._print_logs:
+ print(msg)
+ self._warnings.append(args[0])
- def warn(self, message, indent=0):
- self._warnings.append(message)
- print(" " * indent, end="")
- print("WARNING: {}".format(message))
+class ExportProgressLogger(_ExportLogger):
+ def __init__(self, age_path=None):
+ super().__init__(False, age_path)
-class ExportLogger:
- """Yet Another Logger(TM)"""
+ # Long running operations like the Blender bake_image call make it seem like we've hung
+ # because it is difficult to inspect the progress of Blender's internal operators. The best
+ # solution here is to move printing into a thread that can detect long-running ops and display
+ # something visible such as a moving elipsis
+ self._thread = threading.Thread(target=self._progress_thread)
+ self._queued_lines = []
+ self._print_condition = threading.Condition()
+ self._volatile_line = ""
- def __init__(self, ageFile):
- # 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
- my_path = Path(ageFile)
- my_path = my_path.with_name("{}_export".format(my_path.stem)).with_suffix(".log")
- self._file = open(str(my_path), "w")
+ # Progress manager
+ self._progress_alive = False
+ self._progress_steps = []
+ self._step_id = -1
+ self._step_max = 0
+ self._step_progress = 0
+ self._time_start_step = 0
- for i in dir(self._file):
- if not hasattr(self, i):
- setattr(self, i, getattr(self._file, i))
+ def __exit__(self, type, value, traceback):
+ if value is not None:
+ export_time = time.perf_counter() - self._time_start_overall
+ with self._print_condition:
+ self._progress_print_step(done=(self._step_progress == self._step_max), error=True)
+ self._progress_print_line("\nABORTED AFTER {:.2f}s".format(export_time))
+ self._progress_print_heading("ERROR")
+ self._progress_print_line(str(value))
+ self._progress_print_heading()
+ self._progress_alive = False
+ return super().__exit__(type, value, traceback)
- def __enter__(self):
- self._stdout, sys.stdout = sys.stdout, self._file
- self._stderr, sys.stderr = sys.stderr, self._file
+ def progress_add_step(self, name):
+ assert self._step_id == -1
+ self._progress_steps.append(name)
- def __exit__(self, type, value, traceback):
- sys.stdout = self._stdout
- sys.stderr = self._stderr
+ def progress_advance(self):
+ """Advances the progress bar to the next step"""
+ if self._step_id != -1:
+ self._progress_print_step(done=True)
+ assert self._step_id < len(self._progress_steps)
+
+ self._step_id += 1
+ self._step_max = 0
+ self._step_progress = 0
+ self._time_start_step = time.perf_counter()
+ self._progress_print_step()
+
+ def progress_complete_step(self):
+ """Manually completes the current step"""
+ assert self._step_id != -1
+ self._progress_print_step(done=True)
+
+ def progress_end(self):
+ self._progress_print_step(done=True)
+ assert self._step_id+1 == len(self._progress_steps)
+
+ export_time = time.perf_counter() - self._time_start_overall
+ with self._print_condition:
+ if self._age_path is not None:
+ self.msg("\nExported '{}' in {:.2f}s", self._age_path.name, export_time)
+ self._progress_print_line("\nEXPORTED '{}' IN {:.2f}s".format(self._age_path.name, export_time))
+ else:
+ self._progress_print_line("\nCOMPLETED IN {:.2f}s".format(export_time))
+ self._progress_print_heading()
+ self._progress_print_line("")
+ self._progress_alive = False
+
+ # Ensure the got dawg thread goes good-bye
+ self._thread.join(timeout=5.0)
+ assert not self._thread.is_alive()
+
+ def progress_increment(self):
+ """Increments the progress of the current step"""
+ assert self._step_id != -1
+ self._step_progress += 1
+ if self._step_max != 0:
+ self._progress_print_step()
+
+ def _progress_print_line(self, line):
+ with self._print_condition:
+ self._queued_lines.append(line)
+ self._print_condition.notify()
+
+ def _progress_print_volatile(self, line):
+ with self._print_condition:
+ self._volatile_line = line
+ self._print_condition.notify()
- def flush(self):
- self._file.flush()
- self._stdout.flush()
- self._stderr.flush()
+ def _progress_print_heading(self, text=None):
+ if text:
+ num_chars = len(text)
+ border = "-" * int((_HEADING_SIZE - (num_chars + 2)) / 2)
+ pad = " " if num_chars % 2 == 1 else ""
+ line = "{border} {pad}{text} {border}".format(border=border, pad=pad, text=text)
+ self._progress_print_line(line)
+ else:
+ self._progress_print_line("-" * _HEADING_SIZE)
- def write(self, str):
- self._file.write(str)
- self._stdout.write(str)
+ def _progress_print_step(self, done=False, error=False):
+ with self._print_condition:
+ if done:
+ stage = "DONE IN {:.2f}s".format(time.perf_counter() - self._time_start_step)
+ print_func = self._progress_print_line
+ self._progress_print_volatile("")
+ else:
+ if self._step_max != 0 and self._step_progress != 0:
+ stage = "{} of {}".format(self._step_progress, self._step_max)
+ else:
+ stage = ""
+ print_func = self._progress_print_line if error else self._progress_print_volatile
- def writelines(self, seq):
- self._file.writelines(seq)
- self._stdout.writelines(seq)
+ line = "{}\t(step {}/{}): {}".format(self._progress_steps[self._step_id], self._step_id+1,
+ len(self._progress_steps), stage)
+ print_func(line)
+
+ def _progress_get_max(self):
+ return self._step_max
+ def _progress_set_max(self, value):
+ assert self._step_id != -1
+ self._step_max = value
+ self._progress_print_step()
+ progress_range = property(_progress_get_max, _progress_set_max)
+
+ def progress_start(self, action):
+ super().progress_start(action)
+ self._progress_print_heading("Korman")
+ self._progress_print_heading(action)
+ self._progress_alive = True
+ self._thread.start()
+
+ def _progress_thread(self):
+ num_dots = 0
+ while self._progress_alive:
+ with self._print_condition:
+ signalled = self._print_condition.wait(timeout=1.0)
+ print(end='\r')
+
+ # First, we need to print out any queued whole lines.
+ # NOTE: no need to lock anything here as Blender uses CPython (GIL)
+ if self._queued_lines:
+ print(*self._queued_lines, sep='\n')
+ self._queued_lines.clear()
+
+ # Now, we need to print out the current volatile line, if any.
+ if self._volatile_line:
+ print(self._volatile_line, end="")
+
+ # If the proc is long running, let us display some elipses so as to not alarm the user
+ if self._time_start_step != 0:
+ if (time.perf_counter() - self._time_start_step) > _MAX_TIME_UNTIL_ELIPSES:
+ num_dots = 0 if signalled or num_dots == _MAX_ELIPSES else num_dots + 1
+ else:
+ num_dots = 0
+ print('.' * num_dots, end=" " * (_MAX_ELIPSES - num_dots))
+
+ def _progress_get_current(self):
+ return self._step_progress
+ def _progress_set_current(self, value):
+ assert self._step_id != -1
+ self._step_progress = value
+ if self._step_max != 0:
+ self._progress_print_step()
+ progress_value = property(_progress_get_current, _progress_set_current)
+
+
+class ExportVerboseLogger(_ExportLogger):
+ def __init__(self, age_path):
+ super().__init__(True, age_path)
+ self.progress_range = 0
+ self.progress_value = 0
+
+ def __exit__(self, type, value, traceback):
+ if value is not None:
+ export_time = time.perf_counter() - self._time_start_overall
+ self.msg("\nAborted after {:.2f}s", export_time)
+ self.msg("Error: {}", value)
+ return super().__exit__(type, value, traceback)
diff --git a/korman/exporter/material.py b/korman/exporter/material.py
index edeb1b6..f66f52d 100644
--- a/korman/exporter/material.py
+++ b/korman/exporter/material.py
@@ -125,7 +125,7 @@ class MaterialConverter:
def export_material(self, bo, bm):
"""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)
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
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
unique_name = "{}_WaveSet7".format(bm.name)
@@ -215,7 +215,7 @@ class MaterialConverter:
def export_bumpmap_slot(self, bo, bm, hsgmat, slot, idx):
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
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):
if name is None:
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)
if bm is not None and not slot.use_map_normal:
self._propagate_material_settings(bm, layer)
@@ -274,10 +274,10 @@ class MaterialConverter:
for i, uvchan in enumerate(bo.data.uv_layers):
if uvchan.name == slot.uv_layer:
layer.UVWSrc = i
- print(" Using UV Map #{} '{}'".format(i, name))
+ self._report.msg("Using UV Map #{} '{}'", i, name, indent=3)
break
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
xform = hsMatrix44()
@@ -447,7 +447,8 @@ class MaterialConverter:
name = "{}_DynEnvMap".format(viewpt.name)
pl_env = self._mgr.find_object(pl_class, bl=bo, name=name)
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):
pl_env.addTargetNode(self._mgr.find_key(plSceneObject, bl=bo))
pl_env.addMatLayer(layer.key)
@@ -457,7 +458,7 @@ class MaterialConverter:
oRes = bl_env.resolution
eRes = helpers.ensure_power_of_two(oRes)
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
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_opacity_start=layer_props.detail_opacity_start, detail_opacity_stop=layer_props.detail_opacity_stop)
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,]
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)
def _export_texture_type_none(self, bo, layer, texture):
@@ -609,16 +611,20 @@ class MaterialConverter:
"""This exports an externally prepared layer and image"""
key = _Texture(image=image)
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,]
else:
- print(" Found another user of '{}'".format(key))
+ self._report.msg("Found another user of '{}'", key, indent=2)
self._pending[key].append(layer.key)
def finalize(self):
+ self._report.progress_advance()
+ self._report.progress_range = len(self._pending)
+ inc_progress = self._report.progress_increment
+
for key, layers in self._pending.items():
name = str(key)
- print("\n[Mipmap '{}']".format(name))
+ self._report.msg("\n[Mipmap '{}']", name)
image = key.image
oWidth, oHeight = image.size
@@ -628,7 +634,8 @@ class MaterialConverter:
eWidth = helpers.ensure_power_of_two(oWidth)
eHeight = helpers.ensure_power_of_two(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)
# Some basic mipmap settings.
@@ -640,11 +647,11 @@ class MaterialConverter:
with helper as glimage:
if key.mipmap:
numLevels = glimage.num_levels
- print(" Generating mip levels")
+ self._report.msg("Generating mip levels", indent=1)
glimage.generate_mipmap()
else:
numLevels = 1
- print(" Stuffing image data")
+ self._report.msg("Stuffing image data", indent=1)
# Uncompressed bitmaps are BGRA
fmt = compression == plBitmap.kUncompressed
@@ -653,7 +660,7 @@ class MaterialConverter:
# this mipmap for per-page textures :(
data = []
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
image.reload()
@@ -663,9 +670,9 @@ class MaterialConverter:
mgr = self._mgr
pages = {}
- print(" Adding to Layer(s)")
+ self._report.msg("Adding to Layer(s)", indent=1)
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
# If we haven't created this plMipmap in the page (either layer's page or Textures.prp),
@@ -680,6 +687,7 @@ class MaterialConverter:
else:
mipmap = pages[page]
layer.object.texture = mipmap.key
+ inc_progress()
def get_materials(self, bo):
return self._obj2mat.get(bo, [])
@@ -722,6 +730,10 @@ class MaterialConverter:
layer.runtime = utils.color(bm.diffuse_color)
layer.specular = utils.color(bm.specular_color)
+ @property
+ def _report(self):
+ return self._exporter().report
+
def _resize_image(self, image, width, height):
image.scale(width, height)
image.update()
diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py
index 98fb63c..711243c 100644
--- a/korman/exporter/mesh.py
+++ b/korman/exporter/mesh.py
@@ -174,11 +174,15 @@ class MeshConverter:
def finalize(self):
"""Prepares all baked Plasma geometry to be flushed to the disk"""
+ self._report.progress_advance()
+ self._report.progress_range = len(self._dspans)
+ inc_progress = self._report.progress_increment
+ log_msg = self._report.msg
+ log_msg("\nFinalizing Geometry")
for loc in self._dspans.values():
for dspan in loc.values():
- print("\n[DrawableSpans '{}']".format(dspan.key.name))
- print(" Composing geometry data")
+ log_msg("[DrawableSpans '{}']", dspan.key.name, indent=1)
# This mega-function does a lot:
# 1. Converts SourceSpans (geospans) to Icicles and bakes geometry into plGBuffers
@@ -186,10 +190,7 @@ class MeshConverter:
# 3. Builds the plSpaceTree
# 4. Clears the SourceSpans
dspan.composeGeometry(True, True)
-
- # Might as well say something else just to fascinate anyone who is playing along
- # at home (and actually enjoys reading these lawgs)
- print(" Bounds and SpaceTree in the saddle")
+ inc_progress()
def _export_geometry(self, bo, mesh, materials, geospans):
geodata = [_GeoData(len(mesh.vertices)) for i in materials]
@@ -422,7 +423,8 @@ class MeshConverter:
_diindices = {}
for geospan, pass_index in geospans:
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)
if dspan not in _diindices:
_diindices[dspan] = [idx,]
@@ -497,3 +499,7 @@ class MeshConverter:
@property
def _mgr(self):
return self._exporter().mgr
+
+ @property
+ def _report(self):
+ return self._exporter().report
diff --git a/korman/exporter/rtlight.py b/korman/exporter/rtlight.py
index d1790ce..c249218 100644
--- a/korman/exporter/rtlight.py
+++ b/korman/exporter/rtlight.py
@@ -43,19 +43,19 @@ class LightConverter:
# If you change these calculations, be sure to update the AnimationConverter!
intens, attenEnd = self.convert_attenuation(bl)
if bl.falloff_type == "CONSTANT":
- print(" Attenuation: No Falloff")
+ self._report.msg("Attenuation: No Falloff", indent=2)
pl.attenConst = intens
pl.attenLinear = 0.0
pl.attenQuadratic = 0.0
pl.attenCutoff = attenEnd
elif bl.falloff_type == "INVERSE_LINEAR":
- print(" Attenuation: Inverse Linear")
+ self._report.msg("Attenuation: Inverse Linear", indent=2)
pl.attenConst = 1.0
pl.attenLinear = self.convert_attenuation_linear(intens, attenEnd)
pl.attenQuadratic = 0.0
pl.attenCutoff = attenEnd
elif bl.falloff_type == "INVERSE_SQUARE":
- print(" Attenuation: Inverse Square")
+ self._report.msg("Attenuation: Inverse Square", indent=2)
pl.attenConst = 1.0
pl.attenLinear = 0.0
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))
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.depth = bl.size if bl.shape == "SQUARE" else bl.size_y
pl.height = bl.plasma_lamp.size_height
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)
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)
# Spot lights have a few more things...
@@ -102,7 +102,7 @@ class LightConverter:
pl.falloff = 1.0
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):
bl_light = bo.data
@@ -132,18 +132,18 @@ class LightConverter:
# Apply the colors
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)
else:
- print(" Diffuse: OFF")
+ self._report.msg("Diffuse: OFF", indent=2)
pl_light.diffuse = hsColorRGBA(0.0, 0.0, 0.0, energy)
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.specular = hsColorRGBA(*spec_color)
else:
- print(" Specular: OFF")
+ self._report.msg("Specular: OFF", indent=2)
pl_light.specular = hsColorRGBA(0.0, 0.0, 0.0, energy)
rtlamp = bl_light.plasma_lamp
@@ -202,7 +202,7 @@ class LightConverter:
# 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.
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)
state = layer.state
@@ -243,7 +243,7 @@ class LightConverter:
def find_material_light_keys(self, bo, bm):
"""Given a blender material, we find the keys of all matching Plasma RT Lights.
NOTE: We return a tuple of lists: ([permaLights], [permaProjs])"""
- print(" Searching for runtime lights...")
+ self._report.msg("Searching for runtime lights...", indent=1)
permaLights = []
permaProjs = []
@@ -272,16 +272,17 @@ class LightConverter:
break
else:
# 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
# This is probably where PermaLight vs PermaProj should be sorted out...
pl_light = self.get_light_key(obj, lamp, None)
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)
else:
- print(" [{}] PermaLight '{}'".format(lamp.type, obj.name))
+ self._report.msg("[{}] PermaLight '{}'", lamp.type, obj.name, indent=2)
permaLights.append(pl_light)
return (permaLights, permaProjs)
@@ -308,3 +309,7 @@ class LightConverter:
@property
def mgr(self):
return self._exporter().mgr
+
+ @property
+ def _report(self):
+ return self._exporter().report
diff --git a/korman/korlib/__init__.py b/korman/korlib/__init__.py
index 77b9457..7d1cfcc 100644
--- a/korman/korlib/__init__.py
+++ b/korman/korlib/__init__.py
@@ -71,4 +71,5 @@ except ImportError:
size = stream.readInt()
return (header, size)
else:
+ from .console import ConsoleToggler
from .texture import TEX_DETAIL_ALPHA, TEX_DETAIL_ADD, TEX_DETAIL_MULTIPLY
diff --git a/korman/korlib/console.py b/korman/korlib/console.py
new file mode 100644
index 0000000..d84106e
--- /dev/null
+++ b/korman/korlib/console.py
@@ -0,0 +1,83 @@
+# This file is part of Korman.
+#
+# Korman is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Korman is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Korman. If not, see .
+
+import bpy
+import ctypes
+import math
+import sys
+
+class ConsoleToggler:
+ _instance = None
+
+ def __init__(self, want_console=None):
+ if want_console is not None:
+ self._console_wanted = want_console
+
+ def __new__(cls, want_console=None):
+ if cls._instance is None:
+ assert want_console is not None
+ cls._instance = object.__new__(cls)
+ cls._instance._console_was_visible = cls.is_console_visible()
+ cls._instance._console_wanted = want_console
+ cls._instance._context_active = False
+ cls._instance.keep_console = False
+ return cls._instance
+
+ def __enter__(self):
+ if self._context_active:
+ raise RuntimeError("ConsoleToggler context manager is not reentrant")
+ self._console_visible = self.is_console_visible()
+ self._context_active = True
+ self.activate_console()
+ return self
+
+ def __exit__(self, type, value, traceback):
+ if not self._console_was_visible and self._console_wanted:
+ if self.keep_console:
+ # Blender thinks the console is currently not visible. However, it actually is.
+ # So, we will fire off the toggle operator to keep Blender's internal state valid
+ bpy.ops.wm.console_toggle()
+ else:
+ self.hide_console()
+ self._context_active = False
+ self.keep_console = False
+ return False
+
+ def activate_console(self):
+ if sys.platform == "win32":
+ hwnd = ctypes.windll.kernel32.GetConsoleWindow()
+ if self._console_wanted:
+ ctypes.windll.user32.ShowWindow(hwnd, 1)
+ if self._console_was_visible or self._console_wanted:
+ ctypes.windll.user32.BringWindowToTop(hwnd)
+
+ @staticmethod
+ def hide_console():
+ if sys.platform == "win32":
+ hwnd = ctypes.windll.kernel32.GetConsoleWindow()
+ ctypes.windll.user32.ShowWindow(hwnd, 0)
+
+ @staticmethod
+ def is_console_visible():
+ if sys.platform == "win32":
+ hwnd = ctypes.windll.kernel32.GetConsoleWindow()
+ return bool(ctypes.windll.user32.IsWindowVisible(hwnd))
+
+ @staticmethod
+ def is_platform_supported():
+ # If you read Blender's source code, GHOST_toggleConsole (the "Toggle System Console" menu
+ # item) is only implemented on Windows. The majority of our audience is on Windows as well,
+ # so I honestly don't see this as an issue...
+ return sys.platform == "win32"
diff --git a/korman/korlib/texture.py b/korman/korlib/texture.py
index 11a289e..194add9 100644
--- a/korman/korlib/texture.py
+++ b/korman/korlib/texture.py
@@ -76,14 +76,14 @@ class GLTexture:
# It will simplify our state tracking a bit.
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
channel from the image color data
"""
width = self._get_tex_param(bgl.GL_TEXTURE_WIDTH, level)
height = self._get_tex_param(bgl.GL_TEXTURE_HEIGHT, level)
- if not quiet:
- print(" Level #{}: {}x{}".format(level, width, height))
+ if report is not None:
+ report.msg("Level #{}: {}x{}", level, width, height, indent=2)
# Grab the image data
size = width * height * 4
@@ -138,7 +138,7 @@ class GLTexture:
@property
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):
if data[i] != 255:
return True
diff --git a/korman/nodes/node_conditions.py b/korman/nodes/node_conditions.py
index 43c078e..d4fec71 100644
--- a/korman/nodes/node_conditions.py
+++ b/korman/nodes/node_conditions.py
@@ -427,18 +427,18 @@ class PlasmaVolumeSensorNode(PlasmaNodeBase, bpy.types.Node):
suffix = "Exit"
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)
logicmod = logicKey.object
logicmod.setLogicFlag(plLogicModifier.kMultiTrigger, True)
logicmod.notify = self.generate_notify_msg(exporter, so, "satisfies")
# 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)
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)
volsens = volKey.object
diff --git a/korman/operators/op_export.py b/korman/operators/op_export.py
index 0a8ad45..d65ee5c 100644
--- a/korman/operators/op_export.py
+++ b/korman/operators/op_export.py
@@ -22,6 +22,7 @@ import pstats
from .. import exporter
from ..properties.prop_world import PlasmaAge
from ..properties.modifiers.logic import game_versions
+from ..korlib import ConsoleToggler
class ExportOperator(bpy.types.Operator):
"""Exports ages for Cyan Worlds' Plasma Engine"""
@@ -45,6 +46,14 @@ class ExportOperator(bpy.types.Operator):
"description": "Version of the Plasma Engine to target",
"default": "pvPots", # This should be changed when moul is easier to target!
"items": game_versions}),
+
+ "verbose": (BoolProperty, {"name": "Display Verbose Log",
+ "description": "Shows the verbose export log in the console",
+ "default": False}),
+
+ "show_console": (BoolProperty, {"name": "Display Log Console",
+ "description": "Forces the Blender System Console open during the export",
+ "default": True}),
}
# This wigs out and very bad things happen if it's not directly on the operator...
@@ -58,6 +67,10 @@ class ExportOperator(bpy.types.Operator):
# The crazy mess we're doing with props on the fly means we have to explicitly draw them :(
layout.prop(age, "version")
layout.prop(age, "bake_lighting")
+ row = layout.row()
+ row.enabled = ConsoleToggler.is_platform_supported()
+ row.prop(age, "show_console")
+ layout.prop(age, "verbose")
layout.prop(age, "profile_export")
def __getattr__(self, attr):
diff --git a/korman/properties/modifiers/render.py b/korman/properties/modifiers/render.py
index 97a7715..dcad670 100644
--- a/korman/properties/modifiers/render.py
+++ b/korman/properties/modifiers/render.py
@@ -427,10 +427,10 @@ class PlasmaVisControl(PlasmaModifierProperties):
else:
this_sv = bo.plasma_modifiers.softvolume
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)
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)
if sv_bo is None:
raise ExportError("'{}': Invalid object '{}' for VisControl soft volume".format(bo.name, self.softvolume))
diff --git a/korman/properties/modifiers/sound.py b/korman/properties/modifiers/sound.py
index 4ed90cf..cce45b3 100644
--- a/korman/properties/modifiers/sound.py
+++ b/korman/properties/modifiers/sound.py
@@ -173,7 +173,7 @@ class PlasmaSound(bpy.types.PropertyGroup):
name = "Sfx-{}_{}".format(so.key.name, self.sound_data)
else:
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)
# If this object is a soft volume itself, we will use our own soft region.