1
$\begingroup$

What's my goal:

I want to have a property on an area or something similar. The idea is that with this I can have two areas of the same type open, but display a panel only in one of them.

The problem:

The problem is that it looks like you can't add properties to an area. If I do bpy.types.Area.test = bpy.props.BoolProperty() it doesn't become an actual property of the Area but only returns the builtin function for the BoolProperty.

Is there any way to add a property to the area or something related to it, or to achieve the same effect of showing a panel only in some areas of the same type and not in others?

$\endgroup$
3
  • $\begingroup$ Is it necessary to be with python?? $\endgroup$
    – A D
    Jul 21 '20 at 15:07
  • $\begingroup$ @AD I need it for an addon so probably yes. What would be the alternative? $\endgroup$ Jul 21 '20 at 15:23
  • $\begingroup$ I don't use python but it is possible in-app by residing the window which can be then saved as a preset, IDK but maybe you can load that preset everytime by python, I have literally no idea it is possible or not I have never used python. $\endgroup$
    – A D
    Jul 21 '20 at 16:57
4
$\begingroup$

Use the poll method

enter image description here

Test script, using Text Editor > Templates > Python > UI Panel Simple and adding a poll method such that only the largest PROPERTIES type area in the screen areas polls

class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"
    
    @classmethod
    def poll(cls, context):
        areas = sorted(
            (a for a in context.screen.areas if a.type == 'PROPERTIES'),
             key=lambda a: a.width * a.height)

        return areas and context.area == areas[-1]

other methods to consider would be looking for the lowest index type area in the screen collection. As a rule of thumb when enumerating the screen areas collection, recently split off areas are added last.

>>> for i, a in enumerate(C.screen.areas):
...     i, a.type
...     
(0, 'PROPERTIES')
(1, 'CONSOLE')
(2, 'OUTLINER')
(3, 'PROPERTIES')
(4, 'TEXT_EDITOR')

>>> C.screen.areas[:].index(C.area)
1

A property can be saved on the screen object. Using a group property that keeps track of area index and type is another method I've used in the past to attach a draw callback to only one instance of an area in a screen.

>>> bpy.types.Screen.my_panel_area_index = bpy.props.IntProperty()
>>> C.screen.my_panel_area_index
0
$\endgroup$
1
  • $\begingroup$ The plan was to use the poll method with the property attached to the area. The index idea seems interesting, I'll have to try how that behaves when you add and remove areas and if it works to simply check a checkbox in the panel to hide it. $\endgroup$ Jul 21 '20 at 18:59
2
$\begingroup$

Based on the answer by @batFINGER I was able to find a solution for my exact problem:

Instead of the suggested index I found that you can use str(area) to get something that doesn't change when you add or remove areas. From my experiments the returned string is in the form <bpy_struct, Area at 0x000001E3A93FE5E8>and only changes when you open another file or reopen blender.


The basic idea is that I have a collection property attached to every workspace like this: bpy.types.WorkSpace.my_areas = bpy.props.CollectionProperty(type=MyAreaProperties)

This refers to a property group which looks like this:

class MyAreaProperties(bpy.types.PropertyGroup):
    name: bpy.props.StringProperty()
    index: bpy.props.IntProperty()

    show_my_panel: bpy.props.BoolProperty(default=True)

I then have a function that I call in the poll function of my panels:

def enabled_in_area(context,panel_property):
    area_props = context.workspace.my_areas

    if str(context.area) in area_props:
        return getattr(area_props[str(context.area)],panel_property)

    else:
        area_props.add().name = str(context.area)
        return enabled_in_area(context,panel_property)

This does two things. It checks if the area is added to the workspaces collection property by seeing if str(context.area) is in the collection. If that is the case it returns if the panel should be shown based on the given panel_property. In this example that would be show_my_panel, but you could have multiple panels here and use the same function for it. If the area has not been added to the collection, it adds an item and sets the name to str(context.area)


This takes care of the basic hiding and showing of panels in different areas. The remaining problem is that the return value of str(area) changes when you reopen the blend file. Therefore we need to store the information of which is what panel differently for that case. I referred to @batFINGER's solution of using the areas index here.

I'm using an app handler here for running a function before the file is saved. This stores the indices for all areas in all workspaces that have been added to the collection:

@bpy.app.handlers.persistent
def save_area_indeces():
    for ws in bpy.data.workspaces:
        for i, area in enumerate(ws.screens[0].areas):
            if str(area) in ws.my_areas:
                ws.my_areas[str(area)].index = i

bpy.app.handlers.save_pre.append(save_area_indeces)

Finally we need to load these indices after opening the file:

@bpy.app.handlers.persistent
def load_areas():
    for ws in bpy.data.workspaces:
        for area in ws.lp_areas:
            area.name = str(ws.screens[0].areas[area.index])

bpy.app.handlers.load_post.append(post_load)

With this set up we can now show the checkbox with self.layout.prop(context.workspace.my_areas[str(context.area)],"show_my_panel") to show or hide our panel.

$\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.