mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
@@ -125,6 +125,7 @@ classes = [
|
||||
operator.ShowSystemInfo,
|
||||
prop.StrProperty,
|
||||
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
|
||||
operator.BIM_OT_attribute_search_values,
|
||||
prop.ObjProperty,
|
||||
prop.MultipleFileSelect,
|
||||
prop.Attribute,
|
||||
|
||||
@@ -52,26 +52,33 @@ def draw_attributes(
|
||||
copy_operator: Optional[str] = None,
|
||||
popup_active_attribute: Optional[bonsai.bim.prop.Attribute] = None,
|
||||
callback: Optional[Callable[[bonsai.bim.prop.Attribute, bpy.types.UILayout], None]] = None,
|
||||
enable_search: bool = False,
|
||||
) -> None:
|
||||
"""Draw editable UI for prop.Attributes.
|
||||
|
||||
You can set attribute active in popup with `active_attribute`
|
||||
meaning you will be able to type into attribute's field without having to click
|
||||
on it first
|
||||
|
||||
:param enable_search: Add search button to string, integer, and float attributes
|
||||
"""
|
||||
for attribute in props:
|
||||
row = layout.row(align=True)
|
||||
if attribute == popup_active_attribute:
|
||||
row.activate_init = True
|
||||
draw_attribute(attribute, row, copy_operator)
|
||||
draw_attribute(attribute, row, copy_operator, enable_search=enable_search)
|
||||
if callback:
|
||||
callback(attribute, row)
|
||||
|
||||
|
||||
def draw_attribute(
|
||||
attribute: bonsai.bim.prop.Attribute, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
|
||||
attribute: bonsai.bim.prop.Attribute,
|
||||
layout: bpy.types.UILayout,
|
||||
copy_operator: Optional[str] = None,
|
||||
enable_search: bool = False,
|
||||
) -> None:
|
||||
value_name = attribute.get_value_name(display_only=True)
|
||||
|
||||
if value_name == "enum_value":
|
||||
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
|
||||
elif value_name == "filepath_value":
|
||||
@@ -104,6 +111,13 @@ def draw_attribute(
|
||||
op.target_prop = attribute.path_from_id("string_value")
|
||||
op.include_time = attribute.special_type == "DATETIME"
|
||||
|
||||
if enable_search and attribute.data_type in ("string", "integer", "float"):
|
||||
op = layout.operator("bim.attribute_search_values", text="", icon="VIEWZOOM")
|
||||
op.attribute_name = attribute.name
|
||||
op.attribute_ifc_class = attribute.ifc_class
|
||||
op.data_path = attribute.path_from_id(value_name)
|
||||
op.data_type = attribute.data_type
|
||||
|
||||
if attribute.is_optional:
|
||||
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ class BIM_PT_materials(Panel):
|
||||
return
|
||||
ifc_definition_id = self.props.active_material_id
|
||||
if self.props.editing_material_type == "ATTRIBUTES":
|
||||
bonsai.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
|
||||
bonsai.bim.helper.draw_attributes(self.props.material_attributes, self.layout, enable_search=True)
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_material", text="Save Material", icon="CHECKMARK").material = ifc_definition_id
|
||||
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
|
||||
|
||||
@@ -44,6 +44,7 @@ from pathlib import Path
|
||||
from collections import namedtuple
|
||||
from typing import Union, TYPE_CHECKING
|
||||
from collections.abc import Iterable
|
||||
from natsort import natsorted
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.prop import MultipleFileSelect
|
||||
@@ -1299,3 +1300,97 @@ class ShowSystemInfo(bpy.types.Operator):
|
||||
|
||||
col.separator()
|
||||
col.label(text="(The information has been copied to the clipboard.)")
|
||||
|
||||
|
||||
def update_attribute_search_value(self, context):
|
||||
should_click_ok = False
|
||||
attr_name, attribute_obj = BIM_OT_attribute_search_values.resolve_data_path(self.data_path)
|
||||
|
||||
value = self.search_value
|
||||
if self.data_type == "integer":
|
||||
value = int(value)
|
||||
elif self.data_type == "float":
|
||||
value = float(value)
|
||||
|
||||
setattr(attribute_obj, attr_name, value)
|
||||
|
||||
if self.first_launch:
|
||||
self.first_launch = False
|
||||
else:
|
||||
if not should_click_ok:
|
||||
context.window.screen = context.window.screen
|
||||
|
||||
|
||||
class BIM_OT_attribute_search_values(bpy.types.Operator):
|
||||
"""Search for attribute values. This implementation is based on bim.enum_property_search"""
|
||||
|
||||
bl_idname = "bim.attribute_search_values"
|
||||
bl_label = "Search Attribute Values"
|
||||
bl_description = "Search for attribute values within a collection"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
first_launch: bpy.props.BoolProperty(default=True, options={"SKIP_SAVE"})
|
||||
attribute_name: bpy.props.StringProperty(name="Attribute Name")
|
||||
attribute_ifc_class: bpy.props.StringProperty(name="Attribute IFC Class")
|
||||
data_path: bpy.props.StringProperty(name="Data Path")
|
||||
data_type: bpy.props.StringProperty(name="Data Type")
|
||||
search_value: bpy.props.StringProperty(
|
||||
name="Search",
|
||||
description="Search for attribute values",
|
||||
update=update_attribute_search_value,
|
||||
default="",
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
collection_values: bpy.props.CollectionProperty(type=StrProperty, options={"SKIP_SAVE"})
|
||||
|
||||
@staticmethod
|
||||
def resolve_data_path(data_path: str) -> tuple[str, object]:
|
||||
"""Resolve the data path of an object's attribute to get the attribute name and the object."""
|
||||
path_parts = data_path.split(".")
|
||||
obj_path = ".".join(path_parts[:-1])
|
||||
attr_name = path_parts[-1]
|
||||
attribute_obj = eval(f"bpy.context.scene.{obj_path}")
|
||||
return attr_name, attribute_obj
|
||||
|
||||
def invoke(self, context, event):
|
||||
attr_name, attribute_obj = self.resolve_data_path(self.data_path)
|
||||
self.search_value = str(getattr(attribute_obj, attr_name, ""))
|
||||
|
||||
unique_values = self.get_unique_attribute_values()
|
||||
string_values = natsorted(unique_values)
|
||||
|
||||
for value in string_values:
|
||||
self.collection_values.add().name = value
|
||||
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def get_unique_attribute_values(self):
|
||||
ifc_file = tool.Ifc.get()
|
||||
unique_values = set()
|
||||
ifc_class = self.attribute_ifc_class
|
||||
|
||||
elements = ifc_file.by_type(ifc_class, include_subtypes=True)
|
||||
|
||||
for element in elements:
|
||||
# We check just direct entity attributes and simply check if the attribute exists
|
||||
if hasattr(element, self.attribute_name):
|
||||
value = getattr(element, self.attribute_name)
|
||||
if value is not None:
|
||||
unique_values.add(str(value))
|
||||
|
||||
return list(unique_values)
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.label(text=f"Select {self.attribute_name} value:")
|
||||
row = self.layout.row()
|
||||
row.prop_search(
|
||||
self,
|
||||
"search_value",
|
||||
self,
|
||||
"collection_values",
|
||||
text="",
|
||||
results_are_suggestions=True,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
Reference in New Issue
Block a user