mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Attributes UI to support attributes with string subitems
Example - https://files.catbox.moe/oew1ue.mp4
This commit is contained in:
@@ -105,6 +105,8 @@ classes = [
|
||||
operator.BIM_OT_select_object,
|
||||
operator.BIM_OT_show_description,
|
||||
operator.BIM_OT_multiple_file_selector,
|
||||
operator.BIM_OT_attribute_add_subitem,
|
||||
operator.BIM_OT_attribute_remove_subitem,
|
||||
operator.ClippingPlaneCutWithCappings,
|
||||
operator.CloseBlendWarning,
|
||||
operator.CloseError,
|
||||
|
||||
@@ -83,6 +83,22 @@ def draw_attribute(
|
||||
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
|
||||
elif value_name == "filepath_value":
|
||||
attribute.filepath_value.layout_file_select(layout, filter_glob=attribute.filter_glob, text=attribute.name)
|
||||
|
||||
elif value_name == "subitems_values":
|
||||
col = layout.column()
|
||||
layout = col.row(align=True)
|
||||
layout.label(text=f"{attribute.name}:")
|
||||
data_path = tool.Blender.get_full_data_path(attribute, value_name)
|
||||
for i, item in enumerate(attribute.subitems_values, 1):
|
||||
row = col.row(align=True)
|
||||
row.alignment = "EXPAND"
|
||||
row.prop(item, "name", text=f"# {i}")
|
||||
op = row.operator("bim.attribute_remove_subitem", text="", icon="X")
|
||||
op.data_path = data_path
|
||||
op.index = i - 1
|
||||
op = layout.operator("bim.attribute_add_subitem", icon="ADD", text="")
|
||||
op.data_path = data_path
|
||||
|
||||
elif attribute.name in ("ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"):
|
||||
props = tool.Sequence.get_work_schedule_props()
|
||||
for item in props.durations_attributes:
|
||||
@@ -167,6 +183,8 @@ def import_attribute(
|
||||
) -> None:
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
# Complex data types (aggregates and entities) are handled only by callback.
|
||||
if data_type == ("list", "string"):
|
||||
data_type = "list[string]"
|
||||
if isinstance(data_type, tuple) or data_type == "entity":
|
||||
callback(attribute.name(), None, data) if callback else None
|
||||
return
|
||||
@@ -177,6 +195,7 @@ def import_attribute(
|
||||
new.is_optional = attribute.optional()
|
||||
new.data_type = data_type if isinstance(data_type, str) else ""
|
||||
new.ifc_class = data["type"]
|
||||
|
||||
is_handled_by_callback = callback(attribute.name(), new, data) if callback else None
|
||||
data_type = new.data_type # Allow callback to override data type.
|
||||
|
||||
@@ -222,6 +241,12 @@ def import_attribute(
|
||||
|
||||
if enum_value is not None:
|
||||
new.enum_value = enum_value
|
||||
elif data_type == "list[string]":
|
||||
value: Union[list[str], None] = data[attribute.name()]
|
||||
if value:
|
||||
for item in value:
|
||||
new.subitems_values.add().name = str(item).replace("\n", "\\n")
|
||||
|
||||
add_attribute_description(new, data)
|
||||
add_attribute_min_max(attribute, new)
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ from natsort import natsorted
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.prop import MultipleFileSelect, Attribute
|
||||
from bpy.stub_internal import rna_enums
|
||||
|
||||
|
||||
class SetTab(bpy.types.Operator):
|
||||
@@ -1407,3 +1408,43 @@ class BIM_OT_attribute_search_values(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_attribute_add_subitem(bpy.types.Operator):
|
||||
bl_idname = "bim.attribute_add_subitem"
|
||||
bl_label = "Add Subitem"
|
||||
bl_description = "Add subitem to the current attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
data_path: bpy.props.StringProperty()
|
||||
"""Full data path."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
data_path: str
|
||||
|
||||
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||
col: "bpy.types.bpy_prop_collection_idprop[StrProperty]"
|
||||
col = eval(self.data_path)
|
||||
col.add()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_attribute_remove_subitem(bpy.types.Operator):
|
||||
bl_idname = "bim.attribute_remove_subitem"
|
||||
bl_label = "Add Subitem"
|
||||
bl_description = "Add subitem to the current attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
data_path: bpy.props.StringProperty()
|
||||
"""Full data path."""
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
data_path: str
|
||||
index: int
|
||||
|
||||
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||
col: "bpy.types.bpy_prop_collection_idprop[StrProperty]"
|
||||
col = eval(self.data_path)
|
||||
col.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -234,7 +234,9 @@ def update_attribute_value(self: "Attribute", context: bpy.types.Context) -> Non
|
||||
|
||||
def update_is_null(self: "Attribute", context: bpy.types.Context) -> None:
|
||||
if self.is_null:
|
||||
if self.data_type != "enum" and self.get_value() != (default := self.get_value_default()):
|
||||
if self.data_type == "list[string]":
|
||||
self.subitems_values.clear()
|
||||
elif self.data_type != "enum" and self.get_value() != (default := self.get_value_default()):
|
||||
self.set_value(default)
|
||||
if self.is_null is not True:
|
||||
self.is_null = True
|
||||
@@ -286,7 +288,7 @@ def get_display_name(self: "Attribute") -> str:
|
||||
return f"{name}, {unit_symbol}"
|
||||
|
||||
|
||||
AttributeDataType = Literal["string", "integer", "float", "boolean", "enum", "file"]
|
||||
AttributeDataType = Literal["string", "integer", "float", "boolean", "enum", "file", "list[string]"]
|
||||
AttributeSpecialType = Literal["", "DATE", "DATETIME", "LENGTH", "AREA", "VOLUME", "FORCE", "LOGICAL", "URI"]
|
||||
|
||||
|
||||
@@ -300,6 +302,8 @@ class Attribute(PropertyGroup):
|
||||
name="Data Type",
|
||||
items=[(i, i, "") for i in get_args(AttributeDataType)],
|
||||
)
|
||||
|
||||
# Value containers.
|
||||
string_value: StringProperty(name="Value", update=update_attribute_value, description=tooltip)
|
||||
bool_value: BoolProperty(name="Value", update=update_attribute_value, description=tooltip)
|
||||
int_value: IntProperty(
|
||||
@@ -330,8 +334,11 @@ class Attribute(PropertyGroup):
|
||||
filepath_value: PointerProperty(type=MultipleFileSelect)
|
||||
filter_glob: StringProperty()
|
||||
is_null: BoolProperty(name="Is Null", update=update_is_null)
|
||||
is_optional: BoolProperty(name="Is Optional")
|
||||
is_selected: BoolProperty(name="Is Selected", default=False)
|
||||
subitems_values: CollectionProperty(type=StrProperty) # pyright: ignore[reportRedeclaration]
|
||||
|
||||
# Attribute parameters.
|
||||
is_optional: BoolProperty(name="Is Optional")
|
||||
value_min: FloatProperty(description="This is used to validate int_value and float_value")
|
||||
value_min_constraint: BoolProperty(default=False, description="True if the numerical value has a lower bound")
|
||||
value_max: FloatProperty(description="This is used to validate int_value and float_value")
|
||||
@@ -359,8 +366,10 @@ class Attribute(PropertyGroup):
|
||||
filepath_value: MultipleFileSelect
|
||||
filter_glob: str
|
||||
is_null: bool
|
||||
is_optional: bool
|
||||
is_selected: bool
|
||||
subitems_values: bpy.types.bpy_prop_collection_idprop[StrProperty]
|
||||
|
||||
is_optional: bool
|
||||
value_min: float
|
||||
value_min_constraint: bool
|
||||
value_max: float
|
||||
@@ -368,13 +377,16 @@ class Attribute(PropertyGroup):
|
||||
metadata: str
|
||||
update: str
|
||||
|
||||
def get_value(self) -> Union[str, float, int, bool, None]:
|
||||
def get_value(self) -> Union[str, float, int, bool, list[str], None]:
|
||||
if self.is_optional and self.is_null:
|
||||
return None
|
||||
if self.data_type == "string":
|
||||
return self.string_value.replace("\\n", "\n")
|
||||
if self.data_type == "file":
|
||||
return [f.name for f in self.filepath_value.file_list]
|
||||
elif self.data_type == "list[string]":
|
||||
return [s.name for s in self.subitems_values]
|
||||
|
||||
value_name = self.get_value_name()
|
||||
if value_name == "enum_value":
|
||||
value = tool.Blender.get_enum_safe(self, "enum_value")
|
||||
@@ -385,7 +397,7 @@ class Attribute(PropertyGroup):
|
||||
value = value == "TRUE"
|
||||
return value
|
||||
|
||||
def get_value_default(self) -> Union[str, float, int, bool]:
|
||||
def get_value_default(self) -> Union[str, float, int, bool, list[str]]:
|
||||
data_type = self.data_type
|
||||
if data_type == "string":
|
||||
return ""
|
||||
@@ -399,10 +411,12 @@ class Attribute(PropertyGroup):
|
||||
return "0"
|
||||
elif data_type == "file":
|
||||
return ""
|
||||
elif data_type == "list[string]":
|
||||
return []
|
||||
else:
|
||||
assert_never(data_type)
|
||||
|
||||
def get_value_name(self, display_only: bool = False) -> str:
|
||||
def get_value_name(self, display_only: bool = False):
|
||||
"""Get name of the value attribute.
|
||||
|
||||
:param display_only: Should be `True` if the value won't be accessed directly
|
||||
@@ -423,6 +437,8 @@ class Attribute(PropertyGroup):
|
||||
return "enum_value"
|
||||
elif data_type == "file":
|
||||
return "filepath_value"
|
||||
elif data_type == "list[string]":
|
||||
return "subitems_values"
|
||||
else:
|
||||
assert_never(data_type)
|
||||
|
||||
|
||||
@@ -1534,6 +1534,22 @@ class Blender(bonsai.core.tool.Blender):
|
||||
obj.matrix_world = matrix
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def get_full_data_path(cls, bpy_struct: bpy.types.bpy_struct, path: str = "") -> str:
|
||||
"""Get full data path to Blender entity or it's attributes.
|
||||
|
||||
:param bpy_struct: Blender entity.
|
||||
:param path: Additional path to add to entity.
|
||||
|
||||
:return: Path in a format
|
||||
``bpy.data.scenes['Scene'].BIMExplorerProperties.entity_attributes[4].enum_value``
|
||||
"""
|
||||
if path:
|
||||
bpy_prop: bpy.types.bpy_prop # pyright: ignore[reportAttributeAccessIssue]
|
||||
bpy_prop = bpy_struct.path_resolve(path, False)
|
||||
return repr(bpy_prop)
|
||||
return repr(bpy_struct)
|
||||
|
||||
@classmethod
|
||||
def set_prop_from_path(cls, bpy_object: bpy.types.bpy_struct, prop_path: str, value: Any) -> None:
|
||||
"""Set `data_block` property value using path from `path_from_id`."""
|
||||
|
||||
Reference in New Issue
Block a user