diff --git a/src/bonsai/bonsai/bim/module/cad/workspace.py b/src/bonsai/bonsai/bim/module/cad/workspace.py index 573fef6d4f..0571d0c776 100644 --- a/src/bonsai/bonsai/bim/module/cad/workspace.py +++ b/src/bonsai/bonsai/bim/module/cad/workspace.py @@ -23,7 +23,7 @@ import bonsai.bim.module.type.prop as type_prop import ifcopenshell.util.unit from bpy.types import WorkSpaceTool from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData -from typing import Union +from functools import partial def load_custom_icons(): @@ -330,29 +330,7 @@ def add_header_apply_button(layout, text, apply_operator, cancel_operator, ui_co row.label(text="Tools") -def add_layout_hotkey_operator( - layout: bpy.types.UILayout, text: str, hotkey: str, description: Union[str, None], ui_context: str = "" -) -> bpy.types.OperatorProperties: - parts = hotkey.split("_") - modifier, key = parts - op_text = "" if ui_context == "TOOL_HEADER" else text - custom_icon = custom_icon_previews.get(text.upper().replace(" ", "_"), custom_icon_previews["IFC"]).icon_id - modifier_icon, modifier_str = tool.Blender.KEY_MODIFIERS.get(modifier, ("NONE", "")) - - row = layout if ui_context == "TOOL_HEADER" else layout.row(align=True) - op = row.operator("bim.cad_hotkey", text=op_text, icon_value=custom_icon) - - if ui_context != "TOOL_HEADER" and len(parts) == 2: - layout = layout.row(align=True) # Create a new line for hotkey display - layout.label(text="", icon=modifier_icon) - layout.label(text="", icon=f"EVENT_{key}") - - hotkey_description = f"Hotkey: {modifier_str} {key}" - description = "\n\n".join(filter(None, [hotkey_description if description else ""])) - op.hotkey = hotkey - op.description = description or hotkey_description - - return op +add_layout_hotkey_operator = partial(tool.Blender.add_layout_hotkey_operator, tool_name="cad", module_name=__name__) custom_icon_previews = None diff --git a/src/bonsai/bonsai/bim/module/covering/workspace.py b/src/bonsai/bonsai/bim/module/covering/workspace.py index 904bd98a71..b0f41254b6 100644 --- a/src/bonsai/bonsai/bim/module/covering/workspace.py +++ b/src/bonsai/bonsai/bim/module/covering/workspace.py @@ -24,6 +24,7 @@ import bonsai.tool as tool from bonsai.bim.helper import prop_with_search from bonsai.bim.module.model.data import AuthoringData from bpy.types import WorkSpaceTool +from functools import partial class CoveringTool(WorkSpaceTool): @@ -45,9 +46,7 @@ class CoveringTool(WorkSpaceTool): CoveringToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) -def add_layout_hotkey(layout: bpy.types.UILayout, text: str, hotkey: str, description: str) -> None: - args = ("covering", layout, text, hotkey, description) - tool.Blender.add_layout_hotkey_operator(*args) +add_layout_hotkey = partial(tool.Blender.add_layout_hotkey_operator, tool_name="covering", module_name=__name__) class CoveringToolUI: diff --git a/src/bonsai/bonsai/bim/module/drawing/workspace.py b/src/bonsai/bonsai/bim/module/drawing/workspace.py index 16d26ea7e0..1296e859d5 100644 --- a/src/bonsai/bonsai/bim/module/drawing/workspace.py +++ b/src/bonsai/bonsai/bim/module/drawing/workspace.py @@ -27,7 +27,7 @@ import ifcopenshell.util.representation from bonsai.bim.module.drawing.data import DecoratorData, AnnotationData from bonsai.bim.helper import prop_with_search from bpy.types import WorkSpaceTool -from typing import Union +from functools import partial class LaunchAnnotationTypeManager(bpy.types.Operator): @@ -135,23 +135,9 @@ class AnnotationTool(WorkSpaceTool): AnnotationToolUI.draw(context, layout) -def add_layout_hotkey_operator( - layout: bpy.types.UILayout, text: str, hotkey: str, description: Union[str, None] -) -> tuple[bpy.types.OperatorProperties, bpy.types.UILayout]: - modifiers = { - "A": "EVENT_ALT", - "S": "EVENT_SHIFT", - } - modifier, key = hotkey.split("_") - - row = layout.row(align=True) - row.label(text="", icon=modifiers[modifier]) - row.label(text="", icon=f"EVENT_{key}") - - op = row.operator("bim.annotation_hotkey", text=text) - op.hotkey = hotkey - op.description = description - return op, row +add_layout_hotkey_operator = partial( + tool.Blender.add_layout_hotkey_operator, tool_name="annotation", module_name=__name__ +) # TODO: move to operator diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index da2f64faac..d0da684ffa 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -30,6 +30,7 @@ from bonsai.bim.module.model.data import AuthoringData, ItemData from bonsai.bim.module.system.data import PortData from bonsai.bim.module.model.prop import get_ifc_class from typing import Optional, Union +from functools import partial def load_custom_icons(): @@ -365,43 +366,7 @@ class CableTool(BimTool): ifc_element_type = "IfcCableSegmentType" -def add_layout_hotkey_operator( - layout: bpy.types.UILayout, - text: str, - hotkey: str, - description: Union[str, None], - ui_context: str = "", - *, - operator: str = "bim.hotkey", -) -> bpy.types.OperatorProperties: - """ - :param operator: Operator to display in UI. Displaying the specific operator in UI can be useful - to provide poll error messages. - """ - parts = hotkey.split("_") if hotkey else [] - modifier, key = (parts + ["", ""])[:2] - - op_text = "" if ui_context == "TOOL_HEADER" else text - custom_icon = custom_icon_previews.get(text.upper().replace(" ", "_"), custom_icon_previews["IFC"]).icon_id - modifier_icon, modifier_str = tool.Blender.KEY_MODIFIERS.get(modifier, ("NONE", "")) - - row = layout.row(align=True) - op = row.operator(operator, text=op_text, icon_value=custom_icon) - - if ui_context != "TOOL_HEADER": - row.label(text="", icon=modifier_icon) - row.label(text="", icon=f"EVENT_{key}" if key else "BLANK1") - - hotkey_description = f"Hotkey: {modifier_str} {key}".strip() - description = "\n\n".join(filter(None, [description, hotkey_description])) - - if operator == "bim.hotkey": - op.hotkey = hotkey - if ui_context == "TOOL_HEADER": - op.description = text + "\n" + description - else: - op.description = description - return op +add_layout_hotkey_operator = partial(tool.Blender.add_layout_hotkey_operator, tool_name="bim", module_name=__name__) def format_ifc_camel_case(string): diff --git a/src/bonsai/bonsai/bim/module/spatial/workspace.py b/src/bonsai/bonsai/bim/module/spatial/workspace.py index 7853f3a4af..26c91d8037 100644 --- a/src/bonsai/bonsai/bim/module/spatial/workspace.py +++ b/src/bonsai/bonsai/bim/module/spatial/workspace.py @@ -23,6 +23,7 @@ import bonsai.tool as tool from bonsai.bim.module.model.data import AuthoringData from bpy.types import WorkSpaceTool import bonsai.core.spatial +from functools import partial class SpatialTool(WorkSpaceTool): @@ -49,9 +50,7 @@ class SpatialTool(WorkSpaceTool): SpatialToolUI.draw(context, layout) -def add_layout_hotkey(layout: bpy.types.UILayout, text: str, hotkey: str, description: str) -> None: - args = ("spatial", layout, text, hotkey, description) - tool.Blender.add_layout_hotkey_operator(*args) +add_layout_hotkey = partial(tool.Blender.add_layout_hotkey_operator, tool_name="spatial", module_name=__name__) class SpatialToolUI: diff --git a/src/bonsai/bonsai/bim/module/structural/workspace.py b/src/bonsai/bonsai/bim/module/structural/workspace.py index cec8115a2d..4991150133 100644 --- a/src/bonsai/bonsai/bim/module/structural/workspace.py +++ b/src/bonsai/bonsai/bim/module/structural/workspace.py @@ -21,6 +21,7 @@ import os import bpy import bonsai.tool as tool from bpy.types import WorkSpaceTool +from functools import partial class StructuralTool(WorkSpaceTool): @@ -41,9 +42,7 @@ class StructuralTool(WorkSpaceTool): StructuralToolUI.draw(context, layout) -def add_layout_hotkey(layout: bpy.types.UILayout, text: str, hotkey: str, description: str) -> None: - args = ("structural", layout, text, hotkey, description) - tool.Blender.add_layout_hotkey_operator(*args) +add_layout_hotkey = partial(tool.Blender.add_layout_hotkey_operator, tool_name="structural", module_name=__name__) # NOTES before adding new operators: diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index da4db4f5d6..6ea77101a1 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -500,18 +500,55 @@ class Blender(bonsai.core.tool.Blender): @classmethod def add_layout_hotkey_operator( - cls, tool_name: str, layout: bpy.types.UILayout, text: str, hotkey: str, description: str + cls, + layout: bpy.types.UILayout, + text: str, + hotkey: str, + description: str, + ui_context: str = "", + *, + tool_name: str, + module_name: str, + operator: Optional[str] = None, ) -> tuple[bpy.types.OperatorProperties, bpy.types.UILayout]: + """ + :param module_name: Provide `__name__` of the current module, + so method could pick up icon previews based on the module's `custom_icon_previews` attribute. + :param operator: Operator to display in UI. Displaying the specific operator in UI can be useful + to provide poll error messages. + """ + if tool_name == "bim": + hotkey_operator = "bim.hotkey" + else: + hotkey_operator = f"bim.{tool_name}_hotkey" + operator_to_use = operator or hotkey_operator + modifier, key = hotkey.split("_") - - row = layout.row(align=True) + op_text = "" if ui_context == "TOOL_HEADER" else text modifier_icon, modifier_str = cls.KEY_MODIFIERS.get(modifier, ("NONE", "")) - row.label(text="", icon=modifier_icon) - row.label(text="", icon=f"EVENT_{key}") - op = row.operator(f"bim.{tool_name}_hotkey", text=text) - op.hotkey = hotkey - op.description = description + row = layout if ui_context == "TOOL_HEADER" else layout.row(align=True) + module = sys.modules[module_name] + icon_previews: Union[bpy.utils.previews.ImagePreviewCollection, None] + icon_previews = getattr(module, "custom_icon_previews", None) + if icon_previews: + custom_icon = icon_previews.get(text.upper().replace(" ", "_"), icon_previews["IFC"]).icon_id + op = row.operator(operator_to_use, text=op_text, icon_value=custom_icon) + else: + op = row.operator(operator_to_use, text=op_text) + if ui_context != "TOOL_HEADER": + row.label(text="", icon=modifier_icon) + row.label(text="", icon=f"EVENT_{key}") + + if operator_to_use == hotkey_operator: + hotkey_description = f"Hotkey: {modifier_str} {key}".strip() + description = "\n\n".join(filter(None, [description, hotkey_description])) + + op.hotkey = hotkey + if ui_context == "TOOL_HEADER": + op.description = text + "\n" + description + else: + op.description = description return op, row @classmethod