1

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()

kitfox
  • 1,616
  • 5
  • 26
  • 40
  • Related https://blender.stackexchange.com/questions/203747/addon-how-to-make-blender-run-check-redraw-when-option-changed 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 – batFINGER Jan 23 '21 at 02:48
  • 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. – kitfox Jan 23 '21 at 03:06
  • 1
    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? – Armored Wolf Jan 23 '21 at 04:09
  • 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. – batFINGER Jan 23 '21 at 04:14
  • 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 https://blender.stackexchange.com/a/159155/15543 – batFINGER Jan 23 '21 at 05:56
  • I made some revisions, but it's still not working. I've posted a new question which makes progress on what I posted above: https://blender.stackexchange.com/questions/209199/how-to-get-panel-to-use-properties-from-a-propertygroup – kitfox Jan 23 '21 at 11:34
  • Ok going around in circles a bit here. Have used other one I closed to demo what I mean re pointer property here. Took out the operator, but as mentioned prior if I run the code with reference to ModalOperator or some class I havent defined and use layout.operator(ModalOperator.bl_idname) it will run and register without error, however it will start spitting errors to the console as soon as I try and see the panel. and the panel will show no more after any error. – batFINGER Jan 23 '21 at 12:36

1 Answers1

2

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.

batFINGER
  • 84,216
  • 10
  • 108
  • 233