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?
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
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
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP