1
$\begingroup$

I am assembling my first add-on and I have a particular order I would like my panels to stay in inside the UI. This is easily done by rearranging the order in which each panel is registered.

However, one of my panels I would like to hide it’s header and as soon as I use the bl_options to remove the header my panel jumps to the top of all my panel list.

I have a quick test code shown below with 3 panels. As you can see I have it setup so panel 3 shows up second in the panel UI. Run the script and you will see the 3 panels N Panel in the order as intended. Then un-comment the line "#bl_options = {‘HIDE_HEADER’}". Then IMPORTANT close blender. Reopen blender, run the script and the panel 3 header is hidden but it moves to the top of the list. If you DO NOT close Blender and rerun the script after the change the panel stays in place and hides the header…it appears to work. But this is the last UI register. You have to close Blender and reopen to get it to reset the UI and see the issue.

import bpy

def main(context):
for ob in context.scene.objects:
print(ob)

class SimpleOperator(bpy.types.Operator):
bl_idname = “object.simple_operator”
bl_label = “Simple Object Operator”

@classmethod
def poll(cls, context):
    return context.active_object is not None

def execute(self, context):
    main(context)
    return {'FINISHED'}

class HelloWorldPanel1(bpy.types.Panel):
bl_label = “Hello World Panel1”
bl_idname = “OBJECT_PT_hello1”
bl_space_type = ‘VIEW_3D’
bl_region_type = ‘UI’
bl_category = ‘Mine’

def draw(self, context):
    layout = self.layout
    row = layout.row()
    row.operator("mesh.primitive_cube_add")

class HelloWorldPanel2(bpy.types.Panel):
bl_label = “Hello World Panel2”
bl_idname = “OBJECT_PT_hello2”
bl_space_type = ‘VIEW_3D’
bl_region_type = ‘UI’
bl_category = ‘Mine’

def draw(self, context):
    layout = self.layout
    row = layout.row()
    row.operator("mesh.primitive_cube_add")

class HelloWorldPanel3(bpy.types.Panel):
bl_label = “Hello World Panel3”
bl_idname = “OBJECT_PT_hello3”
bl_space_type = ‘VIEW_3D’
bl_region_type = ‘UI’
bl_category = ‘Mine’
#bl_options = {‘HIDE_HEADER’}

def draw(self, context):
    layout = self.layout
    row = layout.row()
    row.operator("mesh.primitive_cube_add")

classes = [
SimpleOperator,
HelloWorldPanel1,
HelloWorldPanel3,
HelloWorldPanel2,
]

def register():
for cls in classes:
bpy.utils.register_class(cls)

def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)

if name == “main”:
register()

Any ideas of how to keep this from happening would be greatly appreciated.

$\endgroup$
1
$\begingroup$

Sub Panels

Blender now has subpanels. Recommend making "Holding Panels" and make your other panels children.

Example https://blender.stackexchange.com/a/155517/15543

On rearranging panels

This is easily done by rearranging the order in which each panel is registered.

Maybe a poison chalice. Remember the user can move panels around. The expected result is the register order. Been my experience this is (generally but) not always the case. Registering panels in startup scripts places them higher than via addon.

As mentioned above, would now use subpanels.

If the same issue is the case re a headerless panels panels moving to top, recommend using an appended or prepended draw method.

Here is example rejigged from link above

Panel 2 and 3 are children of panel 1. Both panel 1 and panel 3 are set to hide header.

enter image description here 2 results of running script below. Both preserve order. The render of the no header subpanel is strange (bg color). Panel 1 is Ok, perhaps explains why no header panels are moved to top.

import bpy


class EXAMPLE_PT_panel_1(bpy.types.Panel):
    bl_label = "Panel 1"
    bl_category = "Example tab"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {"HIDE_HEADER"}

    def draw(self, context):
        layout = self.layout
        layout.label(text="This is panel 1.")



class EXAMPLE_PT_panel_2(bpy.types.Panel):
    bl_label = "Panel 2"
    bl_category = "Example tab"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {"DEFAULT_CLOSED"}
    bl_parent_id = "EXAMPLE_PT_panel_1"
    def draw(self, context):
        layout = self.layout
        layout.label(text="This is panel 2.")


class EXAMPLE_PT_panel_3(bpy.types.Panel):
    bl_label = "Panel 3"
    bl_category = "Example tab"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_options = {'HIDE_HEADER'}
    bl_parent_id = "EXAMPLE_PT_panel_1"
    def draw(self, context):
        layout = self.layout
        layout.label(text="This is panel 3.")


classes = [ EXAMPLE_PT_panel_2, EXAMPLE_PT_panel_3]

from random import shuffle
shuffle(classes)
classes.insert(0, EXAMPLE_PT_panel_1)
print(classes)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)


def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    register()

Here is another example that dynamically generates n panels. Test script uses driver namespace to hold panels to unregister while running from __main__

Gives some options to play around with. There is a bl_order flag that the base PropertyPanel (Custom Props) uses always be at the bottom.

import bpy
from  bpy.app import driver_namespace as dns

for p in dns.get("panels", []):
    if getattr(p, "is_registered"):
        bpy.utils.unregister_class(p)
else:
    dns.get("panels", []).clear()

from random import shuffle

use_parent_zero = True
hide_second_headers = False
n = 5 # number of panels   
shuffle_order = True
random_shuffle = False

order = list(range(n))
if shuffle_order:
    shuffle(order)

class TestPanel:
    bl_category = 'TestPanel'
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"
    bl_order = 0

    def draw(self, context):
        layout = self.layout
        cls = self.__class__
        layout.label(text=f"{cls.bl_label} Order {cls.bl_order}")


panels = [type(f"Panel{i}",
                (TestPanel, bpy.types.Panel),
                {"bl_label" : f"Panel {i}",
                 "bl_idname" : f"TEST_PT_Panel{i}",
                 "bl_order" :  TestPanel.bl_order + order[i],
                 }) for i in range(n)
                 ]

panels.sort(key=lambda p : p.bl_order) 
if random_shuffle: 
    shuffle(panels)  
print([(p.bl_label, p.bl_order) for p in panels])           
for i, p in enumerate(panels):
    if i and use_parent_zero:
        p.bl_parent_id = panels[0].bl_idname
    if i % 2 and hide_second_headers :
        p.bl_options = {'HIDE_HEADER'}

    bpy.utils.register_class(p)

dns["panels"] = panels

About the only conclusion is sub panels are displayed in registered order.

$\endgroup$
3
  • $\begingroup$ Thank you! Using the Parent is really what I am after..this will resolve my issue. Thank you for your time. $\endgroup$
    – fwfa
    May 20 '20 at 17:04
  • $\begingroup$ Cheers. Added another test script to look at bl_order. Consider accepting answer (check box LHS of answer) if this has answered your question. $\endgroup$
    – batFINGER
    May 20 '20 at 17:27
  • $\begingroup$ Excellent...Thank you for the additional test script....lots for me to learn $\endgroup$
    – fwfa
    May 20 '20 at 17:45

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.