Move bulk properties to GlobalPsetProperties

This commit is contained in:
Andrej730
2025-07-08 11:12:08 +05:00
parent 188b6a03a8
commit ad8913a979
6 changed files with 162 additions and 88 deletions
+5 -11
View File
@@ -33,16 +33,16 @@ classes = (
operator.UnsharePset,
operator.BIM_OT_add_property_to_edit,
operator.BIM_OT_remove_property_to_edit,
operator.BIM_OT_clear_list,
operator.BIM_OT_rename_parameters,
operator.BIM_OT_bulk_edit_clear_list,
operator.BIM_OT_pset_bulk_rename_parameters,
operator.BIM_OT_add_edit_custom_property,
operator.BIM_OT_bulk_remove_psets,
prop.IfcPropertyEnumeratedValue,
prop.IfcProperty,
prop.PsetProperties,
prop.RenameProperties,
prop.AddEditProperties,
prop.DeletePsets,
prop.RenamePropertyEntry,
prop.AddEditPropertyEntry,
prop.DeletePsetEntry,
prop.GlobalPsetProperties,
ui.BIM_PT_object_psets,
ui.BIM_PT_object_qtos,
@@ -71,9 +71,6 @@ def register():
bpy.types.Scene.GroupPsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Scene.ProfilePsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Scene.WorkSchedulePsetProperties = bpy.props.PointerProperty(type=prop.PsetProperties)
bpy.types.Scene.RenameProperties = bpy.props.CollectionProperty(type=prop.RenameProperties)
bpy.types.Scene.AddEditProperties = bpy.props.CollectionProperty(type=prop.AddEditProperties)
bpy.types.Scene.DeletePsets = bpy.props.CollectionProperty(type=prop.DeletePsets)
bpy.types.Scene.GlobalPsetProperties = bpy.props.PointerProperty(type=prop.GlobalPsetProperties)
@@ -86,7 +83,4 @@ def unregister():
del bpy.types.Scene.GroupPsetProperties
del bpy.types.Scene.ProfilePsetProperties
del bpy.types.Scene.WorkSchedulePsetProperties
del bpy.types.Scene.RenameProperties
del bpy.types.Scene.AddEditProperties
del bpy.types.Scene.DeletePsets
del bpy.types.Scene.GlobalPsetProperties
+1 -1
View File
@@ -26,7 +26,7 @@ import bonsai.bim.schema
# TODO: Should this cache belong here? Dunno. Maybe.
is_expanded = {}
is_expanded: dict[int, bool] = {}
def refresh():
+81 -34
View File
@@ -18,6 +18,7 @@
import bpy
import json
from bpy.types import Context, OperatorProperties
import ifcopenshell.api
import ifcopenshell.api.pset
import ifcopenshell.util.attribute
@@ -29,6 +30,10 @@ import bonsai.tool as tool
import bonsai.core.pset as core
import bonsai.bim.module.pset.data
from bonsai.bim.ifc import IfcStore
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.pset.prop import RenamePropertyEntry, AddEditPropertyEntry
class TogglePsetExpansion(bpy.types.Operator, tool.Ifc.Operator):
@@ -263,55 +268,85 @@ class CopyPropertyToSelection(bpy.types.Operator, tool.Ifc.Operator):
class BIM_OT_add_property_to_edit(bpy.types.Operator):
bl_label = "Add Edit Rule"
bl_label = "Add Property to Edit"
bl_idname = "bim.add_property_to_edit"
bl_options = {"REGISTER", "UNDO"}
option: bpy.props.StringProperty()
index: bpy.props.IntProperty(default=-1)
option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
items=[(t, t, "") for t in tool.Pset.BULK_OPERATION_TYPES],
)
index: bpy.props.IntProperty(default=-1) # pyright: ignore[reportRedeclaration]
if TYPE_CHECKING:
option: tool.Pset.BulkOperationType
index: int
@classmethod
def description(cls, context: bpy.types.Context, properties: bpy.types.OperatorProperties) -> str:
return f"Add property entry to for bulk operation '{properties.option}'."
def execute(self, context):
if self.index == -1:
getattr(context.scene, self.option).add()
tool.Pset.get_bulk_operation_collection(self.option).add()
else:
getattr(context.scene, self.option)[self.index].enum_values.add()
assert self.option == "ADD_EDIT"
props = tool.Pset.get_global_pset_props()
props.psets_to_add_edit[self.index].enum_values.add()
return {"FINISHED"}
class BIM_OT_remove_property_to_edit(bpy.types.Operator):
bl_label = "Remove Property to Be Renamed"
bl_label = "Remove Property from Editing"
bl_idname = "bim.remove_property_to_edit"
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
index2: bpy.props.IntProperty(default=-1)
option: bpy.props.StringProperty()
index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
index2: bpy.props.IntProperty(default=-1) # pyright: ignore[reportRedeclaration]
option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
items=[(t, t, "") for t in tool.Pset.BULK_OPERATION_TYPES],
)
if TYPE_CHECKING:
index: int
index2: int
option: tool.Pset.BulkOperationType
@classmethod
def description(cls, context: bpy.types.Context, properties: bpy.types.OperatorProperties) -> str:
return f"Remove property entry from bulk operation '{properties.option}'."
def execute(self, context):
if self.index2 == -1:
getattr(context.scene, self.option).remove(self.index)
tool.Pset.get_bulk_operation_collection(self.option).remove(self.index)
else:
getattr(context.scene, self.option)[self.index].enum_values.remove(self.index2)
assert self.option == "ADD_EDIT"
props = tool.Pset.get_global_pset_props()
props.psets_to_add_edit[self.index].enum_values.remove(self.index2)
return {"FINISHED"}
class BIM_OT_clear_list(bpy.types.Operator):
class BIM_OT_bulk_edit_clear_list(bpy.types.Operator):
bl_label = "Clear List of Properties"
bl_idname = "bim.clear_list"
bl_idname = "bim.pset_bulk_edit_clear_list"
bl_options = {"REGISTER", "UNDO"}
option: bpy.props.StringProperty()
option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
items=[(t, t, "") for t in tool.Pset.BULK_OPERATION_TYPES],
)
if TYPE_CHECKING:
option: tool.Pset.BulkOperationType
def execute(self, context):
getattr(context.scene, self.option).clear()
tool.Pset.get_bulk_operation_collection(self.option).clear()
return {"FINISHED"}
class BIM_OT_rename_parameters(bpy.types.Operator, tool.Ifc.Operator):
class BIM_OT_pset_bulk_rename_parameters(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Rename Parameters"
bl_idname = "bim.rename_parameters"
bl_idname = "bim.pset_bulk_rename_parameters"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Rename parameters that are subclasses of IfcElement"
def _execute(self, context):
props_to_map = context.scene.RenameProperties
props_to_map = tool.Pset.get_global_pset_props().psets_to_rename
ifc_file = tool.Ifc.get()
all_ifc_elements = ifc_file.by_type("IfcElement")
@@ -319,20 +354,27 @@ class BIM_OT_rename_parameters(bpy.types.Operator, tool.Ifc.Operator):
for definition in ifc_element.IsDefinedBy:
if definition.is_a("IfcRelDefinesByProperties"):
prop_set = definition.RelatingPropertyDefinition
self.rename_property(prop_set, props_to_map, ifc_element)
self.rename_property(prop_set, props_to_map)
self.report({"INFO"}, "Finished applying changes")
return {"FINISHED"}
def rename_property(self, property_set, properties_to_map, ifc_element):
def rename_property(
self,
property_set: ifcopenshell.entity_instance,
properties_to_map: "bpy.types.bpy_prop_collection_idprop[RenamePropertyEntry]",
) -> None:
property_container: tuple[ifcopenshell.entity_instance, ...]
if property_set.is_a() == "IfcPropertySet":
property_container = property_set.HasProperties
elif property_set.is_a() == "IfcElementQuantity":
property_container = property_set.Quantities
else:
assert False
for obj_prop in property_container:
for prop2map in properties_to_map:
if prop2map.pset_name != property_set.Name:
if prop2map.name != property_set.Name:
continue
if prop2map.existing_property_name == obj_prop.Name:
obj_prop.Name = prop2map.new_property_name
@@ -341,46 +383,51 @@ class BIM_OT_rename_parameters(bpy.types.Operator, tool.Ifc.Operator):
class BIM_OT_add_edit_custom_property(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Add or Edit a Custom Property"
bl_idname = "bim.add_edit_custom_property"
bl_description = "Edit pset properties for selected objects."
bl_options = {"REGISTER", "UNDO"}
index: bpy.props.IntProperty()
def _execute(self, context):
self.file = tool.Ifc.get()
props = context.scene.AddEditProperties
entries = tool.Pset.get_global_pset_props().psets_to_add_edit
elements_changed = 0
for obj in tool.Blender.get_selected_objects():
ifc_element = tool.Ifc.get_entity(obj)
if not ifc_element:
continue
for prop in props:
for prop in entries:
value = getattr(prop, prop.get_value_name())
primary_measure_type = prop.primary_measure_type
if prop.template_type == "IfcPropertyEnumeratedValue":
value_ifc_entity = self.generate_enum_entity(prop)
elif prop.template_type == "IfcPropertySingleValue":
value_ifc_entity = getattr(self.file, f"create{primary_measure_type}")(value)
value_ifc_entity = self.file.create_entity(primary_measure_type, value)
else:
assert False
new_pset = ifcopenshell.api.pset.add_pset(self.file, product=ifc_element, name=prop.pset_name)
ifcopenshell.api.pset.edit_pset(
self.file, pset=new_pset, properties={prop.property_name: value_ifc_entity}
)
self.report({"INFO"}, "Finished applying changes")
ifcopenshell.api.pset.edit_pset(self.file, pset=new_pset, properties={prop.name: value_ifc_entity})
if entries:
elements_changed += 1
self.report({"INFO"}, f"Finished applying changes, {elements_changed} elements changed.")
return {"FINISHED"}
def generate_enum_entity(self, prop):
def generate_enum_entity(self, prop: "AddEditPropertyEntry") -> ifcopenshell.entity_instance:
prop_type = prop.get_value_name()
prop_enum = self.file.create_entity(
"IFCPROPERTYENUMERATION",
Name=prop.property_name,
Name=prop.name,
EnumerationValues=tuple(
self.file.create_entity(prop.primary_measure_type, ev[prop_type]) for ev in prop.enum_values
),
)
prop_enum_value = self.file.create_entity(
"IFCPROPERTYENUMERATEDVALUE",
Name=prop.property_name,
Name=prop.name,
EnumerationValues=tuple(
self.file.create_entity(prop.primary_measure_type, ev[prop_type])
for ev in prop.enum_values
@@ -400,7 +447,7 @@ class BIM_OT_bulk_remove_psets(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
self.file = tool.Ifc.get()
props = context.scene.DeletePsets
props = tool.Pset.get_global_pset_props()
for obj in tool.Blender.get_selected_objects():
ifc_element = tool.Ifc.get_entity(obj)
@@ -408,8 +455,8 @@ class BIM_OT_bulk_remove_psets(bpy.types.Operator, tool.Ifc.Operator):
continue
psets = ifcopenshell.util.element.get_psets(ifc_element)
for prop in props:
pset = prop.pset_name
for prop in props.psets_to_delete:
pset = prop.name
if pset in psets:
try:
ifcopenshell.api.pset.remove_pset(
+20 -11
View File
@@ -233,14 +233,14 @@ def get_object_qto_name(self: "PsetProperties", context: object) -> tool.Blender
# TODO: unsafe?
def get_template_type(self: "AddEditProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
def get_template_type(self: "AddEditPropertyEntry", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
version = tool.Ifc.get_schema()
for t in ("IfcPropertySingleValue", "IfcPropertyEnumeratedValue"):
yield (t, t, ifcopenshell.util.doc.get_entity_doc(version, t).get("description", ""))
# TODO: unsafe?
def get_primary_measure_type(self: "AddEditProperties", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
def get_primary_measure_type(self: "AddEditPropertyEntry", context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
if not AddEditCustomPropertiesData.is_loaded:
AddEditCustomPropertiesData.load()
return AddEditCustomPropertiesData.data["primary_measure_type"]
@@ -295,20 +295,20 @@ class PsetProperties(PropertyGroup):
prop_value: str
class RenameProperties(PropertyGroup):
pset_name: StringProperty(name="Pset")
class RenamePropertyEntry(PropertyGroup):
name: StringProperty(name="Pset")
existing_property_name: StringProperty(name="Existing Property Name")
new_property_name: StringProperty(name="New Property Name")
if TYPE_CHECKING:
pset_name: str
name: str
existing_property_name: str
new_property_name: str
class AddEditProperties(PropertyGroup):
class AddEditPropertyEntry(PropertyGroup):
pset_name: StringProperty(name="Pset")
property_name: StringProperty(name="Property")
name: StringProperty(name="Property")
string_value: StringProperty(name="Value")
bool_value: BoolProperty(name="Value")
int_value: IntProperty(name="Value")
@@ -319,7 +319,7 @@ class AddEditProperties(PropertyGroup):
if TYPE_CHECKING:
pset_name: str
property_name: str
name: str
string_value: str
bool_value: bool
int_value: int
@@ -342,17 +342,26 @@ class AddEditProperties(PropertyGroup):
return "float_value"
class DeletePsets(PropertyGroup):
pset_name: StringProperty(name="Pset")
# This class is needed just to make tooltip more descriptive.
class DeletePsetEntry(PropertyGroup):
name: StringProperty(name="Pset to Remove")
if TYPE_CHECKING:
pset_name: str
name: str
class GlobalPsetProperties(PropertyGroup):
pset_filter: StringProperty(name="Pset Filter", options={"TEXTEDIT_UPDATE"})
qto_filter: StringProperty(name="Qto Filter", options={"TEXTEDIT_UPDATE"})
# Bulk operations.
psets_to_delete: CollectionProperty(type=DeletePsetEntry) # pyright: ignore[reportRedeclaration]
psets_to_rename: CollectionProperty(type=RenamePropertyEntry) # pyright: ignore[reportRedeclaration]
psets_to_add_edit: CollectionProperty(type=AddEditPropertyEntry) # pyright: ignore[reportRedeclaration]
if TYPE_CHECKING:
pset_filter: str
qto_filter: str
psets_to_delete: bpy.types.bpy_prop_collection_idprop[DeletePsetEntry]
psets_to_rename: bpy.types.bpy_prop_collection_idprop[RenamePropertyEntry]
psets_to_add_edit: bpy.types.bpy_prop_collection_idprop[AddEditPropertyEntry]
+30 -30
View File
@@ -815,28 +815,28 @@ class BIM_PT_rename_parameters(Panel):
bl_options = {"DEFAULT_CLOSED"}
def draw(self, context):
assert self.layout
layout = self.layout
props = context.scene.RenameProperties
entries = tool.Pset.get_global_pset_props().psets_to_rename
row = layout.row()
op = row.operator("bim.add_property_to_edit", icon="ADD")
op.option = "RenameProperties"
op.option = "RENAME"
if props:
for index, prop in enumerate(props):
if entries:
for index, prop in enumerate(entries):
row = layout.row(align=True)
prop_with_search(row, prop, "pset_name", text="")
row.prop(prop, "name", text="")
row.prop(prop, "existing_property_name", text="")
row.prop(prop, "new_property_name", text="")
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
op.index = index
op.option = "RenameProperties"
op.option = "RENAME"
if props:
row = layout.row(align=True)
row.operator("bim.rename_parameters", icon="CHECKMARK")
clear = row.operator("bim.clear_list", icon="CANCEL", text="")
clear.option = "RenameProperties"
row.operator("bim.pset_bulk_rename_parameters", icon="CHECKMARK")
clear = row.operator("bim.pset_bulk_edit_clear_list", icon="CANCEL", text="")
clear.option = "RENAME"
class BIM_PT_add_edit_custom_properties(Panel):
@@ -849,30 +849,31 @@ class BIM_PT_add_edit_custom_properties(Panel):
bl_options = {"DEFAULT_CLOSED"}
def draw(self, context):
assert self.layout
layout = self.layout
props = context.scene.AddEditProperties
entries = tool.Pset.get_global_pset_props().psets_to_add_edit
row = layout.row()
op = row.operator("bim.add_property_to_edit", icon="ADD")
op.option = "AddEditProperties"
op.option = "ADD_EDIT"
op.index = -1
if props:
for index, prop in enumerate(props):
if entries:
for index, prop in enumerate(entries):
row = layout.row(align=True)
prop_with_search(row, prop, "pset_name", text="")
row.prop(prop, "property_name", text="")
row.prop(prop, "name", text="")
if prop.template_type == "IfcPropertySingleValue":
row.prop(prop, prop.get_value_name(), text="")
prop_with_search(row, prop, "primary_measure_type", text="")
row.prop(prop, "template_type", text="")
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
op.index = index
op.option = "AddEditProperties"
op.option = "ADD_EDIT"
if prop.template_type == "IfcPropertyEnumeratedValue":
op = row.operator("bim.add_property_to_edit", icon="ADD", text="Add Enum")
op.option = "AddEditProperties"
op.option = "ADD_EDIT"
op.index = index
for index2, prop2 in enumerate(prop.enum_values):
row = layout.row()
@@ -883,13 +884,12 @@ class BIM_PT_add_edit_custom_properties(Panel):
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
op.index = index
op.index2 = index2
op.option = "AddEditProperties"
op.option = "ADD_EDIT"
if props:
row = layout.row(align=True)
op = row.operator("bim.add_edit_custom_property", icon="CHECKMARK", text="Apply Changes")
clear = row.operator("bim.clear_list", icon="CANCEL", text="")
clear.option = "AddEditProperties"
clear = row.operator("bim.pset_bulk_edit_clear_list", icon="CANCEL", text="")
clear.option = "ADD_EDIT"
class BIM_PT_delete_psets(Panel):
@@ -902,23 +902,23 @@ class BIM_PT_delete_psets(Panel):
bl_options = {"DEFAULT_CLOSED"}
def draw(self, context):
assert self.layout
layout = self.layout
props = context.scene.DeletePsets
entries = tool.Pset.get_global_pset_props().psets_to_delete
row = layout.row()
op = row.operator("bim.add_property_to_edit", icon="ADD")
op.option = "DeletePsets"
op.option = "DELETE"
if props:
for index, prop in enumerate(props):
if entries:
for index, prop in enumerate(entries):
row = layout.row(align=True)
prop_with_search(row, prop, "pset_name", text="")
row.prop(prop, "name", text="")
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
op.index = index
op.option = "DeletePsets"
op.option = "DELETE"
if props:
row = layout.row(align=True)
op = row.operator("bim.bulk_remove_psets", icon="CHECKMARK", text="Apply Changes")
clear = row.operator("bim.clear_list", icon="CANCEL", text="")
clear.option = "DeletePsets"
clear = row.operator("bim.pset_bulk_edit_clear_list", icon="CANCEL", text="")
clear.option = "DELETE"
+25 -1
View File
@@ -30,16 +30,40 @@ from typing import Union, Literal, Any, TYPE_CHECKING, assert_never
if TYPE_CHECKING:
from bonsai.bim.module.pset.prop import PsetProperties, GlobalPsetProperties
from bonsai.bim.module.pset.prop import (
PsetProperties,
GlobalPsetProperties,
AddEditPropertyEntry,
RenamePropertyEntry,
DeletePsetEntry,
)
class Pset(bonsai.core.tool.Pset):
PSET_TYPE = Literal["PSET", "QTO"]
BulkOperationType = Literal["ADD_EDIT", "RENAME", "DELETE"]
BULK_OPERATION_TYPES = ("ADD_EDIT", "RENAME", "DELETE")
@classmethod
def get_global_pset_props(cls) -> GlobalPsetProperties:
return bpy.context.scene.GlobalPsetProperties
@classmethod
def get_bulk_operation_collection(cls, operation_type: BulkOperationType) -> Union[
bpy.types.bpy_prop_collection_idprop[AddEditPropertyEntry],
bpy.types.bpy_prop_collection_idprop[RenamePropertyEntry],
bpy.types.bpy_prop_collection_idprop[DeletePsetEntry],
]:
props = cls.get_global_pset_props()
if operation_type == "ADD_EDIT":
return props.psets_to_add_edit
elif operation_type == "RENAME":
return props.psets_to_rename
elif operation_type == "DELETE":
return props.psets_to_delete
else:
assert False
@classmethod
def get_element_pset(
cls, element: ifcopenshell.entity_instance, pset_name: str