1
$\begingroup$

I'm writing a script that contains both an operator and a panel. Both the operator and the panel are created in the script's register() method. In my panel's draw() method, I'd like to be able to get a reference to the operator object so I can access its properties. Is there a way to look up the operator object using its ID?

--- Edit

To elaborate on the above - I'm trying to create an object picker field. The idea is that I have a string field called 'target'. I want my NormalToolPropsPanel panel to display a string field representing the target field of my operator. Then when the user clicks on the eyedropper icon, they can pick an object in the scene and its name will be filled into the field.

class ModalDrawOperator(bpy.types.Operator):
    """Adjust normals"""
    bl_idname = "kitfox.normal_tool"
    bl_label = "Normal Tool Kitfox"


    prop_brush_type : bpy.props.EnumProperty(
        items=(
            ('FIXED', "Fixed", "Normals are in a fixed direction"),
            ('ATTRACT', "Attract", "Normals point toward target object"),
            ('REPEL', "Repel", "Normals point away from target object")
        ),
        default='FIXED'
    )
    
    prop_strength : bpy.props.FloatProperty(
        name="Strength", description="Amount to adjust mesh normal", default = 1, min=0, max = 1
    )

    prop_normal = bpy.props.FloatVectorProperty(name="Normal", description="Direction of normal in Fixed mode", default = (0, 1, 0))
    prop_target = bpy.props.StringProperty(name="Target", description="Object Attract and Repel mode reference", default="")
    

    def mouse_down(self, context, event):
        #Do the work here
        pass

    def modal(self, context, event):
        context.area.tag_redraw()

        if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
            # allow navigation
            return {'PASS_THROUGH'}

        elif event.type == 'MOUSEMOVE':
            pass
        elif event.type == 'LEFTMOUSE':
            self.mouse_down(context, event)
            pass

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            print("norm tool cancelled")
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
            # the arguments we pass the the callback
            args = (self, context)
            # Add the region OpenGL drawing callback
            # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
            self._context = context
            self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback, args, 'WINDOW', 'POST_VIEW')

            self.mouse_path = []

            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}

#------------------------

class NTTargetPickerOperator(bpy.types.Operator):
    """Pick object with the mouse"""
    bl_idname = "kitfox.nt_pick_target_object"
    bl_label = "Normal Tool Pick Target Object Kitfox"

    def mouse_down(self, context, event):
        mouse_pos = (event.mouse_region_x, event.mouse_region_y)

        ctx = bpy.context

        region = context.region
        rv3d = context.region_data
        view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, mouse_pos)
        ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, mouse_pos)

        viewlayer = bpy.context.view_layer
        result, location, normal, index, object, matrix = context.scene.ray_cast(viewlayer.depsgraph, ray_origin, view_vector)
        
        if result:
            print("--picked " + object.name)

            #This doesn't work - I want to set the property 'target' on my operator
            bpy.ops.kitfox.normal_tool["prop_target.set"] = object.name
            

    def modal(self, context, event):

        if event.type == 'LEFTMOUSE':
            self.mouse_down(context, event)
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            print("pick target object cancelled")
            return {'CANCELLED'}
        else:
            return {'PASS_THROUGH'}

    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
            # the arguments we pass the the callback
            args = (self, context)
            # Add the region OpenGL drawing callback
            # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
            self._context = context
            self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback, args, 'WINDOW', 'POST_VIEW')

            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}

#------------------------

class NormalToolPropsPanel(bpy.types.Panel):

    """Properties Panel for the Normal Tool on tool shelf"""
    bl_label = "Normal Tool Properties Panel"
    bl_idname = "3D_VIEW_PT_normal_tool_props"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'

        

    def draw(self, context):
        layout = self.layout

        obj = context.object

        props = layout.operator(ModalDrawOperator.bl_idname)

        row = layout.row()

        #Not working - I want to refer to the property in my operator
        row.prop(bpy.ops.kitfox.normal_tool, "prop_target", expand=True)

        pickOp = layout.operator(NTTargetPickerOperator.bl_idname, icon="EYEDROPPER")

#------------------------


def register():
    bpy.utils.register_class(NTTargetPickerOperator)
    bpy.utils.register_class(ModalDrawOperator)
    bpy.utils.register_class(NormalToolPropsPanel)



def unregister():
    bpy.utils.unregister_class(NTTargetPickerOperator)
    bpy.utils.unregister_class(ModalDrawOperator)
    bpy.utils.unregister_class(NormalToolPropsPanel)


if __name__ == "__main__":
    register()

$\endgroup$
7
  • $\begingroup$ Related blender.stackexchange.com/questions/203747/… or look at the code of a number of importers in recent versions, where it has become common to use a panel referencing context.active_operator $\endgroup$
    – batFINGER
    Jan 23 '21 at 2:48
  • $\begingroup$ I looked at the linked comment, but saw nothing about looking up an operator by id. Also, the operator will not necessarily be the active operator. $\endgroup$
    – kitfox
    Jan 23 '21 at 3:06
  • 1
    $\begingroup$ Can you elaborate what you're trying to do with your script (what it's for)?, are you trying to imitate functionality you saw in blender? $\endgroup$ Jan 23 '21 at 4:09
  • $\begingroup$ Please add some detail and clarity to question. In a panel draw method props = self.layout.operator("foo.bar") is a reference to its properties, which can be set in draw method with props.prop = 33 , trying to use layout.prop(props, "prop") however will not work. (Given your since deleted answer) Please elaborate on what is the expectation here. $\endgroup$
    – batFINGER
    Jan 23 '21 at 4:14
  • $\begingroup$ Thankyou for the edits. Only ID (and bones) objects can have custom properties. ID objects have bpy.types.ID as a subclass. An operator bpy.types.Operator is not an ID object. AFAICT this could be replaced by having a "kitfox tools" property group with the target object as a pointer property to bpy.types.Object which can be set via eyedropper see blender.stackexchange.com/a/159155/15543 $\endgroup$
    – batFINGER
    Jan 23 '21 at 5:56
2
$\begingroup$

Make a settings group and use a pointer property for target object

enter image description here

With regard to edit

To elaborate on the above - I'm trying to create an object picker field. The idea is that I have a string field called 'target'. I want my NormalToolPropsPanel panel to display a string field representing the target field of my operator. Then when the user clicks on the eyedropper icon, they can pick an object in the scene and its name will be filled into the field.

as commented feel this is better done with a pointer property.

import bpy
from bpy_extras import view3d_utils


class NormalToolSettings(bpy.types.PropertyGroup):
    brush_type : bpy.props.EnumProperty(
        items=(
            ('FIXED', "Fixed", "Normals are in a fixed direction"),
            ('ATTRACT', "Attract", "Normals point toward target object"),
            ('REPEL', "Repel", "Normals point away from target object")
        ),
        default='FIXED'
    )
    
    strength : bpy.props.FloatProperty(
        name="Strength", description="Amount to adjust mesh normal", default = 1, min=0, max = 1
    )

    normal : bpy.props.FloatVectorProperty(name="Normal", description="Direction of normal in Fixed mode", default = (0, 1, 0))
    target : bpy.props.PointerProperty(type=bpy.types.Object)
        

#---------------------------

class NormalToolPropsPanel(bpy.types.Panel):

    """Properties Panel for the Normal Tool on tool shelf"""
    bl_label = "Normal Tool Properties Panel"
    bl_idname = "VIEW3D_PT_normal_tool_props"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "KitFox"
        

    def draw(self, context):
        layout = self.layout

        scene = context.scene
        settings = scene.my_tool

        col = layout.column()
        col.prop(settings, "strength")
        col.prop(settings, "brush_type")
        col.prop(settings, "normal")
        col.prop(settings, "target")                       
#---------------------------


def register():
    bpy.utils.register_class(NormalToolSettings)
    bpy.utils.register_class(NormalToolPropsPanel)

    bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=NormalToolSettings)


def unregister():
    bpy.utils.unregister_class(NormalToolSettings)
    bpy.utils.unregister_class(NormalToolPropsPanel)
    


if __name__ == "__main__":
    register()

If you wish to set this to something else in modal then when you know the object

context.scene.my_tool.target = hit_object

and it will update in the UI.

Tip: Look at the subtypes of FloatVectorProperty for other options to keep the value normalized.

$\endgroup$

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.