diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index 3e87bea..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 @@ -43,7 +44,7 @@ class Exporter: def run(self): log = logger.ExportVerboseLogger if self._op.verbose else logger.ExportProgressLogger - with log(self._op.filepath) as self.report: + 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) diff --git a/korman/exporter/logger.py b/korman/exporter/logger.py index 656819e..059e04b 100644 --- a/korman/exporter/logger.py +++ b/korman/exporter/logger.py @@ -13,6 +13,7 @@ # 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 threading import time @@ -40,6 +41,8 @@ class _ExportLogger: return self def __exit__(self, type, value, traceback): + if value is not None: + ConsoleToggler().keep_console = True self._file.close() return False 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/operators/op_export.py b/korman/operators/op_export.py index a1de7b9..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""" @@ -49,6 +50,10 @@ class ExportOperator(bpy.types.Operator): "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... @@ -62,6 +67,9 @@ 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")