5
$\begingroup$

I have a StringProperty that I want to show the name of the selected object. Currently, I update the property by invoking an operator manually from a panel. However, I want the property to be updated automatically every time the selected object in the viewport changes.

What is the best way to achieve this?

I have considered running a modal operator, which checks the selection change all the time. However, that does not seem like an optimal solution. I have also tried playing around with handlers (bpy.app.handlers.scene_update_post as mentioned here), but it seems to have the same result as a modal operator (constant checking in infinite loop). Is it possible to trigger the update only when the selection changes by some callback? Or alternatively, when the right mouse button is clicked (assuming selection only happens this way)?

Edit, for clarification:

I specifically need a StringProperty to be updated, and not just a regular text label. This is because the StringProperty (and other properties) will display some specific information about the selected object, which the user then can change to change the selected object.

My current code, where the StringProperty is updated manually by click:

import bpy

# Define string property.
class MyGroup(bpy.types.PropertyGroup):
    bpy.types.Scene.my_string = bpy.props.StringProperty(name='my_string')

# Define operator to change string property.
last_selection = []
class UpdateStringOperator(bpy.types.Operator):
    bl_idname = "update.mystring" 
    bl_label = ""
    def invoke(self, context, event):
        global last_selection
        if bpy.context.selected_objects != last_selection:
            last_selection = bpy.context.selected_objects
            bpy.context.scene.my_string = bpy.context.selected_objects[0].name
        return {'FINISHED'}

# Draw property and operator.
class MyPanel(bpy.types.Panel):
    ...
    def draw(self, context):
        layout = self.layout
        layout.row().prop(context.scene, "my_string")
        layout.row().operator("update.mystring", text="Update")

# Register.
def register():  
    bpy.utils.register_module(__name__)
def unregister(): 
    bpy.utils.unregister_module(__name__)
if __name__ == "__main__": 
    register()
$\endgroup$
5
  • 3
    $\begingroup$ reimplementing the selection tool may help if you include the update inside of it $\endgroup$
    – Chebhou
    May 25, 2015 at 19:08
  • $\begingroup$ @Chebhou That is definitely not a bad idea. Would it be possibe to add such a modification in an addon without having to change the settings? If that is the case, I will accept it with some sample code as an answer. $\endgroup$
    – dwitvliet
    May 25, 2015 at 23:41
  • 1
    $\begingroup$ "I have a StringProperty that I want to show the name of the selected object" why not just display the name of the currently selected object in a panel? I'm curious why you need a custom string property to display an already existing and identical string property that is bpy.context.selected_objects[0].name? $\endgroup$
    – patmo141
    May 27, 2015 at 0:07
  • 1
    $\begingroup$ @patmo141 I need a StringProperty to be updated. Not a text label. I need to be able to change the name and other properties of the object by changing the corresponding StringProperties, which should be updated on selecting the object. $\endgroup$
    – dwitvliet
    May 27, 2015 at 2:35
  • $\begingroup$ Did you find a solution>? $\endgroup$
    – Derekcbr
    Oct 5, 2022 at 10:17

2 Answers 2

2
+50
$\begingroup$

This is the full script for selection and update, where emped the update function in our custom selection tool, the last function replaces the shortcuts of the old select with this operator so no damage is done and when unregistering the addon the shortcuts are reset to the previous state. run the script and open the Tools panel ( T in viewport ) and select an objectto see the string update


import bpy
from bpy.props import BoolProperty, IntVectorProperty

class MyGroup(bpy.types.PropertyGroup):
    bpy.types.Scene.my_string = bpy.props.StringProperty(name='my_string')

# Define operator to change string property.
last_selection = []

# the selection operator with the update func :
class SelectionOperator(bpy.types.Operator):
    """ Costum selection
    """
    bl_idname = "view3d.select_costum" 
    bl_label = "costum selection"

    extend = BoolProperty(default = False)
    deselect = BoolProperty(default = False)
    toggle = BoolProperty(default = False)
    center = BoolProperty(default = False)
    enumerate = BoolProperty(default = False)
    object = BoolProperty(default = False)
    location = IntVectorProperty(default = (0,0),subtype ='XYZ', size = 2)

    def execute(self, context):
        #select the object
        bpy.ops.view3d.select(extend=self.extend, deselect=self.deselect, toggle=self.toggle, center=self.center, enumerate=self.enumerate, object=self.object, location=(self.location[0] , self.location[1] ))
        #change the property
        global last_selection
        if bpy.context.selected_objects != last_selection:
               last_selection = bpy.context.selected_objects
               bpy.context.scene.my_string = bpy.context.selected_objects[0].name

        return {'FINISHED'}

    def invoke(self, context, event):
        if context.space_data.type == 'VIEW_3D':
            self.location[0] = event.mouse_region_x
            self.location[1]  = event.mouse_region_y
            return self.execute(context)
        else:
                self.report({'WARNING'}, "Active space must be a View3d")
                return {'CANCELLED'}

def replace_shortkey( old_op_name, new_op_name) :

        wm = bpy.context.window_manager
        keyconfig = wm.keyconfigs.active
        keymap = keyconfig.keymaps['3D View']
        items = keymap.keymap_items

        item = items.get(old_op_name, None)
        while item :

                props = item.properties

                extend    = props.extend.real
                deselect  = props.deselect.real
                toggle    = props.toggle.real
                center    = props.center.real
                enumerate = props.enumerate.real
                object    = props.object.real

                item.idname = new_op_name

                props.extend    = extend 
                props.deselect  = deselect
                props.toggle    = toggle
                props.center    = center
                props.enumerate = enumerate
                props.object    = object

                item = items.get( old_op_name, None)


# Draw property and operator.
class HelloWorldPanel(bpy.types.Panel):
    """    """
    bl_label = "active object Panel"
    bl_idname = "OBJECT_PT_my_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'

    def draw(self, context):
        layout = self.layout
        layout.row().prop(context.scene, "my_string")

# Register.
def register():
    bpy.utils.register_module(__name__)
    replace_shortkey( 'view3d.select', SelectionOperator.bl_idname )

def unregister():
    replace_shortkey(SelectionOperator.bl_idname, 'view3d.select')
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__": 
    register()
$\endgroup$
3
  • $\begingroup$ Thank you, this is exactly what I was looking for. Definitely the cleanest and most flexible solution. $\endgroup$
    – dwitvliet
    May 27, 2015 at 16:01
  • $\begingroup$ @Banana you are welcome , glad I could help $\endgroup$
    – Chebhou
    May 27, 2015 at 16:04
  • $\begingroup$ @Banana I still don't understand why you need to go through all that, can you help me understand? Chebhou's code is effective, but I just don't see why you need all that for what your problem describes. $\endgroup$
    – patmo141
    May 27, 2015 at 16:39
1
$\begingroup$

Why not just access the selected object's name when you need it? Blender panels are updated when blender re-draws. This only happens with user interaction so blender will check the selected object at a rate that is appropriate and you never have to worry! That's what the whole "context" thing is for. Unless there is some other reason you need to monitor selection I think you might be over complicating things.

class MyPanel(bpy.types.Panel):
    def draw(self, context):
        layout = self.layout
        row = layout.row()
        if len(context.selected_objects):
            ob = context.selected_objects[0]
            row.prop(ob, "name")
$\endgroup$
4
  • 1
    $\begingroup$ This does not answer my question. I don't think you read it properly. I need a StringProperty to be updated. Not a text label. I'm not sure I could be much more clear as I specified it both in the title of the question and bolded in the text. The reason: I need to be able to change the name and other properties of the object by changing the corresponding StringProperties, which should be updated on selecting the object. $\endgroup$
    – dwitvliet
    May 27, 2015 at 2:33
  • 1
    $\begingroup$ Ok, I understand what you want, but I don't understand why you want it. Why do you need a separate string property to represent what is already a string property? I see that you want to be able to edit the properties of the selected object like in the object properties panel. I still am unclear why you need a separate duplcate property. Why not just edit the one you are trying to edit? $\endgroup$
    – patmo141
    May 27, 2015 at 15:14
  • $\begingroup$ ok, answer edited so it's a property not a label $\endgroup$
    – patmo141
    May 27, 2015 at 15:24
  • $\begingroup$ The only thing I can think of, is that you want to change Properties that are not Strings, based on some string input from the user. Eg, you parse/process that string, and then update some Float, Int or other property. Is that what you want? $\endgroup$
    – patmo141
    May 27, 2015 at 15:26

You must log in to answer this question.

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