TransWikia.com

How can I have multiple areas open but only add a panel to one of them with Python?

Blender Asked by Joshua Knauber on November 8, 2021

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?

2 Answers

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.

Answered by Joshua Knauber on November 8, 2021

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

Answered by batFINGER on November 8, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP