Fetch latest commits

This commit is contained in:
carlos
2022-07-20 19:17:52 +02:00
71 changed files with 100942 additions and 591 deletions
+13 -10
View File
@@ -108,7 +108,7 @@ def export_attributes(props, callback=None):
return attributes
def prop_with_search(layout, data, prop_name, **kwargs):
def prop_with_search(layout, data, prop_name, **kwargs):
# kwargs are layout.prop arguments (text, icon, etc.)
row = layout.row(align=True)
# Magick courtesy of https://blender.stackexchange.com/a/203443/86891
@@ -118,15 +118,18 @@ def prop_with_search(layout, data, prop_name, **kwargs):
op.prop_name = prop_name
def col_with_margins(layout, margin_left=0.025, margin_right=None):
margin_right = margin_left if margin_right is None else margin_right
split = layout.split(factor=margin_left, align=True)
cols = [split.column() for _ in range(2)]
cols[0].label(text="")
subsplit = cols[-1].split(factor=(1. - margin_right), align=True)
subcol = subsplit.column()
subsplit.column().label(text="")
return subcol
def get_enum_items(data, prop_name, context):
# Retrieve items from a dynamic EnumProperty, which is otherwise not supported
# Or throws an error in the console when the items callback returns an empty list
# See https://blender.stackexchange.com/q/215781/86891
prop = data.__annotations__[prop_name]
items = prop.keywords.get("items")
if items is None:
return
if not isinstance(items, (list, tuple)):
# items are retrieved through a callback, not a static list :
items = items(data, context)
return items
class IfcHeaderExtractor:
@@ -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
@@ -20,6 +20,7 @@ import bpy
import ifccsv
import ifcopenshell
import json
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
@@ -44,15 +45,15 @@ class VisualiseDiff(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
# ifc_file = IfcStore.get_file() # In case we get from Store
ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file
ifc_file = tool.Ifc.get()
with open(context.scene.DiffProperties.diff_json_file, "r") as file:
diff = json.load(file)
for obj in context.visible_objects:
obj.color = (1.0, 1.0, 1.0, 0.2)
global_id = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
if not global_id:
element = tool.Ifc.get_entity(obj)
if not element:
continue
global_id = element.GlobalId
if global_id in diff["deleted"]:
obj.color = (1.0, 0.0, 0.0, 0.2)
elif global_id in diff["added"]:
@@ -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="")
@@ -575,10 +575,13 @@ def ensure_material_assigned(usecase_path, ifc_file, settings):
if om is not None and om.BIMObjectProperties.ifc_definition_id
]
if material[0].id() in object_material_ids:
if material and material[0].id() in object_material_ids:
continue
if len(obj.data.materials) == 1:
obj.data.materials.clear()
if not material:
continue
obj.data.materials.append(IfcStore.get_element(material[0].id()))
@@ -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,
}
@@ -36,7 +36,7 @@ classes = (
operator.RefreshLibrary,
operator.RewindLibrary,
operator.SaveLibraryFile,
operator.AppendEntiryLibrary,
operator.AppendEntireLibrary,
operator.SelectLibraryFile,
operator.ToggleFilterCategories,
operator.ToggleLinkVisibility,
@@ -107,10 +107,9 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector):
def commit(self, data):
IfcStore.library_path = data["filepath"]
IfcStore.library_file = ifcopenshell.open(data["filepath"])
def draw(self, context):
IFCFileSelector.draw(self, context)
self.layout.prop(self, "append_all", text= "Append Entire Library")
self.layout.prop(self, "append_all", text="Append Entire Library")
class RefreshLibrary(bpy.types.Operator):
@@ -289,10 +288,10 @@ class SaveLibraryFile(bpy.types.Operator):
return {"FINISHED"}
class AppendEntiryLibrary(bpy.types.Operator):
class AppendEntireLibrary(bpy.types.Operator):
bl_idname = "bim.append_entire_library"
bl_label = "Append Entiry Library"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
@@ -303,13 +302,15 @@ class AppendEntiryLibrary(bpy.types.Operator):
def _execute(self, context):
self.file = IfcStore.get_file()
self.library = IfcStore.library_file
lib_elements = ifcopenshell.util.selector.Selector().parse(self.library, '.IfcTypeProduct | .IfcMaterial | .IfcCostSchedule| .IfcProfileDef')
lib_elements = ifcopenshell.util.selector.Selector().parse(
self.library, ".IfcTypeProduct | .IfcMaterial | .IfcCostSchedule| .IfcProfileDef"
)
for element in lib_elements:
bpy.ops.bim.append_library_element(definition= element.id())
bpy.ops.bim.append_library_element(definition=element.id())
return {"FINISHED"}
class AppendLibraryElement(bpy.types.Operator):
bl_idname = "bim.append_library_element"
bl_label = "Append Library Element"
@@ -661,16 +662,19 @@ class LinkIfc(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_description = "Link a Blender file"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
files: bpy.props.CollectionProperty(name="Files", type=bpy.types.OperatorFileListElement)
directory: bpy.props.StringProperty(subtype="DIR_PATH")
filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"})
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
def execute(self, context):
new = context.scene.BIMProjectProperties.links.add()
filepath = self.filepath
if self.use_relative_path:
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
new.name = filepath
bpy.ops.bim.load_link(filepath=self.filepath)
for file in self.files:
filepath = os.path.join(self.directory, file.name)
new = context.scene.BIMProjectProperties.links.add()
if self.use_relative_path:
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
new.name = filepath
bpy.ops.bim.load_link(filepath=self.filepath)
return {"FINISHED"}
def invoke(self, context, event):
@@ -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,
}
@@ -124,43 +124,52 @@ class EnablePsetEditing(bpy.types.Operator):
continue # Other types not yet supported
if prop_template.TemplateType == "P_SINGLEVALUE":
self.load_single_value(prop_template, data)
elif prop_template.TemplateType.startswith("Q_"):
self.load_single_value(prop_template, data)
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
self.load_enumerated_value(prop_template, data)
def load_single_value(self, prop_template, data):
try:
data_type = ifcopenshell.util.attribute.get_primitive_type(
IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
)
except:
# TODO: Occurs if the data type is something that exists in
# IFC4 and not in IFC2X3. To fully fix this we need to
# generate the IFC2X3 pset template definitions.
return
prop = self.props.properties.add()
prop.name = prop_template.Name
prop.value_type = "IfcPropertySingleValue"
metadata = prop.metadata
metadata.name = prop_template.Name
metadata.is_null = data.get(prop_template.Name, None) is None
metadata.is_optional = True
metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
metadata.data_type = data_type
metadata.data_type = self.get_data_type(prop_template)
if data_type == "string":
if metadata.data_type == "string":
metadata.string_value = "" if metadata.is_null else data[prop_template.Name]
elif data_type == "integer":
elif metadata.data_type == "integer":
metadata.int_value = 0 if metadata.is_null else data[prop_template.Name]
elif data_type == "float":
elif metadata.data_type == "float":
metadata.float_value = 0.0 if metadata.is_null else data[prop_template.Name]
elif data_type == "boolean":
elif metadata.data_type == "boolean":
metadata.bool_value = False if metadata.is_null else data[prop_template.Name]
def get_data_type(self, prop_template):
if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
return "float"
elif prop_template.TemplateType == "Q_COUNT":
return "integer"
try:
return ifcopenshell.util.attribute.get_primitive_type(
IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
)
except:
# TODO: Occurs if the data type is something that exists in
# IFC4 and not in IFC2X3. To fully fix this we need to
# generate the IFC2X3 pset template definitions.
pass
def load_enumerated_value(self, prop_template, data):
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
selected_enum_items = data.get(prop_template.Name, [])
prop = self.props.properties.add()
prop.name = prop_template.Name
prop.value_type = "IfcPropertyEnumeratedValue"
metadata = prop.metadata
metadata.name = prop_template.Name
@@ -358,7 +367,7 @@ class GuessQuantity(bpy.types.Operator):
self.qto_calculator = QtoCalculator()
obj = context.active_object
prop = obj.PsetProperties.properties.get(self.prop)
prop.float_value = self.guess_quantity(obj, context)
prop.metadata.float_value = self.guess_quantity(obj, context)
return {"FINISHED"}
def guess_quantity(self, obj, context):
@@ -402,7 +411,7 @@ class CopyPropertyToSelection(bpy.types.Operator, Operator):
def _execute(self, context):
is_pset = tool.Ifc.get().by_id(context.active_object.PsetProperties.active_pset_id).is_a("IfcPropertySet")
pset_name = context.active_object.PsetProperties.active_pset_name
prop_value = context.active_object.PsetProperties.properties.get(self.name).get_value()
prop_value = context.active_object.PsetProperties.properties.get(self.name).metadata.get_value()
for obj in context.selected_objects:
core.copy_property_to_selection(
tool.Ifc,
@@ -158,16 +158,11 @@ 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")
active_pset_name: StringProperty(name="Pset Name")
properties: CollectionProperty(name="Properties", type=Attribute)
properties: CollectionProperty(name="Properties", type=IfcProperty)
pset_name: EnumProperty(items=getMaterialPsetNames, name="Pset Name")
@@ -189,14 +184,14 @@ class ResourcePsetProperties(PropertyGroup):
class ProfilePsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
properties: CollectionProperty(name="Properties", type=Attribute)
properties: CollectionProperty(name="Properties", type=IfcProperty)
pset_name: EnumProperty(items=getProfilePsetNames, name="Pset Name")
class WorkSchedulePsetProperties(PropertyGroup):
active_pset_id: IntProperty(name="Active Pset ID")
active_pset_name: StringProperty(name="Pset Name")
properties: CollectionProperty(name="Properties", type=Attribute)
properties: CollectionProperty(name="Properties", type=IfcProperty)
pset_name: EnumProperty(items=getWorkSchedulePsetNames, name="Pset Name")
@@ -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,12 +485,12 @@ 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="")
prop_with_search(row, prop, "primary_measure_type", text="")
prop_with_search(row, prop, "template_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"
@@ -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 = []
@@ -31,6 +31,7 @@ import blenderbim.core.root as core
import blenderbim.tool as tool
from ifcopenshell.api.void.data import Data as VoidData
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import get_enum_items
class Operator:
@@ -131,7 +132,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 get_enum_items(props, "contexts", 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,6 +20,7 @@ import bpy
from . import ui, prop, operator
classes = (
operator.EditBlenderCollection,
operator.ActivateIfcClassFilter,
operator.ActivateIfcBuildingStoreyFilter,
operator.ColourByAttribute,
@@ -31,18 +32,32 @@ classes = (
operator.SelectGlobalId,
operator.SelectIfcClass,
operator.SelectPset,
operator.UnhideAllElements,
operator.FilterModelElements,
operator.IfcSelector,
operator.SaveSelectorQuery,
operator.OpenQueryLibrary,
operator.LoadQuery,
prop.BIMFilterClasses,
prop.BIMFilterBuildingStoreys,
prop.BIMSearchProperties,
prop.SearchCollection,
prop.SearchQueryFilter,
prop.SearchQuery,
prop.SearchQueryGroup,
prop.IfcSelectorProperties,
ui.BIM_PT_search,
ui.BIM_UL_ifc_class_filter,
ui.BIM_UL_ifc_building_storey_filter,
ui.BIM_PT_IFCSelector,
)
def register():
bpy.types.Scene.BIMSearchProperties = bpy.props.PointerProperty(type=prop.BIMSearchProperties)
bpy.types.Scene.IfcSelectorProperties = bpy.props.PointerProperty(type=prop.IfcSelectorProperties)
def unregister():
del bpy.types.Scene.BIMSearchProperties
del bpy.types.Scene.IfcSelectorProperties
@@ -20,9 +20,21 @@ import re
import bpy
import ifcopenshell
import ifcopenshell.util.element
from ifcopenshell.util.selector import Selector
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from itertools import cycle
from bpy.types import PropertyGroup, Operator
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
colour_list = [
@@ -54,13 +66,29 @@ def does_keyword_exist(pattern, string, context):
return True
class SelectGlobalId(bpy.types.Operator):
class EditBlenderCollection(Operator):
bl_idname = "bim.edit_blender_collection"
bl_label = "Add or Remove blender collection item"
bl_options = {"REGISTER", "UNDO"}
option: StringProperty()
collection: StringProperty()
index: IntProperty()
def execute(self, context):
if self.option == "add":
getattr(context.bim_prop_group, self.collection).add()
else:
getattr(context.bim_prop_group, self.collection).remove(self.index)
return {"FINISHED"}
class SelectGlobalId(Operator):
"""Click to select the objects that match with the given Global ID"""
bl_idname = "bim.select_global_id"
bl_label = "Select GlobalId"
bl_options = {"REGISTER", "UNDO"}
global_id: bpy.props.StringProperty()
global_id: StringProperty()
def execute(self, context):
ifc_file = tool.Ifc.get()
@@ -73,13 +101,13 @@ class SelectGlobalId(bpy.types.Operator):
return {"FINISHED"}
class SelectIfcClass(bpy.types.Operator):
class SelectIfcClass(Operator):
"""Click to select all objects that match with the given IFC class"""
bl_idname = "bim.select_ifc_class"
bl_label = "Select IFC Class"
bl_options = {"REGISTER", "UNDO"}
ifc_class: bpy.props.StringProperty()
ifc_class: StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
@@ -92,7 +120,7 @@ class SelectIfcClass(bpy.types.Operator):
return {"FINISHED"}
class SelectAttribute(bpy.types.Operator):
class SelectAttribute(Operator):
"""Click to select all objects that match with the given Attribute Name and Value"""
bl_idname = "bim.select_attribute"
@@ -118,7 +146,7 @@ class SelectAttribute(bpy.types.Operator):
return {"FINISHED"}
class SelectPset(bpy.types.Operator):
class SelectPset(Operator):
"""Click to select all objects that match with the given Pset Name, Properties Name and Value"""
bl_idname = "bim.select_pset"
@@ -152,7 +180,7 @@ class SelectPset(bpy.types.Operator):
return {"FINISHED"}
class ColourByAttribute(bpy.types.Operator):
class ColourByAttribute(Operator):
"""Click to colour different objects according to given Attribute Name"""
bl_idname = "bim.colour_by_attribute"
@@ -203,7 +231,7 @@ class ColourByAttribute(bpy.types.Operator):
data["area"].spaces[0].shading.color_type = "OBJECT"
class ColourByPset(bpy.types.Operator):
class ColourByPset(Operator):
"""Click to colour different objects according to given Prop Name"""
bl_idname = "bim.colour_by_pset"
@@ -262,7 +290,7 @@ class ColourByPset(bpy.types.Operator):
data["area"].spaces[0].shading.color_type = "OBJECT"
class ColourByClass(bpy.types.Operator):
class ColourByClass(Operator):
"""Click to colour different objects according to their IFC Classes"""
bl_idname = "bim.colour_by_class"
@@ -308,7 +336,7 @@ class ColourByClass(bpy.types.Operator):
data["area"].spaces[0].shading.color_type = "OBJECT"
class ResetObjectColours(bpy.types.Operator):
class ResetObjectColours(Operator):
"""Reset the colour of selected objects"""
bl_idname = "bim.reset_object_colours"
@@ -320,11 +348,11 @@ class ResetObjectColours(bpy.types.Operator):
return {"FINISHED"}
class ToggleFilterSelection(bpy.types.Operator):
class ToggleFilterSelection(Operator):
"Click to select/deselect current selection"
bl_idname = "bim.toggle_filter_selection"
bl_label = "Toggle Filter Selection"
action: bpy.props.EnumProperty(items=(("SELECT", "Select", ""), ("DESELECT", "Deselect", "")))
action: EnumProperty(items=(("SELECT", "Select", ""), ("DESELECT", "Deselect", "")))
def execute(self, context):
props = bpy.context.scene.BIMSearchProperties
@@ -341,7 +369,7 @@ class ToggleFilterSelection(bpy.types.Operator):
return {"FINISHED"}
class ActivateIfcClassFilter(bpy.types.Operator):
class ActivateIfcClassFilter(Operator):
"""Filter the current selection by IFC class"""
bl_idname = "bim.activate_ifc_class_filter"
@@ -387,7 +415,7 @@ class ActivateIfcClassFilter(bpy.types.Operator):
row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT"
class ActivateIfcBuildingStoreyFilter(bpy.types.Operator):
class ActivateIfcBuildingStoreyFilter(Operator):
"""Filter the current selection by Building Storey"""
bl_idname = "bim.activate_ifc_building_storey_filter"
@@ -433,3 +461,188 @@ class ActivateIfcBuildingStoreyFilter(bpy.types.Operator):
row = self.layout.row(align=True)
row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT"
row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT"
class UnhideAllElements(Operator):
"""Filter model elements based on selection"""
bl_idname = "bim.reset_3d_view"
bl_label = "Reset 3D View"
bl_idname = "bim.unhide_all_elements"
bl_label = "Unhide All Elements"
def execute(self, context):
for obj in bpy.data.scenes["Scene"].objects:
obj.hide_set(False)
return {"FINISHED"}
class FilterModelElements(Operator):
"""Filter model elements based on selection"""
bl_idname = "bim.filter_model_elements"
bl_label = "Filter Model Elements"
option: StringProperty("select|isolate|hide")
def execute(self, context):
selector = context.scene.IfcSelectorProperties
selection = selector.selector_query_syntax if selector.manual_override else self.add_groups(selector)
selector.selector_query_syntax = selection
self.update_model_view(context, selection)
return {"FINISHED"}
def add_groups(self, selector):
selection = ""
for group_index, group in enumerate(selector.groups):
if group_index != 0:
selection += " | "
selection += "(" if len(selector.groups) > 1 else ""
selection = self.add_queries(selection, group)
selection += ")" if len(selector.groups) > 1 else ""
return selection
def add_queries(self, selection, group):
for query_index, query in enumerate(group.queries):
if query_index != 0:
selection += " & " if query.and_or == "and" else " | "
if query.selector == "IFC Class":
active_option = query.active_option.split(": ")[1]
selection += f".{active_option}"
selection = self.add_filters(selection, query)
elif query.selector == "GlobalId":
selection += f"#{query.global_id}"
elif query.selector == "IfcElementType":
index = int(query.active_sub_option.split(":")[0])
selection += f"* #{query.sub_options[index].global_id}"
elif query.selector == "IfcSpatialElement":
index = int(query.active_sub_option.split(":")[0])
selection += f"@ #{query.sub_options[index].global_id}"
return selection
def add_filters(self, selection, query):
for f_index, f in enumerate(query.filters):
if f_index != 0:
selection += " & " if f.and_or == "and" else " | "
selection += f".{query.active_option}"
selection += "["
if f.selector == "IfcPropertySet":
selection += f'{f.active_option.split(": ")[1]}.{f.active_sub_option.split(": ")[1]} {"!" if f.negation else ""}="{f.value}"'
elif f.selector == "Attribute":
selection += f'{f.attribute} {"!" if f.negation else ""}= "{f.value}"'
selection += "]"
return selection
def update_model_view(self, context, selection):
query = Selector.parse(IfcStore.file, selection)
sel_element_ids = [e.id() for e in query]
bpy.ops.object.select_all(action="DESELECT")
for obj in bpy.data.scenes["Scene"].objects:
obj.hide_set(False) # reset 3d view
if self.option == "select":
if obj.BIMObjectProperties.ifc_definition_id in sel_element_ids:
obj.select_set(True)
elif self.option == "isolate":
if obj.BIMObjectProperties.ifc_definition_id not in sel_element_ids:
obj.hide_set(True)
elif self.option == "hide":
if obj.BIMObjectProperties.ifc_definition_id in sel_element_ids:
obj.hide_set(True)
class IfcSelector(Operator):
"""Select elements in model with IFC Selector"""
bl_idname = "bim.ifc_selector"
bl_label = "Select elements with IFC Selector"
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=800)
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def execute(self, context):
return {"FINISHED"}
def draw(self, context):
from . import ui
ui.IfcSelectorUI.draw(context, self.layout)
class SaveSelectorQuery(Operator):
bl_idname = "bim.save_selector_query"
bl_label = "Save Selector Query"
save_name: StringProperty()
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=400)
def draw(self, context):
layout = self.layout
layout.prop(self, "save_name")
def execute(self, context):
ifc_selector = context.scene.IfcSelectorProperties
new = ifc_selector.query_library.add()
new.name = self.save_name
new.query = ifc_selector.selector_query_syntax
return {"FINISHED"}
class OpenQueryLibrary(Operator):
"""Open Query Library"""
bl_idname = "bim.open_query_library"
bl_label = "Open Query Library"
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=400)
def close_panel(event):
x, y = event.mouse_x, event.mouse_y
bpy.context.window.cursor_warp(10, 10)
move_back = lambda: bpy.context.window.cursor_warp(x, y)
bpy.app.timers.register(move_back, first_interval=0.001)
def draw(self, context):
layout = self.layout
ifc_selector = context.scene.IfcSelectorProperties
for index, query in enumerate(ifc_selector.query_library):
row = layout.row(align=True)
row.prop(query, "query", text=query.name)
op = row.operator("bim.load_query", icon="SORT_ASC", text="")
op.index = index
row.context_pointer_set(name="bim_prop_group", data=ifc_selector)
op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="")
op.option = "remove"
op.collection = "query_library"
op.index = index
def execute(self, context):
return {"FINISHED"}
class LoadQuery(Operator):
bl_idname = "bim.load_query"
bl_label = "Load Query"
index: IntProperty()
def execute(self, context):
ifc_selector = context.scene.IfcSelectorProperties
ifc_selector.selector_query_syntax = ifc_selector.query_library[self.index].query
return {"FINISHED"}
@@ -17,9 +17,13 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from ifcopenshell import util
from ifcopenshell.util.selector import Selector
import blenderbim.tool as tool
from blenderbim.bim.prop import ObjProperty
from blenderbim.bim.prop import ObjProperty, StrProperty
from blenderbim.bim.ifc import IfcStore
from bpy.types import PropertyGroup
from blenderbim.tool.ifc import Ifc
from . import ui, prop, operator
from bpy.props import (
PointerProperty,
@@ -90,3 +94,146 @@ class BIMSearchProperties(PropertyGroup):
filter_classes_index: IntProperty(name="Filter Classes Index")
filter_building_storeys: CollectionProperty(type=BIMFilterBuildingStoreys, name="Filter Level")
filter_building_storeys_index: IntProperty(name="Filter Level Index")
def get_classes(self, ifc_product):
declaration = tool.Ifc.schema().declaration_by_name(ifc_product)
declarations = util.schema.get_subtypes(declaration)
names = [d.name() for d in declarations]
return [(c, c, "") for c in sorted(names)]
def load_sub_options(self, context):
if self.selector not in ["IfcClass"]:
self.load_option = "sub_options"
load_selection_options(self, context)
def load_selection_options(self, context):
ifc = IfcStore.file
load_option = self.load_option
op = getattr(self, load_option)
op.clear()
options = []
if load_option == "options":
self.sub_options.clear()
if self.selector == "IFC Class":
options = get_classes(self, "IfcElement")
options.append(("IfcSpace", "IfcSpace", ""))
elif self.selector == "IfcSpatialElement":
options = get_classes(self, "IfcSpatialElement")
elif self.selector == "IfcElementType":
options = get_classes(self, "IfcElementType")
elif self.selector == "GlobalId":
return
elif self.selector == "IfcPropertySet":
psets = Selector.parse(ifc, ".IfcPropertySet")
options = set([o.Name for o in psets])
elif load_option == "sub_options":
if self.selector in ["IfcSpatialElement", "IfcElementType"]:
active_option = self.active_option.split(": ")[1]
options = Selector.parse(ifc, f".{active_option}")
elif self.selector == "IfcPropertySet":
options = set()
active_pset = self.active_option.split(": ")[1]
psets_in_file = Selector.parse(ifc, f'.IfcPropertySet[Name="{active_pset}"]')
for pset in psets_in_file:
for prop in pset.HasProperties:
options.add(prop.Name)
for index, option in enumerate(options):
new = op.add()
if self.selector in ["IfcSpatialElement", "IfcElementType"]:
if self.load_option == "sub_options":
new.name = f"{index}: {option.Name}"
new.global_id = option.GlobalId
else:
new.name = f"{index}: {option[0]}"
elif self.selector == "IfcPropertySet":
new.name = f"{index}: {option}"
else:
new.name = f"{index}: {option[0]}"
self.load_option = "options"
def load_spatial_elements(self, context):
ifc = IfcStore.file
col_items = Selector.parse(ifc, f".{self.selected_spatial_element}")
collection = getattr(self, "sub_spatial_elements", None)
collection.clear()
for index, c in enumerate(col_items):
new = collection.add()
new.name = f"{index}-{c.Name}"
class SearchCollection(PropertyGroup):
name: StringProperty()
long_name: StringProperty()
global_id: StringProperty()
query: StringProperty()
class IfcSelector:
and_or: EnumProperty(
items=[(i, i, i) for i in ["and", "or"]],
)
negation: BoolProperty(name="not")
comparison: EnumProperty(
items=[
("=", "equal to", ""),
("*=", "contains", ""),
(">=", "greater than or equal to", ""),
("<=", "lesser than or equal to", ""),
(">", "greater than", ""),
("<", "less than", ""),
],
)
load_option: StringProperty(
default="options", description="controls whether or not options or sub_options are loaded"
)
options: CollectionProperty(type=SearchCollection)
active_option: StringProperty(update=load_sub_options)
sub_options: CollectionProperty(type=SearchCollection)
active_sub_option: StringProperty()
value: StringProperty(description="generic 'value' that can be used in multiple scenarios")
class SearchQueryFilter(PropertyGroup, IfcSelector):
selector: EnumProperty(
items=[(i, i, i) for i in ["-", "IfcPropertySet", "Attribute"]],
name="Filter selection by",
update=load_selection_options,
default="-",
)
attribute: EnumProperty(
items=[(i, i, i) for i in ["GlobalId", "Name", "Description", "ObjectType", "Tag", "PredefinedType"]],
name="Filter selection by",
update=load_selection_options,
)
class SearchQuery(PropertyGroup, IfcSelector):
filters: CollectionProperty(type=SearchQueryFilter)
selector: EnumProperty(
items=[(i, i, i) for i in ["-", "IFC Class", "IfcSpatialElement", "IfcElementType", "GlobalId"]],
name="Selector type",
update=load_selection_options,
default="-",
)
class SearchQueryGroup(PropertyGroup, IfcSelector):
queries: CollectionProperty(type=SearchQuery)
class IfcSelectorProperties(PropertyGroup, IfcSelector):
groups: CollectionProperty(type=SearchQueryGroup)
selector_query_syntax: StringProperty()
query_library: CollectionProperty(type=SearchCollection)
active_query: StringProperty()
active_query: StringProperty()
manual_override: BoolProperty(default=False, description="Toggle to allow manual typing of query-syntax")
@@ -102,3 +102,152 @@ class BIM_UL_ifc_building_storey_filter(bpy.types.UIList):
split = split.column()
split.scale_x = 0.5
split.label(text=str(item.total))
class BIM_PT_IFCSelector(Panel):
bl_label = "IFC Selector"
bl_idname = "BIM_PT_ifc_selector"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
layout = self.layout
layout.operator("bim.ifc_selector")
# this doesn't inherit from Panel since it's just a class to abstract away UI code from the IfcSelector Operator
class IfcSelectorUI:
@classmethod
def draw(self, context, layout):
ifc_selector = context.scene.IfcSelectorProperties
row = layout.row()
row.context_pointer_set(name="bim_prop_group", data=ifc_selector)
op = row.operator("bim.edit_blender_collection", text="Add selection group")
op.option = "add"
op.collection = "groups"
layout.separator()
self.draw_query_group_ui(self, ifc_selector, layout)
if len(ifc_selector.groups) != 0:
row = layout.row(align=True)
row.prop(ifc_selector, "selector_query_syntax", text="Query Syntax")
row.prop(ifc_selector, "manual_override", text="")
select = row.operator("bim.filter_model_elements", text="", icon="RESTRICT_SELECT_OFF")
select.option = "select"
isolate = row.operator("bim.filter_model_elements", text="", icon="ZOOM_SELECTED")
isolate.option = "isolate"
hide = row.operator("bim.filter_model_elements", text="", icon="HIDE_ON")
hide.option = "hide"
row.operator("bim.unhide_all_elements", text="", icon="HIDE_OFF")
row = layout.row(align=True)
row.alignment = "CENTER"
row.operator("bim.save_selector_query", text="Save Query")
row.operator("bim.open_query_library", text="Load Query")
def draw_query_group_ui(self, ifc_selector, layout):
for index, group in enumerate(ifc_selector.groups):
row = layout.row()
row.alignment = "CENTER"
row.label(text="or") if index != 0 else None
box = layout.box()
row = box.row(align=True)
row.alignment = "CENTER"
row.label(text=f"Group #{str(index+1)}")
row.context_pointer_set(name="bim_prop_group", data=group)
op = row.operator("bim.edit_blender_collection", text="Add query", icon="PLUS")
op.option = "add"
op.collection = "queries"
row.context_pointer_set(name="bim_prop_group", data=ifc_selector)
op = row.operator("bim.edit_blender_collection", text="Remove selection group")
op.option = "remove"
op.collection = "groups"
op.index = index
self.draw_query_ui(self, group, box)
def draw_query_ui(self, group, box):
for index, query in enumerate(group.queries):
row = box.row()
row.alignment = "LEFT"
row.prop(query, "and_or", text="") if index != 0 else None
if query.and_or == "or":
row = box.row()
row.alignment = "LEFT"
row.prop(query, "selector", text="")
if query.selector in ["IFC Class", "IfcSpatialElement", "IfcElementType"]:
row.label(text="Equals")
row.prop_search(query, "active_option", query, "options", text="")
row.prop_search(query, "active_sub_option", query, "sub_options", text="") if len(
query.sub_options
) != 0 else None
self.draw_filter_ui(self, box, query)
elif query.selector in ["GlobalId", "Attribute"]:
row.prop(query, "negation", text="")
row.prop(query, "comparison", text="")
row.prop(query, "value", text="")
row.context_pointer_set(name="bim_prop_group", data=group)
op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="")
op.option = "remove"
op.collection = "queries"
op.index = index
row.context_pointer_set(name="bim_prop_group", data=query)
op = (
row.operator("bim.edit_blender_collection", text="Add filter")
if query.selector == "IFC Class"
else None
)
if op:
op.option = "add"
op.collection = "filters"
def draw_filter_ui(self, box, query):
for filter_index, f in enumerate(query.filters):
row = box.row()
row.alignment = "LEFT"
row.label(text="")
row.prop(f, "and_or", text="") if filter_index != 0 else None
if f.and_or == "or":
row = box.row()
row.alignment = "LEFT"
row.label(text="")
row.prop(f, "selector", text="")
if f.selector == "Attribute":
row.prop(f, "attribute", text="")
row.prop(
f,
"negation",
)
row.prop(f, "comparison", text="")
row.prop(f, "value", text="")
elif f.selector == "IfcPropertySet":
row.prop_search(f, "active_option", f, "options", text="")
row.prop_search(f, "active_sub_option", f, "sub_options", text="")
row.prop(f, "negation")
row.prop(f, "comparison", text="")
row.prop(f, "value", text="")
row.context_pointer_set(name="bim_prop_group", data=query)
op = row.operator("bim.edit_blender_collection", icon="REMOVE", text="")
op.option = "remove"
op.collection = "filters"
op.index = filter_index
@@ -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,
}
+44 -17
View File
@@ -27,6 +27,7 @@ from . import schema
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty
from blenderbim.bim.ui import IFCFileSelector
from blenderbim.bim.helper import get_enum_items
from mathutils import Vector, Matrix, Euler
from math import radians
@@ -527,9 +528,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 +539,53 @@ 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 = get_enum_items(self.data, self.prop_name, context)
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]
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")
+1 -1
View File
@@ -65,7 +65,7 @@ class Material(blenderbim.core.tool.Material):
materials = sorted(tool.Ifc.get().by_type(material_type), key=get_name)
categories = {}
if material_type == "IfcMaterial":
[categories.setdefault(m.Category, []).append(m) for m in materials]
[categories.setdefault(getattr(m, "Category", "Uncategorised"), []).append(m) for m in materials]
for category, mats in categories.items():
cat = props.materials.add()
cat.name = category or ""
+4 -4
View File
@@ -108,12 +108,12 @@ On Windows:
$ mklink /D "\path\to\blender\X.XX\scripts\addons\blenderbim\bim" "src\blenderbim\blenderbim\bim"
# Remove the IfcOpenShell dependency Python code
$ rd \S \Q "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api"
$ rd \S \Q "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util"
$ rd /S /Q "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api"
$ rd /S /Q "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util"
# Replace them with links to the Git repository
$ mklink \D "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api" "src\ifcopenshell-python\ifcopenshell\api"
$ mklink \D "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util" "src\ifcopenshell-python\ifcopenshell\util"
$ mklink /D "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\api" "src\ifcopenshell-python\ifcopenshell\api"
$ mklink /D "\path\to\blender\X.XX\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util" "src\ifcopenshell-python\ifcopenshell\util"
After you modify your code in the Git repository, you will need to restart
@@ -111,6 +111,17 @@ Scenario: Assign material - Assign a material profile set
And I press "bim.assign_material"
Then nothing happens
Scenario: Assign material - Assign a material constituent set
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialConstituentSet"
And I press "bim.assign_material"
Then nothing happens
Scenario: Select by material
Given an empty IFC project
And I press "bim.load_materials"
+73 -21
View File
@@ -1,6 +1,78 @@
@pset
Feature: Pset
Scenario: Enable pset editing - object
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
And I set "active_object.PsetProperties.pset_name" to "Pset_WallCommon"
And I press "bim.add_pset(obj='IfcWall/Cube', obj_type='Object')"
And the variable "pset" is "{ifc}.by_type('IfcPropertySet')[-1].id()"
When I press "bim.enable_pset_editing(pset_id={pset}, obj='IfcWall/Cube', obj_type='Object')"
Then nothing happens
Scenario: Enable pset editing - material
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterial"
And I press "bim.assign_material"
And the object "IfcWall/Cube" is selected
And I press "bim.add_pset(obj='Default', obj_type='Material')"
And the variable "pset" is "{ifc}.by_type('IfcMaterialProperties')[-1].id()"
When I press "bim.enable_pset_editing(pset_id={pset}, obj='Default', obj_type='Material')"
Then nothing happens
Scenario: Enable pset editing - profile
Given an empty IFC project
And I add an empty
And the object "Empty" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I press "bim.add_material(obj='')"
And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet"
And I press "bim.assign_material"
And I press "bim.enable_editing_assigned_material()"
And the variable "profile_set" is "{ifc}.by_type('IfcMaterialProfileSet')[-1].id()"
And I press "bim.add_profile(profile_set={profile_set})"
And the variable "material_profile" is "{ifc}.by_type('IfcMaterialProfile')[-1].id()"
And I press "bim.enable_editing_material_set_item(material_set_item={material_profile})"
And I set "active_object.BIMObjectMaterialProperties.profile_classes" to "IfcParameterizedProfileDef"
And I press "bim.assign_parameterized_profile(ifc_class="IfcIShapeProfileDef", material_profile={material_profile})"
And I press "bim.load_profiles"
And I set "scene.ProfilePsetProperties.pset_name" to "Pset_ProfileMechanical"
And I press "bim.add_pset(obj_type='Profile')"
And the variable "pset" is "{ifc}.by_type('IfcProfileProperties')[-1].id()"
When I press "bim.enable_pset_editing(pset_id={pset}, obj='', obj_type='Profile')"
Then nothing happens
Scenario: Enable pset editing - work schedule
Given an empty IFC project
And I press "bim.add_work_schedule"
And the variable "work_schedule" is "{ifc}.by_type('IfcWorkSchedule')[0].id()"
And I press "bim.enable_editing_tasks(work_schedule={work_schedule})"
And I set "scene.WorkSchedulePsetProperties.pset_name" to "Pset_WorkControlCommon"
And I press "bim.add_pset(obj_type='WorkSchedule')"
And the variable "pset" is "{ifc}.by_type('IfcPropertySet')[-1].id()"
When I press "bim.enable_pset_editing(pset_id={pset}, obj='', obj_type='WorkSchedule')"
Scenario: Enable pset editing - resource
Given an empty IFC project
And I press "bim.load_resources"
And I press "bim.add_resource(ifc_class='IfcSubContractResource', resource=0)"
And I set "scene.ResourcePsetProperties.pset_name" to "Pset_ConstructionResource"
And I press "bim.add_pset(obj_type='Resource')"
And the variable "pset" is "{ifc}.by_type('IfcPropertySet')[-1].id()"
When I press "bim.enable_pset_editing(pset_id={pset}, obj='', obj_type='Resource')"
Then nothing happens
Scenario: Copy property to selected - copy property
Given an empty IFC project
And I add a cube
@@ -17,26 +89,6 @@ Scenario: Copy property to selected - copy property
And I press "bim.add_pset(obj='IfcWall/Cube.001', obj_type='Object')"
And the variable "pset" is "{ifc}.by_type('IfcPropertySet')[-1].id()"
And I press "bim.enable_pset_editing(obj='IfcWall/Cube.001', obj_type='Object', pset_id={pset})"
And I set "active_object.PsetProperties.properties[2].string_value" to "Foo"
And I set "active_object.PsetProperties.properties[2].metadata.string_value" to "Foo"
When I press "bim.copy_property_to_selection(name='FireRating')"
Then nothing happens
Scenario: Copy property to selected - copy quantity
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
And additionally the object "IfcWall/Cube.001" is selected
And I set "active_object.PsetProperties.pset_name" to "Pset_BuildingElementCommon"
And I press "bim.add_qto(obj='IfcWall/Cube.001', obj_type='Object')"
And the variable "qto" is "{ifc}.by_type('IfcQuantitySet')[-1].id()"
And I press "bim.enable_pset_editing(obj='IfcWall/Cube.001', obj_type='Object', pset_id={qto})"
And I set "active_object.PsetProperties.properties[0].float_value" to "1"
When I press "bim.copy_property_to_selection(name='Length')"
Then nothing happens