5
$\begingroup$

Use case : Attempting to create a Logger Utility Operator to allow logging to the Info View.

If I have an operator and call it via the UI, using the report() function, the output will appear in the Info View.

When I create another operator to call the first, the report() function doesn't output to the Info View.

Is there anyway to have this work with or an alternative method to report?

I have included sample code. - A base operator that calls report() with a provided logging level and message. - A Forwarding operator which calls the base operator. - UI panel in the scene tab, allow execution of the the two operators.

'''Test Info reporting'''

import bpy
from bpy.props import StringProperty, IntProperty

class LoggingOperator(bpy.types.Operator):
    """Log a message to the Info View"""
    bl_idname = "info.log"
    bl_label = "Direct Log message -> Info View"
    bl_options = {'INTERNAL'}

    lookup = ['DEBUG', 'INFO', 'WARNING', 'ERROR'] 
    level = bpy.props.IntProperty(name="Level", default=1)
    message = bpy.props.StringProperty(name="Message", default="Reporting : Base message")

    def execute(self, context):
        print("Base operator called")
        log = {self.lookup[self.level]}
        self.report(log, self.message)
        return {'FINISHED'}

class ForwardLoggingOperator(bpy.types.Operator):
    """Call an operator that logs stuff"""
    bl_idname = "info.log_1"
    bl_label = "Indirect Log > Calls Operator"
    bl_options = {'INTERNAL'}

    def execute(self, context):
        print("Forwarding Operator called")
        bpy.ops.info.log(level=1, message="Forward : Redirected message")
        return {'FINISHED'}

class ScenePanel(bpy.types.Panel):
    bl_label = "Test Scene Panel"

    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

    @classmethod
    def poll(cls, context):
        return True        

    def draw(self, context):      
        layout = self.layout
        row = layout.column()
        row.operator("info.log")
        row.operator("info.log_1")

bpy.utils.register_class(LoggingOperator)
bpy.utils.register_class(ForwardLoggingOperator)
bpy.utils.register_class(ScenePanel)
$\endgroup$
  • 2
    $\begingroup$ why you don't want to use self.report directly ? $\endgroup$ – Chebhou Apr 27 '16 at 1:25
  • 1
    $\begingroup$ @Chebhou - The context is that we currently have a large modularised plugin codebase which passes around the operator reference just to access the report functionality. Invoking an external operator would enable us to consolidate our logging functionality, (potentially then usable by our other plugins) and decouple a lot of the code. An alternative approach is to store the operator reference globally such that can be invoked at any time during execution, but I didn't have much success there either. So....open to this alternate solutions. Let me know if further clarification is req $\endgroup$ – neomonkeus Apr 27 '16 at 9:26
2
+25
$\begingroup$

You may want to fake the report banner by creating your own and appending it to the Info_header, while having a timer operator manage it ( show and hide, set the message ) as in the following example :

import bpy
from bpy.props import StringProperty, IntProperty

#properties to pass and store the message
bpy.types.Scene.message = bpy.props.StringProperty()
bpy.types.Scene.icon    = bpy.props.StringProperty()

#fake report banner
def report(self, context):
    self.layout.label(text=context.scene.message, icon=context.scene.icon, icon_value=0)

#timer modal operator
class LoggingOperator(bpy.types.Operator):
    """Log a message to the Info View"""
    bl_idname = "info.log"
    bl_label = "Direct Log message -> Info View"

    _timer = None
    lookup = ['DEBUG', 'INFO', 'WARNING', 'ERROR'] 
    level = bpy.props.IntProperty(name="Level", default=1)
    message = bpy.props.StringProperty(name="Message", default="Reporting : Base message")

    def modal(self, context, event):
        if event.type == 'TIMER':
            self.cancel(context)
            return {'FINISHED'}
        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(2, context.window)
        wm.modal_handler_add(self)

        context.scene.icon = self.lookup[self.level]
        context.scene.message = self.message
        bpy.types.INFO_HT_header.append(report)  
        bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) #force UI redraw
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)
        bpy.types.INFO_HT_header.remove(report)
        bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) #force UI redraw


class ForwardLoggingOperator(bpy.types.Operator):
    """Call an operator that logs stuff"""
    bl_idname = "info.log_1"
    bl_label = "Indirect Log > Calls Operator"
    bl_options = {'INTERNAL'}

    def execute(self, context):
        print("Forwarding Operator called")
        bpy.ops.info.log(level=1, message="Forward : Redirected message")
        print(context.active_operator )
        return {'FINISHED'}


class ScenePanel(bpy.types.Panel):
    bl_label = "Test Scene Panel"

    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

    @classmethod
    def poll(cls, context):
        return True        

    def draw(self, context):      
        layout = self.layout
        row = layout.column()
        layout.template_reports_banner()
        row.operator("info.log")
        row.operator("info.log_1")

bpy.utils.register_class(LoggingOperator)
bpy.utils.register_class(ForwardLoggingOperator)
bpy.utils.register_class(ScenePanel)
$\endgroup$
0
$\begingroup$

As far as I know, info is never shown on screen for report() commands that are not directly executed from the operator. If you try to include a command like bpy.ops.mesh.remove_doubles() that usually prints info, it won't show up if you wrap it in another operator. Supposedly this is to omit "unintended" prints from sub-operators that you access from your operator and give each operator the control over their info printing.

I'm not sure if this functionality can be overridden with some variables or changes to the source would be needed but it's probable you need to do all the report() from top level if you wish to use that part of UI to print info.

$\endgroup$
  • 1
    $\begingroup$ That is what the actual current implementation does in the real codebase. Due to the modular nature of the code, the operator ref is passed around the resulting in coupling and fragmeneted logging logic, which I want to undo. $\endgroup$ – neomonkeus Apr 27 '16 at 16:49
  • $\begingroup$ Yes I understand but how essential is it to make it print in the UI, in the info row, since it is actually correctly logging everything in the command console nonetheless? As far as on-screen info is concerned, shouldn't you be aiming at one unique-ish report() per operator run ideally where top level report() would make sense? $\endgroup$ – kheetor Apr 27 '16 at 17:03
  • $\begingroup$ Actually the intention is to log multiple messages to the report view, we already do this in the addon (scene io). The ability to do this would allow us to unify the logging system and enable us to addresses usability issues we currently have by having with a single location for standardised/prettified "user friendly" message. We also log in more verbose to the console as required. $\endgroup$ – neomonkeus Apr 27 '16 at 17:21
  • $\begingroup$ Well what I did in similar case having first ruled out report() and blender command console as too unreadable and uncontrollable was to actually write my own dedicated logging to a certain text data in Blender and show that data in a text editor window if any text editor window was open. Not as usable but it made it possible to print multiline messages in longer wording, timestamps etc that altogether made it somewhat usable despite the shortcomings. $\endgroup$ – kheetor Apr 27 '16 at 17:27
  • $\begingroup$ Yeah, usability is the main draw to keep with the Info View. If the above was possible then functionality like you mentioned to dump logs to text file could be easily implemented in the "Logging" module. $\endgroup$ – neomonkeus Apr 27 '16 at 17:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.