Ifc class suggestion (#2290)

* You can now use suggestions in ifc class enum search popup

* Move data to data submodule

* Fix error when suggestion mapping contains only one value

* Add predefined types to the search popup

* Use defaultdict to be able to add suggestions later

* Support predefined types and format name to display the ifc class
and fix bug where the prop is not defined in the suggestion dictionary

* Move data retrieval to data module

* Simplify prop search items retrieval

* Support prop search for more enums
This commit is contained in:
Gorgious56
2022-07-19 10:19:58 +02:00
committed by GitHub
parent f4aac566d5
commit 1b1588359f
18 changed files with 108 additions and 111 deletions
@@ -69,9 +69,3 @@ class BIMBrickProperties(PropertyGroup):
libraries: EnumProperty(name="Libraries", items=get_libraries)
namespace: EnumProperty(name="Namespace", items=get_namespaces)
brick_equipment_class: EnumProperty(name="Brick Equipment Class", items=get_brick_equipment_classes)
getter_enum = {
"libraries": get_libraries,
"namespace": get_namespaces,
"brick_equipment_class": get_brick_equipment_classes,
}
@@ -64,8 +64,8 @@ class BIM_PT_context(bpy.types.Panel):
row.operator("bim.remove_context", icon="X", text="").context = ifc_context["id"]
row = box.row(align=True)
row.prop(props, "subcontexts", text="")
row.prop(props, "target_views", text="")
blenderbim.bim.helper.prop_with_search(row, props, "subcontexts", text="")
blenderbim.bim.helper.prop_with_search(row, props, "target_views", text="")
op = row.operator("bim.add_context", icon="ADD", text="")
op.context_type = ifc_context["context_type"]
op.context_identifier = props.subcontexts
@@ -137,5 +137,3 @@ class BIMObjectMaterialProperties(PropertyGroup):
parameterized_profile_classes: EnumProperty(
items=getParameterizedProfileClasses, name="Parameterized Profile Classes"
)
getter_enum = {"material": get_materials}
@@ -51,7 +51,7 @@ class BIM_PT_materials(Panel):
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
else:
row = self.layout.row(align=True)
row.prop(self.props, "material_type", text="")
prop_with_search(row, self.props, "material_type", text="")
row.operator("bim.load_materials", text="", icon="IMPORT")
return
@@ -189,7 +189,7 @@ class BIM_PT_object_material(Panel):
return self.draw_material_ui()
row = self.layout.row(align=True)
row.prop(self.props, "material_type", text="")
prop_with_search(row, self.props, "material_type", text="")
if self.props.material_type == "IfcMaterial" or self.props.material_type == "IfcMaterialList":
prop_with_search(row, self.props, "material", text="")
row.operator("bim.assign_material", icon="ADD", text="")
@@ -66,7 +66,3 @@ class BIMPatchProperties(PropertyGroup):
ifc_patch_output: StringProperty(default="", name="IFC Patch Output IFC")
ifc_patch_args: StringProperty(default="", name="Arguments")
ifc_patch_args_attr: CollectionProperty(type=Attribute, name="Arguments")
getter_enum = {
"ifc_patch_recipes": get_ifcpatch_recipes,
}
@@ -167,28 +167,3 @@ class BIMProjectProperties(PropertyGroup):
def get_library_element_index(self, lib_element):
return next((i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element))
getter_enum = {
"collection_mode": lambda self, context: [
("DECOMPOSITION", "Decomposition", "Collections represent aggregates and spatial containers"),
("SPATIAL_DECOMPOSITION", "Spatial Decomposition", "Collections represent spatial containers"),
("IFC_CLASS", "IFC Class", "Collections represent IFC class"),
("NONE", "None", "No collections are created"),
],
"filter_mode": lambda self, context: [
("NONE", "None", "No filtering is performed"),
("DECOMPOSITION", "Decomposition", "Filter objects by decomposition"),
("IFC_CLASS", "IFC Class", "Filter objects by class"),
("IFC_TYPE", "IFC Type", "Filter objects by type"),
("WHITELIST", "Whitelist", "Filter objects using a custom whitelist query"),
("BLACKLIST", "Blacklist", "Filter objects using a custom blacklist query"),
],
"merge_mode": lambda self, context: [
("NONE", "None", "No objects are merged"),
("IFC_CLASS", "IFC Class", "One object per IFC class"),
("IFC_TYPE", "IFC Type", "One object per IFC construction type"),
("MATERIAL", "Material", "One object per material"),
],
"export_schema": get_export_schema,
"template_file": get_template_file,
}
@@ -158,11 +158,6 @@ class PsetProperties(PropertyGroup):
pset_name: EnumProperty(items=get_pset_names, name="Pset Name")
qto_name: EnumProperty(items=get_qto_names, name="Qto Name")
getter_enum = {
"qto_name": get_qto_names,
"pset_name": get_pset_names,
}
class MaterialPsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
@@ -223,14 +218,6 @@ class AddEditProperties(PropertyGroup):
)
enum_values: CollectionProperty(name="Enum Values", type=Attribute)
getter_enum = {
"primary_measure_type": get_primary_measure_type,
"template_type": lambda self, context: [
("IfcPropertySingleValue", "IfcPropertySingleValue", "IfcPropertySingleValue"),
("IfcPropertyEnumeratedValue", "IfcPropertyEnumeratedValue", "IfcPropertyEnumeratedValue"),
],
}
def get_value_name(self):
ifc_data_type = IfcStore.get_schema().declaration_by_name(self.primary_measure_type)
data_type = ifcopenshell.util.attribute.get_primitive_type(ifc_data_type)
@@ -252,7 +252,7 @@ class BIM_PT_material_psets(Panel):
props = context.active_object.active_material.PsetProperties
row = self.layout.row(align=True)
row.prop(props, "pset_name", text="")
prop_with_search(row, props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
op.obj = context.active_object.active_material.name
op.obj_type = "Material"
@@ -348,7 +348,7 @@ class BIM_PT_resource_psets(Panel):
props = context.scene.ResourcePsetProperties
row = self.layout.row(align=True)
row.prop(props, "pset_name", text="")
prop_with_search(row, props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
op.obj_type = "Resource"
@@ -381,7 +381,7 @@ class BIM_PT_profile_psets(Panel):
props = context.scene.ProfilePsetProperties
row = self.layout.row(align=True)
row.prop(props, "pset_name", text="")
prop_with_search(row, props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
op.obj_type = "Profile"
@@ -410,7 +410,7 @@ class BIM_PT_work_schedule_psets(Panel):
props = context.scene.WorkSchedulePsetProperties
row = self.layout.row(align=True)
row.prop(props, "pset_name", text="")
prop_with_search(row, props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
op.obj_type = "WorkSchedule"
@@ -450,7 +450,7 @@ class BIM_PT_rename_parameters(Panel):
if props:
for index, prop in enumerate(props):
row = layout.row(align=True)
row.prop(prop, "pset_name", text="")
prop_with_search(row, prop, "pset_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="")
@@ -485,7 +485,7 @@ class BIM_PT_add_edit_custom_properties(Panel):
if props:
for index, prop in enumerate(props):
row = layout.row(align=True)
row.prop(prop, "pset_name", text="")
prop_with_search(row, prop, "pset_name", text="")
row.prop(prop, "property_name", text="")
if prop.template_type == "IfcPropertySingleValue":
row.prop(prop, prop.get_value_name(), text="")
@@ -537,7 +537,7 @@ class BIM_PT_delete_psets(Panel):
if props:
for index, prop in enumerate(props):
row = layout.row(align=True)
row.prop(prop, "pset_name", text="")
prop_with_search(row, prop, "pset_name", text="")
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
op.index = index
op.option = "DeletePsets"
@@ -146,10 +146,6 @@ class PsetTemplate(PropertyGroup):
template_type: EnumProperty(items=get_template_type, name="Template Type")
applicable_entity: StringProperty(name="Applicable Entity")
getter_enum = {
"template_type": get_template_type,
}
class EnumerationValues(PropertyGroup):
string_value: StringProperty(name="Value")
@@ -168,9 +164,6 @@ class PropTemplate(PropertyGroup):
name="Template Type",
)
enum_values: CollectionProperty(type=EnumerationValues)
getter_enum = {
"primary_measure_type": get_primary_measure_type,
}
def get_value_name(self):
ifc_data_type = IfcStore.get_schema().declaration_by_name(self.primary_measure_type)
@@ -195,8 +188,3 @@ class BIMPsetTemplateProperties(PropertyGroup):
active_pset_template: PointerProperty(type=PsetTemplate)
active_prop_template: PointerProperty(type=PropTemplate)
new_template_filename: StringProperty("New TemplateFileName")
getter_enum = {
"pset_template_files": getPsetTemplateFiles,
"pset_templates": getPsetTemplates,
}
@@ -16,9 +16,11 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from collections import defaultdict
import bpy
import ifcopenshell.util.element
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
def refresh():
@@ -35,6 +37,7 @@ class IfcClassData:
cls.data = {}
cls.data["ifc_products"] = cls.ifc_products()
cls.data["ifc_classes"] = cls.ifc_classes()
cls.data["ifc_classes_suggestions"] = cls.ifc_classes_suggestions()
cls.data["contexts"] = cls.contexts()
cls.data["has_entity"] = cls.has_entity()
cls.data["name"] = cls.name()
@@ -74,6 +77,41 @@ class IfcClassData:
names.extend(("IfcDoorStyle", "IfcWindowStyle"))
return [(c, c, "") for c in sorted(names)]
@classmethod
def ifc_classes_suggestions(cls):
suggestions = defaultdict(list)
suggestions.update(
{
"IfcWall": ["Glazing", "Glass", "Pane"],
"IfcWindow": ["Glazing", "Glass", "Pane"],
"IfcPlate": ["Glazing", "Glass", "Pane"],
"IfcFurniture": ["Signage"],
"IfcSlab": ["Hob"],
"IfcCovering": ["Flashing", "Capping"],
"IfcCableSegment": ["Lighting Rod"],
"IfcSensor": ["Card Reader", "Fob Reader"],
"IfcSwitchingDevice": ["Reed Switch", "Electric Isolating Switch"],
"IfcActuator": ["Electric Strike"],
"IfcAirTerminalBox": ["VAV Box"],
"IfcUnitaryEquipment": ["Fan Coil Unit"],
}
)
file = IfcStore.get_file()
if file:
for ifc_class in cls.ifc_classes():
ifc_class = ifc_class[0]
declaration = IfcStore.get_schema().declaration_by_name(ifc_class)
for attribute in declaration.attributes():
if attribute.name() == "PredefinedType":
for e in attribute.type_of_attribute().declared_type().enumeration_items():
if e in (
"NOTDEFINED",
"USERDEFINED",
):
continue
suggestions[ifc_class].append(e.title())
return suggestions
@classmethod
def contexts(cls):
results = []
@@ -131,7 +131,7 @@ class AssignClass(bpy.types.Operator, Operator):
ifc_class = self.ifc_class or props.ifc_class
predefined_type = self.userdefined_type if self.predefined_type == "USERDEFINED" else self.predefined_type
ifc_context = self.context_id
if not ifc_context and props.getter_enum["contexts"](props, context):
if not ifc_context and props._annotations__["contexts"].keywords.get("items")(props, context):
ifc_context = int(props.contexts or "0") or None
if ifc_context:
ifc_context = tool.Ifc.get().by_id(ifc_context)
@@ -88,6 +88,11 @@ def get_ifc_classes(self, context):
return IfcClassData.data["ifc_classes"]
def get_ifc_classes_suggestions():
if not IfcClassData.is_loaded:
IfcClassData.load()
return IfcClassData.data["ifc_classes_suggestions"]
def get_contexts(self, context):
if not IfcClassData.is_loaded:
IfcClassData.load()
@@ -101,9 +106,6 @@ class BIMRootProperties(PropertyGroup):
ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None)
ifc_userdefined_type: StringProperty(name="Userdefined Type")
getter_enum = {
"contexts": get_contexts,
"ifc_product": get_ifc_products,
"ifc_class": get_ifc_classes,
"ifc_predefined_type": getIfcPredefinedTypes,
getter_enum_suggestions = {
"ifc_class": get_ifc_classes_suggestions,
}
@@ -20,7 +20,7 @@ import bpy
import blenderbim.bim.helper
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes
from blenderbim.bim.helper import draw_attributes, prop_with_search
from ifcopenshell.api.structural.data import Data
from blenderbim.bim.module.structural.data import StructuralData
@@ -482,7 +482,7 @@ class BIM_PT_structural_loads(Panel):
row.operator("bim.disable_structural_load_editing_ui", text="", icon="SCREEN_BACK")
row = self.layout.row(align=True)
row.prop(self.props, "structural_load_types", text="")
prop_with_search(row, self.props, "structural_load_types", text="")
row.operator("bim.add_structural_load", text="", icon="ADD").ifc_class = self.props.structural_load_types
else:
row.operator("bim.load_structural_loads", text="", icon="GREASEPENCIL")
@@ -551,7 +551,7 @@ class BIM_PT_boundary_conditions(Panel):
row.operator("bim.disable_boundary_condition_editing_ui", text="", icon="SCREEN_BACK")
row = self.layout.row(align=True)
row.prop(self.props, "boundary_condition_types", text="")
prop_with_search(row, self.props, "boundary_condition_types", text="")
row.operator(
"bim.add_boundary_condition", text="", icon="ADD"
).ifc_class = self.props.boundary_condition_types
@@ -47,7 +47,7 @@ class BIM_PT_styles(Panel):
if self.props.is_editing:
row.operator("bim.disable_editing_styles", text="", icon="CANCEL")
else:
row.prop(self.props, "style_type", text="")
blenderbim.bim.helper.prop_with_search(row, self.props, "style_type", text="")
row.operator("bim.load_styles", text="", icon="IMPORT").style_type = self.props.style_type
return
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from blenderbim.bim.helper import prop_with_search
import blenderbim.tool as tool
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
@@ -46,7 +47,7 @@ class BIM_PT_systems(Panel):
row.operator("bim.disable_system_editing_ui", text="", icon="CANCEL")
row = self.layout.row(align=True)
row.prop(self.props, "system_class", text="")
prop_with_search(row, self.props, "system_class", text="")
row.operator("bim.add_system", text="", icon="ADD")
else:
row.operator("bim.load_systems", text="", icon="GREASEPENCIL")
@@ -68,9 +68,3 @@ class BIMUnitProperties(PropertyGroup):
conversion_unit_types: EnumProperty(items=get_conversion_unit_types, name="Conversion Unit Types")
named_unit_types: EnumProperty(items=get_named_unit_types, name="Named Unit Types")
unit_attributes: CollectionProperty(name="Unit Attributes", type=Attribute)
getter_enum = {
"unit_classes": get_unit_classes,
"conversion_unit_types": get_conversion_unit_types,
"named_unit_types": get_named_unit_types,
}
+45 -17
View File
@@ -527,9 +527,10 @@ class ConfigureVisibility(bpy.types.Operator):
def update_enum_property_search_prop(self, context):
for i, prop in enumerate(self.collection_name):
for i, prop in enumerate(self.collection_names):
if prop.name == self.dummy_name:
setattr(context.data, self.prop_name, self.collection_identifier[i].name)
setattr(context.data, self.prop_name, self.collection_identifiers[i].name)
break
class BIM_OT_enum_property_search(bpy.types.Operator):
@@ -537,28 +538,55 @@ class BIM_OT_enum_property_search(bpy.types.Operator):
bl_label = "Search For Property"
bl_options = {"REGISTER", "UNDO"}
dummy_name: bpy.props.StringProperty(name="Property", update=update_enum_property_search_prop)
collection_name: bpy.props.CollectionProperty(type=StrProperty)
collection_identifier: bpy.props.CollectionProperty(type=StrProperty)
collection_names: bpy.props.CollectionProperty(type=StrProperty)
collection_identifiers: bpy.props.CollectionProperty(type=StrProperty)
prop_name: bpy.props.StringProperty()
def invoke(self, context, event):
self.clear_collections()
self.data = context.data
getter = self.data.getter_enum.get(self.prop_name, None)
if getter is None:
items = self.data.__annotations__[self.prop_name].keywords.get("items")
if items is None:
return {"FINISHED"}
self.collection_name.clear()
self.collection_identifier.clear()
for item in getter(self.data, context):
self.collection_identifier.add().name = item[0]
if item[0] == getattr(self.data, self.prop_name):
self.dummy_name = item[1] # We found the current enum value
self.collection_name.add().name = item[1]
if not isinstance(items, list):
items = items(self.data, context)
self.add_items_regular(items)
self.add_items_suggestions()
return context.window_manager.invoke_props_dialog(self)
def execute(self, context):
return {"FINISHED"}
def draw(self, context):
# Mandatory to access context.data in update :
self.layout.context_pointer_set(name="data", data=self.data)
self.layout.prop_search(self, "dummy_name", self, "collection_name")
self.layout.prop_search(self, "dummy_name", self, "collection_names")
def execute(self, context):
return {"FINISHED"}
def clear_collections(self):
self.collection_names.clear()
self.collection_identifiers.clear()
def add_item(self, identifier: str, name: str):
self.collection_identifiers.add().name = identifier
self.collection_names.add().name = name
def add_items_regular(self, items):
self.identifiers = []
for item in items:
self.identifiers.append(item[0])
self.add_item(identifier=item[0], name=item[1])
if item[0] == getattr(self.data, self.prop_name):
self.dummy_name = item[1] # We found the current enum name
def add_items_suggestions(self):
getter_suggestions = getattr(self.data, "getter_enum_suggestions", None)
if getter_suggestions is not None:
mapping = getter_suggestions.get(self.prop_name)
if mapping is None:
return
for key, values in mapping().items():
if key in self.identifiers:
if not isinstance(values, (tuple, list)):
values = [values]
for value in values:
self.add_item(identifier=key, name=key + " (" + value + ")")
-4
View File
@@ -240,10 +240,6 @@ class Attribute(PropertyGroup):
value = str(value)
setattr(self, self.get_value_name(), value)
getter_enum = {
"enum_value": getAttributeEnumValues,
}
class ModuleVisibility(PropertyGroup):
name: StringProperty(name="Name")