Compare commits

...

10 Commits

Author SHA1 Message Date
Dion Moult 9b3f964000 Fix #2060. Large lists of types in a library can now be filtered. 2022-08-23 13:26:38 +10:00
Dion Moult de6b578d9c Fix #1375. Spatial zones as non-hierarchical entities are now supported. 2022-08-22 17:31:44 +10:00
Dion Moult f50e790b11 Minor fix 2022-08-22 17:02:05 +10:00
Dion Moult 9821c3af67 Fix #2317 add graphical support for viewing and editing referenced structures 2022-08-22 13:52:12 +10:00
Dion Moult 0844d6f52e Fix #2354 - add support for selector queries for enumerated properties 2022-08-19 20:10:54 +10:00
Dion Moult 520e7653e4 #1375 #2317 add API support for referencing in spatial structures 2022-08-19 18:43:39 +10:00
Dion Moult 9512075506 Minor fix 2022-08-19 12:41:08 +10:00
Dion Moult bdd61713ea WIP refactor sequence module 2022-08-19 11:20:18 +10:00
Dion Moult 97cf1da4ff #2339 Read directly from raw data instead of using strings for IfcDiff output 2022-08-18 17:57:11 +10:00
Dion Moult 836a162d57 #2342 Support different IFC schema versions for obj2ifc 2022-08-18 17:15:39 +10:00
41 changed files with 634 additions and 225 deletions
+5 -1
View File
@@ -326,7 +326,11 @@ class IfcImporter:
self.elements = [e for e in self.elements if e.Representation and not e.is_a("IfcFeatureElement")]
self.elements = set(self.elements[offset:offset_limit])
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
if self.ifc_import_settings.has_filter or offset or offset_limit < len(self.elements):
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
else:
self.element_types = set(self.file.by_type("IfcElementType"))
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
@@ -33,7 +33,11 @@ class DiffData:
@classmethod
def load(cls):
cls.data = {"diff_json": cls.diff_json(), "changes": cls.changes()}
cls.data["diff_json"] = cls.diff_json()
cls.data["total_added"] = cls.total_added()
cls.data["total_deleted"] = cls.total_deleted()
cls.data["total_changed"] = cls.total_changed()
cls.data["changes"] = cls.changes()
cls.is_loaded = True
@classmethod
@@ -48,6 +52,24 @@ class DiffData:
cls.diff = json.load(file)
return cls.diff
@classmethod
def total_added(cls):
diff = cls.diff_json()
if diff:
return len(diff["added"])
@classmethod
def total_deleted(cls):
diff = cls.diff_json()
if diff:
return len(diff["deleted"])
@classmethod
def total_changed(cls):
diff = cls.diff_json()
if diff:
return len(diff["changed"].keys())
@classmethod
def changes(cls):
diff = cls.diff_json()
@@ -56,4 +78,9 @@ class DiffData:
element = tool.Ifc.get_entity(bpy.context.active_object)
if not element or not hasattr(element, "GlobalId"):
return {}
return {k.upper().replace("_", " "): str(v) for k, v in diff["changed"].get(element.GlobalId, {}).items()}
changes = {k.upper().replace("_", " "): str(v) for k, v in diff["changed"].get(element.GlobalId, {}).items()}
if element.GlobalId in diff["added"]:
changes["Added"] = True
elif element.GlobalId in diff["deleted"]:
changes["Deleted"] = True
return changes
@@ -17,11 +17,11 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import json
import ifccsv
import ifcopenshell
import json
import blenderbim.bim.handler
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
class SelectDiffJsonFile(bpy.types.Operator):
@@ -29,6 +29,7 @@ class SelectDiffJsonFile(bpy.types.Operator):
bl_label = "Select Diff JSON File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
def execute(self, context):
context.scene.DiffProperties.diff_json_file = self.filepath
@@ -72,9 +73,10 @@ class SelectDiffOldFile(bpy.types.Operator):
bl_label = "Select Diff Old File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
def execute(self, context):
context.scene.DiffProperties.diff_old_file = self.filepath
context.scene.DiffProperties.old_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -87,9 +89,10 @@ class SelectDiffNewFile(bpy.types.Operator):
bl_label = "Select Diff New File"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
def execute(self, context):
context.scene.DiffProperties.diff_new_file = self.filepath
context.scene.DiffProperties.new_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
@@ -113,14 +116,14 @@ class ExecuteIfcDiff(bpy.types.Operator):
import ifcdiff
ifc_diff = ifcdiff.IfcDiff(
context.scene.DiffProperties.diff_old_file,
context.scene.DiffProperties.diff_new_file,
context.scene.DiffProperties.old_file,
context.scene.DiffProperties.new_file,
self.filepath,
[r.relationship for r in context.scene.DiffProperties.diff_relationships],
context.scene.DiffProperties.diff_filter_elements,
)
diff = ifc_diff.diff()
ifc_diff.diff()
ifc_diff.export()
context.scene.DiffProperties.diff_json_file = self.filepath
context.scene.DiffProperties.diff_result = diff
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
@@ -44,9 +44,8 @@ class Relationships(PropertyGroup):
class DiffProperties(PropertyGroup):
diff_json_file: StringProperty(default="", name="Diff JSON File", update=update_diff_json_file)
diff_old_file: StringProperty(default="", name="Diff Old IFC File")
diff_new_file: StringProperty(default="", name="Diff New IFC File")
diff_relationships: CollectionProperty(type=Relationships, name="Diff Relationships")
diff_filter_elements: StringProperty(default="", name="Diff Filter")
diff_result: StringProperty(default="", name="Diff Result")
diff_json_file: StringProperty(default="", name="JSON Output", update=update_diff_json_file)
old_file: StringProperty(default="", name="Old IFC File")
new_file: StringProperty(default="", name="New IFC File")
diff_relationships: CollectionProperty(type=Relationships, name="Relationships")
diff_filter_elements: StringProperty(default="", name="Filter")
+30 -22
View File
@@ -40,55 +40,63 @@ class BIM_PT_diff(Panel):
layout.use_property_split = True
scene = context.scene
bim_properties = scene.DiffProperties
props = scene.DiffProperties
layout.label(text="IFC Diff Setup:")
row = layout.row(align=True)
row.prop(bim_properties, "diff_old_file")
row.prop(props, "old_file")
row.operator("bim.select_diff_old_file", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(bim_properties, "diff_new_file")
row.prop(props, "new_file")
row.operator("bim.select_diff_new_file", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(bim_properties, "diff_relationships")
row.context_pointer_set("bim_prop_group", bim_properties)
row.prop(props, "diff_relationships")
row.context_pointer_set("bim_prop_group", props)
add = row.operator("bim.edit_blender_collection", icon="ADD", text="")
add.option = "add"
add.collection = "diff_relationships"
for index, r in enumerate(bim_properties.diff_relationships):
for index, r in enumerate(props.diff_relationships):
row = layout.row(align=True)
row.context_pointer_set("bim_prop_group", bim_properties)
row.context_pointer_set("bim_prop_group", props)
row.prop(r, "relationship", text=" ")
remove = row.operator("bim.edit_blender_collection", icon="REMOVE", text="")
remove.option = "remove"
remove.collection = "diff_relationships"
remove.index = index
row = layout.row(align=True)
row.prop(bim_properties, "diff_filter_elements")
row.prop(props, "diff_filter_elements")
row.operator("bim.ifc_selector", icon="FILTER", text="")
row = layout.row()
row.operator("bim.execute_ifc_diff")
if bim_properties.diff_result:
row = layout.row()
row.alignment = "CENTER"
row.label(text=bim_properties.diff_result)
# TODO: show if there ifc diff operation is sucessful
row = layout.row(align=True)
row.prop(bim_properties, "diff_json_file")
row.prop(props, "diff_json_file")
row.operator("bim.select_diff_json_file", icon="FILE_FOLDER", text="")
row.operator("bim.visualise_diff", icon="HIDE_OFF", text="")
if DiffData.data["changes"]:
if DiffData.data["diff_json"]:
row = layout.row()
row.label(text="Diff Results:")
row.alignment = "CENTER"
row.label(text=f"{DiffData.data['total_added']} added")
row.label(text=f"{DiffData.data['total_deleted']} deleted")
row.label(text=f"{DiffData.data['total_changed']} changed")
if DiffData.data["changes"]:
box = layout.box()
row = box.row()
row.label(text="Active Object Changes:")
for key, value in DiffData.data["changes"].items():
row = layout.row()
row.label(text=key)
row.label(text=value)
row = box.row()
if key == "Added":
icon = "ADD"
elif key == "Deleted":
icon = "X"
else:
icon = "GREASEPENCIL"
row.label(text=key, icon=icon)
@@ -41,7 +41,7 @@ class LoadGroups(bpy.types.Operator):
context.scene.ExpandedGroups.json_string = "{}"
for ifc_definition_id, group in Data.groups.items():
if not group["HasAssignments"]:
if not group["HasAssignments"]:
new = self.props.groups.add()
new.ifc_definition_id = ifc_definition_id
new.name = group["Name"]
@@ -50,7 +50,7 @@ class LoadGroups(bpy.types.Operator):
if group["IsGroupedBy"]:
# assumes 1:1 cardinality, will need to be updated to reflect IFC4 changes
# where the cardinality is 0:? - vulevukusej
# where the cardinality is 0:? - vulevukusej
sub_groups = [g for g in group["IsGroupedBy"][0].RelatedObjects if g.is_a("IfcGroup")]
new.has_children = True if len(sub_groups) != 0 else False
@@ -146,7 +146,7 @@ class AddGroupToGroup(bpy.types.Operator):
self.file = IfcStore.get_file()
result = ifcopenshell.api.run("group.add_group", self.file)
ifcopenshell.api.run(
"group.assign_group", IfcStore.get_file(), **{"product": [result], "group": self.file.by_id(self.group)}
"group.assign_group", IfcStore.get_file(), products=[result], group=self.file.by_id(self.group)
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_groups(is_refresh=True)
@@ -255,10 +255,8 @@ class AssignGroup(bpy.types.Operator):
ifcopenshell.api.run(
"group.assign_group",
self.file,
**{
"product": [self.file.by_id(product.BIMObjectProperties.ifc_definition_id)],
"group": self.file.by_id(self.group),
}
products=[self.file.by_id(product.BIMObjectProperties.ifc_definition_id)],
group=self.file.by_id(self.group),
)
Data.load(self.file)
return {"FINISHED"}
@@ -332,7 +330,6 @@ class UpdateGroup(bpy.types.Operator):
**{
"group": group,
"products": new_products,
}
)
Data.load(IfcStore.get_file())
@@ -89,7 +89,7 @@ class BimTool(WorkSpaceTool):
row = layout.row(align=True)
if relating_types_ids:
row.label(text="", icon="FILE_3D")
row.prop(data=props, property="relating_type_id", text="")
prop_with_search(row, props, "relating_type_id", text="")
else:
row.label(text="No Construction Type", icon="FILE_3D")
if ifc_classes:
@@ -169,14 +169,14 @@ class MaterialPsetProperties(PropertyGroup):
class TaskPsetProperties(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)
qto_name: EnumProperty(items=getTaskQtoNames, name="Qto Name")
class ResourcePsetProperties(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=getResourcePsetNames, name="Pset Name")
qto_name: EnumProperty(items=getResourceQtoNames, name="Qto Name")
@@ -59,12 +59,8 @@ class AddWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Add Work Plan"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
core.add_work_plan(tool.Ifc, tool.Sequence)
return {"FINISHED"}
class EditWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
@@ -72,12 +68,8 @@ class EditWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
bl_label = "Edit Work Plan"
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
core.edit_work_plan(tool.Ifc, tool.Sequence)
return {"FINISHED"}
class RemoveWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
@@ -86,33 +78,27 @@ class RemoveWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
work_plan: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
core.remove_work_plan(tool.Ifc, tool.Sequence, work_plan=self.work_plan)
return {"FINISHED"}
core.remove_work_plan(tool.Ifc, tool.Sequence, work_plan=tool.Ifc.get().by_id(self.work_plan))
class EnableEditingWorkPlan(bpy.types.Operator):
class EnableEditingWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_work_plan"
bl_label = "Enable Editing Work Plan"
bl_options = {"REGISTER", "UNDO"}
work_plan: bpy.props.IntProperty()
def execute(self, context):
core.enable_editing_work_plan(tool.Sequence, work_plan=self.work_plan)
return {"FINISHED"}
def _execute(self, context):
core.enable_editing_work_plan(tool.Sequence, work_plan=tool.Ifc.get().by_id(self.work_plan))
class DisableEditingWorkPlan(bpy.types.Operator):
class DisableEditingWorkPlan(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_work_plan"
bl_options = {"REGISTER", "UNDO"}
bl_label = "Disable Editing Work Plan"
def execute(self, context):
def _execute(self, context):
core.disable_editing_work_plan(tool.Sequence)
return {"FINISHED"}
class EnableEditingWorkPlanSchedules(bpy.types.Operator):
@@ -52,7 +52,7 @@ class BIM_PT_work_plans(Panel):
text="{} Work Plans Found".format(SequenceData.number_of_work_plans_loaded),
icon="TEXT",
)
row.operator("bim.add_work_plan", icon="ADD")
row.operator("bim.add_work_plan", icon="ADD", text="")
for work_plan_id, work_plan in SequenceData.work_plans.items():
self.draw_work_plan_ui(work_plan_id, work_plan)
@@ -23,8 +23,10 @@ classes = (
operator.AssignContainer,
operator.ChangeSpatialLevel,
operator.CopyToContainer,
operator.DereferenceStructure,
operator.DisableEditingContainer,
operator.EnableEditingContainer,
operator.ReferenceStructure,
operator.RemoveContainer,
operator.SelectContainer,
operator.SelectSimilarContainer,
@@ -33,9 +33,9 @@ class SpatialData:
def load(cls):
cls.data = {
"parent_container_id": cls.get_parent_container_id(),
"is_contained": cls.is_contained(),
"is_directly_contained": cls.is_directly_contained(),
"label": cls.get_label(),
"label": cls.label(),
"references": cls.references(),
}
cls.is_loaded = True
@@ -50,11 +50,7 @@ class SpatialData:
return container.Decomposes[0].RelatingObject.id()
@classmethod
def is_contained(cls):
return ifcopenshell.util.element.get_container(tool.Ifc.get_entity(bpy.context.active_object))
@classmethod
def get_label(cls):
def label(cls):
container = ifcopenshell.util.element.get_container(tool.Ifc.get_entity(bpy.context.active_object))
if container:
label = f"{container.is_a()}/{container.Name or ''}"
@@ -62,6 +58,11 @@ class SpatialData:
label += "*"
return label
@classmethod
def references(cls):
results = ifcopenshell.util.element.get_referenced_structures(tool.Ifc.get_entity(bpy.context.active_object))
return sorted([f"{r.is_a()}/{r.Name or ''}" for r in results])
@classmethod
def is_directly_contained(cls):
return bool(getattr(tool.Ifc.get_entity(bpy.context.active_object), "ContainedInStructure", False))
@@ -27,14 +27,41 @@ from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.spatial.data import SpatialData
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.reference_structure"
bl_label = "Reference Structure"
bl_options = {"REGISTER", "UNDO"}
structure: bpy.props.IntProperty()
def _execute(self, context):
sprops = context.scene.BIMSpatialProperties
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
for container in containers:
core.reference_structure(tool.Ifc, tool.Spatial, structure=container, element=element)
class AssignContainer(bpy.types.Operator, Operator):
class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.dereference_structure"
bl_label = "Dereference Structure"
bl_options = {"REGISTER", "UNDO"}
structure: bpy.props.IntProperty()
def _execute(self, context):
sprops = context.scene.BIMSpatialProperties
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
for container in containers:
core.dereference_structure(tool.Ifc, tool.Spatial, structure=container, element=element)
class AssignContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_container"
bl_label = "Assign Container"
bl_options = {"REGISTER", "UNDO"}
@@ -48,7 +75,7 @@ class AssignContainer(bpy.types.Operator, Operator):
)
class EnableEditingContainer(bpy.types.Operator, Operator):
class EnableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_container"
bl_label = "Enable Editing Container"
bl_options = {"REGISTER", "UNDO"}
@@ -57,7 +84,7 @@ class EnableEditingContainer(bpy.types.Operator, Operator):
core.enable_editing_container(tool.Spatial, obj=context.active_object)
class ChangeSpatialLevel(bpy.types.Operator, Operator):
class ChangeSpatialLevel(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.change_spatial_level"
bl_label = "Change Spatial Level"
bl_options = {"REGISTER", "UNDO"}
@@ -67,7 +94,7 @@ class ChangeSpatialLevel(bpy.types.Operator, Operator):
core.change_spatial_level(tool.Spatial, parent=tool.Ifc.get().by_id(self.parent))
class DisableEditingContainer(bpy.types.Operator, Operator):
class DisableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_container"
bl_label = "Disable Editing Container"
bl_options = {"REGISTER", "UNDO"}
@@ -76,7 +103,7 @@ class DisableEditingContainer(bpy.types.Operator, Operator):
core.disable_editing_container(tool.Spatial, obj=context.active_object)
class RemoveContainer(bpy.types.Operator, Operator):
class RemoveContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_container"
bl_label = "Remove Container"
bl_options = {"REGISTER", "UNDO"}
@@ -86,11 +113,16 @@ class RemoveContainer(bpy.types.Operator, Operator):
core.remove_container(tool.Ifc, tool.Collector, obj=obj)
class CopyToContainer(bpy.types.Operator, Operator):
class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
"""
Copies selected objects to selected containers
Check the mark next to a container in the container list to select it
Several containers can be selected at a time
Copies selected 3D elements in the viewport to checkmarked spatial containers
Example: bulk copy a wall to multiple storeys
1. Select one or more 3D elements in the viewport
2. Enable the checkmark next to one or more containers in the container list below to select it
3. Press this button
4. The copied elements will have a new position relative to the destination containers
"""
bl_idname = "bim.copy_to_container"
@@ -105,7 +137,7 @@ class CopyToContainer(bpy.types.Operator, Operator):
blenderbim.bim.handler.purge_module_data()
class SelectContainer(bpy.types.Operator, Operator):
class SelectContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.select_container"
bl_label = "Select Container"
bl_options = {"REGISTER", "UNDO"}
@@ -114,7 +146,7 @@ class SelectContainer(bpy.types.Operator, Operator):
core.select_container(tool.Ifc, tool.Spatial, obj=context.active_object)
class SelectSimilarContainer(bpy.types.Operator, Operator):
class SelectSimilarContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.select_similar_container"
bl_label = "Select Similar Container"
bl_options = {"REGISTER", "UNDO"}
@@ -55,13 +55,15 @@ class BIM_PT_spatial(Panel):
if props.containers and props.active_container_index < len(props.containers):
op = row.operator("bim.assign_container", icon="CHECKMARK")
op.structure = props.containers[props.active_container_index].ifc_definition_id
row.operator("bim.reference_structure", icon="LINKED", text="")
row.operator("bim.dereference_structure", icon="UNLINKED", text="")
row.operator("bim.copy_to_container", icon="COPYDOWN", text="")
row.operator("bim.disable_editing_container", icon="CANCEL", text="")
self.layout.template_list("BIM_UL_containers", "", props, "containers", props, "active_container_index")
else:
row = self.layout.row(align=True)
if SpatialData.data["is_contained"]:
if SpatialData.data["label"]:
row.label(text=SpatialData.data["label"])
row.operator("bim.select_container", icon="TRACKER", text="")
row.operator("bim.select_similar_container", icon="RESTRICT_SELECT_OFF", text="")
@@ -71,6 +73,9 @@ class BIM_PT_spatial(Panel):
else:
row.label(text="This object is not spatially contained")
row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="")
for reference in SpatialData.data["references"]:
row = self.layout.row()
row.label(text=reference, icon="LINKED")
class BIM_UL_containers(UIList):
@@ -663,7 +663,7 @@ class AddStructuralLoadGroup(bpy.types.Operator):
self.file = IfcStore.get_file()
load_group = ifcopenshell.api.run("structural.add_structural_load_group", self.file)
ifcopenshell.api.run(
"group.assign_group", self.file, product=[load_group], group=self.file.by_id(self.load_case)
"group.assign_group", self.file, products=[load_group], group=self.file.by_id(self.load_case)
)
Data.load(IfcStore.get_file())
return {"FINISHED"}
@@ -756,7 +756,7 @@ class AddStructuralActivity(bpy.types.Operator):
structural_member=element,
)
ifcopenshell.api.run(
"group.assign_group", self.file, product=[activity], group=self.file.by_id(self.load_group)
"group.assign_group", self.file, products=[activity], group=self.file.by_id(self.load_group)
)
Data.load(IfcStore.get_file())
bpy.ops.bim.enable_editing_structural_load_group_activities(load_group=self.load_group)
+3 -3
View File
@@ -148,7 +148,7 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
)
group = ifc.run("group.add_group")
ifc.run("group.edit_group", group=group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"})
ifc.run("group.assign_group", group=group, product=[element])
ifc.run("group.assign_group", group=group, products=[element])
collector.assign(camera)
pset = ifc.run("pset.add_pset", product=element, name="EPset_Drawing")
ifc.run(
@@ -203,7 +203,7 @@ def add_annotation(ifc, collector, drawing_tool, drawing=None, object_type=None)
context=context,
ifc_representation_class=drawing_tool.get_ifc_representation_class(object_type),
)
ifc.run("group.assign_group", group=drawing_tool.get_drawing_group(drawing), product=[element])
ifc.run("group.assign_group", group=drawing_tool.get_drawing_group(drawing), products=[element])
collector.assign(obj)
drawing_tool.enable_editing(obj)
@@ -251,7 +251,7 @@ def sync_references(ifc, collector, drawing_tool, drawing=None):
annotation = drawing_tool.generate_reference_annotation(drawing, reference_element, context)
if annotation:
ifc.run("drawing.assign_product", relating_product=reference_element, related_object=annotation)
ifc.run("group.assign_group", group=group, product=[annotation])
ifc.run("group.assign_group", group=group, products=[annotation])
collector.assign(ifc.get_object(annotation))
if reference_obj and ifc.is_moved(reference_obj):
+4 -9
View File
@@ -1,5 +1,5 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>, 2022 Yassine Oualid <yassine@sigmadimensions.com>
# Copyright (C) 2021, 2022 Dion Moult <dion@thinkmoult.com>, Yassine Oualid <yassine@sigmadimensions.com>
#
# This file is part of BlenderBIM Add-on.
#
@@ -23,17 +23,12 @@ def add_work_plan(ifc, sequence):
def remove_work_plan(ifc, sequence, work_plan=None):
ifc.run("sequence.remove_work_plan", **{"work_plan": ifc.get().by_id(work_plan)})
ifc.run("sequence.remove_work_plan", work_plan=work_plan)
sequence.load_work_plans()
def load_work_plan_attributes(sequence, work_plan=None):
data = sequence.get_ifc_work_plan_attributes(work_plan)
sequence.load_work_plan_attributes(data)
def enable_editing_work_plan(sequence, work_plan=None):
load_work_plan_attributes(sequence, work_plan)
sequence.load_work_plan_attributes(work_plan)
sequence.enable_editing_work_plan(work_plan)
@@ -44,6 +39,6 @@ def disable_editing_work_plan(sequence):
def edit_work_plan(ifc, sequence):
work_plan = sequence.get_current_ifc_work_plan()
attributes = sequence.get_work_plan_attributes()
ifc.run("sequence.edit_work_plan", **{"work_plan": work_plan, "attributes": attributes})
ifc.run("sequence.edit_work_plan", work_plan=work_plan, attributes=attributes)
sequence.disable_editing_work_plan()
sequence.load_work_plans()
+10
View File
@@ -19,6 +19,16 @@
import blenderbim.core
def reference_structure(ifc, spatial, structure=None, element=None):
if spatial.can_reference(structure, element):
return ifc.run("spatial.reference_structure", product=element, relating_structure=structure)
def dereference_structure(ifc, spatial, structure=None, element=None):
if spatial.can_reference(structure, element):
return ifc.run("spatial.dereference_structure", product=element, relating_structure=structure)
def assign_container(ifc, collector, spatial, structure_obj=None, element_obj=None):
if not spatial.can_contain(structure_obj, element_obj):
return
+4 -5
View File
@@ -423,20 +423,19 @@ class Selector:
@interface
class Sequence:
def get_work_plans(cls): pass
def load_work_plans(cls): pass
def enable_editing_work_plan(cls, work_plan): pass
def disable_editing_work_plan(cls): pass
def enable_editing_work_plan(cls, work_plan): pass
def export_attributes(cls): pass
def get_current_ifc_work_plan(cls): pass
def get_ifc_work_plan_attributes(cls): pass
def load_work_plan_attributes(cls): pass
def import_attributes(cls): pass
def export_attributes(cls): pass
def load_work_plans(cls): pass
@interface
class Spatial:
def can_contain(cls, structure_obj, element_obj): pass
def can_reference(cls, structure, element): pass
def disable_editing(cls, obj): pass
def duplicate_object_and_data(cls, obj): pass
def enable_editing(cls, obj): pass
+1 -1
View File
@@ -102,7 +102,7 @@ class Collector(blenderbim.core.tool.Collector):
if element.is_a("IfcSpatialStructureElement"):
return bpy.data.collections.get(obj.name, bpy.data.collections.new(obj.name))
else:
if element.is_a("IfcSpatialElement"):
if element.is_a("IfcSpatialStructureElement") or element.is_a("IfcExternalSpatialStructureElement"):
return bpy.data.collections.get(obj.name, bpy.data.collections.new(obj.name))
if element.is_a("IfcGrid"):
+26 -60
View File
@@ -26,27 +26,19 @@ import blenderbim.bim.module.sequence.helper as helper
class Sequence(blenderbim.core.tool.Sequence):
@classmethod
def get_work_plans(cls):
work_plans = {}
for work_plan in tool.Ifc.get().by_type("IfcWorkPlan"):
work_plans[work_plan.id()] = {"Name": work_plan.Name}
return work_plans
@classmethod
def load_work_plans(cls):
work_plans = tool.Sequence.get_work_plans()
props = bpy.context.scene.BIMWorkPlanProperties
props.work_plans.clear()
for ifc_definition_id, work_plan in work_plans.items():
for work_plan in tool.Ifc.get().by_type("IfcWorkPlan"):
new = props.work_plans.add()
new.ifc_definition_id = ifc_definition_id
new.name = work_plan["Name"] or "Unnamed"
new.ifc_definition_id = work_plan.id()
new.name = work_plan.Name or "Unnamed"
@classmethod
def enable_editing_work_plan(cls, work_plan):
if work_plan:
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id()
bpy.context.scene.BIMWorkPlanProperties.editing_type = "ATTRIBUTES"
@classmethod
@@ -55,60 +47,34 @@ class Sequence(blenderbim.core.tool.Sequence):
@classmethod
def get_current_ifc_work_plan(cls):
active_work_plan = bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id
ifc_work_plan = tool.Ifc.get().by_id(active_work_plan)
return ifc_work_plan
return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id)
@classmethod
def get_ifc_work_plan_attributes(cls, work_plan):
if work_plan:
ifc_work_plan = tool.Ifc.get().by_id(work_plan)
data = ifc_work_plan.get_info()
del data["OwnerHistory"]
if data["Creators"]:
data["Creators"] = [p.id() for p in data["Creators"]]
data["CreationDate"] = ifcopenshell.util.date.ifc2datetime(data["CreationDate"])
data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"])
if data["FinishTime"]:
data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"])
data["IsDecomposedBy"] = []
for rel in ifc_work_plan.IsDecomposedBy:
data["IsDecomposedBy"].extend([o.id() for o in rel.RelatedObjects])
return data
def load_work_plan_attributes(cls, work_plan):
def callback(name, prop, data):
if name in ["CreationDate", "StartTime", "FinishTime"]:
prop.string_value = "" if prop.is_null else data[name]
return True
@classmethod
def load_work_plan_attributes(cls, data):
props = bpy.context.scene.BIMWorkPlanProperties
props.work_plan_attributes.clear()
blenderbim.bim.helper.import_attributes(
"IfcWorkPlan", props.work_plan_attributes, data, tool.Sequence.import_attributes
)
@classmethod
def import_attributes(name, prop, data):
if name in ["CreationDate", "StartTime", "FinishTime"]:
prop.string_value = "" if prop.is_null else data[name].isoformat()
return True
blenderbim.bim.helper.import_attributes2(work_plan, props.work_plan_attributes, callback)
@classmethod
def get_work_plan_attributes(cls):
props = bpy.context.scene.BIMWorkPlanProperties
attributes = blenderbim.bim.helper.export_attributes(
props.work_plan_attributes, tool.Sequence.export_attributes
)
return attributes
def callback(attributes, prop):
if "Date" in prop.name or "Time" in prop.name:
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True
elif prop.name == "Duration" or prop.name == "TotalFloat":
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
@classmethod
def export_attributes(attributes, prop):
if "Date" in prop.name or "Time" in prop.name:
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True
elif prop.name == "Duration" or prop.name == "TotalFloat":
if prop.is_null:
attributes[prop.name] = None
return True
attributes[prop.name] = helper.parse_duration(prop.string_value)
return True
props = bpy.context.scene.BIMWorkPlanProperties
return blenderbim.bim.helper.export_attributes(props.work_plan_attributes, callback)
+17 -1
View File
@@ -35,12 +35,28 @@ class Spatial(blenderbim.core.tool.Spatial):
if not structure.is_a("IfcSpatialStructureElement"):
return False
else:
if not structure.is_a("IfcSpatialElement"):
if not structure.is_a("IfcSpatialStructureElement") and not structure.is_a(
"IfcExternalSpatialStructureElement"
):
return False
if not hasattr(element, "ContainedInStructure"):
return False
return True
@classmethod
def can_reference(cls, structure, element):
if not structure or not element:
return False
if tool.Ifc.get_schema() == "IFC2X3":
if not structure.is_a("IfcSpatialStructureElement"):
return False
else:
if not structure.is_a("IfcSpatialElement"):
return False
if not hasattr(element, "ReferencedInStructures"):
return False
return True
@classmethod
def disable_editing(cls, obj):
obj.BIMObjectSpatialProperties.is_editing = False
+5 -5
View File
@@ -30,9 +30,9 @@ class Obj2Ifc:
def __init__(self, path):
self.path = path
def execute(self):
def execute(self, version="IFC4"):
self.basename = Path(self.path).stem
self.create_ifc_file()
self.create_ifc_file(version)
mesh_set = pymeshlab.MeshSet()
mesh_set.load_new_mesh(self.path)
@@ -87,10 +87,10 @@ class Obj2Ifc:
# OBJ swaps Y and Z axis
return [coordinates[0], -coordinates[2], coordinates[1]]
def create_ifc_file(self):
self.file = ifcopenshell.api.run("project.create_file", version="IFC2X3")
def create_ifc_file(self, version):
self.file = ifcopenshell.api.run("project.create_file", version=version)
person = ifcopenshell.api.run("owner.add_person", self.file)
person.Id = person.GivenName = None
person[0] = person.GivenName = None
person.FamilyName = "user"
org = ifcopenshell.api.run("owner.add_organisation", self.file)
org.Id = None
+5 -5
View File
@@ -30,9 +30,9 @@ class Obj2Ifc:
def __init__(self, path):
self.path = path
def execute(self):
def execute(self, version="IFC4"):
self.basename = Path(self.path).stem
self.create_ifc_file()
self.create_ifc_file(version)
self.scene = pywavefront.Wavefront(self.path, create_materials=True, collect_faces=True)
for mesh in self.scene.mesh_list:
ifc_faces = []
@@ -73,10 +73,10 @@ class Obj2Ifc:
ifcopenshell.api.run("spatial.assign_container", self.file, product=product, relating_structure=self.storey)
self.file.write(self.path.replace(".obj", ".ifc"))
def create_ifc_file(self):
self.file = ifcopenshell.api.run("project.create_file", version="IFC2X3")
def create_ifc_file(self, version):
self.file = ifcopenshell.api.run("project.create_file", version=version)
person = ifcopenshell.api.run("owner.add_person", self.file)
person.Id = person.GivenName = None
person[0] = person.GivenName = None
person.FamilyName = "user"
org = ifcopenshell.api.run("owner.add_organisation", self.file)
org.Id = None
+26 -1
View File
@@ -63,7 +63,7 @@ Scenario: Enable pset editing - work schedule
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
Scenario: Enable pset editing - resource with time series properties
Given an empty IFC project
And I press "bim.load_resources"
And I press "bim.add_resource(ifc_class='IfcSubContractResource', resource=0)"
@@ -73,6 +73,31 @@ Scenario: Enable pset editing - resource
When I press "bim.enable_pset_editing(pset_id={pset}, obj='', obj_type='Resource')"
Then nothing happens
Scenario: Enable pset editing - resource with regular single properties
Given an empty IFC project
And I press "bim.load_resources"
And I press "bim.add_resource(ifc_class='IfcCrewResource', resource=0)"
And the variable "resource" is "{ifc}.by_type('IfcCrewResource')[-1].id()"
And I press "bim.add_resource(ifc_class='IfcLaborResource', resource={resource})"
And I set "scene.BIMResourceProperties.active_resource_index" to "1"
And I set "scene.ResourcePsetProperties.pset_name" to "EPset_Productivity"
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: Enable pset editing - task
Given an empty IFC project
And I press "bim.add_work_schedule"
And the variable "work_schedule" is "IfcStore.get_file().by_type('IfcWorkSchedule')[0].id()"
And I press "bim.enable_editing_tasks(work_schedule={work_schedule})"
And I press "bim.add_summary_task(work_schedule={work_schedule})"
And I set "scene.TaskPsetProperties.qto_name" to "Qto_TaskBaseQuantities"
And I press "bim.add_qto(obj_type='Task')"
And the variable "pset" is "{ifc}.by_type('IfcElementQuantity')[-1].id()"
When I press "bim.enable_pset_editing(pset_id={pset}, obj='', obj_type='Task')"
Then nothing happens
Scenario: Copy property to selected - copy property
Given an empty IFC project
And I add a cube
@@ -41,6 +41,31 @@ Scenario: Copy to container
And I press "bim.copy_to_container"
Then the object "IfcWall/Cube.001" is in the collection "IfcSite/My Site"
Scenario: Reference structure
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 press "bim.enable_editing_container"
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
And I press "bim.reference_structure"
Then nothing happens
Scenario: Dereference structure
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 press "bim.enable_editing_container"
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
And I press "bim.reference_structure"
And I press "bim.dereference_structure"
Then nothing happens
Scenario: Select container
Given an empty IFC project
And I add a cube
+2 -2
View File
@@ -223,7 +223,7 @@ class TestAddDrawing:
ifc.run(
"group.edit_group", group="group", attributes={"Name": "name", "ObjectType": "DRAWING"}
).should_be_called()
ifc.run("group.assign_group", group="group", product=["element"]).should_be_called()
ifc.run("group.assign_group", group="group", products=["element"]).should_be_called()
collector.assign("obj").should_be_called()
ifc.run("pset.add_pset", product="element", name="EPset_Drawing").should_be_called().will_return("pset")
ifc.run(
@@ -293,7 +293,7 @@ class TestAddAnnotation:
ifc_representation_class="ifc_representation_class",
).should_be_called().will_return("element")
drawing.get_drawing_group("drawing").should_be_called().will_return("group")
ifc.run("group.assign_group", group="group", product=["element"]).should_be_called()
ifc.run("group.assign_group", group="group", products=["element"]).should_be_called()
collector.assign("obj").should_be_called()
drawing.enable_editing("obj").should_be_called()
subject.add_annotation(ifc, collector, drawing, drawing="drawing", object_type="object_type")
+14
View File
@@ -20,6 +20,20 @@ import blenderbim.core.spatial as subject
from test.core.bootstrap import ifc, collector, spatial
class TestReferenceStructure:
def test_run(self, ifc, spatial):
spatial.can_reference("structure", "element").should_be_called().will_return(True)
ifc.run("spatial.reference_structure", product="element", relating_structure="structure").should_be_called()
subject.reference_structure(ifc, spatial, structure="structure", element="element")
class TestDereferenceStructure:
def test_run(self, ifc, spatial):
spatial.can_reference("structure", "element").should_be_called().will_return(True)
ifc.run("spatial.dereference_structure", product="element", relating_structure="structure").should_be_called()
subject.dereference_structure(ifc, spatial, structure="structure", element="element")
class TestAssignContainer:
def test_run(self, ifc, collector, spatial):
spatial.can_contain("structure_obj", "element_obj").should_be_called().will_return(True)
+19 -3
View File
@@ -56,7 +56,7 @@ class TestAssign(NewFile):
assert len(wall_obj.users_collection) == 1
assert "IfcProject" in wall_obj.users_collection[0].name
def test_in_decomposition_mode_spatial_elements_are_placed_in_a_collection_of_the_same_name(self):
def test_in_decomposition_mode_spatial_structure_elements_are_placed_in_a_collection_of_the_same_name(self):
bpy.ops.bim.create_project()
space_obj = bpy.data.objects.new("IfcSpace/Name", None)
space_element = tool.Ifc.get().createIfcSpace()
@@ -72,6 +72,22 @@ class TestAssign(NewFile):
assert len(space_obj.users_collection) == 1
assert space_obj.users_collection[0].name == space_obj.name
def test_in_decomposition_mode_spatial_zone_elements_are_not_placed_in_a_collection_of_the_same_name(self):
bpy.ops.bim.create_project()
space_obj = bpy.data.objects.new("IfcSpaceZone/Name", None)
space_element = tool.Ifc.get().createIfcSpatialZone()
tool.Ifc.link(space_element, space_obj)
bpy.context.scene.collection.objects.link(space_obj)
ifcopenshell.api.run(
"aggregate.assign_object",
tool.Ifc.get(),
relating_object=tool.Ifc.get().by_type("IfcSite")[0],
product=space_element,
)
subject.assign(space_obj)
assert len(space_obj.users_collection) == 1
assert space_obj.users_collection[0].name != space_obj.name
def test_in_decomposition_mode_aggregates_are_placed_in_a_collection_of_the_same_name(self):
bpy.ops.bim.create_project()
element_obj = bpy.data.objects.new("IfcElementAssembly/Name", None)
@@ -222,7 +238,7 @@ class TestAssign(NewFile):
tool.Ifc.link(element, element_obj)
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get())
group.ObjectType = "DRAWING"
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), product=[element], group=group)
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=group)
subject.assign(element_obj)
assert element_obj.users_collection[0].name == "IfcGroup/Unnamed"
assert bpy.data.collections.get("Views").children.get("IfcGroup/Unnamed")
@@ -235,7 +251,7 @@ class TestAssign(NewFile):
tool.Ifc.link(element, element_obj)
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get())
group.ObjectType = "DRAWING"
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), product=[element], group=group)
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=group)
subject.assign(element_obj)
assert element_obj.users_collection[0].name == "IfcGroup/Unnamed"
assert bpy.data.collections.get("Views").children.get("IfcGroup/Unnamed")
+2 -2
View File
@@ -260,7 +260,7 @@ class TestGetDrawingGroup(NewFile):
tool.Ifc.set(ifc)
element = ifc.createIfcAnnotation()
group = ifcopenshell.api.run("group.add_group", ifc)
ifcopenshell.api.run("group.assign_group", ifc, product=[element], group=group)
ifcopenshell.api.run("group.assign_group", ifc, products=[element], group=group)
assert subject.get_drawing_group(element) == group
@@ -280,7 +280,7 @@ class TestGetGroupElements(NewFile):
tool.Ifc.set(ifc)
element = ifc.createIfcAnnotation()
group = ifcopenshell.api.run("group.add_group", ifc)
ifcopenshell.api.run("group.assign_group", ifc, product=[element], group=group)
ifcopenshell.api.run("group.assign_group", ifc, products=[element], group=group)
assert subject.get_group_elements(group) == (element,)
+32 -2
View File
@@ -31,7 +31,7 @@ class TestImplementsTool(NewFile):
class TestCanContain(NewFile):
def test_a_spatial_element_can_contain_an_element(self):
def test_a_spatial_structure_element_can_contain_an_element(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
structure = ifc.createIfcSite()
@@ -42,7 +42,7 @@ class TestCanContain(NewFile):
tool.Ifc.link(element, element_obj)
assert subject.can_contain(structure_obj, element_obj) is True
def test_a_spatial_element_can_contain_an_element_ifc2x3(self):
def test_a_spatial_structure_element_can_contain_an_element_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
structure = ifc.createIfcSite()
@@ -53,6 +53,17 @@ class TestCanContain(NewFile):
tool.Ifc.link(element, element_obj)
assert subject.can_contain(structure_obj, element_obj) is True
def test_a_spatial_zone_element_cannot_contain_an_element(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
structure = ifc.createIfcSpatialZone()
structure_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(structure, structure_obj)
element = ifc.createIfcWall()
element_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(element, element_obj)
assert subject.can_contain(structure_obj, element_obj) is False
def test_unlinked_elements_cannot_contain_anything(self):
structure_obj = bpy.data.objects.new("Object", None)
element_obj = bpy.data.objects.new("Object", None)
@@ -92,6 +103,25 @@ class TestCanContain(NewFile):
assert subject.can_contain(structure_obj, element_obj) is True
class TestCanReference(NewFile):
def test_an_element_can_reference_a_spatial_element(self):
ifc = ifcopenshell.file()
assert subject.can_reference(ifc.createIfcSite(), ifc.createIfcWall()) is True
def test_an_element_can_reference_a_spatial_element_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
assert subject.can_reference(ifc.createIfcSite(), ifc.createIfcWall()) is True
def test_a_non_spatial_element_cannot_reference_anything(self):
ifc = ifcopenshell.file()
assert subject.can_reference(ifc.createIfcWall(), ifc.createIfcWall()) is False
def test_a_non_element_cannot_reference_anything(self):
ifc = ifcopenshell.file()
assert subject.can_reference(ifc.createIfcSite(), ifc.createIfcTask()) is False
class TestDisableEditing(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
-1
View File
@@ -121,7 +121,6 @@ class IfcDiff:
print(" - {} item(s) were changed either geometrically or with data".format(len(self.change_register.keys())))
print("# Diff finished in {:.2f} seconds".format(time.time() - start))
logging.disable(logging.NOTSET)
return f"# Diff finished in {time.time() - start:.2f} seconds"
def export(self):
with open(self.output_file, "w", encoding="utf-8") as diff_file:
@@ -24,7 +24,7 @@ class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"product": None,
"products": None,
"group": None,
}
for key, value in settings.items():
@@ -37,13 +37,13 @@ class Usecase:
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": self.settings["product"],
"RelatedObjects": self.settings["products"],
"RelatingGroup": self.settings["group"],
}
)
rel = self.settings["group"].IsGroupedBy[0]
related_objects = set(rel.RelatedObjects) or set()
for obj in self.settings["product"]:
for obj in self.settings["products"]:
related_objects.add(obj)
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
@@ -0,0 +1,40 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"product": None, "relating_structure": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["product"].ReferencedInStructures:
if rel.RelatingStructure != self.settings["relating_structure"]:
continue
related_elements = list(rel.RelatedElements)
related_elements.remove(self.settings["product"])
if related_elements:
rel.RelatedElements = related_elements
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
self.file.remove(rel)
@@ -0,0 +1,57 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"product": None,
"relating_structure": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
referenced_in_structures = self.settings["product"].ReferencedInStructures
references_elements = self.settings["relating_structure"].ReferencesElements
for rel in referenced_in_structures:
if rel.RelatingStructure == self.settings["relating_structure"]:
return
if references_elements:
related_elements = list(references_elements[0].RelatedElements)
related_elements.append(self.settings["product"])
references_elements[0].RelatedElements = related_elements
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": references_elements[0]})
else:
references_elements = self.file.create_entity(
"IfcRelReferencedInSpatialStructure",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedElements": [self.settings["product"]],
"RelatingStructure": self.settings["relating_structure"],
}
)
return references_elements
@@ -23,7 +23,7 @@ def get_psets(element, psets_only=False, qtos_only=False, should_inherit=True):
"""Retrieve property sets, their related properties' names & values and ids.
:param element: The IFC Element entity
:param psets_only: Default as False. Set to true if only property sets are needed.
:param psets_only: Default as False. Set to true if only property sets are needed.
:param qtos_only: Default as False. Set to true if only quantities are needed.
:param should_inherit: Default as True. Set to false if you don't want to inherit property sets from the Type.
:return: dictionnary: key, value pair of psets' names and their properties' names & values
@@ -133,7 +133,7 @@ def get_predefined_type(element):
def get_type(element):
"""
Retrieves the Element Type entity related to an element entity.
:param element: The IFC Element entity
:return: The Element Type entity defining the element
@@ -178,7 +178,7 @@ def get_material(element, should_skip_usage=False, should_inherit=True):
def get_elements_by_material(ifc_file, material):
"""
Retrieves the elements related to a material.
:param ifc_file: The IFC file
:param material: The IFC Material entity
:return: The elements related to the material
@@ -212,7 +212,7 @@ def get_elements_by_material(ifc_file, material):
def get_elements_by_style(ifc_file, style):
"""
Retrieves the elements related to a style.
:param ifc_file: The IFC file
:param style: The IFC Style entity
:return: The elements related to the style
@@ -282,17 +282,22 @@ def get_layers(ifc_file, element):
def get_container(element, should_get_direct=False):
"""
Retrieves the container of an element.
:param element: The IFC element
:param should_get_direct: If True, the container of the element is returned. If False, the aggregate's container is returned
:return: The container of the element, or its aggregate's container.
Retrieves the spatial structure container of an element.
:param element: The IFC element
:type element: ifcopenshell.entity_instance.entity_instance
:param should_get_direct: If True, a result is only returned if the element
is directly contained in a spatial structure element. If False, an
indirect spatial container may be returned, such as if an element is a
part of an aggregate, and then if that aggregate is contained in a
spatial structure element.
:type should_get_direct: bool
:return: The direct or indirect container of the element or None.
Example::
element = file.by_type("IfcWall")[0]
container = ifcopenshell.util.element.get_container(element)
element = file.by_type("IfcWall")[0]
container = ifcopenshell.util.element.get_container(element)
"""
if should_get_direct:
if hasattr(element, "ContainedInStructure") and element.ContainedInStructure:
@@ -305,17 +310,34 @@ def get_container(element, should_get_direct=False):
return element.ContainedInStructure[0].RelatingStructure
def get_referenced_structures(element):
"""
Retreives a list of referenced structural elements
:param element: The IFC element
:type element: ifcopenshell.entity_instance.entity_instance
Example::
element = file.by_type("IfcWall")[0]
print(ifcopenshell.util.element.get_referenced_structures(element))
"""
if hasattr(element, "ReferencedInStructures"):
return [r.RelatingStructure for r in element.ReferencedInStructures]
return []
def get_decomposition(element):
"""
Retrieves the decomposition of an element.
:param element: The IFC element
:return: The decomposition of the element
Example::
element = file.by_type("IfcProject")[0]
decomposition = ifcopenshell.util.element.get_decomposition(element)
element = file.by_type("IfcProject")[0]
decomposition = ifcopenshell.util.element.get_decomposition(element)
"""
queue = [element]
results = []
@@ -333,7 +355,7 @@ def get_decomposition(element):
def get_aggregate(element):
"""
Retrieves the aggregate of an element.
:param element: The IFC element
:return: The aggregate of the element
@@ -349,14 +371,14 @@ def get_aggregate(element):
def get_parts(element):
"""
Retrieves the parts of an element.
:param element: The IFC element
:return: The parts of the element
Example::
element = file.by_type("IfcElementAssembly")[0]
parts = ifcopenshell.util.element.get_parts(element)
"""
if hasattr(element, "IsDecomposedBy") and element.IsDecomposedBy:
return element.IsDecomposedBy[0].RelatedObjects
@@ -238,8 +238,12 @@ class Selector:
def filter_element(cls, element, element_value, comparison, value):
if comparison.startswith("not"):
return not cls.filter_element(element, element_value, comparison[3:], value)
elif comparison == "equal" and isinstance(element_value, list):
return value in element_value
elif comparison == "equal":
return element_value == value
elif comparison == "contains" and isinstance(element_value, list):
return bool([ev for ev in element_value if value in str(ev)])
elif comparison == "contains":
return value in str(element_value)
elif comparison == "morethan":
@@ -0,0 +1,53 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import test.bootstrap
import ifcopenshell.api
import ifcopenshell.util.element
class TestDereferenceStructure(test.bootstrap.IFC4):
def test_removing_a_container(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement, relating_structure=element)
ifcopenshell.api.run("spatial.dereference_structure", self.file, product=subelement, relating_structure=element)
assert ifcopenshell.util.element.get_referenced_structures(subelement) == []
def test_doing_nothing_if_no_container(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.dereference_structure", self.file, product=subelement, relating_structure=element)
assert ifcopenshell.util.element.get_referenced_structures(subelement) == []
def test_updating_the_rel_when_a_reference_is_removed_with_multipled_elements(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement1, relating_structure=element)
ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement2, relating_structure=element)
ifcopenshell.api.run("spatial.dereference_structure", self.file, product=subelement1, relating_structure=element)
assert self.file.by_type("IfcRelReferencedInSpatialStructure")[0].RelatedElements == (subelement2,)
def test_deleting_the_rel_when_a_container_is_removed_with_no_elements(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement, relating_structure=element)
ifcopenshell.api.run("spatial.dereference_structure", self.file, product=subelement, relating_structure=element)
assert len(self.file.by_type("IfcRelReferencedInSpatialStructure")) == 0
@@ -0,0 +1,50 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import test.bootstrap
import ifcopenshell.api
import ifcopenshell.util.element
class TestReferenceStructure(test.bootstrap.IFC4):
def test_referencing_a_structure(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
rel = ifcopenshell.api.run(
"spatial.reference_structure", self.file, product=subelement, relating_structure=element
)
assert ifcopenshell.util.element.get_referenced_structures(subelement) == [element]
assert rel.is_a("IfcRelReferencedInSpatialStructure")
def test_doing_nothing_if_the_structure_is_already_referenced(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement, relating_structure=element)
total_elements = len([e for e in self.file])
ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement, relating_structure=element)
assert len([e for e in self.file]) == total_elements
def test_that_old_relationships_are_updated_if_they_still_contain_elements(self):
element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement1, relating_structure=element1)
ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement2, relating_structure=element1)
rel = subelement1.ReferencedInStructures[0]
assert len(rel.RelatedElements) == 2
@@ -538,6 +538,18 @@ class TestGetContainerIFC4(test.bootstrap.IFC4):
assert subject.get_container(subelement, should_get_direct=True) is None
class TestGetReferencedStructures(test.bootstrap.IFC4):
def test_getting_references_of_an_element(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
assert subject.get_referenced_structures(element) == []
building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
ifcopenshell.api.run("spatial.reference_structure", self.file, product=element, relating_structure=building)
assert subject.get_referenced_structures(element) == [building]
building2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
ifcopenshell.api.run("spatial.reference_structure", self.file, product=element, relating_structure=building2)
assert subject.get_referenced_structures(element) == [building, building2]
class TestGetDecompositionIFC4(test.bootstrap.IFC4):
def test_getting_decomposed_subelements_of_an_element(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcElementAssembly")
@@ -37,8 +37,8 @@ class TestSelector(test.bootstrap.IFC4):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element.Name = "Foobar"
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSlab")
assert subject.Selector.parse(self.file, '.IfcElement[Name]') == [element]
assert subject.Selector.parse(self.file, '.IfcElement[Description]') == []
assert subject.Selector.parse(self.file, ".IfcElement[Name]") == [element]
assert subject.Selector.parse(self.file, ".IfcElement[Description]") == []
def test_selecting_by_attribute(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
@@ -51,8 +51,8 @@ class TestSelector(test.bootstrap.IFC4):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"})
assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Foo]') == [element]
assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Fox]') == []
assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Foo]") == [element]
assert subject.Selector.parse(self.file, ".IfcElement[Foo_Bar.Fox]") == []
def test_selecting_by_string_property(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
@@ -60,6 +60,15 @@ class TestSelector(test.bootstrap.IFC4):
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Foo": "Bar"})
assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Foo="Bar"]') == [element]
def test_selecting_by_enumerated_property(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Pset_WallCommon")
template = ifcopenshell.util.pset.get_template("IFC4").get_by_name("Pset_WallCommon")
ifcopenshell.api.run(
"pset.edit_pset", self.file, pset=pset, properties={"Status": ["NEW"]}, pset_template=template
)
assert subject.Selector.parse(self.file, '.IfcElement[Pset_WallCommon.Status="NEW"]') == [element]
def test_selecting_by_integer_property(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Foo_Bar")
@@ -121,17 +130,20 @@ class TestSelector(test.bootstrap.IFC4):
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset_2, properties={"Foo": "BOO"})
assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Foo != "Bar"]') == [element_2]
assert subject.Selector.parse(self.file, '.IfcElement[Foo_Bar.Foo != "BOO"]') == [element_1]
def test_selecting_when_attribute_is_none(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
assert subject.Selector.parse(self.file, '.IfcElement[PredefinedType !="non-existent predefined type"]') == [element]
assert subject.Selector.parse(self.file, '.IfcElement[PredefinedType !="non-existent predefined type"]') == [
element
]
def test_selecting_a_property_which_includes_non_standard_characters(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="a !%$§&/()?|*-+,€~#@µ^°a")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"a !%$§&/()?|*-+,€~#@µ^°a": "Bar"})
assert subject.Selector.parse(self.file, '.IfcElement[a !%$§&/()?|*-+,€~#@µ^°a.a !%$§&/()?|*-+,€~#@µ^°a="Bar"]') == [element]
assert subject.Selector.parse(
self.file, '.IfcElement[a !%$§&/()?|*-+,€~#@µ^°a.a !%$§&/()?|*-+,€~#@µ^°a="Bar"]'
) == [element]
def test_comparing_if_value_is_in_a_list(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")