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

# Conflicts:
#	src/ifcgeom/IfcGeomTree.h
#	src/ifcgeom_schema_agnostic/IfcGeomIterator.h
#	src/ifcgeom_schema_agnostic/IteratorImplementation.cpp
#	src/ifcgeom_schema_agnostic/IteratorImplementation.h
#	src/ifcparse/Ifc4x3_rc3.cpp
#	src/ifcparse/Ifc4x3_rc3.h
#	src/serializers/SvgSerializer.cpp
This commit is contained in:
Thomas Krijnen
2021-08-10 14:30:07 +02:00
299 changed files with 23606 additions and 2849 deletions
@@ -97,3 +97,53 @@ def remove_post_listener(usecase_path, name, callback):
def remove_all_listeners():
pre_listeners.clear()
post_listeners.clear()
def extract_docs(module, usecase):
import typing
import inspect
import collections
results = []
inputs = collections.OrderedDict()
function_init = getattr(getattr(ifcopenshell.api, module), usecase).Usecase.__init__
function_execute = getattr(getattr(ifcopenshell.api, module), usecase).Usecase.execute
node_data = {"module": module, "usecase": usecase}
signature = inspect.signature(function_init)
for name, parameter in signature.parameters.items():
if name == "self":
continue
inputs[name] = {"name": name}
if isinstance(parameter.default, (str, float, int, bool)):
inputs[name]["default"] = parameter.default
type_hints = typing.get_type_hints(function_init)
for name, socket_data in inputs.items():
type_hint = type_hints[name]
if isinstance(type_hint, typing._UnionGenericAlias):
inputs[name]["type"] = [t.__name__ for t in typing.get_args(type_hint)]
else:
inputs[name]["type"] = type_hint.__name__
description = ""
for i, line in enumerate(function_init.__doc__.split("\n")):
line = line.strip()
if i == 0:
node_data["name"] = line
elif line.startswith(":return:"):
node_data["output"] = {"name": line.split(":")[2].strip(), "description": line.split(":")[3].strip()}
elif line.startswith(":param"):
param_name = line.split(":")[1].strip().replace("param ", "")
inputs[param_name]["description"] = line.split(":")[2].strip()
elif i >= 2:
description += line
if "output" in node_data:
node_data["output"]["type"] = typing.get_type_hints(function_execute)["return"].__name__
node_data["description"] = description.strip()
node_data["inputs"] = inputs
return node_data
@@ -1,4 +1,5 @@
import ifcopenshell.util.date
import ifcopenshell.util.unit
class Data:
@@ -7,6 +8,7 @@ class Data:
cost_items = {}
physical_quantities = {}
cost_values = {}
categories = []
@classmethod
def purge(cls):
@@ -15,6 +17,11 @@ class Data:
cls.cost_items = {}
cls.physical_quantities = {}
cls.cost_values = {}
cls.categories = []
@classmethod
def set_categories(cls, categories):
cls.categories = categories
@classmethod
def load(cls, file):
@@ -63,20 +70,32 @@ class Data:
del quantity_data["Unit"]
cls.physical_quantities[quantity.id()] = quantity_data
data["CostQuantities"].append(quantity.id())
data["Unit"] = None
data["UnitSymbol"] = "?"
if cost_item.CostQuantities:
quantity = cost_item.CostQuantities[0]
unit = ifcopenshell.util.unit.get_property_unit(quantity, cls.file)
if unit:
data["Unit"] = unit.id()
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
else:
data["Unit"] = None
data["UnitSymbol"] = None
@classmethod
def load_cost_item_values(cls, cost_item, data):
data["CostValues"] = []
data["TotalCostValue"] = 0.0
data["TotalAppliedValue"] = 0.0
data["CategoryValues"] = {}
for cost_value in cost_item.CostValues or []:
cls.load_cost_item_value(cost_item, cost_value)
cls.load_cost_item_value(data, cost_item, cost_value)
data["CostValues"].append(cost_value.id())
data["TotalAppliedValue"] += cls.cost_values[cost_value.id()]["AppliedValue"]
data["TotalCostValue"] = data["TotalCostQuantity"] * data["TotalAppliedValue"]
@classmethod
def load_cost_item_value(cls, cost_item, cost_value):
def load_cost_item_value(cls, cost_item_data, cost_item, cost_value):
value_data = cost_value.get_info()
del value_data["AppliedValue"]
del value_data["UnitBasis"]
@@ -86,9 +105,14 @@ class Data:
value_data["FixedUntilDate"] = ifcopenshell.util.date.ifc2datetime(value_data["FixedUntilDate"])
value_data["Components"] = [c.id() for c in value_data["Components"] or []]
value_data["AppliedValue"] = cls.calculate_applied_value(cost_item, cost_value)
if cost_value.Category not in [None, "*"]:
cost_item_data["CategoryValues"].setdefault(cost_value.Category, 0)
cost_item_data["CategoryValues"][cost_value.Category] += value_data["AppliedValue"]
cls.cost_values[cost_value.id()] = value_data
for component in cost_value.Components or []:
cls.load_cost_item_value(cost_item, component)
cls.load_cost_item_value(cost_item_data, cost_item, component)
@classmethod
def calculate_applied_value(cls, cost_item, cost_value, category_filter=None):
@@ -9,5 +9,5 @@ class Usecase:
for name, value in self.settings["attributes"].items():
if name == "AppliedValue" and value is not None:
# TODO: support all applied value select types
value = self.file.createIfcReal(value)
value = self.file.createIfcMonetaryMeasure(value)
setattr(self.settings["cost_value"], name, value)
@@ -22,8 +22,6 @@ class Usecase:
"unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
"should_force_faceted_brep": False, # If we should force faceted breps for meshes
"should_force_triangulation": False, # If we should force triangulation for meshes
"is_wireframe": False, # If the geometry is a wireframe
"is_curve": False, # If the geometry is a Blender curve
"is_point_cloud": False, # If the geometry is a point cloud
# Possible IFC representation classes:
# IfcExtrudedAreaSolid/IfcRectangleProfileDef
@@ -204,14 +202,14 @@ class Usecase:
)
def create_variable_representation(self):
if self.settings["is_wireframe"]:
return self.create_wireframe_representation()
elif self.settings["is_curve"]:
if isinstance(self.settings["geometry"], bpy.types.Curve):
return self.create_curve3d_representation()
elif isinstance(self.settings["geometry"], bpy.types.Camera):
return self.create_camera_block_representation()
elif not len(self.settings["geometry"].polygons):
return self.create_curve3d_representation()
elif self.settings["is_point_cloud"]:
return self.create_point_cloud_representation()
elif isinstance(self.settings["geometry"], bpy.types.Camera):
return self.create_camera_block_representation()
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcRectangleProfileDef":
return self.create_rectangle_extrusion_representation()
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcCircleProfileDef":
@@ -1,5 +1,6 @@
import numpy as np
import ifcopenshell.api
import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.placement
@@ -37,12 +38,10 @@ class Usecase:
placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
placement = self.file.createIfcLocalPlacement(placement_rel_to, self.get_relative_placement(placement_rel_to))
if self.settings["product"].ObjectPlacement:
old_placement = self.settings["product"].ObjectPlacement
old_placement = self.settings["product"].ObjectPlacement
if old_placement and len(self.file.get_inverse(old_placement)) == 1:
old_placement.PlacementRelTo = None
self.settings["product"].ObjectPlacement = None
for inverse in self.file.get_inverse(old_placement):
ifcopenshell.util.element.replace_attribute(inverse, old_placement, placement)
ifcopenshell.util.element.remove_deep(self.file, old_placement)
self.settings["product"].ObjectPlacement = placement
@@ -1,6 +1,3 @@
import ifcopenshell
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -16,13 +16,13 @@ class Usecase:
if material:
ifcopenshell.api.run("material.unassign_material", self.file, product=self.settings["product"])
if self.settings["type"] == "IfcMaterial":
self.assign_ifc_material()
return self.assign_ifc_material()
elif self.settings["type"] == "IfcMaterialConstituentSet":
material_set = self.file.create_entity(self.settings["type"])
self.create_material_association(material_set)
return self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialLayerSet":
material_set = self.file.create_entity(self.settings["type"])
self.create_material_association(material_set)
return self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialLayerSetUsage":
element_type = ifcopenshell.util.element.get_type(self.settings["product"])
if element_type:
@@ -34,10 +34,10 @@ class Usecase:
else:
material_set = self.file.create_entity("IfcMaterialLayerSet")
material_set_usage = self.create_layer_set_usage(material_set)
self.create_material_association(material_set_usage)
return self.create_material_association(material_set_usage)
elif self.settings["type"] == "IfcMaterialProfileSet":
material_set = self.file.create_entity(self.settings["type"])
self.create_material_association(material_set)
return self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialProfileSetUsage":
element_type = ifcopenshell.util.element.get_type(self.settings["product"])
if element_type:
@@ -51,11 +51,11 @@ class Usecase:
self.update_representation_profile(material_set)
material_set_usage = self.create_profile_set_usage(material_set)
self.create_material_association(material_set_usage)
return self.create_material_association(material_set_usage)
elif self.settings["type"] == "IfcMaterialList":
material_set = self.file.create_entity(self.settings["type"])
material_set.Materials = [self.settings["material"]]
self.create_material_association(material_set)
return self.create_material_association(material_set)
def update_representation_profile(self, material_set):
profile = material_set.CompositeProfile
@@ -93,6 +93,7 @@ class Usecase:
related_objects = list(rel.RelatedObjects)
related_objects.append(self.settings["product"])
rel.RelatedObjects = related_objects
return rel
def create_material_association(self, relating_material):
return self.file.create_entity(
@@ -0,0 +1,30 @@
import ifcopenshell
import ifcopenshell.util.element
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"material": None, "element": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
ifcopenshell.api.run("material.unassign_material", self.file, product=self.settings["element"])
if self.settings["material"].is_a("IfcMaterial"):
ifcopenshell.api.run(
"material.assign_material",
self.file,
product=self.settings["element"],
type="IfcMaterial",
material=self.settings["material"],
)
# No other material type can be copied right now.
# 1. Material lists and constituents may have shape aspects and I
# haven't implemented it yet.
# 2. Material layer and profile sets implicitly define parametric
# geometry and we have no way of guaranteeing that this constraint is
# satisfied.
# 3. Material set usages follow an unofficial constraint that all
# instances must have a usage of their type's material set. We cannot
# guarantee that constraint.
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"usage": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["usage"], name, value)
@@ -1,4 +1,5 @@
import ifcopenshell
import ifcopenshell.util.element
class Usecase:
@@ -9,11 +10,26 @@ class Usecase:
self.settings[key] = value
def execute(self):
if self.settings["product"].is_a("IfcTypeObject"):
material = ifcopenshell.util.element.get_material(self.settings["product"])
if material.is_a() in ["IfcMaterialLayerSet", "IfcMaterialProfileSet"]:
for inverse in self.file.get_inverse(material):
if self.file.schema == "IFC2X3":
if not inverse.is_a("IfcMaterialLayerSetUsage"):
continue
for inverse2 in self.file.get_inverse(inverse):
if inverse2.is_a("IfcRelAssociatesMaterial"):
self.file.remove(inverse2)
else:
if not inverse.is_a("IfcMaterialUsageDefinition"):
continue
for rel in inverse.AssociatedTo:
self.file.remove(rel)
self.file.remove(inverse)
for rel in self.settings["product"].HasAssociations:
if rel.is_a("IfcRelAssociatesMaterial"):
if rel.RelatingMaterial.is_a("IfcMaterialLayerSetUsage") or rel.RelatingMaterial.is_a(
"IfcMaterialProfileSetUsage"
):
if rel.RelatingMaterial.is_a() in ["IfcMaterialLayerSetUsage", "IfcMaterialProfileSetUsage"]:
self.file.remove(rel.RelatingMaterial)
if len(rel.RelatedObjects) == 1:
self.file.remove(rel)
@@ -6,6 +6,8 @@ class Usecase:
self.settings[key] = value
def execute(self):
address = self.file.create_entity(self.settings["ifc_class"], "OFFICE")
addresses = list(self.settings["assigned_object"].Addresses) if self.settings["assigned_object"].Addresses else []
addresses.append(self.file.create_entity(self.settings["ifc_class"], "OFFICE"))
addresses.append(address)
self.settings["assigned_object"].Addresses = addresses
return address
@@ -6,6 +6,8 @@ class Usecase:
self.settings[key] = value
def execute(self):
element = self.file.createIfcActorRole("ARCHITECT")
roles = list(self.settings["assigned_object"].Roles) if self.settings["assigned_object"].Roles else []
roles.append(self.file.createIfcActorRole("ARCHITECT"))
roles.append(element)
self.settings["assigned_object"].Roles = roles
return element
@@ -13,8 +13,32 @@ class Usecase:
self.added_elements = set()
if self.settings["element"].is_a("IfcTypeProduct"):
return self.append_type_product()
elif self.settings["element"].is_a("IfcMaterial"):
return self.append_material()
elif self.settings["element"].is_a("IfcCostSchedule"):
return self.append_cost_schedule()
def is_already_appended(self):
try:
self.file.by_guid(self.settings["element"].GlobalId)
return True
except:
return False
def append_material(self):
if [e for e in self.file.by_type("IfcMaterial") if e.Name == self.settings["element"].Name]:
return
return self.file.add(self.settings["element"])
def append_cost_schedule(self):
if self.is_already_appended():
return
self.whitelisted_inverse_attributes = {"IfcCostSchedule": ["Controls"], "IfcCostItem": ["IsNestedBy"]}
return self.add_element(self.settings["element"])
def append_type_product(self):
if self.is_already_appended():
return
self.whitelisted_inverse_attributes = {
"IfcObjectDefinition": ["HasAssociations"],
"IfcMaterialDefinition": ["HasExternalReferences", "HasProperties"],
@@ -17,6 +17,9 @@ class Usecase:
if self.settings["relating_context"].Declares:
declares = self.settings["relating_context"].Declares[0]
if not hasattr(self.settings["definition"], "HasContext"):
return
has_context = None
if self.settings["definition"].HasContext:
has_context = self.settings["definition"].HasContext[0]
@@ -3,14 +3,18 @@ import ifcopenshell
class Usecase:
def __init__(self, **settings):
self.settings = {"version": "IFC4"}
for key, value in settings.items():
self.settings[key] = value
def __init__(self, version: str = "IFC4"):
"""Create File
def execute(self):
Create a new IFC file object
:param version: The schema version of the IFC file. Choose from "IFC2X3" or "IFC4".
:return: file: The created IFC file object.
"""
self.settings = {"version": version}
def execute(self) -> ifcopenshell.file:
self.file = ifcopenshell.file(schema=self.settings["version"])
# TODO: add all metadata, pending bug #747
self.file.wrapped_data.header.file_name.name = "/dev/null" # Hehehe
self.file.wrapped_data.header.file_name.time_stamp = (
datetime.datetime.utcnow()
@@ -22,5 +26,5 @@ class Usecase:
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
self.file.wrapped_data.header.file_name.originating_system = "IfcOpenShell {}".format(ifcopenshell.version)
self.file.wrapped_data.header.file_name.authorization = "Nobody"
self.file.wrapped_data.header.file_description.description = ('ViewDefinition[DesignTransferView]',)
self.file.wrapped_data.header.file_description.description = ("ViewDefinition[DesignTransferView]",)
return self.file
@@ -22,7 +22,7 @@ class Data:
return
product = file.by_id(product_id)
cls.products[product_id] = {"psets": set(), "qtos": set()}
if product.is_a("IfcElementType"):
if product.is_a("IfcTypeObject"):
cls.add_type_product_psets(product, product_id)
elif product.is_a("IfcMaterialDefinition"):
cls.add_material_psets(product, product_id)
@@ -5,7 +5,7 @@ import ifcopenshell.util.pset
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"pset": None, "name": None, "properties": {}}
self.settings = {"pset": None, "name": None, "properties": {}, "pset_template": None}
for key, value in settings.items():
self.settings[key] = value
@@ -21,9 +21,12 @@ class Usecase:
self.settings["pset"].Name = self.settings["name"]
def load_pset_template(self):
# TODO: add IFC2X3 PsetQto template support
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
self.pset_template = self.psetqto.get_by_name(self.settings["pset"].Name)
if self.settings["pset_template"]:
self.pset_template = self.settings["pset_template"]
else:
# TODO: add IFC2X3 PsetQto template support
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
self.pset_template = self.psetqto.get_by_name(self.settings["pset"].Name)
def update_existing_properties(self):
for prop in self.get_properties():
@@ -19,7 +19,7 @@ class Usecase:
self.file,
ifc_class=self.settings["ifc_class"],
predefined_type=self.settings["predefined_type"],
name=self.settings["name"],
name=self.settings["name"] or "Unammed",
)
# TODO: this is an ambiguity by buildingSMART: Can we nest an IfcCrewResource under an IfcCrewResource ?
# https://forums.buildingsmart.org/t/what-are-allowed-to-be-root-level-construction-resources/3550
@@ -0,0 +1,15 @@
import ifcopenshell.util.date
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"resource": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
resource_time = self.file.create_entity("IfcResourceTime")
self.settings["resource"].Usage = resource_time
return resource_time
@@ -1,23 +1,57 @@
import ifcopenshell
import ifcopenshell.util.date
class Data:
is_loaded = False
resources = {}
resource_times = {}
@classmethod
def purge(cls):
cls.is_loaded = False
cls.resources = {}
cls.resource_times = {}
@classmethod
def load(cls, file):
cls._file = file
if not cls._file:
return
cls.load_resources()
cls.load_resource_times()
cls.is_loaded=True
@classmethod
def load_resources(cls):
cls.resources = {}
for resource in file.by_type("IfcResource"):
for resource in cls._file.by_type("IfcResource"):
data = resource.get_info()
del data["OwnerHistory"]
data["IsNestedBy"] = []
for rel in resource.IsNestedBy:
[data["IsNestedBy"].append(o.id()) for o in rel.RelatedObjects]
data["Nests"] = []
for rel in resource.Nests:
[data["Nests"].append(rel.RelatingObject.id())]
data["ResourceOf"] = []
for rel in resource.ResourceOf:
[data["ResourceOf"].append(o.id()) for o in rel.RelatedObjects]
data["HasContext"] = resource.HasContext[0].RelatingContext.id() if resource.HasContext else None
if resource.Usage:
data["Usage"] = data["Usage"].id()
cls.resources[resource.id()] = data
cls.is_loaded=True
@classmethod
def load_resource_times(cls):
cls.resource_times = {}
for resource_time in cls._file.by_type("IfcResourceTime"):
data = resource_time.get_info()
for key, value in data.items():
if not value:
continue
if "Start" in key or "Finish" in key or key == "StatusTime":
data[key] = ifcopenshell.util.date.ifc2datetime(value)
elif "Work" in key or key =="LevelingDelay":
data[key] = ifcopenshell.util.date.ifc2datetime(value)
cls.resource_times[resource_time.id()] = data
@@ -0,0 +1,36 @@
import datetime
import ifcopenshell.util.date
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"resource_time": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.resource = self.get_resource()
# If the user specifies both an end date and a duration, the duration takes priority
if (
self.settings["attributes"].get("ScheduleWork", None)
and "ScheduleFinish" in self.settings["attributes"].keys()
):
del self.settings["attributes"]["ScheduleFinish"]
if (
self.settings["attributes"].get("ActualWork", None)
and "ActualFinish" in self.settings["attributes"].keys()
):
del self.settings["attributes"]["ActualFinish"]
for name, value in self.settings["attributes"].items():
if value:
if "Start" in name or "Finish" in name or name == "StatusTime":
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
elif name == "ScheduleWork" or name == "ActualWork" or name == "RemainingTime":
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
setattr(self.settings["resource_time"], name, value)
def get_resource(self):
return [e for e in self.file.get_inverse(self.settings["resource_time"]) if e.is_a("IfcResource")][0]
@@ -1,4 +1,5 @@
import ifcopenshell
import ifcopenshell.util.element
class Usecase:
@@ -9,29 +10,33 @@ class Usecase:
self.settings[key] = value
def execute(self):
result = self.file.create_entity(self.settings["product"].is_a())
self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.file.schema)
self.copy_attributes(self.settings["product"], result)
for inverse in self.file.get_inverse(self.settings["product"]):
for i, value in enumerate(inverse):
if value == self.settings["product"]:
new_inverse = self.file.create_entity(inverse.is_a())
self.copy_attributes(inverse, new_inverse)
new_inverse[i] = result
elif isinstance(value, (tuple, list)) and self.settings["product"] in value:
new_value = list(value)
new_value.append(result)
inverse[i] = new_value
if result.is_a("IfcProduct"):
result.Representation = None
elif result.is_a("IfcTypeProduct"):
result.RepresentationMaps = None
result = ifcopenshell.util.element.copy(self.file, self.settings["product"])
self.copy_indirect_attributes(self.settings["product"], result)
# Copying representations is too hard, so for now we just don't do it.
self.remove_representations(result)
return result
def copy_attributes(self, from_element, to_element):
declaration = self.schema.declaration_by_name(from_element.is_a())
for attribute in declaration.all_attributes():
if attribute.name() == "GlobalId":
setattr(to_element, attribute.name(), ifcopenshell.guid.new())
def copy_indirect_attributes(self, from_element, to_element):
for inverse in self.file.get_inverse(from_element):
if inverse.is_a("IfcRelDefinesByProperties"):
inverse = ifcopenshell.util.element.copy(self.file, inverse)
inverse.RelatedObjects = [to_element]
pset = ifcopenshell.util.element.copy_deep(self.file, inverse.RelatingPropertyDefinition)
inverse.RelatingPropertyDefinition = pset
else:
setattr(to_element, attribute.name(), getattr(from_element, attribute.name()))
# TODO: Consider whether this general approach is good or not. Maybe it isn't.
for i, value in enumerate(inverse):
if value == from_element:
new_inverse = ifcopenshell.util.element.copy(self.file, inverse)
new_inverse[i] = to_element
elif isinstance(value, (tuple, list)) and from_element in value:
new_value = list(value)
new_value.append(to_element)
inverse[i] = new_value
def remove_representations(self, element):
if element.is_a("IfcProduct"):
element.Representation = None
elif element.is_a("IfcTypeProduct"):
element.RepresentationMaps = None
@@ -25,7 +25,10 @@ class Usecase:
element.PredefinedType = self.settings["predefined_type"]
except:
element.PredefinedType = "USERDEFINED"
element.ObjectType = self.settings["predefined_type"]
if hasattr(element, "ObjectType"):
element.ObjectType = self.settings["predefined_type"]
elif hasattr(element, "ElementType"):
element.ElementType = self.settings["predefined_type"]
elif hasattr(element, "ObjectType"):
element.ObjectType = self.settings["predefined_type"]
if self.file.schema == "IFC2X3":
@@ -12,9 +12,9 @@ class Usecase:
def execute(self):
self.calendar_cache = {}
self.cascade_task(self.settings["task"])
self.cascade_task(self.settings["task"], is_first_task=True)
def cascade_task(self, task):
def cascade_task(self, task, is_first_task=False):
if not task.TaskTime:
return
@@ -88,13 +88,13 @@ class Usecase:
)
if potential_finish > finish:
start_ifc = ifcopenshell.util.date.datetime2ifc(start, "IfcDateTime")
if task.TaskTime.ScheduleStart == start_ifc:
if task.TaskTime.ScheduleStart == start_ifc and not is_first_task:
return
task.TaskTime.ScheduleStart = start_ifc
task.TaskTime.ScheduleFinish = ifcopenshell.util.date.datetime2ifc(potential_finish, "IfcDateTime")
else:
finish_ifc = ifcopenshell.util.date.datetime2ifc(finish, "IfcDateTime")
if task.TaskTime.ScheduleFinish == finish_ifc:
if task.TaskTime.ScheduleFinish == finish_ifc and not is_first_task:
return
task.TaskTime.ScheduleFinish = finish_ifc
task.TaskTime.ScheduleStart = ifcopenshell.util.date.datetime2ifc(
@@ -109,7 +109,7 @@ class Usecase:
elif finishes:
finish = max(finishes)
finish_ifc = ifcopenshell.util.date.datetime2ifc(finish, "IfcDateTime")
if task.TaskTime.ScheduleFinish == finish_ifc:
if task.TaskTime.ScheduleFinish == finish_ifc and not is_first_task:
return
task.TaskTime.ScheduleFinish = finish_ifc
task.TaskTime.ScheduleStart = ifcopenshell.util.date.datetime2ifc(
@@ -124,7 +124,7 @@ class Usecase:
elif starts:
start = max(starts)
start_ifc = ifcopenshell.util.date.datetime2ifc(start, "IfcDateTime")
if task.TaskTime.ScheduleStart == start_ifc:
if task.TaskTime.ScheduleStart == start_ifc and not is_first_task:
return
task.TaskTime.ScheduleStart = start_ifc
task.TaskTime.ScheduleFinish = ifcopenshell.util.date.datetime2ifc(
@@ -16,12 +16,12 @@ class Usecase:
# If the user specifies both an end date and a duration, the duration takes priority
if (
"ScheduleDuration" in self.settings["attributes"].keys()
self.settings["attributes"].get("ScheduleDuration", None)
and "ScheduleFinish" in self.settings["attributes"].keys()
):
del self.settings["attributes"]["ScheduleFinish"]
if (
"ActualDuration" in self.settings["attributes"].keys()
self.settings["attributes"].get("ActualDuration", None)
and "ActualFinish" in self.settings["attributes"].keys()
):
del self.settings["attributes"]["ActualFinish"]
@@ -11,12 +11,13 @@ class Usecase:
def execute(self):
contained_in_structure = self.settings["product"].ContainedInStructure
if not contained_in_structure:
return
if contained_in_structure:
related_elements = list(contained_in_structure[0].RelatedElements)
related_elements.remove(self.settings["product"])
if related_elements:
contained_in_structure[0].RelatedElements = related_elements
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": contained_in_structure[0]})
else:
self.file.remove(contained_in_structure)
related_elements = list(contained_in_structure[0].RelatedElements)
related_elements.remove(self.settings["product"])
if related_elements:
contained_in_structure[0].RelatedElements = related_elements
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": contained_in_structure[0]})
else:
self.file.remove(contained_in_structure[0])
@@ -23,7 +23,7 @@ class Usecase:
self.update_colour_rgb(element.SurfaceColour, self.settings["surface_colour"])
else:
element.SurfaceColour = self.create_colour_rgb(self.settings["surface_colour"])
element.Transparency = (self.settings["transparency"] - 1) * -1
element.Transparency = self.settings["transparency"]
if element.is_a("IfcSurfaceStyleRendering"):
if element.DiffuseColour:
self.update_colour_rgb(element.DiffuseColour, self.settings["diffuse_colour"])
@@ -44,7 +44,7 @@ class Usecase:
def create_surface_style_rendering(self):
return self.file.create_entity("IfcSurfaceStyleRendering", **{
"SurfaceColour": self.create_colour_rgb(self.settings["surface_colour"]),
"Transparency": (self.settings["transparency"] - 1) * -1,
"Transparency": self.settings["transparency"],
"ReflectanceMethod": "NOTDEFINED",
"DiffuseColour": self.create_colour_rgb(self.settings["diffuse_colour"])
})
@@ -0,0 +1,17 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
return self.file.create_entity("IfcSystem", **{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"Name": "Unnamed"
})
@@ -0,0 +1,27 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"product": None,
"system": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if not self.settings["system"].IsGroupedBy:
return self.file.create_entity("IfcRelAssignsToGroup", **{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": [self.settings["product"]],
"RelatingGroup": self.settings["system"]
})
rel = self.settings["system"].IsGroupedBy[0]
related_objects = set(rel.RelatedObjects) or set()
related_objects.add(self.settings["product"])
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
@@ -0,0 +1,24 @@
class Data:
is_loaded = False
products = {}
systems = {}
@classmethod
def purge(cls):
cls.is_loaded = False
cls.products = {}
cls.systems = {}
@classmethod
def load(cls, file):
cls.products = {}
cls.systems = {}
for system in file.by_type("IfcSystem", include_subtypes=False):
if system.IsGroupedBy:
for rel in system.IsGroupedBy:
for product in rel.RelatedObjects:
cls.products.setdefault(product.id(), []).append(system.id())
data = system.get_info()
del data["OwnerHistory"]
cls.systems[system.id()] = data
cls.is_loaded=True
@@ -0,0 +1,13 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"system": None,
"attributes": {}
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["system"], name, value)
@@ -0,0 +1,11 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"system": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["system"].IsGroupedBy or []:
self.file.remove(rel)
self.file.remove(self.settings["system"])
@@ -0,0 +1,25 @@
import ifcopenshell
import ifcopenshell.api
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"product": None,
"system": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if not self.settings["system"].IsGroupedBy:
return
rel = self.settings["system"].IsGroupedBy[0]
related_objects = set(rel.RelatedObjects) or set()
related_objects.remove(self.settings["product"])
if len(related_objects):
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
else:
self.file.remove(rel)
@@ -1,18 +1,40 @@
class Data:
products = {}
types = {}
@classmethod
def purge(cls):
cls.products = {}
cls.types = {}
@classmethod
def load(cls, file, product_id):
if not file:
return
cls.file = file
product = file.by_id(product_id)
if file.schema == "IFC2X3" and not hasattr(product, "IsDefinedBy"):
if product.is_a("IfcTypeObject"):
cls.load_type(product_id)
else:
cls.load_product(product_id)
@classmethod
def load_type(cls, product_id):
product = cls.file.by_id(product_id)
cls.types[product_id] = None
if cls.file.schema == "IFC2X3":
if getattr(product, "ObjectTypeOf", None):
cls.types[product_id] = [o.id() for o in product.ObjectTypeOf[0].RelatedObjects]
else:
if getattr(product, "Types", None):
cls.types[product_id] = [o.id() for o in product.Types[0].RelatedObjects]
@classmethod
def load_product(cls, product_id):
product = cls.file.by_id(product_id)
if cls.file.schema == "IFC2X3" and not hasattr(product, "IsDefinedBy"):
cls.products[product_id] = None
elif file.schema != "IFC2X3" and not hasattr(product, "IsTypedBy"):
elif cls.file.schema != "IFC2X3" and not hasattr(product, "IsTypedBy"):
cls.products[product_id] = None
elif hasattr(product, "IsTypedBy") and product.IsTypedBy:
type = product.IsTypedBy[0].RelatingType
@@ -0,0 +1,9 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"currency": "DOLLARYDOO"}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
return self.file.create_entity("IfcMonetaryUnit", self.settings["currency"])
@@ -1,44 +1,55 @@
import ifcopenshell
import ifcopenshell.util.unit
class Usecase():
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {
"length": {
"is_metric": True,
"raw": "MILLIMETERS"
},
"area": {
"is_metric": True,
"raw": "METERS"
},
"volume": {
"is_metric": True,
"raw": "METERS"
},
"units": None,
"length": {"is_metric": True, "raw": "MILLIMETERS"},
"area": {"is_metric": True, "raw": "METERS"},
"volume": {"is_metric": True, "raw": "METERS"},
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for unit_type, data in self.settings.items():
if data["is_metric"]:
data["ifc"] = self.create_metric_unit(unit_type, data)
else:
data["ifc"] = self.create_imperial_unit(unit_type, data)
# We're going to refactor this to split unit creation and assignment
if self.settings["units"]:
units = self.settings["units"]
else:
del self.settings["units"] # TODO refactor
units = []
for unit_type, data in self.settings.items():
if data["is_metric"]:
units.append(self.create_metric_unit(unit_type, data))
else:
units.append(self.create_imperial_unit(unit_type, data))
unit_assignment = self.get_unit_assignment()
self.assign_units(unit_assignment, units)
return unit_assignment
def get_unit_assignment(self):
unit_assignment = self.file.by_type("IfcUnitAssignment")
if unit_assignment:
unit_assignment = unit_assignment[0]
# TODO: handle unit rewriting, which is complicated
else:
unit_assignment = self.file.createIfcUnitAssignment([u["ifc"] for u in self.settings.values()])
unit_assignment = self.file.createIfcUnitAssignment()
if self.file.schema == "IFC2X3":
self.file.by_type("IfcProject")[0].UnitsInContext = unit_assignment
else:
self.file.by_type("IfcContext")[0].UnitsInContext = unit_assignment
return unit_assignment
def assign_units(self, unit_assignment, new_units):
units = set(unit_assignment.Units or [])
for unit in new_units:
units.add(unit)
unit_assignment.Units = list(units)
def create_metric_unit(self, unit_type, data):
type_prefix = ""
if unit_type == "area":
@@ -72,7 +83,9 @@ class Usecase():
name = "{}inch".format(name_prefix + " " if name_prefix else "")
elif data["raw"] == "FEET":
name = "{}foot".format(name_prefix + " " if name_prefix else "")
value_component = self.file.create_entity("IfcReal", **{"wrappedValue": ifcopenshell.util.unit.si_conversions[name]})
value_component = self.file.create_entity(
"IfcReal", **{"wrappedValue": ifcopenshell.util.unit.si_conversions[name]}
)
conversion_factor = self.file.createIfcMeasureWithUnit(value_component, si_unit)
return self.file.createIfcConversionBasedUnit(
dimensional_exponents, "{}UNIT".format(unit_type.upper()), name, conversion_factor
@@ -1,3 +1,7 @@
import ifcopenshell
import ifcopenshell.util.unit
class Data:
is_loaded = False
units = {}
@@ -5,16 +9,43 @@ class Data:
@classmethod
def purge(cls):
cls.is_loaded = False
cls.unit_assignment = []
cls.units = {}
@classmethod
def load(cls, file):
if not file:
return
unit_assignment = file.by_type("IfcUnitAssignment")
cls.file = file
cls.unit_assignment = []
cls.units = {}
unit_assignment = cls.file.by_type("IfcUnitAssignment")
if not unit_assignment:
return
for unit in unit_assignment[0].Units:
pass
# TODO: implement along with UI
cls.unit_assignment.append(unit.id())
cls.load_unit(unit)
cls.is_loaded = True
@classmethod
def load_unit(cls, unit):
if unit.is_a("IfcDerivedUnit"):
data = unit.get_info()
data["Elements"] = [{"Unit": e.Unit.id(), "Exponent": e.Exponent} for e in unit.Elements]
for element in unit.Elements:
cls.load_unit(element.Unit)
cls.units[unit.id()] = data
elif unit.is_a("IfcNamedUnit"):
data = unit.get_info()
if unit.is_a("IfcSIUnit"):
data["Dimensions"] = ifcopenshell.util.unit.get_si_dimensions(unit.Name)
else:
data["Dimensions"] = unit.Dimensions.get_info()
if unit.is_a("IfcConversionBasedUnit"):
conversion_factor = unit.ConversionFactor.get_info()
cls.load_unit(unit.ConversionFactor.UnitComponent)
conversion_factor["UnitComponent"] = unit.ConversionFactor.UnitComponent.id()
data["ConversionFactor"] = conversion_factor
cls.units[unit.id()] = data
elif unit.is_a("IfcMonetaryUnit"):
cls.units[unit.id()] = unit.get_info()
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"unit": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["unit"], name, value)
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"unit": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["unit"], name, value)
@@ -0,0 +1,10 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"unit": None, "attributes": {}}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for name, value in self.settings["attributes"].items():
setattr(self.settings["unit"], name, value)
@@ -0,0 +1,18 @@
import ifcopenshell.util.element
class Usecase():
def __init__(self, file, **settings):
self.file = file
self.settings = {"unit": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
unit_assignment = self.file.by_type("IfcUnitAssignment")[0]
units = list(unit_assignment.Units)
units.remove(self.settings["unit"])
if not units:
return
unit_assignment.Units = units
ifcopenshell.util.element.remove_deep(self.file, self.settings["unit"])
@@ -176,12 +176,14 @@ const IfcParse::enumeration_type& %(schema_name)s::%(name)s::Class() { return *%
}
%(schema_name)s::%(name)s::%(name)s(Value v) {
data_ = new IfcEntityInstanceData(%(schema_name_upper)s_%(name)s_type);
IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument();
attr->set(IfcWrite::IfcWriteArgument::EnumerationReference(v,ToString(v)));
data_->setArgument(0,attr);
}
%(schema_name)s::%(name)s::%(name)s(const std::string& v) {
data_ = new IfcEntityInstanceData(%(schema_name_upper)s_%(name)s_type);
IfcWrite::IfcWriteArgument* attr = new IfcWrite::IfcWriteArgument();
attr->set(IfcWrite::IfcWriteArgument::EnumerationReference(FromString(v),ToString(FromString(v))));
data_->setArgument(0,attr);
+27 -2
View File
@@ -264,19 +264,44 @@ class file(object):
eid = kwargs.pop("id", -1)
except:
pass
e = entity_instance((self.schema, type), self)
self.wrapped_data.add(e.wrapped_data, eid)
e.wrapped_data.this.disown()
# Create pairs of {attribute index, attribute value}.
# Keyword arguments are mapped to their corresponding
# numeric index with get_argument_index().
# @todo we should probably check that values for
# attributes are not passed as duplicates using
# both regular arguments and keyword arguments.
attrs = list(enumerate(args)) + [(e.wrapped_data.get_argument_index(name), arg) for name, arg in kwargs.items()]
# Don't store these attributes as transactions
# as the creation it self is already stored with
# it's arguments
if attrs:
transaction = self.transaction
self.transaction = None
for idx, arg in attrs:
e[idx] = arg
# Restore transaction status
if attrs:
self.transaction = transaction
# Once the values are populated add the instance
# to the file.
self.wrapped_data.add(e.wrapped_data, eid)
# The file container now handles the lifetime of
# this instance. Tell SWIG that it is no longer
# the owner.
e.wrapped_data.this.disown()
if self.transaction:
self.transaction.store_create(e)
return e
def __getattr__(self, attr):
@@ -184,6 +184,9 @@ def create_shape(settings, inst, repr=None):
or
Return an OpenCASCADE BRep if settings.USE_PYTHON_OPENCASCADE == True
Note that in Python, you must store a reference to the element returned by this function to prevent garbage
collection when you access its children. See #1124.
example:
settings = ifcopenshell.geom.settings()
@@ -195,9 +198,12 @@ def create_shape(settings, inst, repr=None):
for i, product in enumerate(products):
if product.Representation is not None:
try:
shape = geom.create_shape(settings, inst=product).geometry
created_shape = geom.create_shape(settings, inst=product)
shape = created_shape.geometry # see #1124
shape_gpXYZ = shape.Location().Transformation().TranslationPart() # These are methods of the TopoDS_Shape class from pythonOCC
print(shape_gpXYZ.X(), shape_gpXYZ.Y(), shape_gpXYZ.Z()) # These are methods of the gpXYZ class from pythonOCC
except:
print("Shape creation failed")
"""
return wrap_shape_creation(
settings,
@@ -42,7 +42,7 @@ def ifc2datetime(element):
element.DateComponent.DayComponent,
element.TimeComponent.HourComponent,
element.TimeComponent.MinuteComponent,
element.TimeComponent.SecondComponent,
int(element.TimeComponent.SecondComponent),
# TODO: implement TimeComponent timezone
)
elif element.is_a("IfcCalendarDate"):
@@ -73,10 +73,8 @@ def get_material(element, should_skip_usage=False):
return relationship.RelatingMaterial.ForProfileSet
return relationship.RelatingMaterial
relating_type = get_type(element)
if hasattr(relating_type, "HasAssociations") and relating_type.HasAssociations:
for relationship in relating_type.HasAssociations:
if relationship.is_a("IfcRelAssociatesMaterial"):
return relationship.RelatingMaterial
if relating_type != element and hasattr(relating_type, "HasAssociations") and relating_type.HasAssociations:
return get_material(relating_type, should_skip_usage)
def get_container(element):
@@ -119,7 +117,10 @@ def copy(ifc_file, element):
for i, attribute in enumerate(element):
if attribute is None:
continue
new[i] = attribute
if new.attribute_name(i) == "GlobalId":
new[i] = ifcopenshell.guid.new()
else:
new[i] = attribute
return new
@@ -0,0 +1,107 @@
cobie_type_classes = [
"IfcDoorStyle",
"IfcBuildingElementProxyType",
"IfcChimneyType",
"IfcCoveringType",
"IfcDoorType",
"IfcFootingType",
"IfcPileType",
"IfcRoofType",
"IfcShadingDeviceType",
"IfcWindowType",
"IfcDistributionControlElementType",
"IfcDistributionChamberElementType",
"IfcEnergyConversionDeviceType",
"IfcFlowControllerType",
"IfcFlowMovingDeviceType",
"IfcFlowStorageDeviceType",
"IfcFlowTerminalType",
"IfcFlowTreatmentDeviceType",
"IfcElementAssemblyType",
"IfcBuildingElementPartType",
"IfcDiscreteAccessoryType",
"IfcMechanicalFastenerType",
"IfcReinforcingElementType",
"IfcVibrationIsolatorType",
"IfcFurnishingElementType",
"IfcGeographicElementType",
"IfcTransportElementType",
"IfcSpatialZoneType",
"IfcWindowStyle",
]
cobie_component_classes = [
"IfcBuildingElementProxy",
"IfcChimney",
"IfcCovering",
"IfcDoor",
"IfcShadingDevice",
"IfcWindow",
"IfcDistributionControlElement",
"IfcDistributionChamberElement",
"IfcEnergyConversionDevice",
"IfcFlowController",
"IfcFlowMovingDevice",
"IfcFlowStorageDevice",
"IfcFlowTerminal",
"IfcFlowTreatmentDevice",
"IfcDiscreteAccessory",
"IfcTendon",
"IfcTendonAnchor",
"IfcVibrationIsolator",
"IfcFurnishingElement",
"IfcGeographicElement",
"IfcTransportElement",
]
fmhem_classes = [
"IfcDoorStyle",
"IfcWindowStyle",
"IfcDoorType",
"IfcWindowType",
"IfcRoofType",
"IfcShadingDeviceType",
"IfcDistributionControlElementType",
"IfcEnergyConversionDeviceType",
"IfcFlowControllerType",
"IfcJunctionBoxType",
"IfcFlowMovingDeviceType",
"IfcFlowStorageDeviceType",
"IfcFlowTerminalType",
"IfcFlowTreatmentDeviceType",
"IfcFurnishingElementType",
"IfcTransportElementType",
]
def get_cobie_types(ifc_file):
elements = []
for ifc_class in cobie_type_classes:
try:
elements += ifc_file.by_type(ifc_class)
except:
pass
return elements
def get_cobie_components(ifc_file):
elements = []
for ifc_class in cobie_component_classes:
try:
elements += ifc_file.by_type(ifc_class)
except:
pass
return elements
def get_fmhem_types(ifc_file):
elements = []
for ifc_class in fmhem_classes:
try:
if ifc_class == "IfcEnergyConversionDeviceType":
elements += [e for e in ifc_file.by_type(ifc_class) if not e.is_a("IfcCooledBeamType")]
else:
elements += ifc_file.by_type(ifc_class)
except:
pass
return elements
@@ -18,6 +18,17 @@ def is_a(entity, ifc_class):
return False
def get_subtypes(entity):
def get_classes(declaration):
results = []
if not declaration.is_abstract():
results.append(declaration)
for subtype in declaration.subtypes():
results.extend(get_classes(subtype))
return results
return get_classes(entity)
def reassign_class(ifc_file, element, new_class):
try:
new_element = ifc_file.create_entity(new_class)
@@ -1,62 +1,8 @@
import ifcopenshell.util
import ifcopenshell.util.fm
import ifcopenshell.util.element
import lark
cobie_type_assets = [
"IfcDoorStyle",
"IfcBuildingElementProxyType",
"IfcChimneyType",
"IfcCoveringType",
"IfcDoorType",
"IfcFootingType",
"IfcPileType",
"IfcRoofType",
"IfcShadingDeviceType",
"IfcWindowType",
"IfcDistributionControlElementType",
"IfcDistributionChamberElementType",
"IfcEnergyConversionDeviceType",
"IfcFlowControllerType",
"IfcFlowMovingDeviceType",
"IfcFlowStorageDeviceType",
"IfcFlowTerminalType",
"IfcFlowTreatmentDeviceType",
"IfcElementAssemblyType",
"IfcBuildingElementPartType",
"IfcDiscreteAccessoryType",
"IfcMechanicalFastenerType",
"IfcReinforcingElementType",
"IfcVibrationIsolatorType",
"IfcFurnishingElementType",
"IfcGeographicElementType",
"IfcTransportElementType",
"IfcSpatialZoneType",
"IfcWindowStyle",
]
cobie_component_assets = [
"IfcBuildingElementProxy",
"IfcChimney",
"IfcCovering",
"IfcDoor",
"IfcShadingDevice",
"IfcWindow",
"IfcDistributionControlElement",
"IfcDistributionChamberElement",
"IfcEnergyConversionDevice",
"IfcFlowController",
"IfcFlowMovingDevice",
"IfcFlowStorageDevice",
"IfcFlowTerminal",
"IfcFlowTreatmentDevice",
"IfcDiscreteAccessory",
"IfcTendon",
"IfcTendonAnchor",
"IfcVibrationIsolator",
"IfcFurnishingElement",
"IfcGeographicElement",
"IfcTransportElement",
]
class Selector:
def parse(self, ifc_file, query):
@@ -74,9 +20,10 @@ class Selector:
filter_value: ESCAPED_STRING
pset_or_qto: /[A-Za-z0-9_]+/ "." /[A-Za-z0-9_]+/
lfunction: and | or
inverse_relationship: types | contains_elements
inverse_relationship: types | contains_elements | boundedby
types: "*"
contains_elements: "@"
boundedby: "@@"
and: "&"
or: "|"
comparison: contains | morethanequalto | lessthanequalto | equal | morethan | lessthan
@@ -170,23 +117,18 @@ class Selector:
elif inverse_relationship == "contains_elements" and hasattr(element, "ContainsElements"):
for relationship in element.ContainsElements:
results.extend(relationship.RelatedElements)
elif inverse_relationship == "boundedby" and hasattr(element, "BoundedBy"):
for relationship in element.BoundedBy:
results.append(relationship.RelatedBuildingElement)
return results
def get_class_selector(self, class_selector):
if class_selector.children[0] == "COBie":
elements = []
for ifc_class in cobie_component_assets:
try:
elements += self.file.by_type(ifc_class)
except:
pass
ifcopenshell.util.fm.get_cobie_components(self.file)
elif class_selector.children[0] == "COBieType":
elements = []
for ifc_class in cobie_type_assets:
try:
elements += self.file.by_type(ifc_class)
except:
pass
ifcopenshell.util.fm.get_cobie_types(self.file)
elif class_selector.children[0] == "FMHEM":
ifcopenshell.util.fm.get_fmhem_types(self.file)
else:
elements = self.file.by_type(class_selector.children[0])
if len(class_selector.children) > 1 and class_selector.children[1].data == "filter":
@@ -12,13 +12,14 @@ with open(os.path.join(cwd, "entity_to_type_map_2x3.json")) as f:
with open(os.path.join(cwd, "entity_to_type_map_4.json")) as f:
entity_to_type_map["IFC4"] = json.load(f)
type_to_entity_map["IFC2X3"] = {
value: [key] for key in entity_to_type_map["IFC2X3"] for value in entity_to_type_map["IFC2X3"][key]
}
type_to_entity_map["IFC4"] = {
value: [key] for key in entity_to_type_map["IFC4"] for value in entity_to_type_map["IFC4"][key]
}
for schema in ["IFC2X3", "IFC4"]:
type_to_entity_map[schema] = {}
for element, element_types in entity_to_type_map[schema].items():
for element_type in element_types:
type_to_entity_map[schema].setdefault(element_type, []).append(element)
# TODO: this method just fails for IFC2X3 because 2X3 type mapping is just so broken.
# Uncomment the following line to see how bad it is.
# print(type_to_entity_map[schema])
def get_applicable_types(ifc_class, schema="IFC4"):
@@ -52,6 +52,41 @@ unit_names = [
"WEBER",
]
si_dimensions = {
"METRE": (1, 0, 0, 0, 0, 0, 0),
"SQUARE_METRE": (2, 0, 0, 0, 0, 0, 0),
"CUBIC_METRE": (3, 0, 0, 0, 0, 0, 0),
"GRAM": (0, 1, 0, 0, 0, 0, 0),
"SECOND": (0, 0, 1, 0, 0, 0, 0),
"AMPERE": (0, 0, 0, 1, 0, 0, 0),
"KELVIN": (0, 0, 0, 0, 1, 0, 0),
"MOLE": (0, 0, 0, 0, 0, 1, 0),
"CANDELA": (0, 0, 0, 0, 0, 0, 1),
"RADIAN": (0, 0, 0, 0, 0, 0, 0),
"STERADIAN": (0, 0, 0, 0, 0, 0, 0),
"HERTZ": (0, 0, -1, 0, 0, 0, 0),
"NEWTON": (1, 1, -2, 0, 0, 0, 0),
"PASCAL": (-1, 1, -2, 0, 0, 0, 0),
"JOULE": (2, 1, -2, 0, 0, 0, 0),
"WATT": (2, 1, -3, 0, 0, 0, 0),
"COULOMB": (0, 0, 1, 1, 0, 0, 0),
"VOLT": (2, 1, -3, -1, 0, 0, 0),
"FARAD": (-2, -1, 4, 2, 0, 0, 0),
"OHM": (2, 1, -3, -2, 0, 0, 0),
"SIEMENS": (-2, -1, 3, 2, 0, 0, 0),
"WEBER": (2, 1, -2, -1, 0, 0, 0),
"TESLA": (0, 1, -2, -1, 0, 0, 0),
"HENRY": (2, 1, -2, -2, 0, 0, 0),
"DEGREE_CELSIUS": (0, 0, 0, 0, 1, 0, 0),
"LUMEN": (0, 0, 0, 0, 0, 0, 1),
"LUX": (-2, 0, 0, 0, 0, 0, 1),
"BECQUEREL": (0, 0, -1, 0, 0, 0, 0),
"GRAY": (2, 0, -2, 0, 0, 0, 0),
"SIEVERT": (2, 0, -2, 0, 0, 0, 0),
"OTHERWISE": (0, 0, 0, 0, 0, 0, 0),
}
si_conversions = {
"inch": 0.0254,
"foot": 0.3048,
@@ -87,6 +122,33 @@ si_conversions = {
"btu": 1055.056,
}
prefix_symbols = {
"EXA": "E",
"PETA": "P",
"TERA": "T",
"GIGA": "G",
"MEGA": "M",
"KILO": "k",
"HECTO": "h",
"DECA": "da",
"DECI": "d",
"CENTI": "c",
"MILLI": "m",
"MICRO": "μ",
"NANO": "n",
"PICO": "p",
"FEMTO": "f",
"ATTO": "a",
}
unit_symbols = {
"CUBIC_METRE": "m3",
"GRAM": "g",
"SECOND": "s",
"SQUARE_METRE": "m2",
"METRE": "m",
}
def get_prefix(text):
if text:
@@ -105,11 +167,63 @@ def get_prefix_multiplier(text):
def get_unit_name(text):
text = text.upper().replace("METER", "METRE")
for name in unit_names:
if name in text.upper().replace("METER", "METRE"):
if name in text:
return name
def get_si_dimensions(name):
return si_dimensions.get(name, si_dimensions["OTHERWISE"])
def get_property_unit(prop, ifc_file):
unit = getattr(prop, "Unit", None)
if unit:
return unit
unit_assignment = ifc_file.by_type("IfcUnitAssignment")
if not unit_assignment:
return
entity = prop.wrapped_data.declaration().as_entity()
if prop.is_a("IfcPhysicalSimpleQuantity"):
measure_type = entity.attribute_by_index(3).type_of_attribute().declared_type().name()
elif prop.is_a("IfcPropertySingleValue") and prop.NominalValue:
measure_type = prop.NominalValue.is_a()
for text in ("Ifc", "Measure", "Non", "Positive", "Negative"):
measure_type = measure_type.replace(text, "")
measure_type = measure_type.upper() + "UNIT"
units = [u for u in unit_assignment[0].Units if getattr(u, "UnitType", None) == measure_type]
if units:
return units[0]
def get_symbol_quantity_class(symbol):
# Dumb, but everybody gets it, unlike regex golf
if not symbol:
return "IfcQuantityCount"
symbol = symbol.lower()
if symbol in ["kg", "g", "mt", "kt", "t"]:
return "IfcQuantityWeight"
elif symbol in ["day", "d", "hour", "hr", "h", "minute", "min", "m", "second", "sec", "s"]:
return "IfcQuantityTime"
elif symbol in ["km3", "m3", "cm3", "mm3", "cy", "cft", "cin"]:
return "IfcQuantityVolume"
elif symbol in ["km2", "m2", "cm2", "mm2", "sqy", "sqft", "sqin"]:
return "IfcQuantityArea"
elif symbol in ["km", "m", "cm", "mm", "ly", "lf", "lin", "yd", "ft", "in"]:
return "IfcQuantityLength"
return "IfcQuantityCount"
def get_unit_symbol(unit):
if unit.is_a("IfcSIUnit"):
symbol = ""
symbol += prefix_symbols.get(unit.Prefix, "")
symbol += unit_symbols.get(unit.Name.replace("METER", "METRE"), "?")
return symbol
return "?"
def convert(value, from_prefix, from_unit, to_prefix, to_unit):
"""Converts between length, area, and volume units
@@ -155,6 +269,8 @@ Example::
:returns: The scale factor
:rtype: float
"""
def calculate_unit_scale(file):
units = file.by_type("IfcUnitAssignment")[0]
unit_scale = 1
@@ -34,7 +34,7 @@ class json_logger:
self.instance = instance
def log(self, level, message, *args, **kwargs):
self.statements.append(log_entry_type(level, message % args, kwargs.get('instance'))._asdict())
self.statements.append(log_entry_type(level, message % args, kwargs.get("instance"))._asdict())
def __getattr__(self, level):
return functools.partial(self.log, level, instance=self.instance)
@@ -75,7 +75,7 @@ def assert_valid(attr, val, schema):
while isinstance(attr_type, type_wrappers):
attr_type = attr_type.declared_type()
invalid = False
if isinstance(attr_type, simple_type):
@@ -120,10 +120,10 @@ def validate(f, logger):
numeric identifiers or invalidate entity names are not caught by this function. Some of these might have been
logged and can be retrieved by calling `ifcopenshell.get_log()`. A verification of the type, entity and global
WHERE rules is also not implemented.
For every entity instance in the model, it is checked that the entity is not abstract that every attribute value
is of the correct type and that the inverse attributes are of the correct cardinality.
Express simple types are checked for their valuation type. For select types it is asserted that the value conforms
to one of the leaves. For enumerations it is checked that the value is indeed on of the items. For aggregations it
is checked that the elements and the cardinality conforms. Type declarations (IfcInteger which is an integer) are