Skip to content

Instantly share code, notes, and snippets.

@EugeneDudavkin
Forked from tamask/log.py
Last active December 10, 2023 09:31
Show Gist options
  • Save EugeneDudavkin/f4f0b05d31759384375f6942dd48ecc8 to your computer and use it in GitHub Desktop.
Save EugeneDudavkin/f4f0b05d31759384375f6942dd48ecc8 to your computer and use it in GitHub Desktop.
Route system output (stdout/stderr) of Blender to the app console (put in scripts/startup) (to work in 3.6+ as addon)
# Add-on info
bl_info = {
"name": "Enhancing the console",
"author": "Tamask",
"version": (0, 0, 1),
"blender": (2, 83, 0),
"location": "Python Console",
"description": "Print logs in python console.",
"doc_url": "https://gist.github.com/tamask/0593436d044bfedb21081e3d2d802833#file-log-py",
"tracker_url": "",
"category": "Python Console"
}
import os
import sys
import bpy
output = None
input = None
info = None
error = None
write = None
def reset():
global output
global input
global info
global error
global write
output = Stream('OUTPUT')
input = Stream('INPUT')
info = Stream('INFO')
error = Stream('ERROR')
write = output.write
class Stream:
def __init__(self, enum, context=None):
self.text = ''
self.enum = enum
self.line = None
self.newline = False
self.context = context
if not self.context:
self.context = get_console_context()
def write(self, text):
try:
if not self.context:
self.context = get_console_context()
if self.context:
self.console = getattr(
self.context, 'space_data',
self.context['space_data'])
self.scrollback = self.console.scrollback
else:
if self.enum == 'ERROR':
return sys.__stderr__.write(text)
else:
return sys.__stdout__.write(text)
line = self.line
sb = self.scrollback
if len(sb) == 0:
return
text = str(text)
lines = text.replace('\r\n', '\n').split('\n')
if ((line and not
line == sb[len(sb) - 1]) or self.newline):
self.newline = False
self.line = line = None
if line:
line.body += lines[0]
lines = lines[1:]
if lines and lines[len(lines) - 1] == '':
self.newline = True
lines = lines[:-1]
if bpy.app.version > (3, 6, 0):
with bpy.context.temp_override(**self.context):
for l in lines:
bpy.ops.console.scrollback_append(text=l, type=self.enum)
if bpy.app.version < (3, 6, 0):
for l in lines:
bpy.ops.console.scrollback_append(
self.context, text=l, type=self.enum)
self.line = sb[len(sb) - 1]
except:
import traceback
traceback.print_exc(file=sys.__stderr__)
# no-op interface
def flush(self):
pass
def tell(self):
return 0
def read(self, size=-1):
return ''
def seek(self, offset, whence=0):
pass
def truncate(self, size=None):
pass
@property
def name(self):
return self.enum
def get_console_context():
# do nothing while _RestrictContext
if not hasattr(bpy.context, 'window'):
return {}
context = {
'window': bpy.context.window,
}
for screen in bpy.data.screens:
for area in screen.areas:
if area.type == 'CONSOLE':
context['area'] = area
context['screen'] = screen
for space in area.spaces:
if space.type == 'CONSOLE':
context['space_data'] = space
for region in area.regions:
if region.type == 'WINDOW':
context['region'] = region
return context
return {}
def capture_streams():
sys.stdout = info
sys.stderr = error
def release_streams():
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
_console_draw_handle = None
@bpy.app.handlers.persistent
def install(*args, **kwargs):
global _console_draw_handle
wm = bpy.context.window_manager
if wm.capture_console_output:
reset()
context = get_console_context()
if context:
space = context['space_data']
if _console_draw_handle:
space.draw_handler_remove(_console_draw_handle, 'WINDOW')
_console_draw_handle = space.draw_handler_add(capture_streams, tuple(), 'WINDOW', 'POST_PIXEL')
capture_streams()
def uninstall(*args, **kwargs):
global _console_draw_handle
context = get_console_context()
if context:
space = context['space_data']
if _console_draw_handle:
space.draw_handler_remove(_console_draw_handle, 'WINDOW')
_console_draw_handle = None
release_streams()
def toggle_capture(self, context):
if self.capture_console_output:
install()
else:
uninstall()
bpy.types.WindowManager.capture_console_output = bpy.props.BoolProperty(
name='Capture System Output', default=False, update=toggle_capture,
description='Route system output (stdout/stderr) to this console',)
def console_header_draw(self, context):
layout = self.layout.row()
layout.operator("console.autocomplete", text="Autocomplete")
layout.prop(context.window_manager, 'capture_console_output')
def register():
bpy.types.CONSOLE_HT_header.append(console_header_draw)
def unregister():
bpy.types.CONSOLE_HT_header.remove(console_header_draw)
reset()
if __name__ == "__main__":
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment