SOLVED Write to C4D console in real time

Hi,

I have a script that takes a long time to complete, and I would like to keep an eye on progress.

Is it possible for print() or WriteConsole() events to immediately be posted to the python console, rather than all posted after the script returns control to c4d? If not, is there another way in the c4d api to accomplish this?

An answer to my initial question, for others who may encounter the same issue. This can be used in lieu of print()

import maxon

def ConsolePrint( *args ):
    # concatenate var args into a single, space separated string suitable for maxon.Loggers
    txt = " ".join(str(i) for i in args)
    maxon.Loggers.Python().Write( maxon.TARGETAUDIENCE.ALL, txt, maxon.MAXON_SOURCE_LOCATION(1), maxon.WRITEMETA.UI_SYNC_DRAW )

if __name__ == "__main__":

   ConsolePrint( "Output" )

I found c4d.CallCommand( 13957 ), which clears the console and posts any buffered lines.

However, what I'd like to do is post the buffered lines without clearing. Is this possible?

Hi @ivodow thanks for reaching out us.

With regard to the issue you've reported, assuming that you're not blocking Cinema 4D main thread, the Python Console is designed to spool, as soon as possible, output to the console window. Can you share an example that can reproduce the issue? It might be in the end just a design flaw rather than a C4D issue.

Best, R

Here is a contrived minimal working example that demonstrates the issue. This is only intended to force the issue to occur. My actual code involves loading multiple >2 GB databases and building particle systems.

Thank you.


import c4d


def long_process():

    fullpath = c4d.storage.LoadDialog( type = c4d.FILESELECTTYPE_IMAGES, title = "Select File > 10MB:" )
    if not fullpath:
        return

    for n in xrange( 10 ):

        cnt = 0
        with open( fullpath, 'rb') as f:
            while( True ):
                ansi_byte = f.read(1)
                if( ansi_byte == b'' ):
                    break
                if( cnt % 100000 == 0 ):
                    print( "Scanned " + str(cnt) + " bytes..." )
                cnt += 1

        print( "Pass: " + str(n) )


if __name__ == "__main__":

    c4d.CallCommand( 13957 )

    print( "Start" )

    long_process()

    print( "End" )

    c4d.EventAdd()

Hi,

I might be misunderstanding something here, but your script looks like it is intended for the script manager. A script manager script is blocking (at least when implemented like this). Which means Cinema will only interpret the sys.stdout file object after the script has ended.

To circumvent this, you could either write some kind of plugin (CommandData would be a good choice), use a dangling asynchronous dialog (which is not recommended) or (ab)use one of the scripting nodes (which probably isn't recommended either). Or you could use c4dpy, which will give you a more traditional interpreter environment.

Cheers,
zipit

hi @ivodow, I've reported this behavior to our developers and we'll look into it in the future cause we think there's room for improving it.

For the time being consider it as a limitation.

As an alternative you can consider having a look here where taking advantage of existing loggers is explained.

Cheers, R

@zipit

Hi, could you elaborate a bit on these suggestions?
I'm tackling with the same problem here from inside my CommandData plugin.

  1. open async dialog
  2. start "long processing" with occasional prints
    --> normal stdout (to console) does not show until the the processing ended
    --> stdout redirected to dialog. the dialog does not show until the processing ended (!)

after opening a TYPE_ASYNC GeDialog, isOpen() returns True even if the window is not open, yet.
How could one halt the script / wait until the dialog is REALLY open?
Is there a way to deal with output buffering and get the printing flushed immediately?

best, index

Hi,

uhh, that would be a lot of guess work on my end with that description. It sounds a bit like you have something in place which blocks the main thread, but to pinpoint what exactly is going wrong in your case, you should open a new thread and post some code.

To be clear: Unless you are constantly blocking the main thread, you should be able to print to the console without any problems.

Cheers,
zipit

@zipit said in Write to C4D console in real time:

To be clear: Unless you are constantly blocking the main thread, you should be able to print to the console without any problems.

yes, all is happening in the main thread, no threading involved.
the user should not be able to change the hierarchy while processing.

i for example traverse the whole object hierarchy and center all axes of objects and nulls.
after that i print "xxx axes centered" and continue with other stuff
the whole process can take 2-3 minutes and it would be nice to see the output "live" not just at the end.

is there no possibility to trigger the console to output the buffered lines?

btw, also with threading the prints show only collected after the thread is finished

t = ThreadedCode()
t.Start()
t.Wait(True)

class ThreadedCode(c4d.threading.C4DThread):
	def __init__(self):
		print "thread init"
	def Main(self):
		print "thread start",
		for i in range(0,100000):
			print i
		print "thread done"

Hi @indexofrefraction this is unfortunately not possible, at least not in python, since Python is always using the main thread, even if you have multiple threads, keep in mind this is not true parallelism more information read Python Programming/Threading. Since internally Python is executed on the main thread, this means the main thread will be in any case busy all the time of the execution. Due to some optimization done in R20 see (Disable default Right-Click Menu)[https://plugincafe.maxon.net/topic/11987/disable-default-right-click-menu] for more information. The console UI is not refreshed all the time when something new is popping but only when Draw events are processed.

Except recreating your own console UI, there is no workaround for it.
Cheers,
Maxime.

@m_adam

tx for those infos maxime!
i'm actually using my own "Console" GeDialog. and i redirect stdout to it.
but i have the same problem as with the real Console.

It would be very interesting to know what "recreating your own console UI" means O:-)

maybe some ConsoleDialog.flush() function would work,
but I don't even know how to wait until that ASYNC ConsoleDialog window is really visible.
Both GeDialog.isOpen() or isVisible() are returning false positives after Open()!

import sys
import time

import c4d
from c4d import gui

class ConsoleDialog(gui.GeDialog):
	ID_TEXTBOX = 1001
	ID_BUTTON = 1002

	def __init__(self, pluginid=0):
		self.text = ""
		self.SetTimer(350)
		self.Open(c4d.DLG_TYPE_ASYNC, pluginid, -2, -2, 400, 300)

	def CreateLayout(self):
		self.SetTitle("ConsoleDialog")
		self.GroupBorderNoTitle(c4d.BORDER_NONE)
		self.GroupBorderSpace(4, 2, 2, 4)
		self.AddMultiLineEditText(self.ID_TEXTBOX, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0, c4d.DR_MULTILINE_WORDWRAP | c4d.DR_MULTILINE_READONLY)
		self.AddButton(self.ID_BUTTON, c4d.BFH_RIGHT | c4d.BFV_CENTER, 100, 15, "OK")
		return True

	def write(self, text=""):
		self.text += str(text)
		self.SetString(self.ID_TEXTBOX, self.text)

	def Timer(self, msg):
		if self.text != self.GetString(self.ID_TEXTBOX):
			self.SetString(self.ID_TEXTBOX, self.text)

	def Command(self, id, msg):
		if id == self.ID_BUTTON: self.Close()
		return True

if __name__ == "__main__":
	cd = ConsoleDialog()
	stdout_bak = sys.stdout
	sys.stdout = cd
	print("Hello World!")
	for i in range(0, 5):
		print(i)
		time.sleep(1)
	sys.stdout = stdout_bak

# Problem 1: ConsoleDialog window does not show until after processing
# Problem 2: Print output (probably) does not show until after processing

An answer to my initial question, for others who may encounter the same issue. This can be used in lieu of print()

import maxon

def ConsolePrint( *args ):
    # concatenate var args into a single, space separated string suitable for maxon.Loggers
    txt = " ".join(str(i) for i in args)
    maxon.Loggers.Python().Write( maxon.TARGETAUDIENCE.ALL, txt, maxon.MAXON_SOURCE_LOCATION(1), maxon.WRITEMETA.UI_SYNC_DRAW )

if __name__ == "__main__":

   ConsolePrint( "Output" )

@ivodow
how is your ConsolePrint() different to a simple print() statement?
in my test i didn''t get a real time output with ConsolePrint()

best, index