From b5d3f87c66026d7182d402c921d989da18819a21 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 28 Feb 2019 20:11:35 -0500 Subject: [PATCH] Fix #137 Unfortunately, this does not prevent "DAG zero..." from appearing period. Rather, it just overwrites any junk printed to the console during the export. The ANSI version is rather limited compared to the Windows version and completely untested... --- korman/exporter/logger.py | 16 ++++++--- korman/korlib/__init__.py | 2 +- korman/korlib/console.py | 75 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/korman/exporter/logger.py b/korman/exporter/logger.py index 8118d74..9089dee 100644 --- a/korman/exporter/logger.py +++ b/korman/exporter/logger.py @@ -13,7 +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 ..korlib import ConsoleCursor, ConsoleToggler from .explosions import NonfatalExportError from pathlib import Path import threading @@ -142,6 +142,7 @@ class ExportProgressLogger(_ExportLogger): # 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._cursor = ConsoleCursor() self._thread = threading.Thread(target=self._progress_thread) self._queued_lines = [] self._print_condition = threading.Condition() @@ -283,6 +284,8 @@ class ExportProgressLogger(_ExportLogger): def _progress_thread(self): num_dots = 0 + self._cursor.update() + while self._progress_alive: with self._print_condition: signalled = self._print_condition.wait(timeout=1.0) @@ -290,12 +293,16 @@ class ExportProgressLogger(_ExportLogger): # 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() + with self._cursor: + 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: + # On Windows, if we clear the line, the volatile line is nuked as well. + # Probably a race condition in the Win32 console host. + self._cursor.reset() print(self._volatile_line, end="") # If the proc is long running, let us display some elipses so as to not alarm the user @@ -305,6 +312,7 @@ class ExportProgressLogger(_ExportLogger): else: num_dots = 0 print('.' * num_dots, end=" " * (_MAX_ELIPSES - num_dots)) + self._cursor.update() def _progress_get_current(self): return self._step_progress diff --git a/korman/korlib/__init__.py b/korman/korlib/__init__.py index 78a0fe9..70f717b 100644 --- a/korman/korlib/__init__.py +++ b/korman/korlib/__init__.py @@ -72,7 +72,7 @@ else: from _korlib import * finally: - from .console import ConsoleToggler + from .console import ConsoleCursor, ConsoleToggler from .python import * from .texture import TEX_DETAIL_ALPHA, TEX_DETAIL_ADD, TEX_DETAIL_MULTIPLY diff --git a/korman/korlib/console.py b/korman/korlib/console.py index d84106e..f771b68 100644 --- a/korman/korlib/console.py +++ b/korman/korlib/console.py @@ -18,6 +18,81 @@ import ctypes import math import sys +if sys.platform == "win32": + class _Coord(ctypes.Structure): + _fields_ = [("x", ctypes.c_short), + ("y", ctypes.c_short)] + class _SmallRect(ctypes.Structure): + _fields_ = [("Left", ctypes.c_short), + ("Top", ctypes.c_short), + ("Right", ctypes.c_short), + ("Bottom", ctypes.c_short),] + class _ConsoleScreenBufferInfo(ctypes.Structure): + _fields_ = [("dwSize", _Coord), + ("dwCursorPosition", _Coord), + ("wAttributes", ctypes.c_ushort), + ("srWindow", _SmallRect), + ("dwMaximumWindowSize", _Coord)] + + class _ConsoleCursor: + def __init__(self): + self._handle = ctypes.windll.kernel32.GetStdHandle(-11) + self.position = _Coord(0, 0) + + def __del__(self): + ctypes.windll.kernel32.CloseHandle(self._handle) + + @property + def _screen_buffer_info(self): + info = _ConsoleScreenBufferInfo() + ctypes.windll.kernel32.GetConsoleScreenBufferInfo(self._handle, ctypes.pointer(info)) + return info + + def clear(self): + info = self._screen_buffer_info + curPos = info.dwCursorPosition + num_cols = curPos.y - self.position.y + num_rows = curPos.x - self.position.x + num_chars = (info.dwSize.x * num_cols) + num_rows + if num_chars: + nWrite = ctypes.c_ulong() + empty_char = ctypes.c_char(b' ') + ctypes.windll.kernel32.FillConsoleOutputCharacterA(self._handle, empty_char, + num_chars, self.position, + ctypes.pointer(nWrite)) + + def reset(self): + ctypes.windll.kernel32.SetConsoleCursorPosition(self._handle, self.position) + + def update(self): + info = _ConsoleScreenBufferInfo() + ctypes.windll.kernel32.GetConsoleScreenBufferInfo(self._handle, ctypes.pointer(info)) + self.position = info.dwCursorPosition +else: + class _ConsoleCursor: + def clear(self): + # Only clears the current line, unfortunately. + sys.stdout.write("\x1B[K") + + def reset(self): + # Forcibly clears the line after resetting, just in case more + # than one junk line was printed from somewhere else. + sys.stdout.write("\x1B[u\x1B[K") + + def update(self): + sys.stdout.write("\x1B[s") + + +class ConsoleCursor(_ConsoleCursor): + def __enter__(self): + self.clear() + self.reset() + return self + + def __exit__(self, type, value, traceback): + self.update() + + class ConsoleToggler: _instance = None