I have a button created by my script. I wonder how I can display what the system console displays but inside blender's console. CoDEmanX did this by running a script inside it as shown here, but I want to display what happens in the system console inside blender's console.
4 Answers
- First redirect output from Python's
stdout
into your own buffer,
see: How to redirect output from 'bpy.ops.*'? - Then run
bpy.ops.console.scrollback_append
with the console space set using an override, see: https://blender.stackexchange.com/a/31398/55
Hello world example that prints text into the first console found.
import bpy
def console_get():
for area in bpy.context.screen.areas:
if area.type == 'CONSOLE':
for space in area.spaces:
if space.type == 'CONSOLE':
for region in area.regions:
if region.type == 'WINDOW':
return area, space, region
return None, None, None
def console_write(text):
area, space, region = console_get()
if space is None:
return
context_override = bpy.context.copy()
context_override.update({
"space": space,
"area": area,
"region": region,
})
with bpy.context.temp_override(**context_override):
for line in text.split("\n"):
bpy.ops.console.scrollback_append(text=line, type='OUTPUT')
console_write("Hello World")
-
$\begingroup$ Note that console_write() in the answer only allow string input.
def console_write(text=""):
andtext = str(text)
will make it better. $\endgroup$– rintCommented Dec 2, 2023 at 11:17 -
$\begingroup$ @fmnijk I disagree that this is an obvious improvement, by this logic any function that accepts text could accept non text which is converted into text. By convention non of Blender's API's that accept text will coerce non-text into text, I rather make this an explicit action for the caller. $\endgroup$ Commented Dec 3, 2023 at 10:15
-
$\begingroup$ This is not Blender API. This is a workaround to replace Python built-in print to print in python console by using Blender API. I haven't seen any Modern Language has print() or console.log() allow only string. $\endgroup$– rintCommented Dec 3, 2023 at 13:46
-
$\begingroup$ If it is string input only. I think it should be something like
_console_write()
or__console_write()
which is simple and used to built another useful function. $\endgroup$– rintCommented Dec 3, 2023 at 13:52 -
$\begingroup$ Is the fact that this is needed pretty strange? Why can't print() just work like every other DCC? $\endgroup$ Commented Jan 26 at 3:32
Step 1: Redirect the output to the python console.
You can use the following codes written by @batFINGER (https://blender.stackexchange.com/a/93746/101378):
import bpy
from bpy import context
import builtins as __builtin__
def console_print(*args, **kwargs):
for a in context.screen.areas:
if a.type == 'CONSOLE':
c = {}
c['area'] = a
c['space_data'] = a.spaces.active
c['region'] = a.regions[-1]
c['window'] = context.window
c['screen'] = context.screen
s = " ".join([str(arg) for arg in args])
for line in s.split("\n"):
bpy.ops.console.scrollback_append(c, text=line)
def print(*args, **kwargs):
console_print(*args, **kwargs) # to Python Console
__builtin__.print(*args, **kwargs) # to System Console
The codes above will redirect the outputs in System Console to the Python Console.
Step 2: Add the script to the system path which your Blender can find it.
You can run sys.path
in the python console and then it will output a list of system paths which python in Blender can find the imported scripts.
For example, %APPDATA%\Blender Foundation\Blender\2.92\scripts\addons
.
Write the script in step 1 to a seperated file, such as pycl.py
.
Then use from pycl import print
to use the redirected print
in your scripts:
If all you want is a block of code that you can paste at the start of a script, to have all print("...")
calls within the script show up in Blender's visible Python-console panels, you can use this:
import sys
import bpy
class StdOutOverride:
def write(self, text):
sys.__stdout__.write(text) # also send to standard-output (can comment this out)
if text != '\n': # ignore extra stdout.write('\n') call at end of each print()
for line in text.replace('\t', ' ').split('\n'):
for area in bpy.context.screen.areas:
if area.type == 'CONSOLE':
with bpy.context.temp_override(area=area):
bpy.ops.console.scrollback_append(text=line, type='OUTPUT')
sys.stdout = StdOutOverride()
print("Example text which will display within Blender's Python-console panels.")
The above is not 100% robust though (it can error in certain cases, eg. during rendering, and might mess up line-breaks for large text-blobs that require buffering), so this is the longer (but more robust) version: (I put this into a "prelude" script, which I execute prior to executing the target external script)
import sys
import bpy
class StdOutOverride:
buffer = []
def write(self, text):
sys.__stdout__.write(text) # also send to standard-output (can comment this out)
# at end of each call to print(), a basic write('\n') call gets added; when received, print buffer
if text == '\n':
self.print_to_console()
# for actual-content calls, split text into lines, and print all but last (it'll be printed shortly)
else:
for line in text.replace('\t', ' ').split('\n'):
if len(self.buffer) > 0:
self.print_to_console()
self.buffer.append(line)
def print_to_console(self):
buffer_str = ''.join(map(str, self.buffer))
if hasattr(bpy.context, 'screen') and bpy.context.screen:
for area in bpy.context.screen.areas:
if area.type == 'CONSOLE':
with bpy.context.temp_override(area=area):
# need try-catch, since scrollback_append can error in certain cases (eg. when rendering)
try:
bpy.ops.console.scrollback_append(text=buffer_str, type='OUTPUT')
except Exception as ex:
pass
self.buffer = []
sys.stdout = StdOutOverride()
print("Example text which will display within Blender's Python-console panels.")
Thanks to @ideasman42's answer. I did a little modification so that it works more like the default print function in Python. Feel free to override the default print function. It will not affect other scripts in Text Editor.
Note that in the Blender window, there should be an area opened as a Python Console, otherwise it cannot find the area and thus cannot print anything.
import bpy
from contextlib import contextmanager
@contextmanager
def console_override():
try:
area = next(a for a in bpy.context.screen.areas if a.type == 'CONSOLE')
with bpy.context.temp_override(area=area):
yield area
except StopIteration:
yield None
def print(*texts):
text = ' '.join(str(i) for i in texts)
with console_override() as area:
if area is None:
return
for line in text.split("\n"):
bpy.ops.console.scrollback_append(text=line, type='OUTPUT')
-
1$\begingroup$ take note that the way you override context is deprecated in favor of temp_override $\endgroup$– Harry McKenzie ♦Commented Dec 2, 2023 at 15:33
-
$\begingroup$ I edited the code, now it works in Blender 4.2+. $\endgroup$– rintCommented Aug 17 at 16:07