# Would like to modify an object from a button in a panel

I have created a panel in the Properties area in which the user can specify various parameters and then press a button to create the object. I am trying to write a Python code to allow the user to modify an object that was previously created using either the same panel or a new modify object panel. Two parts of this task are giving me trouble:

1. Identifying the object that has been selected. The problem is that if I click on the screen so that no object is selected, when I do bpy.context.active_object, I still get the most recent object selected. Also, if I deselect all the objects using bpy.context.active_object.select_set(False), I still get the last object selected as the active object. If I can determine whether or not an object is selected, I can determine whether or not to edit an object or create a new object.
2. If an object has been selected and is going to be modified, I would like the geometric properties of that object to appear in the panel. I can't retrieve the properties of a selected object in a draw method, so I am thinking that a modal operator may be the answer. I am including my code with a simple operator and a panel. After I create the operator, the modal operator will respond the next time I click on the object, but the code returns 'FINISHED', so it only detects selecting the object once and then, even if I modify the scene properties, they are displayed in the panel.

Here is my code:

import bpy
from bpy.props import FloatProperty

bpy.types.Scene.length = bpy.props.FloatProperty(name = "length",default = 2)
bpy.types.Scene.height = bpy.props.FloatProperty(name = "height",default = 1)
bpy.types.Scene.width = bpy.props.FloatProperty(name = "width",default = 1)

class DrawBlock(bpy.types.Operator):
"""A rectangular prism"""
bl_idname = "myops.draw_block"
bl_label = "Edit Block"
bl_description = "Draw a rectangular prism"
bl_options = {'REGISTER', 'UNDO'}

height: FloatProperty (name="height")
length: FloatProperty (name="length")
width: FloatProperty (name="width")

def new_block(self,context):
width = bpy.context.scene.width
height = bpy.context.scene.height
length = bpy.context.scene.length

obj=bpy.context.active_object
obj.dimensions=(width,length,height)
obj.location.z=obj.location.z+height/2
obj.name="Block"

def execute(self,context):
obj = bpy.context.active_object
if obj:
w = bpy.context.scene.width
h = bpy.context.scene.height
length = bpy.context.scene.length
obj.dimensions=(w,length,h)
else:
self.new_block(context)
return {'FINISHED'}

def modal(self, context, event):

obj=bpy.context.active_object
if obj:
print("object selected: ",obj)
bpy.context.scene.width=obj.dimensions.x
bpy.context.scene.length=obj.dimensions.y
bpy.context.scene.height=obj.dimensions.z
return {'FINISHED'}

elif event.type in {'RIGHTMOUSE', 'ESC'}:
print("cancelled operation")
return {'CANCELLED'}

else:
return {'PASS_THROUGH'}

return {'RUNNING_MODAL'}

def invoke(self,context,event):
print("inboke block")
self.new_block(context)
return{'RUNNING_MODAL'}

class SCENE_PT_Build(bpy.types.Panel):
"""Creates a Panel in the scene context of the properties editor"""
bl_label = "Build Menu"
bl_idname = "SCENE_PT_build"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"

def draw(self, context):
layout = self.layout
scene = context.scene
obj = context.active_object
row=layout.row()
row.label(text="block geometry:")
row=layout.row()
row.prop(scene, "length")
row=layout.row()
row.prop(scene, "width")
row = layout.row()
row.prop(scene, "height")
row=layout.row()
button_text="New Block"
if obj:
buttom_text="Edit Block"
row.operator("myops.draw_block",text=button_text)

def register():
bpy.utils.register_class(SCENE_PT_Build)
bpy.utils.register_class(DrawBlock)

def unregister():
bpy.utils.unregister_class(DrawBlock)
bpy.utils.unregister_class(SCENE_PT_Build)

if __name__ == "__main__":
register()


I am not sure why the line obj=context.object does not seem to get any objects. I think that a similar line in my real code does.

I am happy to learn about more effective ways of accomplishing this. I have tried using the adjust last operation menu, but I think I have too many parameters for that to be worthwhile.

• I can try to look more later, but there is definitely a difference between "active" and "selected"...you want to get the selected objects, which is bpy.context.selected_objects. There are times when you don't want the active item to be selected...for instance if you want to subtract a vertex group from another, you set the 1st active, then select it, then set the 2nd active, and deselect it. If active was the same as selected, that would be impossible. Or if you wanted to select a collection and then deselect one object in the collection. Apr 29 '21 at 21:33
• Cool! That works. Thanks for the explanation. I would like to be able to put the parameters of the selected object in the properties listed in the panel. I possibly could use an enum property to select the object and use an update function, but selecting the object in the 3d view seems preferable.
– SJK
Apr 30 '21 at 2:11
• if you want it to auto-update on selection, that's harder. Here's an answer that describes how to overload the built in selection function: blender.stackexchange.com/questions/31351/… Apr 30 '21 at 4:14
• This will also depend on the mesh of the object. eg there is no way to give the plane a z dimension. Apr 30 '21 at 8:52
• Can anyone tell me why the SelectionOperator in the answer below does not run when the code is an addon? From what I can tell, neither the invoke nor the execute methods are executed when I click in the 3d view. Probably there is simple solution, but so far I I have not found it.
– SJK
May 2 '21 at 18:47

Ok, I confirmed what I commented above. You need to get the selected object(s), not the active objects.

So context.selected_objects is a list - you either need to always operate on just the first selected object, or all of them. If you just want to assume the first selected one, just replace this:

obj = context.active_object


with:

obj = None
if len(context.selected_objects) > 0:
obj = context.selected_objects[0]


in every function where you're trying to get the active object. Otherwise run a loop over the context.selected_objects to affect all of them.

Also there's a bug here:

if obj:
buttom_text="Edit Block"


should be button_text instead of buttom_text.

Also your invoke function is what gets called when you push the button, so that's ALWAYS creating a new block. If you want your Edit Block to work you need to change the invoke function.

Hope this is helpful!

– SJK
May 1 '21 at 22:28

I hope it is OK if I post a complete code that answers the question. I could not have figured it out without the previous answer and comments, but having a working code to experiment with is very helpful when trying to learn something new.

import bpy
from bpy.props import FloatProperty,BoolProperty, IntVectorProperty

last_selection = []

bpy.types.Scene.length = bpy.props.FloatProperty(name = "length",default = 2)
bpy.types.Scene.height = bpy.props.FloatProperty(name = "height",default = 1)
bpy.types.Scene.width = bpy.props.FloatProperty(name = "width",default = 1)

class DrawBlock(bpy.types.Operator):
"""A rectangular prism"""
bl_idname = "myops.draw_block"
bl_label = "Edit Block"
bl_description = "Draw a rectangular prism"
bl_options = {'REGISTER', 'UNDO'}

def new_block(self,context):
width = bpy.context.scene.width
height = bpy.context.scene.height
length = bpy.context.scene.length

obj=bpy.context.active_object
obj.dimensions=(length,width,height)
obj.location.z=obj.location.z+height/2

def execute(self,context):
obj = bpy.context.active_object
if obj:
w = bpy.context.scene.width
h = bpy.context.scene.height
length = bpy.context.scene.length
obj.dimensions=(length,w,h)
else:
self.new_block(context)
return {'FINISHED'}

class SCENE_PT_Build(bpy.types.Panel):
"""Creates a Panel in the scene context of the properties editor"""
bl_label = "Build Menu"
bl_idname = "SCENE_PT_build"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"

def draw(self, context):
layout = self.layout
scene = context.scene
objlist=bpy.context.selected_objects
obj=[]
for obj in objlist:
if "Cube" in obj.name:
break
row=layout.row()
row.label(text="block geometry:")
row=layout.row()
row.prop(scene, "length")
row=layout.row()
row.prop(scene, "width")
row = layout.row()
row.prop(scene, "height")
row=layout.row()
button_text="New Block"
if obj:
button_text="Edit Block"
row.operator("myops.draw_block",text=button_text)

# This section is mostly copied from #https://blender.stackexchange.com/questions/31351/update-stringproperty-on-object-selection-change

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 = True)
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, deselect_all=True, 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
obj=bpy.context.active_object
if obj != last_selection:
last_selection = obj
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
bpy.context.scene.length = obj.dimensions.x
bpy.context.scene.width = obj.dimensions.y
bpy.context.scene.height = obj.dimensions.z

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)

def register():
bpy.utils.register_class(SelectionOperator)
bpy.utils.register_class(SCENE_PT_Build)
bpy.utils.register_class(DrawBlock)
replace_shortkey( 'view3d.select', SelectionOperator.bl_idname )

def unregister():
replace_shortkey(SelectionOperator.bl_idname, 'view3d.select')
bpy.utils.unregister_class(DrawBlock)
bpy.utils.unregister_class(SCENE_PT_Build)
bpy.utils.unregister_class(SelectionOperator)

if __name__ == "__main__":
register()