From 80ce28ddc8885e89f18af54ddd29a3e8a92acd20 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 12 Jun 2017 15:18:43 -0400 Subject: [PATCH] Show the console during exports The only major issue with the console based progress solution is that the user would have to remember to press "Toggle System Console" before the export. This button corresponds to the operator `bpy.ops.wm.console_toggle`. Unfortunately, Blender does not expose a way for us to query the console state. So, we have to get nasty and use ctypes to ask the OS if the console window is active. The user may already have it open and hidden behind Blender's UI, after all. This changeset causes the console to open during the export (unless disabled in the export operator). If the console was closed before the export, it closes again once the export is finished, unless there is an error. If the console was open, it remains open. Unfortunately, this only works on Windows. But, according to the source code of `bpy.ops.wm.console_toggle`, Blender's `ghost_toggleConsole` only functions on the Win32 subsystem... SDL, X11, and Cocoa are all no-ops. Future work would include a patch submitted to Blender adding an enum property to the console operator to avoid this code duplication. --- korman/exporter/convert.py | 3 +- korman/exporter/logger.py | 3 ++ korman/korlib/__init__.py | 1 + korman/korlib/console.py | 83 +++++++++++++++++++++++++++++++++++ korman/operators/op_export.py | 8 ++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 korman/korlib/console.py 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")