Panel properties influenced by each other

Can the input from one property in a panel influence the options for a different property? For example, I want to be able to select a group in the scene with an EnumProperty drop down box (I was able to achieve this). I then want this input to be used with a template_list to display the objects within the list. I am trying to achieve something similar to the use count box in the particle setting when a group of objects is used. However I would like the group selection to be done by a drop down box.

Thanks so much

Sure you can, you can use if/else in the draw code of a panel to draw things differently or different things. You can’t modify the data of a collection property from within a draw callback however. EnumProperty does come with a an items-callback however, so it can be fully dynamic.

Thank you very much CoDEmanX! So it sounds like what I was proposing is not possible. The enum property unfortunately would be a rather slow way to change the count of objects in a large group compared to the collection property. Is there anyway that I can work around this? I thought that I could define the desired group based on the selected object and then loop through that group to establish the collection property’s list items. However, if I am thinking about this correctly, then I would need to run the script every time I wanted to change the items in the collection property

I’m not exactly sure how you desire this UI-wise. Could you give a quick mock-up that shows the interface and how one element influences the other and what the ui elements show?

Yeah it may be helpful for me to describe my script more. I am trying to make a script that tiles objects of unequal x and y dimensions (see pic). I could not figure out how to do this with the particle system. The objects are based on a group of objects and the script allows you to change their rotation and scale as well as randomize the rotation and scale of each object without having overlapping objects (unless desired).


I got stuck when I wanted to add a box for the object count. I wanted something like this (screenshot from particle system tab):

However, in my case the items in this list are based on the group in the enum property. I could not figure out how to dynamically change a collection property based on the enum input. From your post it sounds like this is not possible. If that is the case I was wondering if there was a work around so that I could still get the object names displayed in the collection property list.

So Plane, Cube.002, Cube.003 and Cube.004 are the objects of a certain group (bpy.data.groups), and this group is determined how? Do you have a dropdown to pick a group?

If so, then it should actually be possible to update a collection property - when a dropdown (=enum prop) changes and its update callback is called, writes should be allowed to datablocks and properties (unlike with draw() callbacks).

import bpy
from bpy.props import *

def groups_items(self, context):
    return [(g.name, g.name, "") for g in bpy.data.groups]

def groups_cb(self, context):
    self.objects.clear()
    for ob in bpy.data.groups[self.group].objects:
        item = self.objects.add()
        item.name = ob.name

class ObjectCount(bpy.types.PropertyGroup):
    count = IntProperty(name="Count", default=1, min=1, max=1000, soft_max=100)

class Amartin(bpy.types.PropertyGroup):
    group = EnumProperty(name="Group", items=groups_items, update=groups_cb)
    objects = CollectionProperty(type=ObjectCount)
    index = IntProperty()

class SCENE_UL_amartin_object_count(bpy.types.UIList):
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            layout.label(item.name)
            layout.prop(item, "count")
        elif self.layout_type in {'GRID'}:
            pass


class HelloWorldPanel(bpy.types.Panel):
    """Description"""
    bl_label = "Hello World Panel"
    bl_idname = "SCENE_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

    def draw(self, context):
        layout = self.layout
        amartin = context.scene.amartin
        
        layout.prop(amartin, "group")
        layout.template_list("SCENE_UL_amartin_object_count", "", amartin, "objects", amartin, "index")
        layout.operator(WM_OT_amartin_objects.bl_idname)
        
class WM_OT_amartin_objects(bpy.types.Operator):
    bl_idname = "wm.amartin_objects"
    bl_label = "Show Object Configuration"

    def execute(self, context):
        amartin = context.scene.amartin
        config = ["{}x {}".format(ob.count, ob.name) for ob in amartin.objects]
        self.report({'ERROR'}, "Group: {}
{}".format(amartin.group, "
".join(config)))
        return {'FINISHED'}
    

def register():
    bpy.utils.register_module(__name__)
    bpy.types.Scene.amartin = PointerProperty(type=Amartin)


def unregister():
    bpy.utils.unregister_module(__name__)
    del bpy.types.Scene.amartin


if __name__ == "__main__":
    register()


That’s perfect! Thanks so much! By the way, CoDEmanX, I see you post on nearly every unsolved blender python post. You are a great help to the blender community. Keep it up!

Also eventually I want to release my script to the blender community. I don’t know if there will be a demand for a tool like this but where is the best place to get feedback so I can make improvements and add features people want? Should I put it in the released scripts and themes section?

You’re welcome :slight_smile:

It’s definitely a good start to post it here under released addons to gather feedback. You may later submit it to the Blender tracker for possible inclusion. It’s also worth to contact some resident devs over IRC (freenode.net #blendercoders / #blenderpython)

Would it be possible to implement this in my panel instead of in the scene properties? I tried this:

class TILE_GEN(bpy.types.Operator):    bl_idname = "mesh.add_say3d_roof"
    bl_label = "Tile"
    bl_description = "Tile Generator"
    
    bl_options = {'REGISTER', 'UNDO'}    
    
     
    
    #Define properties
    Autorefresh=BoolProperty(name='Autorefresh', default=True,description='Autorefresh')
    show_ob=BoolProperty(name='Ob', default=True,description='Show object box')
    
(other properties deleted for brevity)
    
    
    def draw(self, context):
        layout = self.layout
        
(code for other boxes deleted for brevity)        
        
        box=layout.box()
        groups = context.scene.groups
        box=layout.box();box.label('Count')                   
        box.prop(groups, "group")
        box.template_list("SCENE_UL_object_count", "", groups, "objects", groups, "index")
    
    def execute(self, context):
        
        
        if self.Autorefresh==True:           
            generate_roof(self,context,self.depth) 
            return {'FINISHED'}
        
        else:
            return {'PASS_THROUGH'}

However, whenever I changed the group with the enum property, the group could not be changed. It looked like it changed it but then was immediately reset.

If I combined your panel class with my code, though, I was able to change the group with your panel and have it display in mine.

You need to register the properties on global types, if it’s for display only, bpy.types.WindowManager.

I altered my code like this:

def initSceneProperties(scn):    
    sc = bpy.types.Scene
    #Define properties
    sc.Autorefresh= BoolProperty(name='Autorefresh', default=True,description='Autorefresh')
    sc.show_ob=BoolProperty(name='Ob', default=True,description='Show object box')
    
...
    
initSceneProperties(bpy.context.scene)    
                    
class ROOF_GEN(bpy.types.Panel):
    bl_idname = "mesh.add_say3d_roof"
    bl_label = "Roof"
    bl_description = "Tile Generator"
    
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_context = "objectmode"
       
    def draw(self, context):
        sc = bpy.context.scene
        layout = self.layout        
        box=layout.box()
        
        box.prop(sc,'Autorefresh')
        
        box=layout.box();box.label('Show boxes')
        row=box.row();row.prop(sc,'show_ob');row.prop(sc,'show_seed');row.prop(sc,'show_size');
        row=box.row();row.prop(sc,'show_rot');row.prop(sc,'show_scale');row.prop(sc,'show_overlap')
                
...
       
class ROOF_GEN(bpy.types.Operator):
    bl_idname = "mesh.add_say3d_roof"
    bl_label = "Roof"
    bl_description = "Tile Generator"
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):        
        generate_roof()
        return {'FINISHED'}        



However, now I cannot figure out how to get it to update when the autorefresh box is checked

If you want to catch state changes of that BoolProperty, add a callback:

BoolProperty(…, update=callback_function)

BTW, don’t do this:

sc = bpy.context.scene

Use the passed context instead, sc = context.scene

You would usually add properties to an operator and give the class bl_options = {‘REGISTER’, ‘UNDO’} to enable undo support. In the execute code, something is generated. When the user changes a property in the Redo panel, the generation operation will automatically be undone and execute() re-executed with the new parameters.

The downside: the operation will be re-run a lot of times as you drag a slider or similar. It will practically hang Blender if the creation code is slow (e.g. longer than half a second per run).

To avoid such immediate feedback, you can either use a props dialog, or add properties + an execute button to a panel. The operation will only be carried out once per click on OK / the execute button. Panels have the advantage of being persistent, so you can keep the settings (if any global ID type besides WindowManager is used, they will even be saved to .blend and available after a reload / restart).

Adding properties to a panel, which are then used for an operator means, that you duplicate the properties - one time registered on global types for the UI, another time as operator properties (unless you access the global properties directly from within the operator instead of passing the UI properties over to the operator properties).

If you don’t want the user to click a “run” button in a panel after adjusting properties, the only way to go are callbacks on properties. You need to supply a function to every properties’ update-parameter if you want to catch change events for all of them. That function can actually be one and the same for all properties, as long as you don’t need to know the event source.

You should prefer a “local” context over the global bpy.context (it’s passed to execute() in operators as 2nd argument), might be actually a question of style, but I also remember some corner cases in which both could return different contexts.

Thank you for the great explanation! That makes perfect sense