10
$\begingroup$

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.

My .blend file can be found here:

$\endgroup$
2
  • $\begingroup$ but inside blender - is too vauge, where exactly? $\endgroup$
    – ideasman42
    Apr 23, 2017 at 9:10
  • $\begingroup$ @ideasman42 question updated as suggested. $\endgroup$
    – Tak
    Apr 23, 2017 at 10:24

4 Answers 4

14
$\begingroup$

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")
$\endgroup$
5
  • $\begingroup$ Note that console_write() in the answer only allow string input. def console_write(text=""): and text = str(text) will make it better. $\endgroup$
    – rint
    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$
    – ideasman42
    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$
    – rint
    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$
    – rint
    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$ Jan 26 at 3:32
3
$\begingroup$

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.

enter image description here

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:

enter image description here

$\endgroup$
2
$\begingroup$

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.")
$\endgroup$
0
$\begingroup$

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.

def console_get():
    for area in bpy.context.screen.areas:
        if area.type == 'CONSOLE':
            for space in area.spaces:
                if space.type == 'CONSOLE':
                    return area, space
    return None, None

def print(*texts):
    text = ' '.join(str(i) for i in texts)
    area, space = console_get()
    if space is None:
        return

    context = bpy.context.copy()
    context.update(dict(
        space=space,
        area=area,
    ))
    for line in text.split("\n"):
        bpy.ops.console.scrollback_append(context, text=line, type='OUTPUT')
$\endgroup$
1
  • 1
    $\begingroup$ take note that the way you override context is deprecated in favor of temp_override $\endgroup$ Dec 2, 2023 at 15:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .