Skip to content

Instantly share code, notes, and snippets.

@tamask
Created August 8, 2018 00:44
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save tamask/0593436d044bfedb21081e3d2d802833 to your computer and use it in GitHub Desktop.
Save tamask/0593436d044bfedb21081e3d2d802833 to your computer and use it in GitHub Desktop.
Route system output (stdout/stderr) of Blender to the app console (put in scripts/startup)
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]
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 Standard 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.template_header()
if context.area.show_menus:
layout.menu("CONSOLE_MT_console")
layout.operator("console.autocomplete", text="Autocomplete")
layout.prop(context.window_manager, 'capture_console_output')
bpy.types.CONSOLE_HT_header.draw = console_header_draw
def register():
pass
def unregister():
pass
reset()
@tamask
Copy link
Author

tamask commented Aug 8, 2018

Put in scripts/startup. Writing to console can be done programmatically:

import log

log.write('Hello World') # same as log.output.write()
log.output.write('Uses default color')
log.input.write('Uses default color')
log.error.write('Uses default color')
log.info.write('Uses info color')

@tin2tin
Copy link

tin2tin commented Jun 19, 2019

A much, much needed feature. Thank you!
I wish this feature was built-in in Blender. Have you considered submitting it?

If you added the operators here: \2.80\scripts\startup\bl_operators\console.py
And the UI here: \2.80\scripts\startup\bl_ui\console.py

And then submit it as a patch?

@EugeneDudavkin
Copy link

EugeneDudavkin commented Aug 5, 2021

Some tweak to append to existing console header and not overwrite it

def console_header_draw(self, context):
    layout = self.layout.row()

#    layout.template_header()

#    if context.area.show_menus:
#        layout.menu("CONSOLE_MT_console")

    layout.operator("console.autocomplete", text="Autocomplete")
    layout.prop(context.window_manager, 'capture_console_output')

#bpy.types.CONSOLE_HT_header.draw = console_header_draw

def register():
    bpy.types.CONSOLE_HT_header.append(console_header_draw)

def unregister():
    bpy.types.CONSOLE_HT_header.remove(console_header_draw)

reset()

@rakete
Copy link

rakete commented Apr 9, 2024

Making it work in Blender 4.1:

with bpy.context.temp_override(window=self.context.get('window'), area=self.context.get('area'), region=self.context.get('region')):
    for l in lines:
        bpy.ops.console.scrollback_append(text=l, type=self.enum)

@rakete
Copy link

rakete commented Apr 9, 2024

Think this improves it:

if hasattr(bpy.context, 'window'):
    with bpy.context.temp_override(**self.context):
        try:
            for l in lines:
                bpy.ops.console.scrollback_append(text=l, type=self.enum)
        except:
            pass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment