Merge remote-tracking branch 'origin/v0.7.0' into v08attempt1

This commit is contained in:
Thomas Krijnen
2024-04-18 21:39:33 +02:00
86 changed files with 1817 additions and 631 deletions
@@ -0,0 +1,53 @@
name: Publish-ifcsverchok
on:
push:
paths:
- '.github/workflows/ci-ifcsverchok-build.yml'
- 'src/ifcsverchok/*'
branches:
- v0.7.0
env:
major: 0
minor: 0
name: ifcsverchok
jobs:
activate:
runs-on: ubuntu-latest
if: |
github.repository == 'IfcOpenShell/IfcOpenShell'
steps:
- name: Set env
run: echo ok go
build:
needs: activate
name: ifcsverchok
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
- run: echo ${{ env.DATE }}
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%y%m%d')"
- name: Compile
run: |
cd src/ifcsverchok
make dist
- name: Upload Zip file to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: src/ifcsverchok/dist/ifcsverchok-${{steps.date.outputs.date}}.zip
asset_name: ifcsverchok-${{steps.date.outputs.date}}.zip
tag: "ifcsverchok-${{steps.date.outputs.date}}"
overwrite: true
body: "ifcsverchok build for ${{steps.date.outputs.date}}"
+1 -1
View File
@@ -12,7 +12,7 @@ RUN apt-get -y update && apt-get -y install unzip curl
# Install AWS Lambda runtime interface client # Install AWS Lambda runtime interface client
RUN pip install --target ${FUNCTION_DIR} awslambdaric RUN pip install --target ${FUNCTION_DIR} awslambdaric
# Set the IfcOpenShell build version (check available builds at: https://blenderbim.org/docs-python/ifcopenshell-python/installation.html) # Set the IfcOpenShell build version (check available builds at: https://docs.ifcopenshell.org/ifcopenshell-python/installation.html)
ARG IFC_OPENSHELL_BUILD="39-v0.7.0-476ab50" ARG IFC_OPENSHELL_BUILD="39-v0.7.0-476ab50"
# Download and extract IfcOpenShell # Download and extract IfcOpenShell
+1 -1
View File
@@ -15,7 +15,7 @@
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/COPYING</licenseUrl> <licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/COPYING</licenseUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance> <requireLicenseAcceptance>true</requireLicenseAcceptance>
<projectSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectSourceUrl> <projectSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectSourceUrl>
<docsUrl>https://blenderbim.org/docs/</docsUrl> <docsUrl>https://docs.blenderbim.org/</docsUrl>
<!--<mailingListUrl></mailingListUrl>--> <!--<mailingListUrl></mailingListUrl>-->
<bugTrackerUrl>https://github.com/IfcOpenShell/IfcOpenShell/issues</bugTrackerUrl> <bugTrackerUrl>https://github.com/IfcOpenShell/IfcOpenShell/issues</bugTrackerUrl>
<tags>blender bim blenderbim ifc python opensource foss</tags> <tags>blender bim blenderbim ifc python opensource foss</tags>
+5 -1
View File
@@ -857,7 +857,11 @@ class IfcImporter:
print("Done creating geometry") print("Done creating geometry")
def create_spatial_elements(self) -> None: def create_spatial_elements(self) -> None:
self.create_generic_elements(self.spatial_elements, unselectable=True) if bpy.context.preferences.addons["blenderbim"].preferences.spatial_elements_unselectable:
self.create_generic_elements(self.spatial_elements, unselectable=True)
else:
self.create_generic_elements(self.spatial_elements, unselectable=False)
def create_elements(self) -> None: def create_elements(self) -> None:
self.create_generic_elements(self.elements) self.create_generic_elements(self.elements)
@@ -47,10 +47,18 @@ class EnableEditingAttributes(bpy.types.Operator):
obj = bpy.data.objects.get(self.obj) obj = bpy.data.objects.get(self.obj)
elif self.obj_type == "Material": elif self.obj_type == "Material":
obj = bpy.data.materials.get(self.obj) obj = bpy.data.materials.get(self.obj)
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties props = obj.BIMAttributeProperties
props.attributes.clear() props.attributes.clear()
element = tool.Ifc.get_entity(obj)
has_inherited_predefined_type = False
if not element.is_a("IfcTypeObject") and (element_type := ifcopenshell.util.element.get_type(element)):
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
has_inherited_predefined_type = ifcopenshell.util.element.get_predefined_type(element_type) not in (
"NOTDEFINED",
None,
)
def callback(name, prop, data): def callback(name, prop, data):
if name in ("RefLatitude", "RefLongitude"): if name in ("RefLatitude", "RefLongitude"):
new = props.attributes.add() new = props.attributes.add()
@@ -62,10 +70,11 @@ class EnableEditingAttributes(bpy.types.Operator):
new.string_value = "" if new.is_null else json.dumps(data[name]) new.string_value = "" if new.is_null else json.dumps(data[name])
blenderbim.bim.helper.add_attribute_description(new) blenderbim.bim.helper.add_attribute_description(new)
new.description += " The degrees, minutes and seconds should follow this format : [12,34,56]" new.description += " The degrees, minutes and seconds should follow this format : [12,34,56]"
if name in ("PredefinedType", "ObjectType") and has_inherited_predefined_type:
props.attributes.remove(len(props.attributes) - 1)
return True
blenderbim.bim.helper.import_attributes2( blenderbim.bim.helper.import_attributes2(element, props.attributes, callback=callback)
tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes, callback=callback
)
props.is_editing_attributes = True props.is_editing_attributes = True
return {"FINISHED"} return {"FINISHED"}
@@ -20,6 +20,8 @@ import bpy
import json import json
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.classification
import ifcopenshell.util.element
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.helper import blenderbim.bim.helper
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
@@ -87,7 +89,7 @@ class AddManualClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
reference = ifcopenshell.api.run( reference = ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
tool.Ifc.get(), tool.Ifc.get(),
product=product, products=[product],
classification=classification, classification=classification,
identification="X", identification="X",
name="Unnamed", name="Unnamed",
@@ -292,6 +294,7 @@ class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
active_reference = tool.Ifc.get().by_id(self.reference) active_reference = tool.Ifc.get().by_id(self.reference)
identification = active_reference[1] identification = active_reference[1]
elements_by_references: dict[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]] = {}
for obj in objects: for obj in objects:
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context) ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id) element = tool.Ifc.get().by_id(ifc_definition_id)
@@ -300,12 +303,16 @@ class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
if (identification and reference[1] == identification) or ( if (identification and reference[1] == identification) or (
not identification and reference == active_reference not identification and reference == active_reference
): ):
ifcopenshell.api.run( elements_by_references.setdefault(reference, []).append(element)
"classification.remove_reference",
tool.Ifc.get(), if elements_by_references:
reference=reference, for reference, products in elements_by_references.items():
product=element, ifcopenshell.api.run(
) "classification.remove_reference",
tool.Ifc.get(),
reference=reference,
products=products,
)
class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator): class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
@@ -357,15 +364,18 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
classification = element classification = element
break break
for obj in objects: ifc_file = tool.Ifc.get()
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context) products = [
if not ifc_definition_id: ifc_file.by_id(ifc_definition_id)
continue for obj in objects
if (ifc_definition_id := tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context))
]
if products:
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
tool.Ifc.get(), tool.Ifc.get(),
reference=IfcStore.classification_file.by_id(self.reference), reference=IfcStore.classification_file.by_id(self.reference),
product=tool.Ifc.get().by_id(ifc_definition_id), products=products,
classification=classification, classification=classification,
) )
@@ -413,7 +423,7 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
reference = ifcopenshell.api.run( reference = ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
tool.Ifc.get(), tool.Ifc.get(),
product=element, products=[element],
classification=classification, classification=classification,
identification=bsdd_classification.reference_code, identification=bsdd_classification.reference_code,
name=bsdd_classification.name, name=bsdd_classification.name,
@@ -114,7 +114,7 @@ class EditObjective(bpy.types.Operator):
ifcopenshell.api.run( ifcopenshell.api.run(
"constraint.edit_objective", "constraint.edit_objective",
self.file, self.file,
**{"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes} **{"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes},
) )
bpy.ops.bim.load_objectives() bpy.ops.bim.load_objectives()
return {"FINISHED"} return {"FINISHED"}
@@ -179,19 +179,17 @@ class AssignConstraint(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = tool.Ifc.get()
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
for obj in objs: products = [self.file.by_id(obj_id) for obj in objs if (obj_id := obj.BIMObjectProperties.ifc_definition_id)]
obj_id = obj.BIMObjectProperties.ifc_definition_id if products:
if not obj_id:
continue
ifcopenshell.api.run( ifcopenshell.api.run(
"constraint.assign_constraint", "constraint.assign_constraint",
self.file, self.file,
**{ **{
"product": self.file.by_id(obj_id), "products": products,
"constraint": self.file.by_id(self.constraint), "constraint": self.file.by_id(self.constraint),
} },
) )
return {"FINISHED"} return {"FINISHED"}
@@ -207,18 +205,16 @@ class UnassignConstraint(bpy.types.Operator):
return IfcStore.execute_ifc_operator(self, context) return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() self.file = tool.Ifc.get()
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
for obj in objs: products = [self.file.by_id(obj_id) for obj in objs if (obj_id := obj.BIMObjectProperties.ifc_definition_id)]
obj_id = obj.BIMObjectProperties.ifc_definition_id if products:
if not obj_id:
continue
ifcopenshell.api.run( ifcopenshell.api.run(
"constraint.unassign_constraint", "constraint.unassign_constraint",
self.file, self.file,
**{ **{
"product": self.file.by_id(obj_id), "products": products,
"constraint": self.file.by_id(self.constraint), "constraint": self.file.by_id(self.constraint),
} },
) )
return {"FINISHED"} return {"FINISHED"}
@@ -2570,8 +2570,16 @@ class EnableEditingElementFilter(bpy.types.Operator, Operator):
def _execute(self, context): def _execute(self, context):
obj = bpy.context.scene.camera obj = bpy.context.scene.camera
if obj: if not obj:
obj.data.BIMCameraProperties.filter_mode = self.filter_mode return
obj.data.BIMCameraProperties.filter_mode = self.filter_mode
element = tool.Ifc.get_entity(obj)
if query := ifcopenshell.util.element.get_pset(element, "EPset_Drawing", self.filter_mode.title()):
filter_groups = tool.Search.get_filter_groups(f"drawing_{self.filter_mode.lower()}")
try:
tool.Search.import_filter_query(query, filter_groups)
except:
pass
class EditElementFilter(bpy.types.Operator, Operator): class EditElementFilter(bpy.types.Operator, Operator):
@@ -66,6 +66,11 @@ def get_location_hint(self, context):
def update_diagram_scale(self, context): def update_diagram_scale(self, context):
if not context.scene.camera or context.scene.camera.data != self.id_data:
return
element = tool.Ifc.get_entity(context.scene.camera)
if not element:
return
try: try:
element = ( element = (
tool.Ifc.get() tool.Ifc.get()
@@ -87,6 +92,11 @@ def update_diagram_scale(self, context):
def update_is_nts(self, context): def update_is_nts(self, context):
if not context.scene.camera or context.scene.camera.data != self.id_data:
return
element = tool.Ifc.get_entity(context.scene.camera)
if not element:
return
try: try:
element = ( element = (
tool.Ifc.get() tool.Ifc.get()
@@ -175,7 +175,7 @@ class BIM_PT_object_material(Panel):
if ObjectMaterialData.data["type_material"]: if ObjectMaterialData.data["type_material"]:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="FILE_PARENT") row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="CON_CHILDOF")
if ObjectMaterialData.data["material_class"]: if ObjectMaterialData.data["material_class"]:
return self.draw_material_ui() return self.draw_material_ui()
@@ -25,6 +25,8 @@ classes = (
operator.AppendLibraryElement, operator.AppendLibraryElement,
operator.AppendLibraryElementByQuery, operator.AppendLibraryElementByQuery,
operator.AssignLibraryDeclaration, operator.AssignLibraryDeclaration,
operator.BIM_OT_load_clipping_planes,
operator.BIM_OT_save_clipping_planes,
operator.ChangeLibraryElement, operator.ChangeLibraryElement,
operator.CreateClippingPlane, operator.CreateClippingPlane,
operator.CreateProject, operator.CreateProject,
@@ -39,6 +39,8 @@ from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.ui import IFCFileSelector from blenderbim.bim.ui import IFCFileSelector
from blenderbim.bim import import_ifc from blenderbim.bim import import_ifc
from blenderbim.bim import export_ifc from blenderbim.bim import export_ifc
from collections import defaultdict
import json
from math import radians from math import radians
from pathlib import Path from pathlib import Path
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
@@ -1999,3 +2001,61 @@ class FlipClippingPlane(bpy.types.Operator):
obj.rotation_euler[0] += radians(180) obj.rotation_euler[0] += radians(180)
context.view_layer.update() context.view_layer.update()
return {"FINISHED"} return {"FINISHED"}
CLIPPING_PLANES_FILE_NAME = "ClippingPlanes.json" # TODO un-hardcode :=
class BIM_OT_save_clipping_planes(bpy.types.Operator):
bl_idname = "bim.save_clipping_planes"
bl_label = "Save Clipping Planes"
bl_description = "Save Clipping Planes to Disk"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if IfcStore.path:
return context.scene.BIMProjectProperties.clipping_planes
cls.poll_message_set("Please Save The IFC File")
def execute(self, context):
clipping_planes_to_serialize = defaultdict(dict)
clipping_planes = context.scene.BIMProjectProperties.clipping_planes
for clipping_plane in clipping_planes:
obj = clipping_plane.obj
name = obj.name
clipping_planes_to_serialize[name]["location"] = obj.location[0:3]
clipping_planes_to_serialize[name]["rotation"] = obj.rotation_euler[0:3]
with open(Path(IfcStore.path).with_name(CLIPPING_PLANES_FILE_NAME), "w") as file:
json.dump(clipping_planes_to_serialize, file, indent=4)
return {"FINISHED"}
class BIM_OT_load_clipping_planes(bpy.types.Operator):
bl_idname = "bim.load_clipping_planes"
bl_label = "Load Clipping Planes"
bl_description = "Load Clipping Planes from Disk"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if filepath := IfcStore.path:
if Path(filepath).with_name(CLIPPING_PLANES_FILE_NAME).exists():
return True
else:
cls.poll_message_set(f"No Clipping Planes File in Folder {filepath}")
else:
cls.poll_message_set("Please Save The IFC File")
def execute(self, context):
bpy.data.batch_remove(context.scene.BIMProjectProperties.clipping_planes_objs)
context.scene.BIMProjectProperties.clipping_planes.clear()
with open(Path(IfcStore.path).with_name(CLIPPING_PLANES_FILE_NAME), "r") as file:
clipping_planes_dict = json.load(file)
for name, values in clipping_planes_dict.items():
bpy.ops.bim.create_clipping_plane()
obj = context.scene.BIMProjectProperties.clipping_planes_objs[-1]
obj.name = name
obj.location = values["location"]
obj.rotation_euler = values["rotation"]
return {"FINISHED"}
@@ -69,6 +69,7 @@ class ObjectPsetsData(Data):
@classmethod @classmethod
def load(cls): def load(cls):
cls.data = { cls.data = {
"is_occurrence": cls.is_occurrence(),
"psets": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), psets_only=True), "psets": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), psets_only=True),
"inherited_psets": cls.inherited_psets(), "inherited_psets": cls.inherited_psets(),
"pset_name": cls.pset_name(), "pset_name": cls.pset_name(),
@@ -76,6 +77,10 @@ class ObjectPsetsData(Data):
} }
cls.is_loaded = True cls.is_loaded = True
@classmethod
def is_occurrence(cls):
return not tool.Ifc.get_entity(bpy.context.active_object).is_a("IfcTypeObject")
@classmethod @classmethod
def inherited_psets(cls): def inherited_psets(cls):
element = tool.Ifc.get_entity(bpy.context.active_object) element = tool.Ifc.get_entity(bpy.context.active_object)
@@ -126,11 +131,16 @@ class ObjectQtosData(Data):
@classmethod @classmethod
def load(cls): def load(cls):
cls.data = { cls.data = {
"is_occurrence": cls.is_occurrence(),
"qtos": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), qtos_only=True), "qtos": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), qtos_only=True),
"inherited_qsets": cls.inherited_qsets(), "inherited_qsets": cls.inherited_qsets(),
} }
cls.is_loaded = True cls.is_loaded = True
@classmethod
def is_occurrence(cls):
return not tool.Ifc.get_entity(bpy.context.active_object).is_a("IfcTypeObject")
@classmethod @classmethod
def inherited_qsets(cls): def inherited_qsets(cls):
element = tool.Ifc.get_entity(bpy.context.active_object) element = tool.Ifc.get_entity(bpy.context.active_object)
@@ -208,12 +208,15 @@ class BIM_PT_object_psets(Panel):
draw_psetqto_ui(context, 0, {}, props, self.layout, "Object") draw_psetqto_ui(context, 0, {}, props, self.layout, "Object")
if ObjectPsetsData.data["psets"]: if ObjectPsetsData.data["psets"]:
self.layout.label(text="Instance:") if ObjectPsetsData.data["is_occurrence"]:
self.layout.label(text="Occurrence Properties:")
else:
self.layout.label(text="Type Properties:")
for pset in ObjectPsetsData.data["psets"]: for pset in ObjectPsetsData.data["psets"]:
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object") draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object")
if ObjectPsetsData.data["inherited_psets"]: if ObjectPsetsData.data["inherited_psets"]:
self.layout.label(text="Type:") self.layout.label(text="Inherited Type Properties:", icon="CON_CHILDOF")
for pset in ObjectPsetsData.data["inherited_psets"]: for pset in ObjectPsetsData.data["inherited_psets"]:
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object", allow_removing=False) draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object", allow_removing=False)
@@ -252,12 +255,15 @@ class BIM_PT_object_qtos(Panel):
draw_psetqto_ui(context, 0, {}, props, self.layout, "Object") draw_psetqto_ui(context, 0, {}, props, self.layout, "Object")
if ObjectQtosData.data["qtos"]: if ObjectQtosData.data["qtos"]:
self.layout.label(text="Instance:") if ObjectQtosData.data["is_occurrence"]:
self.layout.label(text="Occurrence Quantities:")
else:
self.layout.label(text="Type Quantities:")
for qto in ObjectQtosData.data["qtos"]: for qto in ObjectQtosData.data["qtos"]:
draw_psetqto_ui(context, qto["id"], qto, props, self.layout, "Object") draw_psetqto_ui(context, qto["id"], qto, props, self.layout, "Object")
if ObjectQtosData.data["inherited_qsets"]: if ObjectQtosData.data["inherited_qsets"]:
self.layout.label(text="Type:") self.layout.label(text="Inherited Type Quantities:", icon="CON_CHILDOF")
for qset in ObjectQtosData.data["inherited_qsets"]: for qset in ObjectQtosData.data["inherited_qsets"]:
draw_psetqto_ui(context, qset["id"], qset, props, self.layout, "Object", allow_removing=False) draw_psetqto_ui(context, qset["id"], qset, props, self.layout, "Object", allow_removing=False)
@@ -42,6 +42,7 @@ class IfcClassData:
cls.data["contexts"] = cls.contexts() cls.data["contexts"] = cls.contexts()
cls.data["has_entity"] = cls.has_entity() cls.data["has_entity"] = cls.has_entity()
cls.data["name"] = cls.name() cls.data["name"] = cls.name()
cls.data["has_inherited_predefined_type"] = cls.has_inherited_predefined_type()
cls.data["ifc_class"] = cls.ifc_class() cls.data["ifc_class"] = cls.ifc_class()
cls.data["ifc_predefined_types"] = cls.ifc_predefined_types() cls.data["ifc_predefined_types"] = cls.ifc_predefined_types()
cls.data["can_reassign_class"] = cls.can_reassign_class() cls.data["can_reassign_class"] = cls.can_reassign_class()
@@ -162,6 +163,16 @@ class IfcClassData:
name += f"[{predefined_type}]" name += f"[{predefined_type}]"
return name return name
@classmethod
def has_inherited_predefined_type(cls):
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
if not element:
return
if element_type := ifcopenshell.util.element.get_type(element):
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
return ifcopenshell.util.element.get_predefined_type(element_type) not in ("NOTDEFINED", None)
return False
@classmethod @classmethod
def ifc_class(cls): def ifc_class(cls):
element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active) element = tool.Ifc.get_entity(bpy.context.view_layer.objects.active)
@@ -61,7 +61,10 @@ class BIM_PT_class(Panel):
self.layout.prop(context.scene.BIMRootProperties, "relating_class_object", icon="COPYDOWN") self.layout.prop(context.scene.BIMRootProperties, "relating_class_object", icon="COPYDOWN")
else: else:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text=IfcClassData.data["name"]) row.label(
text=IfcClassData.data["name"],
icon="CON_CHILDOF" if IfcClassData.data["has_inherited_predefined_type"] else "NONE",
)
row.operator("bim.select_ifc_class", text="", icon="RESTRICT_SELECT_OFF") row.operator("bim.select_ifc_class", text="", icon="RESTRICT_SELECT_OFF")
row.operator("bim.unlink_object", icon="UNLINKED", text="") row.operator("bim.unlink_object", icon="UNLINKED", text="")
if IfcClassData.data["can_reassign_class"]: if IfcClassData.data["can_reassign_class"]:
@@ -235,7 +235,7 @@ class LoadSearch(Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
filter_groups = tool.Search.get_filter_groups(self.module) filter_groups = tool.Search.get_filter_groups(self.module)
group = tool.Ifc.get().by_id(int(context.scene.BIMSearchProperties.saved_searches)) group = tool.Ifc.get().by_id(int(context.scene.BIMSearchProperties.saved_searches))
query = tool.Search.import_filter_query(tool.Search.get_group_query(group), filter_groups) tool.Search.import_filter_query(tool.Search.get_group_query(group), filter_groups)
def draw(self, context): def draw(self, context):
props = context.scene.BIMSearchProperties props = context.scene.BIMSearchProperties
+8 -1
View File
@@ -131,6 +131,8 @@ class BIM_PT_section_with_cappings(Panel):
box = layout.box() box = layout.box()
header = box.row(align=True) header = box.row(align=True)
header.label(text="Clipping Planes") header.label(text="Clipping Planes")
header.operator("bim.save_clipping_planes", text="", icon="EXPORT")
header.operator("bim.load_clipping_planes", text="", icon="IMPORT")
header.operator("bim.create_clipping_plane", text="", icon="ADD") header.operator("bim.create_clipping_plane", text="", icon="ADD")
box.template_list( box.template_list(
@@ -150,7 +152,7 @@ class BIM_PT_section_with_cappings(Panel):
class BIM_UL_clipping_plane(bpy.types.UIList): class BIM_UL_clipping_plane(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item and item.obj:
obj = item.obj obj = item.obj
row = layout.row(align=True) row = layout.row(align=True)
row.prop(obj, "name", text="", emboss=False) row.prop(obj, "name", text="", emboss=False)
@@ -202,6 +204,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False
) )
lock_grids_on_import: BoolProperty(name="Should Lock Grids By Default", default=True) lock_grids_on_import: BoolProperty(name="Should Lock Grids By Default", default=True)
spatial_elements_unselectable: BoolProperty(name="Should Make Spatial Elements Unselectable By Default", default=True)
decorations_colour: bpy.props.FloatVectorProperty( decorations_colour: bpy.props.FloatVectorProperty(
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4 name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
) )
@@ -283,6 +286,10 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.prop(self, "should_play_chaching_sound") row.prop(self, "should_play_chaching_sound")
row = layout.row() row = layout.row()
row.prop(self, "lock_grids_on_import") row.prop(self, "lock_grids_on_import")
row = layout.row()
row.prop(self, "spatial_elements_unselectable")
row = layout.row() row = layout.row()
row.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save") row.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save")
+1 -1
View File
@@ -74,7 +74,7 @@ def assign_brick_reference(ifc, brick, element=None, library=None, brick_uri=Non
if not reference: if not reference:
reference = ifc.run("library.add_reference", library=library) reference = ifc.run("library.add_reference", library=library)
ifc.run("library.edit_reference", reference=reference, attributes=brick.export_brick_attributes(brick_uri)) ifc.run("library.edit_reference", reference=reference, attributes=brick.export_brick_attributes(brick_uri))
ifc.run("library.assign_reference", product=element, reference=reference) ifc.run("library.assign_reference", products=[element], reference=reference)
project = brick.get_brickifc_project() project = brick.get_brickifc_project()
if not project: if not project:
project = brick.add_brickifc_project(brick.get_namespace(brick_uri)) project = brick.add_brickifc_project(brick.get_namespace(brick_uri))
+2 -2
View File
@@ -109,8 +109,8 @@ def remove_document(ifc, document_tool, document=None):
def assign_document(ifc, product=None, document=None): def assign_document(ifc, product=None, document=None):
ifc.run("document.assign_document", product=product, document=document) ifc.run("document.assign_document", products=[product], document=document)
def unassign_document(ifc, product=None, document=None): def unassign_document(ifc, product=None, document=None):
ifc.run("document.unassign_document", product=product, document=document) ifc.run("document.unassign_document", products=[product], document=document)
+3 -3
View File
@@ -241,7 +241,7 @@ def add_drawing(ifc, collector, drawing, target_view=None, location_hint=None):
attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"} attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"}
ifc.run("document.edit_information", information=information, attributes=attributes) ifc.run("document.edit_information", information=information, attributes=attributes)
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri}) ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
ifc.run("document.assign_document", product=element, document=reference) ifc.run("document.assign_document", products=[element], document=reference)
drawing.import_drawings() drawing.import_drawings()
@@ -265,7 +265,7 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
ifc.run("group.assign_group", group=new_group, products=[new_annotation]) ifc.run("group.assign_group", group=new_group, products=[new_annotation])
old_reference = drawing_tool.get_drawing_document(new_drawing) old_reference = drawing_tool.get_drawing_document(new_drawing)
ifc.run("document.unassign_document", product=new_drawing, document=old_reference) ifc.run("document.unassign_document", products=[new_drawing], document=old_reference)
information = ifc.run("document.add_information") information = ifc.run("document.add_information")
uri = drawing_tool.get_default_drawing_path(drawing_name) uri = drawing_tool.get_default_drawing_path(drawing_name)
@@ -276,7 +276,7 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati
attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"} attributes = {"Identification": "X", "Name": drawing_name, "Scope": "DRAWING"}
ifc.run("document.edit_information", information=information, attributes=attributes) ifc.run("document.edit_information", information=information, attributes=attributes)
ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri}) ifc.run("document.edit_reference", reference=reference, attributes={"Location": uri})
ifc.run("document.assign_document", product=new_drawing, document=reference) ifc.run("document.assign_document", products=[new_drawing], document=reference)
drawing_tool.import_drawings() drawing_tool.import_drawings()
return new_drawing return new_drawing
+2 -2
View File
@@ -83,8 +83,8 @@ def edit_library_reference(ifc, library):
def assign_library_reference(ifc, obj=None, reference=None): def assign_library_reference(ifc, obj=None, reference=None):
ifc.run("library.assign_reference", product=ifc.get_entity(obj), reference=reference) ifc.run("library.assign_reference", products=[ifc.get_entity(obj)], reference=reference)
def unassign_library_reference(ifc, obj=None, reference=None): def unassign_library_reference(ifc, obj=None, reference=None):
ifc.run("library.unassign_reference", product=ifc.get_entity(obj), reference=reference) ifc.run("library.unassign_reference", products=[ifc.get_entity(obj)], reference=reference)
+2 -2
View File
@@ -195,7 +195,7 @@ def add_usage_constraint(ifc, resource_tool, resource=None, reference_path=None)
}, },
) )
ifc.run("constraint.add_metric_reference", metric=metric, reference_path=reference_path) ifc.run("constraint.add_metric_reference", metric=metric, reference_path=reference_path)
ifc.run("constraint.assign_constraint", product=resource, constraint=objective) ifc.run("constraint.assign_constraint", products=[resource], constraint=objective)
def remove_usage_constraint(ifc, resource_tool, resource, reference_path): def remove_usage_constraint(ifc, resource_tool, resource, reference_path):
@@ -206,7 +206,7 @@ def remove_usage_constraint(ifc, resource_tool, resource, reference_path):
reference = resource_tool.get_metric_reference(metric, is_deep=True) reference = resource_tool.get_metric_reference(metric, is_deep=True)
if reference == reference_path: if reference == reference_path:
ifc.run("constraint.remove_metric", metric=metric) ifc.run("constraint.remove_metric", metric=metric)
ifc.run("constraint.unassign_constraint", product=resource, constraint=constraint) ifc.run("constraint.unassign_constraint", products=[resource], constraint=constraint)
ifc.run("constraint.remove_constraint", constraint=constraint) ifc.run("constraint.remove_constraint", constraint=constraint)
+2 -2
View File
@@ -32,7 +32,7 @@ def reference_structure(
element: Optional[ifcopenshell.entity_instance] = None, element: Optional[ifcopenshell.entity_instance] = None,
) -> Union[ifcopenshell.entity_instance, None]: ) -> Union[ifcopenshell.entity_instance, None]:
if spatial.can_reference(structure, element): if spatial.can_reference(structure, element):
return ifc.run("spatial.reference_structure", product=element, relating_structure=structure) return ifc.run("spatial.reference_structure", products=[element], relating_structure=structure)
def dereference_structure( def dereference_structure(
@@ -42,7 +42,7 @@ def dereference_structure(
element: Optional[ifcopenshell.entity_instance] = None, element: Optional[ifcopenshell.entity_instance] = None,
) -> None: ) -> None:
if spatial.can_reference(structure, element): if spatial.can_reference(structure, element):
return ifc.run("spatial.dereference_structure", product=element, relating_structure=structure) return ifc.run("spatial.dereference_structure", products=[element], relating_structure=structure)
def assign_container( def assign_container(
+7 -5
View File
@@ -733,7 +733,6 @@ class Drawing(blenderbim.core.tool.Drawing):
obj.BIMObjectProperties.ifc_definition_id = ifc_definition_id obj.BIMObjectProperties.ifc_definition_id = ifc_definition_id
@classmethod @classmethod
def import_drawings(cls): def import_drawings(cls):
props = bpy.context.scene.DocProperties props = bpy.context.scene.DocProperties
@@ -1776,7 +1775,6 @@ class Drawing(blenderbim.core.tool.Drawing):
element_obj_names = set() element_obj_names = set()
for element in filtered_elements: for element in filtered_elements:
obj = tool.Ifc.get_object(element) obj = tool.Ifc.get_object(element)
element_obj_names.add(obj.name)
current_representation = tool.Geometry.get_active_representation(obj) current_representation = tool.Geometry.get_active_representation(obj)
if current_representation: if current_representation:
subcontext = current_representation.ContextOfItems subcontext = current_representation.ContextOfItems
@@ -1805,10 +1803,14 @@ class Drawing(blenderbim.core.tool.Drawing):
# Don't hide IfcAnnotations as some of them might exist without representations # Don't hide IfcAnnotations as some of them might exist without representations
if has_context or element.is_a("IfcAnnotation"): if has_context or element.is_a("IfcAnnotation"):
# Note that render visibility is only set on drawing generation time for speed. element_obj_names.add(obj.name)
obj.hide_set(False)
[obj.hide_set(False) for obj in bpy.context.view_layer.objects if obj.name not in element_obj_names] # Note that render visibility is only set on drawing generation time for speed.
[
obj.hide_set(False) # Show the object
for obj in bpy.context.view_layer.objects
if obj.name in element_obj_names or not tool.Ifc.get_entity(obj)
]
cls.import_camera_props(drawing, camera) cls.import_camera_props(drawing, camera)
@@ -91,6 +91,17 @@ class Geometry(blenderbim.core.tool.Geometry):
if element.is_a("IfcRelSpaceBoundary"): if element.is_a("IfcRelSpaceBoundary"):
ifcopenshell.api.run("boundary.remove_boundary", tool.Ifc.get(), boundary=element) ifcopenshell.api.run("boundary.remove_boundary", tool.Ifc.get(), boundary=element)
return bpy.data.objects.remove(obj) return bpy.data.objects.remove(obj)
if element.is_a("IfcGridAxis"):
is_last_axis = False
# Deleting the last W axis is OK
if ((grid := element.PartOfU) and len(grid[0].UAxes) == 1) or (
(grid := element.PartOfV) and len(grid[0].VAxes) == 1
):
is_last_axis = True
if is_last_axis:
return
ifcopenshell.api.run("grid.remove_grid_axis", tool.Ifc.get(), axis=element)
return bpy.data.objects.remove(obj)
collection = obj.BIMObjectProperties.collection collection = obj.BIMObjectProperties.collection
if collection: if collection:
+3 -1
View File
@@ -29,7 +29,9 @@ Some of these add-ons are not shipped with Blender:
- `Sverchok <https://github.com/nortikin/sverchok/>`__ - Sverchok is a visual - `Sverchok <https://github.com/nortikin/sverchok/>`__ - Sverchok is a visual
programming add-on for Blender that allows you to generate parametric programming add-on for Blender that allows you to generate parametric
geometry, create scripts for non-programmers, model solids from FreeCAD, and geometry, create scripts for non-programmers, model solids from FreeCAD, and
much more. much more. There is also
`IfcSverchok <https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/src/ifcsverchok/README.md/>`__
that adds IFC features to Sverchok.
- `BlenderGIS <https://github.com/domlysz/BlenderGIS>`__ - BlenderGIS lets you - `BlenderGIS <https://github.com/domlysz/BlenderGIS>`__ - BlenderGIS lets you
import GIS data, grab elevation data from the web, and generate TINs from import GIS data, grab elevation data from the web, and generate TINs from
survey points and contours. survey points and contours.
+4 -4
View File
@@ -124,21 +124,21 @@ class TestAssignBrickReference:
ifc.run("library.add_reference", library="library").should_be_called().will_return("reference") ifc.run("library.add_reference", library="library").should_be_called().will_return("reference")
brick.export_brick_attributes("brick_uri").should_be_called().will_return("attributes") brick.export_brick_attributes("brick_uri").should_be_called().will_return("attributes")
ifc.run("library.edit_reference", reference="reference", attributes="attributes").should_be_called() ifc.run("library.edit_reference", reference="reference", attributes="attributes").should_be_called()
ifc.run("library.assign_reference", product="element", reference="reference").should_be_called() ifc.run("library.assign_reference", products=["element"], reference="reference").should_be_called()
brick.get_brickifc_project().should_be_called().will_return("project") brick.get_brickifc_project().should_be_called().will_return("project")
brick.add_brickifc_reference("brick_uri", "element", "project").should_be_called() brick.add_brickifc_reference("brick_uri", "element", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick_uri") subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick_uri")
def test_assigning_to_an_existing_reference(self, ifc, brick): def test_assigning_to_an_existing_reference(self, ifc, brick):
brick.get_library_brick_reference("library", "brick_uri").should_be_called().will_return("reference") brick.get_library_brick_reference("library", "brick_uri").should_be_called().will_return("reference")
ifc.run("library.assign_reference", product="element", reference="reference").should_be_called() ifc.run("library.assign_reference", products=["element"], reference="reference").should_be_called()
brick.get_brickifc_project().should_be_called().will_return("project") brick.get_brickifc_project().should_be_called().will_return("project")
brick.add_brickifc_reference("brick_uri", "element", "project").should_be_called() brick.add_brickifc_reference("brick_uri", "element", "project").should_be_called()
subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick_uri") subject.assign_brick_reference(ifc, brick, element="element", library="library", brick_uri="brick_uri")
def test_adding_a_brickifc_project_if_it_doesnt_exist(self, ifc, brick): def test_adding_a_brickifc_project_if_it_doesnt_exist(self, ifc, brick):
brick.get_library_brick_reference("library", "brick_uri").should_be_called().will_return("reference") brick.get_library_brick_reference("library", "brick_uri").should_be_called().will_return("reference")
ifc.run("library.assign_reference", product="element", reference="reference").should_be_called() ifc.run("library.assign_reference", products=["element"], reference="reference").should_be_called()
brick.get_brickifc_project().should_be_called().will_return(None) brick.get_brickifc_project().should_be_called().will_return(None)
brick.get_namespace("brick_uri").should_be_called().will_return("namespace") brick.get_namespace("brick_uri").should_be_called().will_return("namespace")
brick.add_brickifc_project("namespace").should_be_called().will_return("project") brick.add_brickifc_project("namespace").should_be_called().will_return("project")
@@ -277,4 +277,4 @@ class TestSetBrickListRoot:
class TestRemoveBrickRelation: class TestRemoveBrickRelation:
def test_run(self, brick): def test_run(self, brick):
brick.remove_relation("brick_uri", "predicate", "object").should_be_called() brick.remove_relation("brick_uri", "predicate", "object").should_be_called()
subject.remove_brick_relation(brick, brick_uri="brick_uri", predicate="predicate", object="object") subject.remove_brick_relation(brick, brick_uri="brick_uri", predicate="predicate", object="object")
+2 -2
View File
@@ -133,11 +133,11 @@ class TestRemoveDocument:
class TestAssignDocument: class TestAssignDocument:
def test_run(self, ifc): def test_run(self, ifc):
ifc.run("document.assign_document", product="product", document="document").should_be_called() ifc.run("document.assign_document", products=["product"], document="document").should_be_called()
subject.assign_document(ifc, product="product", document="document") subject.assign_document(ifc, product="product", document="document")
class TestUnassignDocument: class TestUnassignDocument:
def test_run(self, ifc): def test_run(self, ifc):
ifc.run("document.unassign_document", product="product", document="document").should_be_called() ifc.run("document.unassign_document", products=["product"], document="document").should_be_called()
subject.unassign_document(ifc, product="product", document="document") subject.unassign_document(ifc, product="product", document="document")
+3 -3
View File
@@ -365,7 +365,7 @@ class TestAddDrawing:
attributes={"Identification": "X", "Name": "name", "Scope": "DRAWING"}, attributes={"Identification": "X", "Name": "name", "Scope": "DRAWING"},
).should_be_called() ).should_be_called()
ifc.run("document.edit_reference", reference="reference", attributes={"Location": "uri"}).should_be_called() ifc.run("document.edit_reference", reference="reference", attributes={"Location": "uri"}).should_be_called()
ifc.run("document.assign_document", product="element", document="reference").should_be_called() ifc.run("document.assign_document", products=["element"], document="reference").should_be_called()
drawing.import_drawings().should_be_called() drawing.import_drawings().should_be_called()
subject.add_drawing(ifc, collector, drawing, target_view="target_view", location_hint="location_hint") subject.add_drawing(ifc, collector, drawing, target_view="target_view", location_hint="location_hint")
@@ -391,7 +391,7 @@ class TestDuplicateDrawing:
ifc.run("group.assign_group", group="new_group", products=["new_annotation"]).should_be_called() ifc.run("group.assign_group", group="new_group", products=["new_annotation"]).should_be_called()
drawing.get_drawing_document("new_drawing").should_be_called().will_return("old_reference") drawing.get_drawing_document("new_drawing").should_be_called().will_return("old_reference")
ifc.run("document.unassign_document", product="new_drawing", document="old_reference").should_be_called() ifc.run("document.unassign_document", products=["new_drawing"], document="old_reference").should_be_called()
ifc.run("document.add_information").should_be_called().will_return("information") ifc.run("document.add_information").should_be_called().will_return("information")
ifc.run("document.add_reference", information="information").should_be_called().will_return("reference") ifc.run("document.add_reference", information="information").should_be_called().will_return("reference")
@@ -405,7 +405,7 @@ class TestDuplicateDrawing:
ifc.run( ifc.run(
"document.edit_reference", reference="reference", attributes={"Location": "drawing_path"} "document.edit_reference", reference="reference", attributes={"Location": "drawing_path"}
).should_be_called() ).should_be_called()
ifc.run("document.assign_document", product="new_drawing", document="reference").should_be_called() ifc.run("document.assign_document", products=["new_drawing"], document="reference").should_be_called()
drawing.import_drawings().should_be_called() drawing.import_drawings().should_be_called()
subject.duplicate_drawing(ifc, drawing, drawing="drawing", should_duplicate_annotations=True) subject.duplicate_drawing(ifc, drawing, drawing="drawing", should_duplicate_annotations=True)
+2 -2
View File
@@ -114,12 +114,12 @@ class TestEditLibraryReference:
class TestAssignLibraryReference: class TestAssignLibraryReference:
def test_run(self, ifc): def test_run(self, ifc):
ifc.get_entity("obj").should_be_called().will_return("product") ifc.get_entity("obj").should_be_called().will_return("product")
ifc.run("library.assign_reference", product="product", reference="reference").should_be_called() ifc.run("library.assign_reference", products=["product"], reference="reference").should_be_called()
subject.assign_library_reference(ifc, obj="obj", reference="reference") subject.assign_library_reference(ifc, obj="obj", reference="reference")
class TestUnassignLibraryReference: class TestUnassignLibraryReference:
def test_run(self, ifc): def test_run(self, ifc):
ifc.get_entity("obj").should_be_called().will_return("product") ifc.get_entity("obj").should_be_called().will_return("product")
ifc.run("library.unassign_reference", product="product", reference="reference").should_be_called() ifc.run("library.unassign_reference", products=["product"], reference="reference").should_be_called()
subject.unassign_library_reference(ifc, obj="obj", reference="reference") subject.unassign_library_reference(ifc, obj="obj", reference="reference")
+2 -2
View File
@@ -23,14 +23,14 @@ from test.core.bootstrap import ifc, collector, spatial
class TestReferenceStructure: class TestReferenceStructure:
def test_run(self, ifc, spatial): def test_run(self, ifc, spatial):
spatial.can_reference("structure", "element").should_be_called().will_return(True) spatial.can_reference("structure", "element").should_be_called().will_return(True)
ifc.run("spatial.reference_structure", product="element", relating_structure="structure").should_be_called() ifc.run("spatial.reference_structure", products=["element"], relating_structure="structure").should_be_called()
subject.reference_structure(ifc, spatial, structure="structure", element="element") subject.reference_structure(ifc, spatial, structure="structure", element="element")
class TestDereferenceStructure: class TestDereferenceStructure:
def test_run(self, ifc, spatial): def test_run(self, ifc, spatial):
spatial.can_reference("structure", "element").should_be_called().will_return(True) spatial.can_reference("structure", "element").should_be_called().will_return(True)
ifc.run("spatial.dereference_structure", product="element", relating_structure="structure").should_be_called() ifc.run("spatial.dereference_structure", products=["element"], relating_structure="structure").should_be_called()
subject.dereference_structure(ifc, spatial, structure="structure", element="element") subject.dereference_structure(ifc, spatial, structure="structure", element="element")
+2 -1
View File
@@ -18,6 +18,7 @@
import bpy import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.api
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.tool as tool import blenderbim.tool as tool
from test.bim.bootstrap import NewFile from test.bim.bootstrap import NewFile
@@ -188,7 +189,7 @@ class TestImportReferences(NewFile):
assert len(props.documents) == 1 assert len(props.documents) == 1
assert props.documents[0].ifc_definition_id == reference.id() assert props.documents[0].ifc_definition_id == reference.id()
assert props.documents[0].name == "Unnamed" assert props.documents[0].name == "Unnamed"
assert props.documents[0].identification == "*" assert props.documents[0].identification == "X"
assert props.documents[0].is_information is False assert props.documents[0].is_information is False
+1 -1
View File
@@ -11,7 +11,7 @@ authors:
- name: "IfcOpenShell contributors"authors: - name: "IfcOpenShell contributors"authors:
repository-code: >- repository-code: >-
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/ifcbimtester https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/ifcbimtester
url: 'https://blenderbim.org/docs-python/bimtester.html' url: 'https://docs.ifcopenshell.org/bimtester.html'
abstract: Wrapper for Gherkin based unit testing for IFC models abstract: Wrapper for Gherkin based unit testing for IFC models
keywords: keywords:
- Gherkin - Gherkin
+1 -1
View File
@@ -153,7 +153,7 @@ interface to access the IfcOpenShell utilities.
1. Install the BlenderBIM Add-on by following the `BlenderBIM Add-on 1. Install the BlenderBIM Add-on by following the `BlenderBIM Add-on
installation documentation installation documentation
<https://blenderbim.org/docs/users/installation.html>`_. <https://docs.blenderbim.org/users/installation.html>`_.
2. Launch Blender. Change to the **Scene Properties** tab in the **Properties 2. Launch Blender. Change to the **Scene Properties** tab in the **Properties
Panel**. Scroll down to the **IFC Collaboration > IFC CSV Import / Export** Panel**. Scroll down to the **IFC Collaboration > IFC CSV Import / Export**
+1 -1
View File
@@ -88,7 +88,7 @@ interface to access the IfcOpenShell utilities.
1. Install the BlenderBIM Add-on by following the `BlenderBIM Add-on 1. Install the BlenderBIM Add-on by following the `BlenderBIM Add-on
installation documentation installation documentation
<https://blenderbim.org/docs/users/installation.html>`_. <https://docs.blenderbim.org/users/installation.html>`_.
2. Launch Blender. Change to the **Scene Properties** tab in the **Properties 2. Launch Blender. Change to the **Scene Properties** tab in the **Properties
Panel**. Scroll down to the **IFC Quality Control > IFC Diff** panel. Panel**. Scroll down to the **IFC Quality Control > IFC Diff** panel.
@@ -227,7 +227,7 @@ The BlenderBIM Add-on is available either as a stable build or a daily build.
1. Install the BlenderBIM Add-on by following the `BlenderBIM Add-on 1. Install the BlenderBIM Add-on by following the `BlenderBIM Add-on
installation documentation installation documentation
<https://blenderbim.org/docs/users/installation.html>`_. <https://docs.blenderbim.org/users/installation.html>`_.
2. Launch Blender. On the top left of the Viewport panel, click the **Editor 2. Launch Blender. On the top left of the Viewport panel, click the **Editor
Type** icon to change the viewport into a **Python Console**. Type** icon to change the viewport into a **Python Console**.
@@ -261,7 +261,7 @@ and run your script using the **Text > Run Script** menu or by clicking on the
Blender. This can help when learning how to write scripts as you can double Blender. This can help when learning how to write scripts as you can double
check the results of your scripts with what you see in the graphical check the results of your scripts with what you see in the graphical
interface. `Read more interface. `Read more
<https://blenderbim.org/docs/users/exploring_an_ifc_model.html>`_. <https://docs.blenderbim.org/users/exploring_an_ifc_model.html>`_.
From source with precompiled binaries From source with precompiled binaries
------------------------------------- -------------------------------------
@@ -12,7 +12,7 @@ Packaged installation
IfcSverchok is packaged like a regular Blender add-on, so installation is the IfcSverchok is packaged like a regular Blender add-on, so installation is the
same as any other Blender add-on. `Download IfcSverchok here same as any other Blender add-on. `Download IfcSverchok here
<https://blenderbim.org/builds/ifcsverchok-230823.zip>`__. <https://github.com/IfcOpenShell/IfcOpenShell/releases/download/ifcsverchok-240417/ifcsverchok-240417.zip>`__.
Like all Blender add-ons, they can be installed using ``Edit > Preferences > Like all Blender add-ons, they can be installed using ``Edit > Preferences >
Addons > Install > Choose Downloaded ZIP > Enable Add-on Checkbox``. You can Addons > Install > Choose Downloaded ZIP > Enable Add-on Checkbox``. You can
@@ -87,6 +87,36 @@ ARGUMENTS_DEPRECATION = {
"material.unassign_material": partial( "material.unassign_material": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products" batching_argument_deprecation, prev_argument="product", new_argument="products"
), ),
"classification.add_reference": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"classification.remove_reference": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"library.assign_reference": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"library.unassign_reference": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"document.assign_document": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"document.unassign_document": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"spatial.reference_structure": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"spatial.dereference_structure": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"constraint.assign_constraint": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"constraint.unassign_constraint": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
} }
@@ -64,7 +64,8 @@ class Usecase:
self.settings["product"].PredefinedType = "USERDEFINED" self.settings["product"].PredefinedType = "USERDEFINED"
elif hasattr(self.settings["product"], "ObjectType"): elif hasattr(self.settings["product"], "ObjectType"):
relating_type = ifcopenshell.util.element.get_type(self.settings["product"]) relating_type = ifcopenshell.util.element.get_type(self.settings["product"])
if relating_type and relating_type.PredefinedType != "NOTDEFINED": # Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
if relating_type and relating_type.PredefinedType not in ("NOTDEFINED", None):
self.settings["product"].ObjectType = None self.settings["product"].ObjectType = None
self.settings["product"].PredefinedType = None self.settings["product"].PredefinedType = None
elif ( elif (
@@ -19,10 +19,11 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.util.schema import ifcopenshell.util.schema
import ifcopenshell.util.date import ifcopenshell.util.date
from typing import Union
class Usecase: class Usecase:
def __init__(self, file, classification=None): def __init__(self, file: ifcopenshell.file, classification: Union[str, ifcopenshell.entity_instance]):
"""Adds a new classification system to the project """Adds a new classification system to the project
External classification systems such as Uniclass or Omniclass are External classification systems such as Uniclass or Omniclass are
@@ -81,7 +82,7 @@ class Usecase:
"classification": classification, "classification": classification,
} }
def execute(self): def execute(self) -> ifcopenshell.entity_instance:
if isinstance(self.settings["classification"], str): if isinstance(self.settings["classification"], str):
classification = self.file.createIfcClassification(Name=self.settings["classification"]) classification = self.file.createIfcClassification(Name=self.settings["classification"])
self.relate_to_project(classification) self.relate_to_project(classification)
@@ -17,12 +17,24 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.schema import ifcopenshell.util.schema
from typing import Optional, Union
class Usecase: class Usecase:
def __init__(self, file, product=None, reference=None, identification=None, name=None, classification=None, is_lightweight=True): def __init__(
"""Adds a new classification reference and assigns it to a product self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
reference: Optional[ifcopenshell.entity_instance] = None,
identification: Optional[str] = None,
name: Optional[str] = None,
classification: Optional[ifcopenshell.entity_instance] = None,
is_lightweight=True,
):
"""Adds a new classification reference and assigns it to the list of products
A classification reference is a single entry such as "Pr_12_23_34" that A classification reference is a single entry such as "Pr_12_23_34" that
is part of an external classification system (such as Uniclass or is part of an external classification system (such as Uniclass or
@@ -33,7 +45,7 @@ class Usecase:
resources such as profiles, documents, libraries, and so on. resources such as profiles, documents, libraries, and so on.
Classification references can be added in two ways. Option 1) specify a Classification references can be added in two ways. Option 1) specify a
custom arbitrary reference, where you have the manually specify the custom arbitrary reference, where you have to manually specify the
identification (e.g. "Pr_12_23_45") and name (e.g. "Door Products"). identification (e.g. "Pr_12_23_45") and name (e.g. "Door Products").
Option 2) add a reference from an IFC classification library. The latter Option 2) add a reference from an IFC classification library. The latter
is preferred if you are using a common classification system such as is preferred if you are using a common classification system such as
@@ -52,13 +64,13 @@ class Usecase:
assigned to both the type and an occurrence, then the assignment at the assigned to both the type and an occurrence, then the assignment at the
occurrence will override the type classification. occurrence will override the type classification.
:param product: The IFC object, property, or resource you want to :param product: The list of IFC objects, properties, or resources you want to
associate the classification reference to. associate the classification reference to.
:type product: ifcopenshell.entity_instance.entity_instance :type product: list[ifcopenshell.entity_instance.entity_instance]
:param reference: The classification reference entity taken from an :param reference: The classification reference entity taken from an
IFC classification library. If you supply this parameter, you will IFC classification library. If you supply this parameter, you will
use option 2. use option 2.
:type product: ifcopenshell.entity_instance.entity_instance, optional :type reference: ifcopenshell.entity_instance.entity_instance, optional
:param identification: If you choose option 1 and do not specify a :param identification: If you choose option 1 and do not specify a
reference, you may manually specify an identification code. The code reference, you may manually specify an identification code. The code
is typically a short identifier and may have punctuation to separate is typically a short identifier and may have punctuation to separate
@@ -70,7 +82,7 @@ class Usecase:
:param classification: The IfcClassification entity in your IFC model :param classification: The IfcClassification entity in your IFC model
(not the library, if you are doing option 2) that the reference is (not the library, if you are doing option 2) that the reference is
part of. part of.
:type product: ifcopenshell.entity_instance.entity_instance :type classification: ifcopenshell.entity_instance.entity_instance
:param is_lightweight: If you are doing option 2, choose whether or not :param is_lightweight: If you are doing option 2, choose whether or not
to only add that particular reference (lighweight) or also add all to only add that particular reference (lighweight) or also add all
of its parent references in the classification hierarchy (not of its parent references in the classification hierarchy (not
@@ -81,8 +93,12 @@ class Usecase:
is generally unnecessary. Using lightweight classifications are is generally unnecessary. Using lightweight classifications are
recommended and is the default. recommended and is the default.
:type is_lightweight: bool, optional :type is_lightweight: bool, optional
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
:return: The newly added IfcClassificationReference :return: The newly added IfcClassificationReference
:rtype: ifcopenshell.entity_instance.entity_instance or `None` if `products` was empty list.
:rtype: Union[ifcopenshell.entity_instance.entity_instance, None]
Example: Example:
@@ -93,7 +109,7 @@ class Usecase:
classification = ifcopenshell.api.run("classification.add_classification", classification = ifcopenshell.api.run("classification.add_classification",
model, classification="MyCustomClassification") model, classification="MyCustomClassification")
ifcopenshell.api.run("classification.add_reference", model, ifcopenshell.api.run("classification.add_reference", model,
product=wall_type, classification=classification, products=[wall_type], classification=classification,
identification="W_01", name="Interior Walls") identification="W_01", name="Interior Walls")
# Option 2: adding a popular classification from a library # Option 2: adding a popular classification from a library
@@ -104,12 +120,12 @@ class Usecase:
reference = [r for r in library.by_type("IfcClassificationReference") reference = [r for r in library.by_type("IfcClassificationReference")
if r.Identification == "XYZ"][0] if r.Identification == "XYZ"][0]
ifcopenshell.api.run("classification.add_reference", model, ifcopenshell.api.run("classification.add_reference", model,
product=wall_type, classification=classification, products=[wall_type], classification=classification,
reference=reference) reference=reference)
""" """
self.file = file self.file = file
self.settings = { self.settings = {
"product": product, "products": products,
"reference": reference, "reference": reference,
"identification": identification, "identification": identification,
"name": name, "name": name,
@@ -117,8 +133,27 @@ class Usecase:
"is_lightweight": is_lightweight, "is_lightweight": is_lightweight,
} }
def execute(self): def execute(self) -> Union[ifcopenshell.entity_instance, None]:
self.is_rooted = self.settings["product"].is_a("IfcRoot") if not self.settings["products"]:
return
if self.settings["reference"]:
referenced = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
if set(self.settings["products"]).issubset(referenced):
# nothing to do, all elements already have this reference assigned
return self.settings["reference"]
self.rooted_products: set[ifcopenshell.entity_instance] = set()
self.non_rooted_products: set[ifcopenshell.entity_instance] = set()
for product in self.settings["products"]:
if product.is_a("IfcRoot"):
self.rooted_products.add(product)
else:
self.non_rooted_products.add(product)
if self.non_rooted_products and self.file.schema == "IFC2X3":
raise TypeError(f"Cannot add reference to non-IfcRoot element in IFC2X3: {self.non_rooted_products}.")
if self.settings["reference"]: if self.settings["reference"]:
return self.add_from_library() return self.add_from_library()
return self.add_from_identification() return self.add_from_identification()
@@ -134,14 +169,10 @@ class Usecase:
else: else:
reference.Identification = self.settings["identification"] reference.Identification = self.settings["identification"]
relationship = self.get_existing_relationship(reference) self.update_relationships(reference)
if relationship:
self.add_to_existing_relationship(relationship)
else:
self.add_new_relationship(reference)
return reference return reference
def add_from_library(self): def add_from_library(self) -> ifcopenshell.entity_instance:
if hasattr(self.settings["reference"], "ItemReference"): if hasattr(self.settings["reference"], "ItemReference"):
identification = self.settings["reference"].ItemReference # IFC2X3 identification = self.settings["reference"].ItemReference # IFC2X3
else: else:
@@ -155,8 +186,9 @@ class Usecase:
old_referenced_source = self.settings["reference"].ReferencedSource old_referenced_source = self.settings["reference"].ReferencedSource
self.settings["reference"].ReferencedSource = None self.settings["reference"].ReferencedSource = None
else: else:
classification_name = self.settings["classification"].Name
existing_classification = [ existing_classification = [
c for c in self.file.by_type("IfcClassification") if c.Name == self.settings["classification"].Name c for c in self.file.by_type("IfcClassification") if c.Name == classification_name
] ]
reference = migrator.migrate(self.settings["reference"], self.file) reference = migrator.migrate(self.settings["reference"], self.file)
@@ -174,15 +206,10 @@ class Usecase:
for element in to_delete: for element in to_delete:
self.file.remove(element) self.file.remove(element)
relationship = self.get_existing_relationship(reference) self.update_relationships(reference)
if relationship:
self.add_to_existing_relationship(relationship)
else:
self.add_new_relationship(reference)
return reference return reference
def get_existing_reference(self, identification): def get_existing_reference(self, identification: Optional[str] = None) -> Union[ifcopenshell.entity_instance, None]:
for reference in self.file.by_type("IfcClassificationReference"): for reference in self.file.by_type("IfcClassificationReference"):
if self.file.schema == "IFC2X3": if self.file.schema == "IFC2X3":
if reference.ItemReference == identification: if reference.ItemReference == identification:
@@ -191,39 +218,39 @@ class Usecase:
if reference.Identification == identification: if reference.Identification == identification:
return reference return reference
def add_new_relationship(self, reference): def update_relationships(self, reference: ifcopenshell.entity_instance) -> None:
if self.is_rooted: root_rel, non_root_rel = None, None
self.file.create_entity( if self.rooted_products:
"IfcRelAssociatesClassification",
GlobalId=ifcopenshell.guid.new(),
RelatedObjects=[self.settings["product"]],
RelatingClassification=reference,
)
else:
self.file.create_entity(
"IfcExternalReferenceRelationship",
RelatingReference=reference,
RelatedResourceObjects=[self.settings["product"]],
)
def add_to_existing_relationship(self, rel):
if self.is_rooted:
related_objects = set(rel.RelatedObjects)
related_objects.add(self.settings["product"])
rel.RelatedObjects = list(related_objects)
else:
related_objects = set(rel.RelatedResourceObjects)
related_objects.add(self.settings["product"])
rel.RelatedResourceObjects = list(related_objects)
def get_existing_relationship(self, reference):
if self.is_rooted:
if self.file.schema == "IFC2X3": if self.file.schema == "IFC2X3":
for rel in self.file.by_type("IfcRelAssociatesClassification"): for rel in self.file.by_type("IfcRelAssociatesClassification"):
if rel.RelatingClassification == reference: if rel.RelatingClassification == reference:
return rel root_rel = rel
elif reference.ClassificationRefForObjects: break
return reference.ClassificationRefForObjects[0] else:
elif self.file.schema != "IFC2X3": root_rel = next(iter(reference.ClassificationRefForObjects), None)
if reference.ExternalReferenceForResources:
return reference.ExternalReferenceForResources[0] if root_rel:
related_objects = set(root_rel.RelatedObjects) | self.rooted_products
root_rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": root_rel})
else:
self.file.create_entity(
"IfcRelAssociatesClassification",
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
GlobalId=ifcopenshell.guid.new(),
RelatedObjects=list(self.rooted_products),
RelatingClassification=reference,
)
if self.non_rooted_products:
# NOTE: Only Ifc4+. Ifc2x3 is already handled by raising TypeError
non_root_rel = next(iter(reference.ExternalReferenceForResources), None)
if non_root_rel:
related_objects = set(non_root_rel.RelatedResourceObjects) | self.non_rooted_products
non_root_rel.RelatedResourceObjects = list(related_objects)
else:
self.file.create_entity(
"IfcExternalReferenceRelationship",
RelatingReference=reference,
RelatedResourceObjects=list(self.non_rooted_products),
)
@@ -17,12 +17,18 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element import ifcopenshell.util.element
class Usecase: class Usecase:
def __init__(self, file, reference=None, product=None): def __init__(
"""Removes a classification reference from a product self,
file: ifcopenshell.file,
reference: ifcopenshell.entity_instance,
products: list[ifcopenshell.entity_instance],
):
"""Removes a classification reference from the list of products
If the classification reference is no longer associated to any products, If the classification reference is no longer associated to any products,
the classification reference itself is also removed. the classification reference itself is also removed.
@@ -30,9 +36,12 @@ class Usecase:
:param reference: The IfcClassificationReference entity of the :param reference: The IfcClassificationReference entity of the
relationship you want to remove. relationship you want to remove.
:type reference: ifcopenshell.entity_instance.entity_instance :type reference: ifcopenshell.entity_instance.entity_instance
:param product: The object entity of the relationship you want to :param product: The list fo object entities of the relationship you want to
remove. remove.
:type reference: ifcopenshell.entity_instance.entity_instance :type product: list[ifcopenshell.entity_instance.entity_instance]
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
:return: None :return: None
:rtype: None :rtype: None
@@ -44,42 +53,75 @@ class Usecase:
classification = ifcopenshell.api.run("classification.add_classification", classification = ifcopenshell.api.run("classification.add_classification",
model, classification="MyCustomClassification") model, classification="MyCustomClassification")
reference = ifcopenshell.api.run("classification.add_reference", model, reference = ifcopenshell.api.run("classification.add_reference", model,
product=wall_type, classification=classification, products=[wall_type], classification=classification,
identification="W_01", name="Interior Walls") identification="W_01", name="Interior Walls")
ifcopenshell.api.run("classification.remove_reference", model, ifcopenshell.api.run("classification.remove_reference", model,
reference=reference, product=wall_type) reference=reference, products=[wall_type])
""" """
self.file = file self.file = file
self.settings = {"reference": reference, "product": product} self.settings = {"reference": reference, "products": products}
def execute(self): def execute(self) -> None:
if self.settings["product"].is_a("IfcRoot"): is_ifc2x3 = self.file.schema == "IFC2X3"
for rel in self.file.by_type("IfcRelAssociatesClassification"): products = set(self.settings["products"])
if rel.RelatingClassification == self.settings["reference"] and rel.RelatedObjects: referenced = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
if self.settings["product"] in rel.RelatedObjects: products -= products.difference(referenced)
related_objects = list(rel.RelatedObjects)
related_objects.remove(self.settings["product"]) # all products are already unassigned from a reference
if len(related_objects): if not products:
rel.RelatedObjects = related_objects return
else:
history = rel.OwnerHistory rooted_products: set[ifcopenshell.entity_instance] = set()
self.file.remove(rel) non_rooted_products: set[ifcopenshell.entity_instance] = set()
if history: for product in self.settings["products"]:
ifcopenshell.util.element.remove_deep2(self.file, history) if product.is_a("IfcRoot"):
else: rooted_products.add(product)
for rel in self.file.by_type("IfcExternalReferenceRelationship"): else:
if rel.RelatingReference == self.settings["reference"] and rel.RelatedResourceObjects: non_rooted_products.add(product)
if self.settings["product"] in rel.RelatedResourceObjects:
related_objects = list(rel.RelatedResourceObjects) if non_rooted_products and is_ifc2x3:
related_objects.remove(self.settings["product"]) raise TypeError(f"Cannot add reference to non-IfcRoot element in IFC2X3: {non_rooted_products}.")
if len(related_objects):
rel.RelatedResourceObjects = related_objects if rooted_products:
else: reference_rels: set[ifcopenshell.entity_instance] = set()
self.file.remove(rel) for product in rooted_products:
reference_rels.update(product.HasAssociations)
reference_rels = {
rel
for rel in reference_rels
if rel.is_a("IfcRelAssociatesClassification")
and rel.RelatingClassification == self.settings["reference"]
}
for rel in reference_rels:
related_objects = set(rel.RelatedObjects) - rooted_products
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
if non_rooted_products:
reference_rels: set[ifcopenshell.entity_instance] = set()
for product in non_rooted_products:
rels = getattr(product, "HasExternalReferences", None)
if rels is None:
rels = getattr(product, "HasExternalReference", [])
reference_rels.update(rels)
reference_rels = {rel for rel in reference_rels if rel.RelatingReference == self.settings["reference"]}
for rel in reference_rels:
related_objects = set(rel.RelatedResourceObjects) - non_rooted_products
if related_objects:
rel.RelatedResourceObjects = list(related_objects)
else:
self.file.remove(rel)
# TODO: we only handle lightweight classifications here # TODO: we only handle lightweight classifications here
if ( referenced_elements = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
not self.settings["reference"].ClassificationRefForObjects if not referenced_elements:
and not self.settings["reference"].ExternalReferenceForResources
):
self.file.remove(self.settings["reference"]) self.file.remove(self.settings["reference"])
@@ -17,11 +17,18 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
import ifcopenshell.api
from typing import Union
class Usecase: class Usecase:
def __init__(self, file, product=None, constraint=None): def __init__(
"""Assigns a constraint to a product self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
constraint: ifcopenshell.entity_instance,
):
"""Assigns a constraint to a list of products
This assigns a relationship between a product and a constraint, so that This assigns a relationship between a product and a constraint, so that
when a product's properties and quantities do not match the requirements when a product's properties and quantities do not match the requirements
@@ -31,36 +38,58 @@ class Usecase:
constraints are inherited from the type. This way, it is not necessary constraints are inherited from the type. This way, it is not necessary
to create lots of constraint assignments. to create lots of constraint assignments.
:param product: The product the constraint applies to. This is anything :param products: The list of products the constraint applies to. This is anything
which can have properties or quantities. which can have properties or quantities.
:type product: ifcopenshell.entity_instance.entity_instance :type products: list[ifcopenshell.entity_instance.entity_instance]
:param constraint: The IfcObjective constraint :param constraint: The IfcObjective constraint
:type constraint: ifcopenshell.entity_instance.entity_instance :type constraint: ifcopenshell.entity_instance.entity_instance
:return: The new or updated IfcRelAssociatesConstraint relationship :return: The new or updated IfcRelAssociatesConstraint relationship
or `None` if `products` was an empty list.
:rtype: ifcopenshell.entity_instance.entity_instance :rtype: ifcopenshell.entity_instance.entity_instance
""" """
self.file = file self.file = file
self.settings = { self.settings = {
"product": product, "products": products,
"constraint": constraint, "constraint": constraint,
} }
def execute(self): def execute(self) -> Union[ifcopenshell.entity_instance, None]:
rel = self.get_constraint_rel() products = set(self.settings["products"])
related_objects = set(rel.RelatedObjects) if rel.RelatedObjects else set() if not products:
related_objects.add(self.settings["product"]) return
rel.RelatedObjects = list(related_objects)
return rel self.constraint = self.settings["constraint"]
rels = self.get_constraint_rels()
related_objects = set()
for rel in rels:
related_objects.update(rel.RelatedObjects)
products_to_assign = products - related_objects
if not products_to_assign:
return rels[0]
rel = next(iter(rels), None)
if rel:
related_objects = set(rel.RelatedObjects) | products_to_assign
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
return rel
def get_constraint_rel(self):
for rel in self.file.by_type("IfcRelAssociatesConstraint"):
if rel.RelatingConstraint == self.settings["constraint"]:
return rel
return self.file.create_entity( return self.file.create_entity(
"IfcRelAssociatesConstraint", "IfcRelAssociatesConstraint",
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
# TODO: owner history "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatingConstraint": self.settings["constraint"], "RelatingConstraint": self.constraint,
"RelatedObjects": list(products_to_assign),
} }
) )
def get_constraint_rels(self) -> list[ifcopenshell.entity_instance]:
rels = []
for rel in self.file.get_inverse(self.constraint):
if rel.is_a("IfcRelAssociatesConstraint"):
rels.append(rel)
return rels
@@ -17,18 +17,24 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element import ifcopenshell.util.element
class Usecase: class Usecase:
def __init__(self, file, product=None, constraint=None): def __init__(
"""Unassigns a constraint to a product self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
constraint: ifcopenshell.entity_instance,
):
"""Unassigns a constraint from a list of products
The constraint will not be deleted and is available to be assigned to The constraint will not be deleted and is available to be assigned to
other products. other products.
:param product: The product the constraint applies to. :param products: The list of products the constraint applies to.
:type product: ifcopenshell.entity_instance.entity_instance :type products: list[ifcopenshell.entity_instance.entity_instance]
:param constraint: The IfcObjective constraint :param constraint: The IfcObjective constraint
:type constraint: ifcopenshell.entity_instance.entity_instance :type constraint: ifcopenshell.entity_instance.entity_instance
:return: None :return: None
@@ -36,14 +42,42 @@ class Usecase:
""" """
self.file = file self.file = file
self.settings = { self.settings = {
"product": product, "products": products,
"constraint": constraint, "constraint": constraint,
} }
def execute(self): def execute(self):
for rel in self.settings["product"].HasAssociations: products = set(self.settings["products"])
if rel.is_a("IfcRelAssociatesConstraint") and rel.RelatingConstraint == self.settings["constraint"]: if not products:
history = rel.OwnerHistory return
self.file.remove(rel)
if history: self.constraint = self.settings["constraint"]
ifcopenshell.util.element.remove_deep2(self.file, history) rels = self.get_constraint_rels()
related_objects = set()
for rel in rels:
related_objects.update(rel.RelatedObjects)
if not related_objects.intersection(products):
return
for rel in rels:
related_objects = set(rel.RelatedObjects)
if not related_objects.intersection(products):
continue
related_objects -= products
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
continue
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
def get_constraint_rels(self) -> list[ifcopenshell.entity_instance]:
rels = []
for rel in self.file.get_inverse(self.constraint):
if rel.is_a("IfcRelAssociatesConstraint"):
rels.append(rel)
return rels
@@ -69,10 +69,12 @@ class Usecase:
def execute(self) -> ifcopenshell.entity_instance: def execute(self) -> ifcopenshell.entity_instance:
if self.file.schema == "IFC2X3": if self.file.schema == "IFC2X3":
reference = self.file.create_entity("IfcDocumentReference") reference = self.file.create_entity("IfcDocumentReference", ItemReference="X")
if self.settings["information"]: if self.settings["information"]:
references = list(self.settings["information"].DocumentReferences or []) references = list(self.settings["information"].DocumentReferences or [])
references.append(reference) references.append(reference)
self.settings["information"].DocumentReferences = references self.settings["information"].DocumentReferences = references
return reference return reference
return self.file.create_entity("IfcDocumentReference", ReferencedDocument=self.settings["information"]) return self.file.create_entity(
"IfcDocumentReference", ReferencedDocument=self.settings["information"], Identification="X"
)
@@ -17,11 +17,19 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
from typing import Union
class Usecase: class Usecase:
def __init__(self, file, product=None, document=None): def __init__(
"""Assigns a document to a product self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
document: ifcopenshell.entity_instance,
):
"""Assigns a document to a list of products
An object may be assigned to zero, one, or multiple documents. Almost An object may be assigned to zero, one, or multiple documents. Almost
any object or property may be assigned to a document, though typically any object or property may be assigned to a document, though typically
@@ -32,14 +40,16 @@ class Usecase:
consistent with other external relationships (such as classification consistent with other external relationships (such as classification
systems or libraries). systems or libraries).
:param product: The object to associate the document to. This could be :param product: The list of objects to associate the document to. This could be
almost any sensible object in IFC. almost any sensible object in IFC.
:type product: ifcopenshell.entity_instance.entity_instance :type product: list[ifcopenshell.entity_instance.entity_instance]
:param document: The IfcDocumentReference to associate to, or :param document: The IfcDocumentReference to associate to, or
alternatively an IfcDocumentInformation, though this is not alternatively an IfcDocumentInformation, though this is not
recommended. recommended.
:type document: ifcopenshell.entity_instance.entity_instance :type document: ifcopenshell.entity_instance.entity_instance
:return: The IfcRelAssociatesDocument relationship :return: The IfcRelAssociatesDocument relationship
or `None` if `products` was an empty list or all products were
already assigned to the `document`.
:rtype: ifcopenshell.entity_instance.entity_instance :rtype: ifcopenshell.entity_instance.entity_instance
Example: Example:
@@ -54,42 +64,51 @@ class Usecase:
reference = ifcopenshell.api.run("document.add_reference", model, information=document) reference = ifcopenshell.api.run("document.add_reference", model, information=document)
# Let's imagine storey represents an IfcBuildingStorey for the ground floor # Let's imagine storey represents an IfcBuildingStorey for the ground floor
ifcopenshell.api.run("document.assign_document", model, product=storey, document=reference) ifcopenshell.api.run("document.assign_document", model, products=[storey], document=reference)
""" """
self.file = file self.file = file
self.settings = { self.settings = {
"product": product, "products": products,
"document": document, "document": document,
} }
def execute(self): def execute(self) -> Union[ifcopenshell.entity_instance, None]:
rel = self.get_document_rel() # TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
related_objects = set(rel.RelatedObjects) if rel.RelatedObjects else set() # NOTE: reuses code from `library.assign_reference`
related_objects.add(self.settings["product"])
rel.RelatedObjects = list(related_objects) referenced_elements = ifcopenshell.util.element.get_referenced_elements(self.settings["document"])
products: set[ifcopenshell.entity_instance] = set(self.settings["products"])
products = products - referenced_elements
if not products:
return
def get_document_rel(self):
if self.file.schema == "IFC2X3": if self.file.schema == "IFC2X3":
for rel in self.file.by_type("IfcRelAssociatesDocument"): rel = next(
if rel.RelatingDocument == self.settings["document"]: (
return rel r
for r in self.file.by_type("IfcRelAssociatesDocument")
if r.RelatingDocument == self.settings["document"]
),
None,
)
else: else:
if ( ifc_class = self.settings["document"].is_a()
hasattr(self.settings["document"], "DocumentRefForObjects") if ifc_class == "IfcDocumentReference":
and self.settings["document"].DocumentRefForObjects rel = next(iter(self.settings["document"].DocumentRefForObjects), None)
): elif ifc_class == "IfcDocumentInformation":
return self.settings["document"].DocumentRefForObjects[0] rel = next(iter(self.settings["document"].DocumentInfoForObjects), None)
elif (
hasattr(self.settings["document"], "DocumentInfoForObjects")
and self.settings["document"].DocumentInfoForObjects
):
return self.settings["document"].DocumentInfoForObjects[0]
return self.file.create_entity( if not rel:
"IfcRelAssociatesDocument", return self.file.create_entity(
**{ "IfcRelAssociatesDocument",
"GlobalId": ifcopenshell.guid.new(), GlobalId=ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatingDocument": self.settings["document"], RelatedObjects=list(products),
} RelatingDocument=self.settings["document"],
) )
related_objects = set(rel.RelatedObjects) | products
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, element=rel)
return rel
@@ -17,16 +17,22 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element import ifcopenshell.util.element
class Usecase: class Usecase:
def __init__(self, file, product=None, document=None): def __init__(
"""Unassigns a document and a product association self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
document: ifcopenshell.entity_instance,
):
"""Unassigns a document and an association to the list of products
:param product: The object that the document reference or information is :param product: The list of objects that the document reference or information is
related to. related to.
:type product: ifcopenshell.entity_instance.entity_instance :type product: list[ifcopenshell.entity_instance.entity_instance]
:param document: The IfcDocumentReference (typically) or in rare cases :param document: The IfcDocumentReference (typically) or in rare cases
the IfcDocumentInformation that is associated with the product the IfcDocumentInformation that is associated with the product
:type document: ifcopenshell.entity_instance.entity_instance :type document: ifcopenshell.entity_instance.entity_instance
@@ -45,24 +51,39 @@ class Usecase:
reference = ifcopenshell.api.run("document.add_reference", model, information=document) reference = ifcopenshell.api.run("document.add_reference", model, information=document)
# Let's imagine storey represents an IfcBuildingStorey for the ground floor # Let's imagine storey represents an IfcBuildingStorey for the ground floor
ifcopenshell.api.run("document.assign_document", model, product=storey, document=reference) ifcopenshell.api.run("document.assign_document", model, products=[storey], document=reference)
# Now let's change our mind and remove the association # Now let's change our mind and remove the association
ifcopenshell.api.run("document.unassign_document", model, product=storey, document=reference) ifcopenshell.api.run("document.unassign_document", model, products=[storey], document=reference)
""" """
self.file = file self.file = file
self.settings = { self.settings = {
"product": product, "products": products,
"document": document, "document": document,
} }
def execute(self): def execute(self):
for rel in self.settings["product"].HasAssociations: # TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument == self.settings["document"]: # NOTE: reuses code from `library.un assign_reference`
if len(rel.RelatedObjects) == 1:
history = rel.OwnerHistory reference_rels: set[ifcopenshell.entity_instance] = set()
self.file.remove(rel) products = set(self.settings["products"])
if history: for product in products:
ifcopenshell.util.element.remove_deep2(self.file, history) reference_rels.update(product.HasAssociations)
else:
rel.RelatedObjects = [o for o in rel.RelatedObjects if o != self.settings["product"]] reference_rels = {
rel
for rel in reference_rels
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument == self.settings["document"]
}
for rel in reference_rels:
related_objects = set(rel.RelatedObjects) - products
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
@@ -26,7 +26,7 @@ class Usecase:
surveyor, and a third-party digital engineer with expertise in IFC to surveyor, and a third-party digital engineer with expertise in IFC to
moderate. For more information, read the BlenderBIM Add-on documentation moderate. For more information, read the BlenderBIM Add-on documentation
for Georeferencing: for Georeferencing:
https://blenderbim.org/docs/users/georeferencing.html https://docs.blenderbim.org/users/georeferencing.html
For more information about the attributes and data types of an For more information about the attributes and data types of an
IfcMapConversion, consult the IFC documentation. IfcMapConversion, consult the IFC documentation.
@@ -17,23 +17,30 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
from typing import Union
class Usecase: class Usecase:
def __init__(self, file, product=None, reference=None): def __init__(
"""Associates a product with a library reference self, file: ifcopenshell.file, products: ifcopenshell.entity_instance, reference: ifcopenshell.entity_instance
):
"""Associates a list products with a library reference
A product may be associated with zero, one, or many references across A product may be associated with zero, one, or many references across
multiple libraries. See ifcopenshell.api.library.add_reference for more multiple libraries. See ifcopenshell.api.library.add_reference for more
detail about how references work. detail about how references work.
:param product: The IfcProduct you want to associate with the reference :param products: The list of IfcProducts you want to associate with the reference
:type product: ifcopenshell.entity_instance.entity_instance :type products: list[ifcopenshell.entity_instance.entity_instance]
:param reference: The IfcLibraryReference you want the product to be :param reference: The IfcLibraryReference you want the product to be
associated with. associated with.
:type reference: ifcopenshell.entity_instance.entity_instance :type reference: ifcopenshell.entity_instance.entity_instance
:return: The IfcRelAssociatesLibrary relationship entity :return: The IfcRelAssociatesLibrary relationship entity
:rtype: ifcopenshell.entity_instance.entity_instance or `None` if `products` was an empty list or all products were
already assigned to the `reference`.
:rtype: Union[ifcopenshell.entity_instance.entity_instance, None]
Example: Example:
@@ -51,40 +58,46 @@ class Usecase:
ifc_class="IfcUnitaryEquipment", predefined_type="AIRHANDLER") ifc_class="IfcUnitaryEquipment", predefined_type="AIRHANDLER")
# And now assign the IFC model's AHU with its Brickschema counterpart # And now assign the IFC model's AHU with its Brickschema counterpart
ifcopenshell.api.run("library.assign_reference", model, reference=reference, product=ahu) ifcopenshell.api.run("library.assign_reference", model, reference=reference, products=[ahu])
""" """
self.file = file self.file = file
self.settings = { self.settings = {
"product": product, "products": products,
"reference": reference, "reference": reference,
} }
def execute(self): def execute(self) -> Union[ifcopenshell.entity_instance, None]:
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
referenced_elements = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
products: set[ifcopenshell.entity_instance] = set(self.settings["products"])
products = products - referenced_elements
if not products:
return
if self.file.schema == "IFC2X3": if self.file.schema == "IFC2X3":
rels = self.get_ifc2x3_rels() rel = next(
(
r
for r in self.file.by_type("IfcRelAssociatesLibrary")
if r.RelatingLibrary == self.settings["reference"]
),
None,
)
else: else:
rels = self.settings["reference"].LibraryRefForObjects rel = next(iter(self.settings["reference"].LibraryRefForObjects), None)
if not rels:
if not rel:
return self.file.create_entity( return self.file.create_entity(
"IfcRelAssociatesLibrary", "IfcRelAssociatesLibrary",
GlobalId=ifcopenshell.guid.new(), GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file), OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
RelatedObjects=[self.settings["product"]], RelatedObjects=list(products),
RelatingLibrary=self.settings["reference"], RelatingLibrary=self.settings["reference"],
) )
for rel in rels: related_objects = set(rel.RelatedObjects) | products
if self.settings["product"] in rel.RelatedObjects: rel.RelatedObjects = list(related_objects)
return rel
rel = rels[0]
related_objects = list(rel.RelatedObjects)
related_objects.append(self.settings["product"])
rel.RelatedObjects = related_objects
ifcopenshell.api.run("owner.update_owner_history", self.file, element=rel) ifcopenshell.api.run("owner.update_owner_history", self.file, element=rel)
return rel return rel
def get_ifc2x3_rels(self):
return [
r for r in self.file.by_type("IfcRelAssociatesLibrary") if r.RelatingLibrary == self.settings["reference"]
]
@@ -18,18 +18,24 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.api
class Usecase: class Usecase:
def __init__(self, file, reference=None, product=None): def __init__(
"""Unassigns a product from a reference self,
file: ifcopenshell.file,
reference: ifcopenshell.entity_instance,
products: list[ifcopenshell.entity_instance],
):
"""Unassigns a product of products from a reference
If the product isn't assigned to the reference, nothing will happen. If the product isn't assigned to the reference, nothing will happen.
:param reference: The IfcLibraryReference to unassign from :param reference: The IfcLibraryReference to unassign from
:type reference: ifcopenshell.entity_instance.entity_instance :type reference: ifcopenshell.entity_instance.entity_instance
:param product: A IfcProduct element to unassign from the reference :param products: A list of IfcProduct elements to unassign from the reference
:type product: ifcopenshell.entity_instance.entity_instance :type products: list[ifcopenshell.entity_instance.entity_instance]
:return: None :return: None
:rtype: None :rtype: None
@@ -49,27 +55,36 @@ class Usecase:
ifc_class="IfcUnitaryEquipment", predefined_type="AIRHANDLER") ifc_class="IfcUnitaryEquipment", predefined_type="AIRHANDLER")
# And now assign the IFC model's AHU with its Brickschema counterpart # And now assign the IFC model's AHU with its Brickschema counterpart
ifcopenshell.api.run("library.assign_reference", model, reference=reference, product=ahu) ifcopenshell.api.run("library.assign_reference", model, reference=reference, products=[ahu])
# Let's change our mind and unassign it. # Let's change our mind and unassign it.
ifcopenshell.api.run("library.unassign_reference", model, reference=reference, product=ahu) ifcopenshell.api.run("library.unassign_reference", model, reference=reference, products=[ahu])
""" """
self.file = file self.file = file
self.settings = {"reference": reference, "product": product} self.settings = {"reference": reference, "products": products}
def execute(self): def execute(self):
rels = self.settings["reference"].LibraryRefForObjects # TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
if not rels:
return reference_rels: set[ifcopenshell.entity_instance] = set()
for rel in rels: products = set(self.settings["products"])
if self.settings["product"] in rel.RelatedObjects: for product in products:
if len(rel.RelatedObjects) == 1: reference_rels.update(product.HasAssociations)
history = rel.OwnerHistory
self.file.remove(rel) reference_rels = {
if history: rel
ifcopenshell.util.element.remove_deep2(self.file, history) for rel in reference_rels
continue if rel.is_a("IfcRelAssociatesLibrary") and rel.RelatingLibrary == self.settings["reference"]
related_objects = list(rel.RelatedObjects) }
related_objects.remove(self.settings["product"])
rel.RelatedObjects = related_objects for rel in reference_rels:
related_objects = set(rel.RelatedObjects) - products
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
@@ -38,7 +38,7 @@ def get_application(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instanc
if not app and ifc.schema == "IFC2X3": if not app and ifc.schema == "IFC2X3":
raise Exception( raise Exception(
"Please create an application to continue. See the owner.create_owner_history docs for more info." "Please create an application to continue. See the owner.create_owner_history docs for more info."
"https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/owner/create_owner_history/index.html" "https://docs.ifcopenshell.org/autoapi/ifcopenshell/api/owner/create_owner_history/index.html"
) )
return (app or [None])[0] return (app or [None])[0]
@@ -58,7 +58,7 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None
if not pao and ifc.schema == "IFC2X3": if not pao and ifc.schema == "IFC2X3":
raise Exception( raise Exception(
"Please create a user to continue. See the owner.create_owner_history docs for more info." "Please create a user to continue. See the owner.create_owner_history docs for more info."
"https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/owner/create_owner_history/index.html" "https://docs.ifcopenshell.org/autoapi/ifcopenshell/api/owner/create_owner_history/index.html"
) )
return (pao or [None])[0] return (pao or [None])[0]
@@ -22,11 +22,16 @@ import ifcopenshell.util.element
class Usecase: class Usecase:
def __init__(self, file, product=None, relating_structure=None): def __init__(
"""Dereferences the a product and space self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
relating_structure: ifcopenshell.entity_instance,
):
"""Dereferences a list of products and space
:param product: The physical IfcElement that exists in the space. :param products: The list of physical IfcElements that exists in the space.
:type product: ifcopenshell.entity_instance.entity_instance :type products: list[ifcopenshell.entity_instance.entity_instance]
:param relating_structure: The IfcSpatialStructureElement element, such :param relating_structure: The IfcSpatialStructureElement element, such
as IfcBuilding, IfcBuildingStorey, or IfcSpace that the element as IfcBuilding, IfcBuildingStorey, or IfcSpace that the element
exists in. exists in.
@@ -59,23 +64,24 @@ class Usecase:
ifcopenshell.api.run("spatial.assign_container", model, products=[column], relating_structure=storey1) ifcopenshell.api.run("spatial.assign_container", model, products=[column], relating_structure=storey1)
# And referenced in the others # And referenced in the others
ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey2) ifcopenshell.api.run("spatial.reference_structure", model, products=[column], relating_structure=storey2)
ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey3) ifcopenshell.api.run("spatial.reference_structure", model, products=[column], relating_structure=storey3)
# Actually, it only goes up to storey 2. # Actually, it only goes up to storey 2.
ifcopenshell.api.run("spatial.dereference_structure", model, product=column, relating_structure=storey3) ifcopenshell.api.run("spatial.dereference_structure", model, products=[column], relating_structure=storey3)
""" """
self.file = file self.file = file
self.settings = {"product": product, "relating_structure": relating_structure} self.settings = {"products": products, "relating_structure": relating_structure}
def execute(self): def execute(self) -> None:
for rel in self.settings["product"].ReferencedInStructures: products = set(self.settings["products"])
if rel.RelatingStructure != self.settings["relating_structure"]: for rel in self.settings["relating_structure"].ReferencesElements:
related_elements = set(rel.RelatedElements)
if not related_elements.intersection(products):
continue continue
related_elements = list(rel.RelatedElements) related_elements = related_elements - products
related_elements.remove(self.settings["product"])
if related_elements: if related_elements:
rel.RelatedElements = related_elements rel.RelatedElements = list(related_elements)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else: else:
history = rel.OwnerHistory history = rel.OwnerHistory
@@ -18,11 +18,18 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.element
from typing import Union
class Usecase: class Usecase:
def __init__(self, file, product=None, relating_structure=None): def __init__(
"""Denote that a product is related to a spatial structure self,
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
relating_structure: ifcopenshell.entity_instance,
):
"""Denote that a list products is related to a list of spatial structures
This is similar to ifcopenshell.api.spatial.assign_container, except This is similar to ifcopenshell.api.spatial.assign_container, except
that containment can only occur between a product and a single spatial that containment can only occur between a product and a single spatial
@@ -39,13 +46,15 @@ class Usecase:
Referencing is non-hierarchical, so a door may be referenced in multiple Referencing is non-hierarchical, so a door may be referenced in multiple
spaces simultaneously. spaces simultaneously.
:param product: The physical IfcElement that exists in the space. :param products: The list of physical IfcElements that exists in the space.
:type product: ifcopenshell.entity_instance.entity_instance :type products: list[ifcopenshell.entity_instance.entity_instance]
:param relating_structure: The IfcSpatialStructureElement element, such :param relating_structure: The IfcSpatialStructureElement element, such
as IfcBuilding, IfcBuildingStorey, or IfcSpace that the element as IfcBuilding, IfcBuildingStorey, or IfcSpace that the element
exists in. exists in.
:type relating_structure: ifcopenshell.entity_instance.entity_instance
:return: The IfcRelReferencedInSpatialStructure relationship instance :return: The IfcRelReferencedInSpatialStructure relationship instance
:rtype: ifcopenshell.entity_instance.entity_instance or `None` if `products` was an empty list.
:rtype: Union[ifcopenshell.entity_instance, None]
Example: Example:
@@ -73,37 +82,43 @@ class Usecase:
ifcopenshell.api.run("spatial.assign_container", model, products=[column], relating_structure=storey1) ifcopenshell.api.run("spatial.assign_container", model, products=[column], relating_structure=storey1)
# And referenced in the others # And referenced in the others
ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey2) ifcopenshell.api.run(
ifcopenshell.api.run("spatial.reference_structure", model, product=column, relating_structure=storey3) "spatial.reference_structure", model, products=[column], relating_structure=[storey2, storey3]
)
""" """
self.file = file self.file = file
self.settings = { self.settings = {
"product": product, "products": products,
"relating_structure": relating_structure, "relating_structure": relating_structure,
} }
def execute(self): def execute(self) -> Union[ifcopenshell.entity_instance, None]:
referenced_in_structures = self.settings["product"].ReferencedInStructures structure = self.settings["relating_structure"]
references_elements = self.settings["relating_structure"].ReferencesElements products = set(self.settings["products"])
for rel in referenced_in_structures: if not products:
if rel.RelatingStructure == self.settings["relating_structure"]: return
return
if references_elements: referenced = ifcopenshell.util.element.get_structure_referenced_elements(structure)
related_elements = list(references_elements[0].RelatedElements) products_to_assign = products - referenced
related_elements.append(self.settings["product"]) rel = next(iter(structure.ReferencesElements), None)
references_elements[0].RelatedElements = related_elements
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": references_elements[0]}) if not products_to_assign:
else: return rel
references_elements = self.file.create_entity(
if rel is None:
rel = self.file.create_entity(
"IfcRelReferencedInSpatialStructure", "IfcRelReferencedInSpatialStructure",
**{ **{
"GlobalId": ifcopenshell.guid.new(), "GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedElements": [self.settings["product"]], "RelatedElements": list(products_to_assign),
"RelatingStructure": self.settings["relating_structure"], "RelatingStructure": structure,
} }
) )
else:
related_elements = set(rel.RelatedElements) | products_to_assign
rel.RelatedElements = list(related_elements)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
return references_elements return rel
@@ -30,7 +30,7 @@ import functools
import subprocess import subprocess
import sys import sys
import time import time
from typing import Union, Any, Callable, TypeVar from typing import Union, Any, Callable, TypeVar, overload
from . import ifcopenshell_wrapper from . import ifcopenshell_wrapper
from . import settings from . import settings
@@ -317,15 +317,25 @@ class entity_instance(object):
return self.wrapped_data.to_string(valid_spf) return self.wrapped_data.to_string(valid_spf)
def is_a(self, *args) -> Union[str, bool]: @overload
def is_a(self) -> str: ...
@overload
def is_a(self, ifc_class: str) -> bool: ...
@overload
def is_a(self, with_schema: bool) -> str: ...
def is_a(self, *args: Union[str, bool]) -> Union[str, bool]:
"""Return the IFC class name of an instance, or checks if an instance belongs to a class. """Return the IFC class name of an instance, or checks if an instance belongs to a class.
The check will also return true if a parent class name is provided. The check will also return true if a parent class name is provided.
:param args: If specified, is a case insensitive IFC class name to check :param args: If specified, is a case insensitive IFC class name to check
:type args: string or if specified as a boolean then will define whether
returned IFC class name should include schema name
(e.g. "IFC4.IfcWall" if `True` and "IfcWall" if `False`).
If omitted will act as `False`.
:type args: Union[str, bool]
:returns: Either the name of the class, or a boolean if it passes the check :returns: Either the name of the class, or a boolean if it passes the check
:rtype: string|bool :rtype: Union[str, bool]
Example: Example:
@@ -23,10 +23,10 @@ from typing import Optional
def get_references(element: ifcopenshell.entity_instance, should_inherit=True) -> set[ifcopenshell.entity_instance]: def get_references(element: ifcopenshell.entity_instance, should_inherit=True) -> set[ifcopenshell.entity_instance]:
results = set() results = set()
if not element.is_a("IfcRoot"): if not element.is_a("IfcRoot"):
if hasattr(element, "HasExternalReferences"): if (references := getattr(element, "HasExternalReferences", None)) is not None or (
return {r.RelatingReference for r in element.HasExternalReferences or []} references := getattr(element, "HasExternalReference", None)
elif hasattr(element, "HasExternalReference"): # Seriously, IFC? ) is not None:
return {r.RelatingReference for r in element.HasExternalReference or []} return {r.RelatingReference for r in references}
if should_inherit: if should_inherit:
element_type = ifcopenshell.util.element.get_type(element) element_type = ifcopenshell.util.element.get_type(element)
if element_type and element_type != element: if element_type and element_type != element:
@@ -18,20 +18,59 @@
# #
# #
def get_constraints(product): import ifcopenshell
from typing import Union
def get_constraints(product: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
"""
Retrieves the constraints assigned to the `product`.
:param product: The IFC element.
:type product: ifcopenshell.entity_instance.entity_instance
:return: List of assigned constraints.
:rtype: list[ifcopenshell.entity_instance.entity_instance]
"""
constraints = [] constraints = []
for rel in product.HasAssociations or []: for rel in product.HasAssociations or []:
if rel.is_a("IfcRelAssociatesConstraint"): if rel.is_a("IfcRelAssociatesConstraint"):
constraints.append(rel.RelatingConstraint) constraints.append(rel.RelatingConstraint)
return constraints return constraints
def get_metrics(constraint):
def get_constrained_elements(constraint: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
"""
Retrieves the elements constrained by a `constraint`.
:param product: The IFC element.
:type product: ifcopenshell.entity_instance.entity_instance
:return: Set of elements constrained by a `constrant`.
:rtype: set[ifcopenshell.entity_instance.entity_instance]
"""
elements = set()
for rel in constraint.file.get_inverse(constraint):
if rel.is_a("IfcRelAssociatesConstraint"):
elements.update(rel.RelatedObjects)
return elements
def get_metrics(constraint: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
"""
Retrieves the list of nested constraints for a IfcObjective `constraint`.
:param product: IfcObjective constraint.
:type product: ifcopenshell.entity_instance.entity_instance
:return: List of nested constraints.
:rtype: list[ifcopenshell.entity_instance.entity_instance]
"""
metrics = [] metrics = []
for metric in constraint.BenchmarkValues or []: for metric in constraint.BenchmarkValues or []:
metrics.append(metric) metrics.append(metric)
return metrics return metrics
def get_metric_reference(metric, is_deep=True):
def get_metric_reference(metric: ifcopenshell.entity_instance, is_deep=True):
def get_reference_Attribute(ref, path): def get_reference_Attribute(ref, path):
if ref: if ref:
if is_deep: if is_deep:
@@ -47,7 +86,10 @@ def get_metric_reference(metric, is_deep=True):
reference = metric.ReferencePath reference = metric.ReferencePath
return get_reference_Attribute(reference, "") return get_reference_Attribute(reference, "")
def get_metric_constraints(resource, attribute):
def get_metric_constraints(
resource: ifcopenshell.entity_instance, attribute
) -> Union[list[ifcopenshell.entity_instance], None]:
metrics = [] metrics = []
for constraint in get_constraints(resource) or []: for constraint in get_constraints(resource) or []:
for metric in get_metrics(constraint) or []: for metric in get_metrics(constraint) or []:
@@ -60,15 +102,14 @@ def get_metric_constraints(resource, attribute):
return metrics return metrics
return None return None
def is_hard_constraint(metric):
if metric.ConstraintGrade == "HARD" and metric.Benchmark == "EQUALTO":
return True
def is_attribute_locked(product, attribute): def is_hard_constraint(metric: ifcopenshell.entity_instance) -> bool:
return metric.ConstraintGrade == "HARD" and metric.Benchmark == "EQUALTO"
def is_attribute_locked(product: ifcopenshell.entity_instance, attribute) -> bool:
is_locked = False is_locked = False
metrics = get_metric_constraints( metrics = get_metric_constraints(product, attribute)
product, attribute
)
for metric in metrics or []: for metric in metrics or []:
if is_hard_constraint(metric): if is_hard_constraint(metric):
is_locked = True is_locked = True
@@ -20,16 +20,17 @@ from __future__ import annotations
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element import ifcopenshell.util.element
from typing import Any, Callable, Optional, Union, Literal, overload from typing import Any, Callable, Optional, Union, Literal, overload
from collections import namedtuple
def get_pset( def get_pset(
element: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance,
name: str, name: str,
prop: Optional[str] = None, prop: Optional[str] = None,
psets_only=False, psets_only: bool = False,
qtos_only=False, qtos_only: bool = False,
should_inherit=True, should_inherit: bool = True,
verbose=False, verbose: bool = False,
) -> Union[Any, dict[str, Any]]: ) -> Union[Any, dict[str, Any]]:
"""Retrieve a single property set or single property """Retrieve a single property set or single property
@@ -429,8 +430,7 @@ def get_predefined_type(element: ifcopenshell.entity_instance) -> str:
element = ifcopenshell.by_type("IfcWall")[0] element = ifcopenshell.by_type("IfcWall")[0]
predefined_type = ifcopenshell.util.element.get_predefined_type(element) predefined_type = ifcopenshell.util.element.get_predefined_type(element)
""" """
element_type = get_type(element) if element_type := get_type(element):
if element_type:
predefined_type = getattr(element_type, "PredefinedType", None) predefined_type = getattr(element_type, "PredefinedType", None)
if predefined_type == "USERDEFINED" or not predefined_type: if predefined_type == "USERDEFINED" or not predefined_type:
predefined_type = getattr(element_type, "ElementType", ...) predefined_type = getattr(element_type, "ElementType", ...)
@@ -563,7 +563,9 @@ def get_material(
return get_material(relating_type, should_skip_usage) return get_material(relating_type, should_skip_usage)
def get_materials(element: ifcopenshell.entity_instance, should_inherit=True) -> list[ifcopenshell.entity_instance]: def get_materials(
element: ifcopenshell.entity_instance, should_inherit: bool = True
) -> list[ifcopenshell.entity_instance]:
"""Gets individual materials of an element """Gets individual materials of an element
If the element has a material set, the individual materials of that set are If the element has a material set, the individual materials of that set are
@@ -826,7 +828,7 @@ def get_layers(
def get_container( def get_container(
element: ifcopenshell.entity_instance, should_get_direct=False, ifc_class: Optional[str] = None element: ifcopenshell.entity_instance, should_get_direct: bool = False, ifc_class: Optional[str] = None
) -> ifcopenshell.entity_instance: ) -> ifcopenshell.entity_instance:
""" """
Retrieves the spatial structure container of an element. Retrieves the spatial structure container of an element.
@@ -902,6 +904,27 @@ def get_referenced_structures(element: ifcopenshell.entity_instance) -> list[ifc
return [r.RelatingStructure for r in getattr(element, "ReferencedInStructures", [])] return [r.RelatingStructure for r in getattr(element, "ReferencedInStructures", [])]
def get_structure_referenced_elements(structure: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
"""Retreives a set of elements referenced by a structure
:param structure: IfcSpatialElement
:type element: ifcopenshell.entity_instance.entity_instance
:return: A set of referenced elements, IfcSpatialReferenceSelect
:rtype: set[ifcopenshell.entity_instance.entity_instance]
Example:
.. code:: python
element = file.by_type("IfcBuildingStorey")[0]
print(ifcopenshell.util.element.get_structure_referenced_elements(element))
"""
referenced = set()
for rel in structure.ReferencesElements:
referenced.update(rel.RelatedElements)
return referenced
def get_decomposition(element: ifcopenshell.entity_instance, is_recursive=True) -> list[ifcopenshell.entity_instance]: def get_decomposition(element: ifcopenshell.entity_instance, is_recursive=True) -> list[ifcopenshell.entity_instance]:
""" """
Retrieves all subelements of an element based on the spatial decomposition Retrieves all subelements of an element based on the spatial decomposition
@@ -1092,6 +1115,66 @@ def get_components(element: ifcopenshell.entity_instance, include_ports=False) -
return is_decomposed_by[0].RelatedObjects return is_decomposed_by[0].RelatedObjects
ReferenceData = namedtuple("ReferenceData", "inverse_attribute, rel_class, relating_element_attribute")
# References below are omitted because they do not introduce
# any additional referenced objects besides the objects
# from their supertype IfcExternalReference
# - IfcExternallyDefinedHatchStyle
# - IfcExternallyDefinedSurfaceStyle
# - IfcExternallyDefinedTextFont
REFERENCE_TYPES: dict[str, ReferenceData] = {
"IfcClassificationReference": ReferenceData(
"ClassificationRefForObjects",
"IfcRelAssociatesClassification",
"RelatingClassification",
),
"IfcDocumentReference": ReferenceData("DocumentRefForObjects", "IfcRelAssociatesDocument", "RelatingDocument"),
"IfcLibraryReference": ReferenceData("LibraryRefForObjects", "IfcRelAssociatesLibrary", "RelatingLibrary"),
}
def get_referenced_elements(reference: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
"""Get all elements with assigned `reference`
:param reference: IfcExternalReference subtype reference
:type reference: ifcopenshell.entity_instance.entity_instance
:return: The elements with assigned `reference`
:rtype: set[ifcopenshell.entity_instance.entity_instance]
Example:
.. code:: python
reference = file.by_type("IfcClassificationReference")[0]
elements = ifcopenshell.util.element.get_referenced_elements(reference)
"""
related_objects: set[ifcopenshell.entity_instance] = set()
ifc_file = reference.file
ifc_class = reference.is_a()
if ifc_file.schema == "IFC2X3":
reference_data = REFERENCE_TYPES.get(ifc_class)
if reference_data:
for rel in ifc_file.by_type(reference_data.rel_class):
if getattr(rel, reference_data.relating_element_attribute) == reference:
related_objects.update(rel.RelatedObjects)
else:
# IfcExternalReference
for external_rel in reference.ExternalReferenceForResources:
related_objects.update(external_rel.RelatedResourceObjects)
reference_data = REFERENCE_TYPES.get(ifc_class)
if reference_data:
for rel in getattr(reference, reference_data.inverse_attribute):
related_objects.update(rel.RelatedObjects)
return related_objects
def replace_attribute(element: ifcopenshell.entity_instance, old: Any, new: Any) -> None: def replace_attribute(element: ifcopenshell.entity_instance, old: Any, new: Any) -> None:
for i, attribute_value in enumerate(element): for i, attribute_value in enumerate(element):
if has_element_reference(attribute_value, old): if has_element_reference(attribute_value, old):
@@ -17,6 +17,7 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import shapely import shapely
import shapely.ops
import numpy as np import numpy as np
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.placement import ifcopenshell.util.placement
+1 -1
View File
@@ -15,7 +15,7 @@ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
] ]
dependencies = ["mathutils", "shapely", "numpy", "isodate", "dateutil", "lark"] dependencies = ["mathutils", "shapely", "numpy", "isodate", "python-dateutil", "lark"]
[project.urls] [project.urls]
"Homepage" = "http://ifcopenshell.org" "Homepage" = "http://ifcopenshell.org"
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import test.bootstrap import test.bootstrap
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.classification import ifcopenshell.util.classification
@@ -23,85 +24,127 @@ import ifcopenshell.util.classification
class TestAddReference(test.bootstrap.IFC4): class TestAddReference(test.bootstrap.IFC4):
def test_adding_a_reference(self): def test_adding_a_reference(self):
is_ifc2x3 = self.file.schema == "IFC2X3"
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name") result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element, element2],
identification="X", identification="X",
name="Foobar", name="Foobar",
classification=result, classification=result,
) )
references = list(ifcopenshell.util.classification.get_references(element)) references = list(ifcopenshell.util.classification.get_references(element))
assert len(references) == 1 assert len(references) == 1
assert references[0].Identification == "X" assert getattr(references[0], "ItemReference" if is_ifc2x3 else "Identification") == "X"
assert references[0].Name == "Foobar" assert references[0].Name == "Foobar"
assert references[0].ReferencedSource == self.file.by_type("IfcClassification")[0] assert references[0].ReferencedSource == self.file.by_type("IfcClassification")[0]
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") references2 = list(ifcopenshell.util.classification.get_references(element))
ifcopenshell.api.run( assert len(references2) == 1
"classification.add_reference", assert getattr(references2[0], "ItemReference" if is_ifc2x3 else "Identification") == "X"
self.file, assert references2[0].Name == "Foobar"
product=element2, assert references2[0] == references[0]
identification="X",
name="Foobar", rel = next(
classification=result, rel
for rel in self.file.by_type("IfcRelAssociatesClassification")
if rel.RelatingClassification == references[0]
) )
assert list(ifcopenshell.util.classification.get_references(element2))[0].Identification == "X" assert len(rel.RelatedObjects) == 2
assert list(ifcopenshell.util.classification.get_references(element2))[0].Name == "Foobar"
assert list(ifcopenshell.util.classification.get_references(element2))[0] == references[0]
def test_adding_a_library_based_reference(self): def test_adding_a_library_based_reference(self):
is_ifc2x3 = self.file.schema == "IFC2X3"
library = ifcopenshell.file() library = ifcopenshell.file()
classification = library.createIfcClassification(Name="Name") classification = library.createIfcClassification(Name="Name")
reference = library.createIfcClassificationReference(Identification="1", ReferencedSource=classification) reference = library.createIfcClassificationReference(Identification="1", ReferencedSource=classification)
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification=classification) result = ifcopenshell.api.run("classification.add_classification", self.file, classification=classification)
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element, element2],
reference=reference, reference=reference,
classification=result, classification=result,
) )
references = list(ifcopenshell.util.classification.get_references(element)) references = list(ifcopenshell.util.classification.get_references(element))
assert len(references) == 1 assert len(references) == 1
assert references[0].Identification == "1" assert getattr(references[0], "ItemReference" if is_ifc2x3 else "Identification") == "1"
assert references[0].ReferencedSource == self.file.by_type("IfcClassification")[0] assert references[0].ReferencedSource == self.file.by_type("IfcClassification")[0]
def test_adding_a_reference_to_a_resource(self): references2 = list(ifcopenshell.util.classification.get_references(element2))
assert len(references2) == 1
assert getattr(references2[0], "ItemReference" if is_ifc2x3 else "Identification") == "1"
assert references2[0].ReferencedSource == self.file.by_type("IfcClassification")[0]
assert references[0] == references2[0]
rel = next(
rel
for rel in self.file.by_type("IfcRelAssociatesClassification")
if rel.RelatingClassification == references[0]
)
assert len(rel.RelatedObjects) == 2
def test_adding_a_reference_to_a_resource_and_to_a_root(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = self.file.createIfcMaterial() element = self.file.createIfcMaterial()
element2 = self.file.createIfcCostValue()
element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name") result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
ifcopenshell.api.run(
"classification.add_reference", if self.file.schema == "IFC2X3":
self.file, with pytest.raises(TypeError):
product=element, ifcopenshell.api.run(
identification="X", "classification.add_reference",
name="Foobar", self.file,
classification=result, products=[element, element2, element3],
) identification="X",
name="Foobar",
classification=result,
)
return
else:
ifcopenshell.api.run(
"classification.add_reference",
self.file,
products=[element, element2, element3],
identification="X",
name="Foobar",
classification=result,
)
references = list(ifcopenshell.util.classification.get_references(element)) references = list(ifcopenshell.util.classification.get_references(element))
assert len(references) == 1 assert len(references) == 1
assert references[0].Identification == "X" assert references[0].Identification == "X"
assert references[0].Name == "Foobar" assert references[0].Name == "Foobar"
assert references[0].ReferencedSource == self.file.by_type("IfcClassification")[0] assert references[0].ReferencedSource == self.file.by_type("IfcClassification")[0]
element2 = self.file.createIfcCostValue() references2 = list(ifcopenshell.util.classification.get_references(element2))
ifcopenshell.api.run( assert references2[0].Identification == "X"
"classification.add_reference", assert references2[0].Name == "Foobar"
self.file, assert references2[0] == references[0]
product=element2,
identification="X", references3 = list(ifcopenshell.util.classification.get_references(element3))
name="Foobar", assert references3[0].Identification == "X"
classification=result, assert references3[0].Name == "Foobar"
) assert references3[0] == references[0]
assert list(ifcopenshell.util.classification.get_references(element2))[0].Identification == "X"
assert list(ifcopenshell.util.classification.get_references(element2))[0].Name == "Foobar"
assert list(ifcopenshell.util.classification.get_references(element2))[0] == references[0]
assert len(self.file.by_type("IfcExternalReferenceRelationship")[0].RelatedResourceObjects) == 2 assert len(self.file.by_type("IfcExternalReferenceRelationship")[0].RelatedResourceObjects) == 2
rel = next(
rel
for rel in self.file.by_type("IfcRelAssociatesClassification")
if rel.RelatingClassification == references[0]
)
assert len(rel.RelatedObjects) == 1
class TestAddReferenceIFC2X3(test.bootstrap.IFC2X3, TestAddReference):
pass
@@ -34,7 +34,7 @@ class TestRemoveClassification(test.bootstrap.IFC4):
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element],
identification="X", identification="X",
name="Foobar", name="Foobar",
classification=result, classification=result,
@@ -51,7 +51,7 @@ class TestRemoveClassification(test.bootstrap.IFC4):
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element],
identification="X", identification="X",
name="Foobar", name="Foobar",
classification=result, classification=result,
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import test.bootstrap import test.bootstrap
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.classification import ifcopenshell.util.classification
@@ -25,58 +26,80 @@ class TestRemoveReference(test.bootstrap.IFC4):
def test_removing_a_reference(self): def test_removing_a_reference(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name") result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
reference = ifcopenshell.api.run( reference = ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element, element2],
identification="X", identification="X",
name="Foobar", name="Foobar",
classification=result, classification=result,
) )
ifcopenshell.api.run("classification.remove_reference", self.file, product=element, reference=reference) ifcopenshell.api.run(
"classification.remove_reference", self.file, products=[element, element2], reference=reference
)
assert len(ifcopenshell.util.classification.get_references(element)) == 0 assert len(ifcopenshell.util.classification.get_references(element)) == 0
assert len(ifcopenshell.util.classification.get_references(element2)) == 0
assert len(self.file.by_type("IfcClassificationReference")) == 0 assert len(self.file.by_type("IfcClassificationReference")) == 0
def test_removing_a_reference_from_a_resource(self): def test_removing_a_reference_from_a_resource_and_from_a_root(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = self.file.createIfcMaterial() element = self.file.createIfcMaterial()
element2 = self.file.createIfcCostValue()
element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name") result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
reference = ifcopenshell.api.run( if self.file.schema == "IFC2X3":
"classification.add_reference", with pytest.raises(TypeError):
self.file, reference = ifcopenshell.api.run(
product=element, "classification.add_reference",
identification="X", self.file,
name="Foobar", products=[element, element2, element3],
classification=result, identification="X",
name="Foobar",
classification=result,
)
return
else:
reference = ifcopenshell.api.run(
"classification.add_reference",
self.file,
products=[element, element2, element3],
identification="X",
name="Foobar",
classification=result,
)
ifcopenshell.api.run(
"classification.remove_reference", self.file, products=[element, element2, element3], reference=reference
) )
ifcopenshell.api.run("classification.remove_reference", self.file, product=element, reference=reference)
assert len(ifcopenshell.util.classification.get_references(element)) == 0 assert len(ifcopenshell.util.classification.get_references(element)) == 0
assert len(ifcopenshell.util.classification.get_references(element2)) == 0
assert len(ifcopenshell.util.classification.get_references(element3)) == 0
assert len(self.file.by_type("IfcClassificationReference")) == 0 assert len(self.file.by_type("IfcClassificationReference")) == 0
def test_retaining_the_reference_if_still_in_use(self): def test_retaining_the_reference_if_still_in_use(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = self.file.createIfcMaterial() element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = self.file.createIfcMaterial() element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name") result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
reference = ifcopenshell.api.run( reference = ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element, element2, element3],
identification="X",
name="Foobar",
classification=result,
)
reference2 = ifcopenshell.api.run(
"classification.add_reference",
self.file,
product=element2,
identification="X", identification="X",
name="Foobar", name="Foobar",
classification=result, classification=result,
) )
assert len(self.file.by_type("IfcClassificationReference")) == 1 assert len(self.file.by_type("IfcClassificationReference")) == 1
ifcopenshell.api.run("classification.remove_reference", self.file, product=element, reference=reference) ifcopenshell.api.run(
"classification.remove_reference", self.file, products=[element, element2], reference=reference
)
assert len(self.file.by_type("IfcClassificationReference")) == 1 assert len(self.file.by_type("IfcClassificationReference")) == 1
ifcopenshell.api.run("classification.remove_reference", self.file, product=element2, reference=reference2) ifcopenshell.api.run("classification.remove_reference", self.file, products=[element3], reference=reference)
assert len(self.file.by_type("IfcClassificationReference")) == 0 assert len(self.file.by_type("IfcClassificationReference")) == 0
class TestRemoveReferenceIFC2X3(test.bootstrap.IFC2X3, TestRemoveReference):
pass
@@ -0,0 +1,64 @@
# 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.constraint
class TestAssignConstraint(test.bootstrap.IFC4):
def test_assign_a_constraint(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
ifcopenshell.api.run(
"constraint.assign_constraint", self.file, products=[element, element2], constraint=constraint
)
assert ifcopenshell.util.constraint.get_constrained_elements(constraint) == {element, element2}
assert len(self.file.by_type("IfcRelAssociatesConstraint")) == 1
def test_doing_nothing_if_the_constraint_is_already_assigned(self):
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run(
"constraint.assign_constraint", self.file, products=[element, element2], constraint=constraint
)
total_elements = len([e for e in self.file])
ifcopenshell.api.run(
"constraint.assign_constraint", self.file, products=[element, element2], constraint=constraint
)
assert len([e for e in self.file]) == total_elements
def test_that_old_relationships_are_updated_if_they_still_contain_elements(self):
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("constraint.assign_constraint", self.file, products=[element1], constraint=constraint)
rel = self.file.by_type("IfcRelAssociatesConstraint")[0]
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run(
"constraint.assign_constraint", self.file, products=[element2, element3], constraint=constraint
)
assert len(rel.RelatedObjects) == 3
class TestAssignConstraintIFC2X3(test.bootstrap.IFC2X3, TestAssignConstraint):
pass
@@ -0,0 +1,67 @@
# 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.constraint
class TestUnassignConstraint(test.bootstrap.IFC4):
def test_unassigning_a_constraint(self):
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run(
"constraint.assign_constraint", self.file, products=[element, element2], constraint=constraint
)
ifcopenshell.api.run(
"constraint.unassign_constraint", self.file, products=[element, element2], constraint=constraint
)
assert ifcopenshell.util.constraint.get_constrained_elements(element) == set()
assert len(self.file.by_type("IfcRelAssociatesConstraint")) == 0
def test_doing_nothing_if_no_constraint(self):
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run(
"constraint.unassign_constraint", self.file, products=[element, element2], constraint=constraint
)
assert ifcopenshell.util.constraint.get_constrained_elements(element) == set()
assert ifcopenshell.util.constraint.get_constrained_elements(element2) == set()
def test_updating_the_rel_when_a_reference_is_removed_with_multipled_elements(self):
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("constraint.assign_constraint", self.file, products=[element1], constraint=constraint)
rel = self.file.by_type("IfcRelAssociatesConstraint")[0]
ifcopenshell.api.run(
"constraint.assign_constraint", self.file, products=[element2, element3], constraint=constraint
)
ifcopenshell.api.run(
"constraint.unassign_constraint", self.file, products=[element1, element2], constraint=constraint
)
assert rel.RelatedObjects == (element3,)
class TestUnassignConstraintIFC2X3(test.bootstrap.IFC2X3, TestUnassignConstraint):
pass
@@ -18,22 +18,25 @@
import test.bootstrap import test.bootstrap
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.element
class TestAssignDocument(test.bootstrap.IFC4): class TestAssignDocument(test.bootstrap.IFC4):
def test_assigning_a_document(self): def test_assigning_a_document(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None) reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
ifcopenshell.api.run("document.assign_document", self.file, product=element, document=reference) ifcopenshell.api.run("document.assign_document", self.file, products=[element], document=reference)
assert element.HasAssociations[0].RelatingDocument == reference assert element.HasAssociations[0].RelatingDocument == reference
assert ifcopenshell.util.element.get_referenced_elements(reference) == {element}
def test_assigning_multiple_documents(self): def test_assigning_multiple_documents(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None) reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
ifcopenshell.api.run("document.assign_document", self.file, product=element, document=reference) ifcopenshell.api.run("document.assign_document", self.file, products=[element, element2], document=reference)
ifcopenshell.api.run("document.assign_document", self.file, product=element2, document=reference)
assert len(self.file.by_type("IfcRelAssociatesDocument")) == 1 assert len(self.file.by_type("IfcRelAssociatesDocument")) == 1
assert element.HasAssociations[0].RelatingDocument == reference assert ifcopenshell.util.element.get_referenced_elements(reference) == {element, element2}
assert element2.HasAssociations[0].RelatingDocument == reference
assert element.HasAssociations[0] == element.HasAssociations[0]
class TestAssignDocumentIFC2X3(test.bootstrap.IFC2X3, TestAssignDocument):
pass
@@ -35,7 +35,7 @@ class TestRemoveReference(test.bootstrap.IFC4):
wall = self.file.createIfcWall() wall = self.file.createIfcWall()
information = ifcopenshell.api.run("document.add_information", self.file, parent=None) information = ifcopenshell.api.run("document.add_information", self.file, parent=None)
reference = ifcopenshell.api.run("document.add_reference", self.file, information=information) reference = ifcopenshell.api.run("document.add_reference", self.file, information=information)
ifcopenshell.api.run("document.assign_document", self.file, product=wall, document=reference) ifcopenshell.api.run("document.assign_document", self.file, products=[wall], document=reference)
assert len(self.file.by_type("IfcRelAssociatesDocument")) == 2 assert len(self.file.by_type("IfcRelAssociatesDocument")) == 2
ifcopenshell.api.run("document.remove_reference", self.file, reference=reference) ifcopenshell.api.run("document.remove_reference", self.file, reference=reference)
assert len(self.file.by_type("IfcDocumentReference")) == 0 assert len(self.file.by_type("IfcDocumentReference")) == 0
@@ -18,23 +18,29 @@
import test.bootstrap import test.bootstrap
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.element
class TestUnassignDocument(test.bootstrap.IFC4): class TestUnassignDocument(test.bootstrap.IFC4):
def test_unassigning_a_document(self): def test_unassigning_a_document(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None) reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
ifcopenshell.api.run("document.assign_document", self.file, product=element, document=reference) ifcopenshell.api.run("document.assign_document", self.file, products=[element], document=reference)
ifcopenshell.api.run("document.unassign_document", self.file, product=element, document=reference) ifcopenshell.api.run("document.unassign_document", self.file, products=[element], document=reference)
assert not element.HasAssociations assert not element.HasAssociations
assert not len(self.file.by_type("IfcRelAssociatesDocument")) assert not len(self.file.by_type("IfcRelAssociatesDocument"))
def test_unassigning_a_document_used_by_multiple_entities(self): def test_unassigning_a_document_used_by_multiple_entities(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None) reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
ifcopenshell.api.run("document.assign_document", self.file, product=element, document=reference) ifcopenshell.api.run(
ifcopenshell.api.run("document.assign_document", self.file, product=element2, document=reference) "document.assign_document", self.file, products=[element, element2, element3], document=reference
ifcopenshell.api.run("document.unassign_document", self.file, product=element, document=reference) )
assert not element.HasAssociations ifcopenshell.api.run("document.unassign_document", self.file, products=[element, element2], document=reference)
assert element2.HasAssociations[0].RelatingDocument == reference assert ifcopenshell.util.element.get_referenced_elements(reference) == {element3}
class TestUnassignDocumentIFC2X3(test.bootstrap.IFC2X3, TestUnassignDocument):
pass
@@ -20,39 +20,48 @@ import test.bootstrap
import ifcopenshell.api import ifcopenshell.api
def validate_ifc_file(ifc_file: ifcopenshell.file, use_json=True):
import ifcopenshell
import ifcopenshell.validate
if use_json:
logger = ifcopenshell.validate.json_logger()
else:
import logging
logger = logging.getLogger("validate")
logger.setLevel(logging.DEBUG)
ifcopenshell.validate.validate(ifc_file, logger, express_rules=True)
if use_json:
if logger.statements:
from pprint import pprint
pprint(logger.statements)
else:
print("IFC is completely valid.")
class TestAssignReference(test.bootstrap.IFC4): class TestAssignReference(test.bootstrap.IFC4):
def test_assigning_a_reference(self): def test_assigning_a_reference(self):
reference = self.file.createIfcLibraryReference() reference = self.file.createIfcLibraryReference()
product = self.file.createIfcWall() product = self.file.createIfcWall()
product2 = self.file.createIfcWall() product2 = self.file.createIfcWall()
ifcopenshell.api.run("library.assign_reference", self.file, product=product, reference=reference) product3 = self.file.createIfcWall()
assert reference.LibraryRefForObjects[0].RelatedObjects == (product,) ifcopenshell.api.run("library.assign_reference", self.file, products=[product], reference=reference)
ifcopenshell.api.run("library.assign_reference", self.file, product=product2, reference=reference) rel = self.file.by_type("IfcRelAssociatesLibrary")[0]
assert reference.LibraryRefForObjects[0].RelatedObjects == (product, product2) assert rel.RelatedObjects == (product,)
ifcopenshell.api.run("library.assign_reference", self.file, products=[product2, product3], reference=reference)
assert set(rel.RelatedObjects) == set((product, product2, product3))
def test_not_assigning_twice(self): def test_not_assigning_twice(self):
reference = self.file.createIfcLibraryReference() reference = self.file.createIfcLibraryReference()
product = self.file.createIfcWall() product = self.file.createIfcWall()
ifcopenshell.api.run("library.assign_reference", self.file, product=product, reference=reference) ifcopenshell.api.run("library.assign_reference", self.file, products=[product], reference=reference)
ifcopenshell.api.run("library.assign_reference", self.file, product=product, reference=reference) ifcopenshell.api.run("library.assign_reference", self.file, products=[product], reference=reference)
assert reference.LibraryRefForObjects[0].RelatedObjects == (product,)
class TestAssignReferenceIFC2X3(test.bootstrap.IFC2X3):
def test_assigning_a_reference(self):
reference = self.file.createIfcLibraryReference()
product = self.file.createIfcWall()
product2 = self.file.createIfcWall()
ifcopenshell.api.run("library.assign_reference", self.file, product=product, reference=reference)
rel = self.file.by_type("IfcRelAssociatesLibrary")[0] rel = self.file.by_type("IfcRelAssociatesLibrary")[0]
assert rel.RelatedObjects == (product,) assert rel.RelatedObjects == (product,)
ifcopenshell.api.run("library.assign_reference", self.file, product=product2, reference=reference)
assert rel.RelatedObjects == (product, product2)
def test_not_assigning_twice(self):
reference = self.file.createIfcLibraryReference() class TestAssignReferenceIFC2X3(test.bootstrap.IFC2X3, TestAssignReference):
product = self.file.createIfcWall() pass
ifcopenshell.api.run("library.assign_reference", self.file, product=product, reference=reference)
ifcopenshell.api.run("library.assign_reference", self.file, product=product, reference=reference)
rel = self.file.by_type("IfcRelAssociatesLibrary")[0]
assert rel.RelatedObjects == (product,)
@@ -24,7 +24,7 @@ class TestRemoveReference(test.bootstrap.IFC4):
def test_removing_a_reference(self): def test_removing_a_reference(self):
reference = self.file.createIfcLibraryReference() reference = self.file.createIfcLibraryReference()
product = self.file.createIfcWall() product = self.file.createIfcWall()
ifcopenshell.api.run("library.assign_reference", self.file, product=product, reference=reference) ifcopenshell.api.run("library.assign_reference", self.file, products=[product], reference=reference)
ifcopenshell.api.run("library.remove_reference", self.file, reference=reference) ifcopenshell.api.run("library.remove_reference", self.file, reference=reference)
assert len(self.file.by_type("IfcLibraryReference")) == 0 assert len(self.file.by_type("IfcLibraryReference")) == 0
assert len(self.file.by_type("IfcRelAssociatesLibrary")) == 0 assert len(self.file.by_type("IfcRelAssociatesLibrary")) == 0
@@ -18,12 +18,20 @@
import test.bootstrap import test.bootstrap
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.element
class TestUnassignReference(test.bootstrap.IFC4): class TestUnassignReference(test.bootstrap.IFC4):
def test_unassigning_a_reference(self): def test_unassigning_a_reference(self):
reference = self.file.createIfcLibraryReference() reference = self.file.createIfcLibraryReference()
product = self.file.createIfcWall() products = [self.file.createIfcWall() for i in range(3)]
ifcopenshell.api.run("library.assign_reference", self.file, product=product, reference=reference) ifcopenshell.api.run("library.assign_reference", self.file, products=products, reference=reference)
ifcopenshell.api.run("library.unassign_reference", self.file, product=product, reference=reference) ifcopenshell.api.run("library.unassign_reference", self.file, products=products[:1], reference=reference)
assert ifcopenshell.util.element.get_referenced_elements(reference) == set(products[1:])
ifcopenshell.api.run("library.unassign_reference", self.file, products=products[1:], reference=reference)
assert ifcopenshell.util.element.get_referenced_elements(reference) == set()
assert len(self.file.by_type("IfcRelAssociatesLibrary")) == 0 assert len(self.file.by_type("IfcRelAssociatesLibrary")) == 0
class TestUnassignReferenceIFC2X3(test.bootstrap.IFC2X3, TestUnassignReference):
pass
@@ -26,28 +26,42 @@ class TestDereferenceStructure(test.bootstrap.IFC4):
def test_removing_a_container(self): def test_removing_a_container(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") 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) subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.dereference_structure", self.file, product=subelement, relating_structure=element) ifcopenshell.api.run(
"spatial.reference_structure", self.file, products=[subelement, subelement2], relating_structure=element
)
ifcopenshell.api.run(
"spatial.dereference_structure", self.file, products=[subelement, subelement2], relating_structure=element
)
assert ifcopenshell.util.element.get_referenced_structures(subelement) == [] assert ifcopenshell.util.element.get_referenced_structures(subelement) == []
assert len(self.file.by_type("IfcRelReferencedInSpatialStructure")) == 0
def test_doing_nothing_if_no_container(self): def test_doing_nothing_if_no_container(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") 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) subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run(
"spatial.dereference_structure", self.file, products=[subelement, subelement2], relating_structure=element
)
assert ifcopenshell.util.element.get_referenced_structures(subelement) == [] assert ifcopenshell.util.element.get_referenced_structures(subelement) == []
assert ifcopenshell.util.element.get_referenced_structures(subelement2) == []
def test_updating_the_rel_when_a_reference_is_removed_with_multipled_elements(self): 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") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
subelement2 = 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) subelement3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement2, relating_structure=element) ifcopenshell.api.run(
ifcopenshell.api.run("spatial.dereference_structure", self.file, product=subelement1, relating_structure=element) "spatial.reference_structure", self.file, products=[subelement1], relating_structure=element
assert self.file.by_type("IfcRelReferencedInSpatialStructure")[0].RelatedElements == (subelement2,) )
ifcopenshell.api.run(
"spatial.reference_structure", self.file, products=[subelement2, subelement3], relating_structure=element
)
ifcopenshell.api.run(
"spatial.dereference_structure", self.file, products=[subelement1, subelement2], relating_structure=element
)
assert element.ReferencesElements[0].RelatedElements == (subelement3,)
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") class TestDereferenceStructureIFC2X3(test.bootstrap.IFC2X3, TestDereferenceStructure):
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") pass
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
@@ -26,25 +26,39 @@ class TestReferenceStructure(test.bootstrap.IFC4):
def test_referencing_a_structure(self): def test_referencing_a_structure(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
rel = ifcopenshell.api.run( subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
"spatial.reference_structure", self.file, product=subelement, relating_structure=element ifcopenshell.api.run(
"spatial.reference_structure", self.file, products=[subelement, subelement2], relating_structure=element
) )
assert ifcopenshell.util.element.get_referenced_structures(subelement) == [element] assert ifcopenshell.util.element.get_structure_referenced_elements(element) == {subelement, subelement2}
assert rel.is_a("IfcRelReferencedInSpatialStructure")
def test_doing_nothing_if_the_structure_is_already_referenced(self): def test_doing_nothing_if_the_structure_is_already_referenced(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") 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) subelement2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run(
"spatial.reference_structure", self.file, products=[subelement, subelement2], relating_structure=element
)
total_elements = len([e for e in self.file]) total_elements = len([e for e in self.file])
ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement, relating_structure=element) ifcopenshell.api.run(
"spatial.reference_structure", self.file, products=[subelement, subelement2], relating_structure=element
)
assert len([e for e in self.file]) == total_elements assert len([e for e in self.file]) == total_elements
def test_that_old_relationships_are_updated_if_they_still_contain_elements(self): 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") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") subelement1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run(
"spatial.reference_structure", self.file, products=[subelement1], relating_structure=element
)
subelement2 = 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) subelement3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.reference_structure", self.file, product=subelement2, relating_structure=element1) ifcopenshell.api.run(
"spatial.reference_structure", self.file, products=[subelement2, subelement3], relating_structure=element
)
rel = subelement1.ReferencedInStructures[0] rel = subelement1.ReferencedInStructures[0]
assert len(rel.RelatedElements) == 2 assert len(rel.RelatedElements) == 3
class TestReferenceStructureIFC2X3(test.bootstrap.IFC2X3, TestReferenceStructure):
pass
@@ -18,6 +18,8 @@
import test.bootstrap import test.bootstrap
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.classification
import ifcopenshell.util.constraint
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.system import ifcopenshell.util.system
from datetime import datetime from datetime import datetime
@@ -186,3 +188,126 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4):
assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 0 assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 0
assert len(self.file.by_type("IfcWall")) == 1 assert len(self.file.by_type("IfcWall")) == 1
assert len(self.file.by_type("IfcMaterial")) == 1 assert len(self.file.by_type("IfcMaterial")) == 1
@deprecation_check
def test_adding_a_reference(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
ifcopenshell.api.run(
"classification.add_reference",
self.file,
product=element,
identification="X",
name="Foobar",
classification=result,
)
references = list(ifcopenshell.util.classification.get_references(element))
assert len(references) == 1
assert references[0].Identification == "X"
assert references[0].Name == "Foobar"
assert references[0].ReferencedSource == self.file.by_type("IfcClassification")[0]
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run(
"classification.add_reference",
self.file,
product=element2,
identification="X",
name="Foobar",
classification=result,
)
assert list(ifcopenshell.util.classification.get_references(element2))[0].Identification == "X"
assert list(ifcopenshell.util.classification.get_references(element2))[0].Name == "Foobar"
assert list(ifcopenshell.util.classification.get_references(element2))[0] == references[0]
@deprecation_check
def test_removing_a_reference(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
reference = ifcopenshell.api.run(
"classification.add_reference",
self.file,
products=[element],
identification="X",
name="Foobar",
classification=result,
)
ifcopenshell.api.run("classification.remove_reference", self.file, product=element, reference=reference)
assert len(ifcopenshell.util.classification.get_references(element)) == 0
assert len(self.file.by_type("IfcClassificationReference")) == 0
@deprecation_check
def test_assigning_a_reference(self):
reference = self.file.createIfcLibraryReference()
product = self.file.createIfcWall()
product2 = self.file.createIfcWall()
ifcopenshell.api.run("library.assign_reference", self.file, product=product, reference=reference)
assert reference.LibraryRefForObjects[0].RelatedObjects == (product,)
ifcopenshell.api.run("library.assign_reference", self.file, product=product2, reference=reference)
assert set(reference.LibraryRefForObjects[0].RelatedObjects) == set((product, product2))
@deprecation_check
def test_unassigning_a_reference(self):
reference = self.file.createIfcLibraryReference()
product = self.file.createIfcWall()
ifcopenshell.api.run("library.assign_reference", self.file, products=[product], reference=reference)
ifcopenshell.api.run("library.unassign_reference", self.file, product=product, reference=reference)
assert len(self.file.by_type("IfcRelAssociatesLibrary")) == 0
@deprecation_check
def test_assigning_a_document(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
ifcopenshell.api.run("document.assign_document", self.file, product=element, document=reference)
assert element.HasAssociations[0].RelatingDocument == reference
@deprecation_check
def test_unassigning_a_document(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
ifcopenshell.api.run("document.assign_document", self.file, products=[element], document=reference)
ifcopenshell.api.run("document.unassign_document", self.file, product=element, document=reference)
assert not element.HasAssociations
assert not len(self.file.by_type("IfcRelAssociatesDocument"))
@deprecation_check
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")
@deprecation_check
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, products=[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) == []
@deprecation_check
def test_assign_a_constraint(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
ifcopenshell.api.run("constraint.assign_constraint", self.file, product=element, constraint=constraint)
assert ifcopenshell.util.constraint.get_constrained_elements(constraint) == {element}
@deprecation_check
def test_unassigning_a_constraint(self):
constraint = ifcopenshell.api.run("constraint.add_objective", self.file)
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run(
"constraint.assign_constraint", self.file, product=element, constraint=constraint
)
ifcopenshell.api.run(
"constraint.unassign_constraint", self.file, product=element, constraint=constraint
)
assert ifcopenshell.util.constraint.get_constrained_elements(element) == set()
assert len(self.file.by_type("IfcRelAssociatesConstraint")) == 0
@@ -34,14 +34,14 @@ class TestGetReferences(test.bootstrap.IFC4):
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element],
reference=reference1, reference=reference1,
classification=classification, classification=classification,
) )
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element],
reference=reference2, reference=reference2,
classification=classification, classification=classification,
) )
@@ -54,7 +54,7 @@ class TestGetReferences(test.bootstrap.IFC4):
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element],
identification="X", identification="X",
name="Foobar", name="Foobar",
classification=result, classification=result,
@@ -74,14 +74,14 @@ class TestGetReferences(test.bootstrap.IFC4):
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element],
reference=reference1, reference=reference1,
classification=classification, classification=classification,
) )
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element_type, products=[element_type],
reference=reference2, reference=reference2,
classification=classification, classification=classification,
) )
@@ -103,14 +103,14 @@ class TestGetReferences(test.bootstrap.IFC4):
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element],
reference=reference1, reference=reference1,
classification=classification, classification=classification,
) )
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element_type, products=[element_type],
reference=reference2, reference=reference2,
classification=classification, classification=classification,
) )
@@ -291,6 +291,16 @@ class TestGetPredefinedTypeIFC4(test.bootstrap.IFC4):
element_type.ProcessType = "FOOBAR" element_type.ProcessType = "FOOBAR"
assert subject.get_predefined_type(element) == "FOOBAR" assert subject.get_predefined_type(element) == "FOOBAR"
def test_getting_an_element_type_predefined_type(self):
element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType")
element_type.PredefinedType = "PARTITIONING"
assert subject.get_predefined_type(element_type) == "PARTITIONING"
def test_getting_an_element_type_null_predefined_type(self):
element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType")
element_type.PredefinedType = "NOTDEFINED"
assert subject.get_predefined_type(element_type) == "NOTDEFINED"
class TestGetTypeIFC4(test.bootstrap.IFC4): class TestGetTypeIFC4(test.bootstrap.IFC4):
def test_getting_the_type_of_a_product(self): def test_getting_the_type_of_a_product(self):
@@ -352,12 +362,16 @@ class TestGetMaterial(test.bootstrap.IFC4):
def test_getting_a_material_layer_set_of_a_product(self): def test_getting_a_material_layer_set_of_a_product(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
rel = ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialLayerSet") rel = ifcopenshell.api.run(
"material.assign_material", self.file, products=[element], type="IfcMaterialLayerSet"
)
assert subject.get_material(element) == rel.RelatingMaterial assert subject.get_material(element) == rel.RelatingMaterial
def test_getting_a_material_profile_set_of_a_product(self): def test_getting_a_material_profile_set_of_a_product(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
rel = ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialProfileSet") rel = ifcopenshell.api.run(
"material.assign_material", self.file, products=[element], type="IfcMaterialProfileSet"
)
assert subject.get_material(element) == rel.RelatingMaterial assert subject.get_material(element) == rel.RelatingMaterial
def test_getting_a_material_layer_set_usage_of_a_product(self): def test_getting_a_material_layer_set_usage_of_a_product(self):
@@ -512,7 +526,9 @@ class TestGetElementsByMaterial(test.bootstrap.IFC4):
material_set = ifcopenshell.api.run("material.add_material_set", self.file, set_type="IfcMaterialProfileSet") material_set = ifcopenshell.api.run("material.add_material_set", self.file, set_type="IfcMaterialProfileSet")
ifcopenshell.api.run("material.add_profile", self.file, profile_set=material_set, material=material) ifcopenshell.api.run("material.add_profile", self.file, profile_set=material_set, material=material)
ifcopenshell.api.run("material.assign_material", self.file, products=[element_type], material=material_set) ifcopenshell.api.run("material.assign_material", self.file, products=[element_type], material=material_set)
ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialProfileSetUsage") ifcopenshell.api.run(
"material.assign_material", self.file, products=[element], type="IfcMaterialProfileSetUsage"
)
usage = self.file.by_type("IfcMaterialProfileSetUsage")[0] usage = self.file.by_type("IfcMaterialProfileSetUsage")[0]
assert subject.get_elements_by_material(self.file, material) == {element, element_type} assert subject.get_elements_by_material(self.file, material) == {element, element_type}
assert subject.get_elements_by_material(self.file, material_set) == {element, element_type} assert subject.get_elements_by_material(self.file, material_set) == {element, element_type}
@@ -703,13 +719,33 @@ class TestGetReferencedStructures(test.bootstrap.IFC4):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
assert subject.get_referenced_structures(element) == [] assert subject.get_referenced_structures(element) == []
building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") 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) ifcopenshell.api.run("spatial.reference_structure", self.file, products=[element], relating_structure=building)
assert subject.get_referenced_structures(element) == [building] assert subject.get_referenced_structures(element) == [building]
building2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding") 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) ifcopenshell.api.run("spatial.reference_structure", self.file, products=[element], relating_structure=building2)
assert subject.get_referenced_structures(element) == [building, building2] assert subject.get_referenced_structures(element) == [building, building2]
class TestGetReferencedStructuresIFC2X3(test.bootstrap.IFC2X3, TestGetReferencedStructures):
pass
class TestGetStructureReferencedElements(test.bootstrap.IFC4):
def test_getting_references_of_an_element(self):
building = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBuilding")
assert subject.get_structure_referenced_elements(building) == set()
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.reference_structure", self.file, products=[element], relating_structure=building)
assert subject.get_structure_referenced_elements(building) == {element}
element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
ifcopenshell.api.run("spatial.reference_structure", self.file, products=[element2], relating_structure=building)
assert subject.get_structure_referenced_elements(building) == {element, element2}
class TestGetStructureReferencedElementsIFC2X3(test.bootstrap.IFC2X3, TestGetStructureReferencedElements):
pass
class TestGetDecompositionIFC4(test.bootstrap.IFC4): class TestGetDecompositionIFC4(test.bootstrap.IFC4):
def test_getting_decomposed_subelements_of_an_element(self): def test_getting_decomposed_subelements_of_an_element(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcElementAssembly") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcElementAssembly")
@@ -769,6 +805,50 @@ class TestGetNestIFC2X3(test.bootstrap.IFC2X3, TestGetNestIFC4):
pass pass
class TestGetReferencedElements(test.bootstrap.IFC4):
# TODO: test other references:
# IfcExternallyDefinedHatchStyle
# IfcExternallyDefinedSurfaceStyle
# IfcExternallyDefinedTextFont
def test_get_elements_referenced_by_classification_reference(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
elements = [ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")]
if self.file.schema != "IFC2X3":
elements.append(self.file.create_entity("IfcCostValue"))
result = ifcopenshell.api.run("classification.add_classification", self.file, classification="Name")
reference = ifcopenshell.api.run(
"classification.add_reference",
self.file,
products=elements,
identification="X",
name="Foobar",
classification=result,
)
assert subject.get_referenced_elements(reference) == set(elements)
def test_get_elements_referenced_by_library_reference(self):
reference = self.file.createIfcLibraryReference()
elements = [
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall"),
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall"),
]
ifcopenshell.api.run("library.assign_reference", self.file, reference=reference, products=elements)
assert subject.get_referenced_elements(reference) == set(elements)
def test_get_elements_referenced_by_document_reference(self):
reference = ifcopenshell.api.run("document.add_reference", self.file, information=None)
elements = [
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall"),
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall"),
]
ifcopenshell.api.run("document.assign_document", self.file, document=reference, products=elements)
assert subject.get_referenced_elements(reference) == set(elements)
class TestGetReferencedElementsIFC2X3(test.bootstrap.IFC2X3, TestGetReferencedElements):
pass
class TestReplaceAttributeIFC4(test.bootstrap.IFC4): class TestReplaceAttributeIFC4(test.bootstrap.IFC4):
def test_replacing_an_elements_attribute(self): def test_replacing_an_elements_attribute(self):
element = self.file.createIfcWall("foo") element = self.file.createIfcWall("foo")
@@ -225,7 +225,7 @@ class TestFilterElements(test.bootstrap.IFC4):
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
self.file, self.file,
product=element, products=[element],
identification="X", identification="X",
name="Foobar", name="Foobar",
classification=result, classification=result,
+2 -2
View File
@@ -3,9 +3,9 @@
N.B.! IfcSverchok nodes are WIP. You can experience Blender crashes while using them, especially if you're undoing (ctrl/cmd-Z) something in the node tree. N.B.! IfcSverchok nodes are WIP. You can experience Blender crashes while using them, especially if you're undoing (ctrl/cmd-Z) something in the node tree.
## Packaged installation[](https://blenderbim.org/docs-python/ifcsverchok/installation.html#packaged-installation "Permalink to this headline") ## Packaged installation[](https://docs.ifcopenshell.org/ifcsverchok/installation.html#packaged-installation "Permalink to this headline")
IfcSverchok is packaged like a regular Blender add-on, so installation is the same as any other Blender add-on. [Download IfcSverchok here](https://blenderbim.org/builds/ifcsverchok-230823.zip). IfcSverchok is packaged like a regular Blender add-on, so installation is the same as any other Blender add-on. [Download IfcSverchok here](https://github.com/IfcOpenShell/IfcOpenShell/releases/download/ifcsverchok-240417/ifcsverchok-240417.zip).
Like all Blender add-ons, they can be installed using `Edit > Preferences > Addons > Install > Choose Downloaded ZIP > Enable Add-on Checkbox`. You can enable add-ons permanently by using `Save User Settings` from the Addons menu. Like all Blender add-ons, they can be installed using `Edit > Preferences > Addons > Install > Choose Downloaded ZIP > Enable Add-on Checkbox`. You can enable add-ons permanently by using `Save User Settings` from the Addons menu.
+29 -30
View File
@@ -32,6 +32,26 @@ import importlib
import logging import logging
logger = logging.getLogger("sverchok.ifc") logger = logging.getLogger("sverchok.ifc")
def ensure_addons_are_enabled(*addon_names: str) -> None:
errors = []
for addon_name in addon_names:
try:
module = importlib.import_module(addon_name)
# `__addon_enabled__` is not present if addon wasn't enabled before
if not getattr(module, "__addon_enabled__", False):
errors.append(f"- Addon {addon_name} appears to be disabled, it should be enabled before IFC Sverchok.")
except ModuleNotFoundError:
errors.append(f"- Addon {addon_name} is not installed.")
if errors:
raise Exception("Some issues were found trying to enable IFC Sverchok:\n" + "\n".join(errors))
ensure_addons_are_enabled("blenderbim", "sverchok")
from sverchok.ui.nodeview_space_menu import add_node_menu from sverchok.ui.nodeview_space_menu import add_node_menu
@@ -69,36 +89,15 @@ def nodes_index():
] ]
node_categories = [ def make_node_categories() -> list[dict[str, list[str]]]:
{ node_categories = [{}]
"IFC": [ for category, nodes in nodes_index():
"SvIfcCreateFile", nodes = [node_name for idname, node_name in nodes]
"SvIfcReadFile", node_categories[0][category] = nodes
"SvIfcWriteFile", return node_categories
"SvIfcCreateEntity",
"SvIfcCreateShape",
"SvIfcReadEntity", node_categories = make_node_categories()
"SvIfcPickIfcClass",
"SvIfcById",
"SvIfcByGuid",
"SvIfcByType",
"SvIfcByQuery",
"SvIfcAdd",
"SvIfcAddPset",
"SvIfcAddSpatialElement",
"SvIfcRemove",
"SvIfcGenerateGuid",
"SvIfcGetProperty",
"SvIfcGetAttribute",
"SvIfcSelectBlenderObjects",
"SvIfcApi",
"SvIfcBMeshToIfcRepr",
"SvIfcSverchokToIfcRepr",
"SvIfcCreateProject",
"SvIfcQuickProjectSetup",
]
}
]
def make_node_list(): def make_node_list():
+17
View File
@@ -4,6 +4,23 @@ With **IfcTester**, you can author and read **Information Delivery Specification
## How to use it ## How to use it
### Command line use
.. code-block:: bash
# run console reporter
python -m ifctester example.ids example.ifc
python -m ifctester example.ids example.ifc -r Html -o report.html
Available flags:
- ``-r`` / ``--reporter``: The reporting method to view audit results. Availabe reporters: Console, Txt, Json, Html, Ods, Bcf
- ``--no-color``: Disable colour output (supported by Console reporting).
- ``--excel-safe``: Make sure exported ODS is safely exported for Excel.
- ``-o`` / ``--output``: Output file (supported for all types of reporting except Console).
### Code example
```python ```python
import ifcopenshell import ifcopenshell
from ifctester import ids, reporter from ifctester import ids, reporter
+6 -3
View File
@@ -31,10 +31,13 @@ parser.add_argument(
"-r", "--reporter", type=str, help="The reporting method to view audit results", default="Console" "-r", "--reporter", type=str, help="The reporting method to view audit results", default="Console"
) )
parser.add_argument( parser.add_argument(
"--no-color", help="Disable colour output supported by Console reporting", action="store_true" "--no-color", help="Disable colour output (supported by Console reporting)", action="store_true"
) )
parser.add_argument( parser.add_argument(
"-o", "--output", help="Output file supported by Json reporting" "--excel-safe", help="Make sure exported ODS is safely exported for Excel", action="store_true"
)
parser.add_argument(
"-o", "--output", help="Output file (supported for all types of reporting except Console)"
) )
args = parser.parse_args() args = parser.parse_args()
@@ -56,7 +59,7 @@ elif args.reporter == "Json":
elif args.reporter == "Html": elif args.reporter == "Html":
engine = reporter.Html(specs) engine = reporter.Html(specs)
elif args.reporter == "Ods": elif args.reporter == "Ods":
engine = reporter.Ods(specs) engine = reporter.Ods(specs, excel_safe=args.excel_safe)
elif args.reporter == "Bcf": elif args.reporter == "Bcf":
engine = reporter.Bcf(specs) engine = reporter.Bcf(specs)
+8 -3
View File
@@ -24,7 +24,7 @@ import ifcopenshell.util.element
import ifcopenshell.util.classification import ifcopenshell.util.classification
from functools import lru_cache from functools import lru_cache
from xmlschema.validators import identities from xmlschema.validators import identities
from typing import Union, Optional, Any, Literal, TYPE_CHECKING from typing import Union, Optional, Any, Literal, TYPE_CHECKING, TypedDict
from logging import Logger from logging import Logger
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -61,12 +61,17 @@ def get_psets(element):
Cardinality = Literal["required", "optional", "prohibited"] Cardinality = Literal["required", "optional", "prohibited"]
class FacetFailure(TypedDict):
element: ifcopenshell.entity_instance
reason: str
class Facet: class Facet:
cardinality: Cardinality cardinality: Cardinality
def __init__(self, *parameters): def __init__(self, *parameters):
self.status = None self.status = None
self.failures = [] self.failures: list[FacetFailure] = []
for i, name in enumerate(self.parameters): for i, name in enumerate(self.parameters):
setattr(self, name.replace("@", ""), parameters[i]) setattr(self, name.replace("@", ""), parameters[i])
@@ -105,7 +110,7 @@ class Facet:
clause_type: str, clause_type: str,
specification: Optional[Specification] = None, specification: Optional[Specification] = None,
requirement: Optional[Facet] = None, requirement: Optional[Facet] = None,
): ) -> str:
if clause_type == "applicability": if clause_type == "applicability":
templates = self.applicability_templates templates = self.applicability_templates
elif clause_type == "requirement": elif clause_type == "requirement":
+10 -4
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>. # along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import os import os
import datetime import datetime
import ifcopenshell import ifcopenshell
@@ -34,14 +35,19 @@ from .facet import (
get_pset, get_pset,
get_psets, get_psets,
Cardinality, Cardinality,
FacetFailure,
) )
from typing import List, Optional, Union from typing import List, Optional, Union, overload, Literal
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
schema = None schema = None
def open(filepath, validate=False): @overload
def open(filepath: str, validate: Literal[False] = False) -> Ids: ...
@overload
def open(filepath: str, validate: Literal[True]) -> None: ...
def open(filepath: str, validate=False) -> Union[Ids, None]:
if validate: if validate:
get_schema().validate(filepath) get_schema().validate(filepath)
return Ids().parse( return Ids().parse(
@@ -265,11 +271,11 @@ class Specification:
if self.maxOccurs != 0: # This is a required or optional specification if self.maxOccurs != 0: # This is a required or optional specification
if not is_pass: if not is_pass:
self.failed_entities.add(element) self.failed_entities.add(element)
facet.failures.append({"element": element, "reason": str(result)}) facet.failures.append(FacetFailure(element=element, reason=str(result)))
else: # This is a prohibited specification else: # This is a prohibited specification
if is_pass: if is_pass:
self.failed_entities.add(element) self.failed_entities.add(element)
facet.failures.append({"element": element, "reason": str(result)}) facet.failures.append(FacetFailure(element=element, reason=str(result)))
self.status = True self.status = True
for facet in self.requirements: for facet in self.requirements:
+171 -66
View File
@@ -16,7 +16,9 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcTester. If not, see <http://www.gnu.org/licenses/>. # along with IfcTester. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import os import os
import re
import sys import sys
import math import math
import logging import logging
@@ -24,12 +26,15 @@ import datetime
import ifcopenshell import ifcopenshell
import ifcopenshell.util.unit import ifcopenshell.util.unit
import ifcopenshell.util.element import ifcopenshell.util.element
from .ids import Specification, Ids
from .facet import Facet, FacetFailure
from typing import TypedDict, Union, Literal, Optional
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
class Reporter: class Reporter:
def __init__(self, ids): def __init__(self, ids: Ids):
self.ids = ids self.ids = ids
def report(self, ids): def report(self, ids):
@@ -42,8 +47,79 @@ class Reporter:
pass pass
ResultsPercent = Union[int, Literal["N/A"]]
class Results(TypedDict):
title: str
date: str
filepath: str
filename: str
specifications: list[ResultsSpecification]
status: bool
total_specifications: int
total_specifications: int
total_specifications_pass: int
total_specifications_fail: int
percent_specifications_pass: ResultsPercent
total_requirements: int
total_requirements_pass: int
total_requirements_fail: int
percent_requirements_pass: ResultsPercent
total_checks: int
total_checks_pass: int
total_checks_fail: int
percent_checks_pass: ResultsPercent
class ResultsSpecification(TypedDict):
name: str
description: str
instructions: str
status: bool
total_applicable: int
total_applicable_pass: int
total_applicable_fail: int
percent_applicable_pass: ResultsPercent
total_checks: int
total_checks_pass: int
total_checks_fail: int
percent_checks_pass: ResultsPercent
required: bool
applicability: list[str]
requirements: list[ResultsRequirement]
class ResultsRequirement(TypedDict):
description: str
status: bool
failed_entities: list[ResultsFailedEntity]
total_applicable: int
total_pass: int
total_fail: int
percent_pass: ResultsPercent
# use different syntax because of the "class" key
ResultsFailedEntity = TypedDict(
"ResultsFailedEntity",
{
"reason": str,
"element": str,
"element_type": str,
"class": str,
"predefined_type": str,
"name": Union[str, None],
"description": Union[str, None],
"id": int,
"global_id": Union[str, None],
"tag": Union[str, None],
},
)
class Console(Reporter): class Console(Reporter):
def __init__(self, ids, use_colour=True): def __init__(self, ids: Ids, use_colour=True):
super().__init__(ids) super().__init__(ids)
self.use_colour = use_colour self.use_colour = use_colour
self.colours = { self.colours = {
@@ -59,14 +135,14 @@ class Console(Reporter):
"reverse": "\033[;7m", "reverse": "\033[;7m",
} }
def report(self): def report(self) -> None:
self.set_style("bold", "blue") self.set_style("bold", "blue")
self.print(self.ids.info.get("title", "Untitled IDS")) self.print(self.ids.info.get("title", "Untitled IDS"))
for specification in self.ids.specifications: for specification in self.ids.specifications:
self.report_specification(specification) self.report_specification(specification)
self.set_style("reset") self.set_style("reset")
def report_specification(self, specification): def report_specification(self, specification: Specification) -> None:
if specification.status is True: if specification.status is True:
self.set_style("bold", "green") self.set_style("bold", "green")
self.print("[PASS] ", end="") self.print("[PASS] ", end="")
@@ -113,7 +189,7 @@ class Console(Reporter):
self.print(" " * 12 + f"... {len(requirement.failures)} in total ...") self.print(" " * 12 + f"... {len(requirement.failures)} in total ...")
self.set_style("reset") self.set_style("reset")
def report_reason(self, failure): def report_reason(self, failure: FacetFailure) -> None:
is_bold = False is_bold = False
for substring in failure["reason"].split('"'): for substring in failure["reason"].split('"'):
if is_bold: if is_bold:
@@ -126,11 +202,11 @@ class Console(Reporter):
self.print(" - " + str(failure["element"])) self.print(" - " + str(failure["element"]))
self.set_style("reset") self.set_style("reset")
def set_style(self, *colours): def set_style(self, *colours: str):
if self.use_colour: if self.use_colour:
sys.stdout.write("".join([self.colours[c] for c in colours])) sys.stdout.write("".join([self.colours[c] for c in colours]))
def print(self, txt, end=None): def print(self, txt: str, end: Optional[str] = None):
if end is not None: if end is not None:
print(txt, end=end) print(txt, end=end)
else: else:
@@ -138,14 +214,14 @@ class Console(Reporter):
class Txt(Console): class Txt(Console):
def __init__(self, ids): def __init__(self, ids: Ids):
super().__init__(ids, use_colour=False) super().__init__(ids, use_colour=False)
self.text = "" self.text = ""
def print(self, txt, end=None): def print(self, txt: str, end: Optional[str] = None):
self.text += txt + "\n" if end is None else txt self.text += txt + "\n" if end is None else end
def to_string(self): def to_string(self) -> None:
print(self.text) print(self.text)
def to_file(self, filepath: str) -> None: def to_file(self, filepath: str) -> None:
@@ -154,11 +230,11 @@ class Txt(Console):
class Json(Reporter): class Json(Reporter):
def __init__(self, ids): def __init__(self, ids: Ids):
super().__init__(ids) super().__init__(ids)
self.results = {} self.results = Results()
def report(self): def report(self) -> Results:
self.results["title"] = self.ids.info.get("title", "Untitled IDS") self.results["title"] = self.ids.info.get("title", "Untitled IDS")
self.results["date"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.results["date"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.results["filepath"] = self.ids.filepath self.results["filepath"] = self.ids.filepath
@@ -203,7 +279,7 @@ class Json(Reporter):
) )
return self.results return self.results
def report_specification(self, specification): def report_specification(self, specification: Specification) -> ResultsSpecification:
applicability = [a.to_string("applicability") for a in specification.applicability] applicability = [a.to_string("applicability") for a in specification.applicability]
total_applicable = len(specification.applicable_entities) total_applicable = len(specification.applicable_entities)
total_checks = 0 total_checks = 0
@@ -216,57 +292,60 @@ class Json(Reporter):
total_checks += total_applicable total_checks += total_applicable
total_checks_pass += total_pass total_checks_pass += total_pass
requirements.append( requirements.append(
{ ResultsRequirement(
"description": requirement.to_string("requirement", specification, requirement), description=requirement.to_string("requirement", specification, requirement),
"status": requirement.status, status=requirement.status,
"failed_entities": self.report_failed_entities(requirement), failed_entities=self.report_failed_entities(requirement),
"total_applicable": total_applicable, total_applicable=total_applicable,
"total_pass": total_pass, total_pass=total_pass,
"total_fail": total_fail, total_fail=total_fail,
"percent_pass": percent_pass, percent_pass=percent_pass,
} )
) )
total_applicable_pass = total_applicable - len(specification.failed_entities) total_applicable_pass = total_applicable - len(specification.failed_entities)
percent_applicable_pass = ( percent_applicable_pass = (
math.floor((total_applicable_pass / total_applicable) * 100) if total_applicable else "N/A" math.floor((total_applicable_pass / total_applicable) * 100) if total_applicable else "N/A"
) )
percent_checks_pass = math.floor((total_checks_pass / total_checks) * 100) if total_checks else "N/A" percent_checks_pass = math.floor((total_checks_pass / total_checks) * 100) if total_checks else "N/A"
return {
"name": specification.name,
"description": specification.description,
"instructions": specification.instructions,
"status": specification.status,
"total_applicable": total_applicable,
"total_applicable_pass": total_applicable_pass,
"total_applicable_fail": total_applicable - total_applicable_pass,
"percent_applicable_pass": percent_applicable_pass,
"total_checks": total_checks,
"total_checks_pass": total_checks_pass,
"total_checks_fail": total_checks - total_checks_pass,
"percent_checks_pass": percent_checks_pass,
"required": specification.minOccurs != 0,
"applicability": applicability,
"requirements": requirements,
}
def report_failed_entities(self, requirement): return ResultsSpecification(
name=specification.name,
description=specification.description,
instructions=specification.instructions,
status=specification.status,
total_applicable=total_applicable,
total_applicable_pass=total_applicable_pass,
total_applicable_fail=total_applicable - total_applicable_pass,
percent_applicable_pass=percent_applicable_pass,
total_checks=total_checks,
total_checks_pass=total_checks_pass,
total_checks_fail=total_checks - total_checks_pass,
percent_checks_pass=percent_checks_pass,
required=specification.minOccurs != 0,
applicability=applicability,
requirements=requirements,
)
def report_failed_entities(self, requirement: Facet) -> list[ResultsFailedEntity]:
return [ return [
{ ResultsFailedEntity(
"reason": f["reason"], {
"element": str(f["element"]), "reason": f["reason"],
"element_type": str(ifcopenshell.util.element.get_type(f["element"])), "element": str(f["element"]),
"class": f["element"].is_a(), "element_type": str(ifcopenshell.util.element.get_type(f["element"])),
"predefined_type": ifcopenshell.util.element.get_predefined_type(f["element"]), "class": f["element"].is_a(),
"name": getattr(f["element"], "Name", None), "predefined_type": ifcopenshell.util.element.get_predefined_type(f["element"]),
"description": getattr(f["element"], "Description", None), "name": getattr(f["element"], "Name", None),
"id": f["element"].id(), "description": getattr(f["element"], "Description", None),
"global_id": getattr(f["element"], "GlobalId", None), "id": f["element"].id(),
"tag": getattr(f["element"], "Tag", None), "global_id": getattr(f["element"], "GlobalId", None),
} "tag": getattr(f["element"], "Tag", None),
}
)
for f in requirement.failures for f in requirement.failures
] ]
def to_string(self): def to_string(self) -> str:
import json import json
return json.dumps(self.results) return json.dumps(self.results)
@@ -279,11 +358,10 @@ class Json(Reporter):
class Html(Json): class Html(Json):
def __init__(self, ids): def __init__(self, ids: Ids):
super().__init__(ids) super().__init__(ids)
self.results = {}
def report(self): def report(self) -> None:
super().report() super().report()
entity_limit = 100 entity_limit = 100
for spec in self.results["specifications"]: for spec in self.results["specifications"]:
@@ -294,7 +372,7 @@ class Html(Json):
requirement["total_entities"] = total requirement["total_entities"] = total
requirement["total_omitted"] = total - entity_limit requirement["total_omitted"] = total - entity_limit
def to_string(self): def to_string(self) -> str:
import pystache import pystache
with open(os.path.join(cwd, "templates", "report.html"), "r") as file: with open(os.path.join(cwd, "templates", "report.html"), "r") as file:
@@ -309,15 +387,42 @@ class Html(Json):
class Ods(Json): class Ods(Json):
def __init__(self, ids): def __init__(self, ids: Ids, excel_safe=False):
super().__init__(ids) super().__init__(ids)
self.excel_safe = excel_safe
self.colours = { self.colours = {
"h": "cccccc", # Header "h": "cccccc", # Header
"p": "97cc64", # Pass "p": "97cc64", # Pass
"f": "fb5a3e", # Fail "f": "fb5a3e", # Fail
"t": "ffffff", # Regular text "t": "ffffff", # Regular text
} }
self.results = {}
def excel_safe_spreadsheet_name(self, name: str) -> str:
if not self.excel_safe:
return name
warning = (
f'WARNING. Sheet name "{name}" is not valid for Excel and will be changed. '
"See: https://support.microsoft.com/en-us/office/rename-a-worksheet-3f1f7148-ee83-404d-8ef0-9ff99fbad1f9"
)
if not name or name == "History":
print(warning)
return "placeholder spreadsheet name"
if name.startswith("'") or name.endswith("'"):
print(warning)
name = name.strip("'")
pattern = r"[\\\/\?\*\:\[\]]"
if re.search(pattern, name):
name = re.sub(pattern, "", name)
print(warning)
if len(name) > 31:
name = name[:31]
print(warning)
return name
def to_file(self, filepath: str) -> None: def to_file(self, filepath: str) -> None:
from odf.opendocument import OpenDocumentSpreadsheet from odf.opendocument import OpenDocumentSpreadsheet
@@ -334,7 +439,7 @@ class Ods(Json):
self.doc.automaticstyles.addElement(style) self.doc.automaticstyles.addElement(style)
self.cell_formats[key] = style self.cell_formats[key] = style
table = Table(name=self.results["title"]) table = Table(name=self.excel_safe_spreadsheet_name(self.results["title"]))
tr = TableRow() tr = TableRow()
for header in ["Specification", "Status", "Total Pass", "Total Checks", "Percentage Pass"]: for header in ["Specification", "Status", "Total Pass", "Total Checks", "Percentage Pass"]:
tc = TableCell(valuetype="string", stylename="h") tc = TableCell(valuetype="string", stylename="h")
@@ -371,7 +476,7 @@ class Ods(Json):
for specification in self.results["specifications"]: for specification in self.results["specifications"]:
if specification["status"]: if specification["status"]:
continue continue
table = Table(name=specification["name"]) table = Table(name=self.excel_safe_spreadsheet_name(specification["name"]))
tr = TableRow() tr = TableRow()
for header in [ for header in [
"Requirement", "Requirement",
@@ -419,12 +524,12 @@ class Ods(Json):
table.addElement(tr) table.addElement(tr)
self.doc.spreadsheet.addElement(table) self.doc.spreadsheet.addElement(table)
self.doc.save(filepath, addsuffix=filepath.lower().endswith(".ods")) self.doc.save(filepath, addsuffix=not filepath.lower().endswith(".ods"))
class Bcf(Json): class Bcf(Json):
def report_failed_entities(self, requirement): def report_failed_entities(self, requirement: Facet) -> list[FacetFailure]:
return [{"reason": f["reason"], "element": f["element"]} for f in requirement.failures] return [FacetFailure(f) for f in requirement.failures]
def to_file(self, filepath: str) -> None: def to_file(self, filepath: str) -> None:
import numpy as np import numpy as np
+7 -7
View File
@@ -706,24 +706,24 @@ class TestClassification:
element0 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") element0 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall")
element1 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSlab") element1 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSlab")
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", ifc, product=element1, reference=ref1, classification=system_a "classification.add_reference", ifc, products=[element1], reference=ref1, classification=system_a
) )
element11 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcColumn") element11 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcColumn")
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", ifc, product=element11, reference=ref11, classification=system_a "classification.add_reference", ifc, products=[element11], reference=ref11, classification=system_a
) )
element22 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBeam") element22 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBeam")
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", "classification.add_reference",
ifc, ifc,
product=element22, products=[element22],
reference=ref22, reference=ref22,
classification=system_a, classification=system_a,
is_lightweight=False, is_lightweight=False,
) )
material = ifc.createIfcMaterial(Name="Material") material = ifc.createIfcMaterial(Name="Material")
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", ifc, product=material, reference=ref1, classification=system_a "classification.add_reference", ifc, products=[material], reference=ref1, classification=system_a
) )
facet = Classification(system="Foobar") facet = Classification(system="Foobar")
@@ -810,15 +810,15 @@ class TestClassification:
wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType")
ifcopenshell.api.run("type.assign_type", ifc, related_objects=[wall], relating_type=wall_type) ifcopenshell.api.run("type.assign_type", ifc, related_objects=[wall], relating_type=wall_type)
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", ifc, product=wall, reference=ref11, classification=system_a "classification.add_reference", ifc, products=[wall], reference=ref11, classification=system_a
) )
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", ifc, product=wall_type, reference=ref22, classification=system_a "classification.add_reference", ifc, products=[wall_type], reference=ref22, classification=system_a
) )
system_b = ifcopenshell.api.run("classification.add_classification", ifc, classification=system_b) system_b = ifcopenshell.api.run("classification.add_classification", ifc, classification=system_b)
ifcopenshell.api.run( ifcopenshell.api.run(
"classification.add_reference", ifc, product=wall_type, reference=refx, classification=system_b "classification.add_reference", ifc, products=[wall_type], reference=refx, classification=system_b
) )
facet = Classification(system="Foobar", value="11") facet = Classification(system="Foobar", value="11")
+1 -1
View File
@@ -250,7 +250,7 @@ https://technical.buildingsmart.org/standards/ifc/ifc-schema-specifications/
https://www.sciencedirect.com/science/article/pii/S0926580523000389 https://www.sciencedirect.com/science/article/pii/S0926580523000389
- IFCOpenShell documentation - IFCOpenShell documentation
https://blenderbim.org/docs-python/ https://docs.ifcopenshell.org/
### Frontend ### Frontend