diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 9565e50f32..5f8c81dba4 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -31,6 +31,7 @@ from blenderbim.bim.module.model.data import AuthoringData from blenderbim.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP from mathutils import Vector from math import cos, degrees +from typing import Union cwd = os.path.dirname(os.path.realpath(__file__)) @@ -286,26 +287,7 @@ def get_override_scene_panel(panel_name): return override_panel -# TODO: possibly override scene panels from other addons too? -# list can be updated from -# https://projects.blender.org/blender/blender/src/branch/main/scripts/startup/bl_ui/properties_scene.py#L421 -OVERRIDE_SCENE_PANELS = ( - "SCENE_PT_scene", - "SCENE_PT_unit", - "SCENE_PT_physics", - "SCENE_PT_rigid_body_world", - "SCENE_PT_rigid_body_world_settings", - "SCENE_PT_rigid_body_cache", - "SCENE_PT_rigid_body_field_weights", - "SCENE_PT_audio", - "SCENE_PT_keying_sets", - "SCENE_PT_custom_props", - # after SCENE_PT_keying_sets - "SCENE_PT_keying_set_paths", - "SCENE_PT_keyframing_settings", -) -if bpy.app.version >= (4, 0): - OVERRIDE_SCENE_PANELS += ("SCENE_PT_simulation",) +OVERRIDE_SCENE_PANELS = tool.Blender.get_scene_panels_list() @persistent diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py index a6133f8d6e..ba5bf6ff2a 100644 --- a/src/blenderbim/blenderbim/tool/blender.py +++ b/src/blenderbim/blenderbim/tool/blender.py @@ -875,3 +875,58 @@ class Blender(blenderbim.core.tool.Blender): bpy.utils.unregister_tool(ws_covering.CoveringTool) except: pass + + @classmethod + def get_scene_panels_list(cls) -> tuple[str, ...]: + # example default blender scene panels can be found in + # https://projects.blender.org/blender/blender/src/branch/main/scripts/startup/bl_ui/properties_scene.py#L421 + scene_panels: list[str] = [] + panels_to_parents: dict[str, str] = dict() + for item_name in dir(bpy.types): + item = getattr(bpy.types, item_name) + # filter only panels + if not hasattr(item, "bl_rna") or not isinstance(item.bl_rna, bpy.types.Panel): + continue + # filter scene panels + if getattr(item, "bl_context", None) != "scene": + continue + scene_panels.append(item_name) + parent_panel = getattr(item, "bl_parent_id", None) + if parent_panel is not None: + panels_to_parents[item_name] = parent_panel + + scene_panels = cls.sort_panels_for_register(scene_panels, panels_to_parents) + return tuple(scene_panels) + + @classmethod + def sort_panels_for_register(cls, items: list[str], items_to_parents: dict[str, str]) -> list[str]: + """sort panels ensuring parents panels will be registered first + as otherwise we'll get errors unregistering them all and registering child panel""" + final_items = [] + unsorted = items.copy() + + # first, add items without parents + for item in unsorted[:]: + if item not in items_to_parents: + final_items.append(item) + unsorted.remove(item) + + # store children for each parent + children: dict[str, list[str]] = dict() + for item in items_to_parents: + children.setdefault(items_to_parents[item], []).append(item) + + # add children recursively, ensuring parents are added first + keep_looking = True + while keep_looking: + keep_looking = False + for item in list(children.keys()): + # check if parent panel was already added + if item not in final_items: + continue + final_items.extend(children[item]) + del children[item] + keep_looking = True + + assert set(items) == set(final_items), "Sorted list doesn't match original" + return final_items diff --git a/src/blenderbim/test/tool/test_blender.py b/src/blenderbim/test/tool/test_blender.py index e05e98f98a..328363bafb 100644 --- a/src/blenderbim/test/tool/test_blender.py +++ b/src/blenderbim/test/tool/test_blender.py @@ -20,6 +20,7 @@ import bpy import ifcopenshell import blenderbim.core.tool import blenderbim.tool as tool +import pytest from test.bim.bootstrap import NewFile from blenderbim.tool.blender import Blender as subject @@ -44,3 +45,17 @@ class TestCopyNodeGraph(NewFile): subject.copy_node_graph(material_to, material_from) assert len(material_to_nodes) == 2 + + +class TestSortPanelsForRegister(NewFile): + def test_run(self): + items = ["A", "B", "C", "D"] + items_to_parents = {"A": "D", "D": "C", "C": "B"} + sorted_items = subject.sort_panels_for_register(items, items_to_parents) + assert tuple(sorted_items) == ("B", "C", "D", "A") + + with pytest.raises(AssertionError): + subject.sort_panels_for_register(items, {"A": "K"}) + + with pytest.raises(AssertionError): + subject.sort_panels_for_register(items, {"J": "A"})