mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
First attempt at ripping out agnostic code into ifcopenshell.api namespace. See #1399.
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.schema # TODO: refactor
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {"product": None, "Name": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["product"].is_a("IfcObject"):
|
||||
pset = self.file.create_entity(
|
||||
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["Name"]}
|
||||
)
|
||||
self.file.create_entity(
|
||||
"IfcRelDefinesByProperties",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
# TODO: owner history
|
||||
"RelatedObjects": [self.settings["product"]],
|
||||
"RelatingPropertyDefinition": pset,
|
||||
}
|
||||
)
|
||||
elif self.settings["product"].is_a("IfcTypeObject"):
|
||||
pset = self.file.create_entity(
|
||||
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["Name"]}
|
||||
)
|
||||
has_property_sets = list(self.settings["product"].HasPropertySets)
|
||||
has_property_sets.append(pset)
|
||||
self.settings["product"].HasPropertySets = has_property_sets
|
||||
elif self.settings["product"].is_a("IfcMaterialDefinition"):
|
||||
pset = self.file.create_entity(
|
||||
"IfcMaterialProperties",
|
||||
**{
|
||||
"Name": self.settings["Name"],
|
||||
"Material": self.settings["product"],
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.schema # TODO: refactor
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {"product": None, "Name": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["product"].is_a("IfcObject"):
|
||||
qto = self.file.create_entity(
|
||||
"IfcElementQuantity", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["Name"]}
|
||||
)
|
||||
self.file.create_entity(
|
||||
"IfcRelDefinesByProperties",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
# TODO: owner history
|
||||
"RelatedObjects": [self.settings["product"]],
|
||||
"RelatingPropertyDefinition": qto,
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,190 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
import blenderbim.bim.schema # TODO: refactor elsewhere
|
||||
|
||||
|
||||
class Data:
|
||||
products = {}
|
||||
psets = {}
|
||||
qtos = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.products = {}
|
||||
cls.psets = {}
|
||||
cls.qtos = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file, product_id):
|
||||
cls._file = file
|
||||
cls._schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema)
|
||||
if not file:
|
||||
return
|
||||
product = file.by_id(product_id)
|
||||
cls.products[product_id] = {"psets": set(), "qtos": set()}
|
||||
if product.is_a("IfcElementType"):
|
||||
cls.add_type_product_psets(product, product_id)
|
||||
elif product.is_a("IfcMaterialDefinition"):
|
||||
cls.add_material_psets(product, product_id)
|
||||
else:
|
||||
cls.add_product_psets(product, product_id)
|
||||
|
||||
@classmethod
|
||||
def add_type_product_psets(cls, product, product_id):
|
||||
if not hasattr(product, "HasPropertySets") or not product.HasPropertySets:
|
||||
return # TODO
|
||||
for definition in product.HasPropertySets:
|
||||
if definition.is_a("IfcPropertySet"):
|
||||
cls.add_pset(definition, product_id)
|
||||
|
||||
@classmethod
|
||||
def add_material_psets(cls, product, product_id):
|
||||
if not product.HasProperties:
|
||||
return
|
||||
for pset in product.HasProperties:
|
||||
cls.add_pset(pset, product_id)
|
||||
|
||||
@classmethod
|
||||
def add_product_psets(cls, product, product_id):
|
||||
if not hasattr(product, "IsDefinedBy") or not product.IsDefinedBy:
|
||||
return
|
||||
for definition in product.IsDefinedBy:
|
||||
if not definition.is_a("IfcRelDefinesByProperties"):
|
||||
continue
|
||||
if definition.RelatingPropertyDefinition.is_a("IfcPropertySet"):
|
||||
cls.add_pset(definition.RelatingPropertyDefinition, product_id)
|
||||
elif definition.RelatingPropertyDefinition.is_a("IfcElementQuantity"):
|
||||
cls.add_qto(definition.RelatingPropertyDefinition, product_id)
|
||||
|
||||
@classmethod
|
||||
def add_pset(cls, pset, product_id):
|
||||
new_pset = {
|
||||
"Name": pset.Name,
|
||||
"is_expanded": True,
|
||||
"Properties": cls.get_properties_from_template(pset.Name) or [],
|
||||
}
|
||||
cls.products[product_id]["psets"].add(int(pset.id()))
|
||||
cls.psets[int(pset.id())] = new_pset
|
||||
try:
|
||||
if hasattr(pset, "HasProperties"):
|
||||
props = pset.HasProperties
|
||||
elif hasattr(pset, "Properties"): # For IfcMaterialProperties
|
||||
props = pset.Properties
|
||||
except:
|
||||
return # I've seen ArchiCAD produce invalid IFCs with empty data
|
||||
# Invalid IFC, but some vendors like Solidworks do this so we accomodate it
|
||||
if not props:
|
||||
return
|
||||
for prop in props:
|
||||
if prop.is_a("IfcPropertySingleValue") and prop.NominalValue:
|
||||
has_existing_prop = False
|
||||
for existing_prop in new_pset["Properties"]:
|
||||
if existing_prop["Name"] == prop.Name:
|
||||
if prop.NominalValue is None:
|
||||
existing_prop["value"] = None
|
||||
elif existing_prop["type"] == "string":
|
||||
existing_prop["value"] = str(prop.NominalValue.wrappedValue)
|
||||
elif existing_prop["type"] == "float":
|
||||
existing_prop["value"] = float(prop.NominalValue.wrappedValue)
|
||||
elif existing_prop["type"] == "integer":
|
||||
existing_prop["value"] = int(prop.NominalValue.wrappedValue)
|
||||
elif existing_prop["type"] == "boolean":
|
||||
existing_prop["value"] = bool(prop.NominalValue.wrappedValue)
|
||||
elif existing_prop["type"] == "enum":
|
||||
existing_prop["value"] = str(prop.NominalValue.wrappedValue)
|
||||
existing_prop["is_null"] = prop.NominalValue.wrappedValue is None
|
||||
has_existing_prop = True
|
||||
break
|
||||
if not has_existing_prop:
|
||||
value = prop.NominalValue.wrappedValue
|
||||
if isinstance(value, str):
|
||||
data_type = "string"
|
||||
elif isinstance(value, float):
|
||||
data_type = "float"
|
||||
elif isinstance(value, bool):
|
||||
data_type = "boolean"
|
||||
elif isinstance(value, int):
|
||||
data_type = "integer"
|
||||
else:
|
||||
data_type = "string"
|
||||
value = str(value)
|
||||
new_pset["Properties"].append(
|
||||
{
|
||||
"Name": prop.Name,
|
||||
"value": value,
|
||||
"type": data_type,
|
||||
"enum_items": [],
|
||||
"is_null": prop.NominalValue.wrappedValue is None,
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def add_qto(cls, qto, product_id):
|
||||
new_qto = {
|
||||
"Name": qto.Name,
|
||||
"is_expanded": True,
|
||||
"Properties": cls.get_properties_from_template(qto.Name) or [],
|
||||
}
|
||||
cls.products[product_id]["qtos"].add(int(qto.id()))
|
||||
cls.qtos[int(qto.id())] = new_qto
|
||||
for prop in qto.Quantities or []:
|
||||
if prop.is_a("IfcPhysicalSimpleQuantity"):
|
||||
value = prop[3]
|
||||
has_existing_prop = False
|
||||
for existing_prop in new_qto["Properties"]:
|
||||
if existing_prop["Name"] == prop.Name:
|
||||
existing_prop["value"] = float(value)
|
||||
existing_prop["is_null"] = value is None
|
||||
has_existing_prop = True
|
||||
break
|
||||
if not has_existing_prop:
|
||||
new_qto["Properties"].append(
|
||||
{
|
||||
"Name": prop.Name,
|
||||
"value": float(value),
|
||||
"type": "float",
|
||||
"enum_items": [],
|
||||
"is_null": value is None,
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_properties_from_template(cls, name):
|
||||
template = blenderbim.bim.schema.ifc.psetqto.get_by_name(name)
|
||||
if not template:
|
||||
return
|
||||
properties = []
|
||||
for prop_template in template.HasPropertyTemplates:
|
||||
if not prop_template.is_a("IfcSimplePropertyTemplate"):
|
||||
continue # Other types not yet supported
|
||||
enum_items = []
|
||||
|
||||
if prop_template.TemplateType == "P_SINGLEVALUE":
|
||||
try:
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(
|
||||
cls._schema.declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
|
||||
)
|
||||
except:
|
||||
# TODO: Occurs if the data type is something that exists in IFC4 and not in IFC2X3. To fully fix
|
||||
# this we need to generate the IFC2X3 pset template definitions.
|
||||
continue
|
||||
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
|
||||
data_type = "enum"
|
||||
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
|
||||
elif prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
|
||||
data_type = "float"
|
||||
elif prop_template.TemplateType == "Q_COUNT":
|
||||
data_type = "integer"
|
||||
else:
|
||||
continue # Other types not yet supported
|
||||
|
||||
properties.append(
|
||||
{
|
||||
"Name": prop_template.Name,
|
||||
"value": None,
|
||||
"type": data_type,
|
||||
"enum_items": enum_items,
|
||||
"is_null": True,
|
||||
}
|
||||
)
|
||||
return properties
|
||||
@@ -0,0 +1,75 @@
|
||||
import blenderbim.bim.schema # TODO: refactor
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {"pset": None, "Name": None, "Properties": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
self.update_pset_name()
|
||||
self.load_pset_template()
|
||||
self.update_existing_properties()
|
||||
new_properties = self.add_new_properties()
|
||||
self.extend_pset_with_new_properties(new_properties)
|
||||
|
||||
def update_pset_name(self):
|
||||
if self.settings["Name"]:
|
||||
self.settings["pset"].Name = self.settings["Name"]
|
||||
|
||||
def load_pset_template(self):
|
||||
self.pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(self.settings["pset"].Name)
|
||||
|
||||
def update_existing_properties(self):
|
||||
for prop in self.get_properties():
|
||||
self.update_existing_property(prop)
|
||||
|
||||
def update_existing_property(self, prop):
|
||||
if prop.Name not in self.settings["Properties"]:
|
||||
return
|
||||
value = self.settings["Properties"][prop.Name]
|
||||
if value is None:
|
||||
prop.NominalValue = None
|
||||
else:
|
||||
primary_measure_type = self.get_primary_measure_type(prop.Name, previous_value=prop.NominalValue)
|
||||
prop.NominalValue = self.file.create_entity(primary_measure_type, value)
|
||||
del self.settings["Properties"][prop.Name]
|
||||
|
||||
def add_new_properties(self):
|
||||
properties = []
|
||||
for name, value in self.settings["Properties"].items():
|
||||
if value is None:
|
||||
continue
|
||||
primary_measure_type = self.get_primary_measure_type(name)
|
||||
properties.append(
|
||||
self.file.create_entity(
|
||||
"IfcPropertySingleValue",
|
||||
**{"Name": name, "NominalValue": self.file.create_entity(primary_measure_type, value)},
|
||||
)
|
||||
)
|
||||
return properties
|
||||
|
||||
def extend_pset_with_new_properties(self, new_properties):
|
||||
props = list(self.get_properties())
|
||||
props.extend(new_properties)
|
||||
if hasattr(self.settings["pset"], "HasProperties"):
|
||||
self.settings["pset"].HasProperties = props
|
||||
elif hasattr(self.settings["pset"], "Properties"):
|
||||
self.settings["pset"].Properties = props
|
||||
|
||||
def get_properties(self):
|
||||
if hasattr(self.settings["pset"], "HasProperties"):
|
||||
return self.settings["pset"].HasProperties or []
|
||||
elif hasattr(self.settings["pset"], "Properties"): # For IfcMaterialProperties
|
||||
return self.settings["pset"].Properties or []
|
||||
|
||||
def get_primary_measure_type(self, name, previous_value=None):
|
||||
if not self.pset_template:
|
||||
return previous_value.is_a() if previous_value else "IfcLabel"
|
||||
for prop_template in self.pset_template.HasPropertyTemplates:
|
||||
if prop_template.Name != name:
|
||||
continue
|
||||
return prop_template.PrimaryMeasureType or "IfcLabel"
|
||||
return previous_value.is_a() if previous_value else "IfcLabel"
|
||||
@@ -0,0 +1,72 @@
|
||||
import blenderbim.bim.schema # TODO: refactor
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {"qto": None, "Name": None, "Properties": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
self.update_qto_name()
|
||||
self.load_qto_template()
|
||||
self.update_existing_properties()
|
||||
new_properties = self.add_new_properties()
|
||||
self.extend_qto_with_new_properties(new_properties)
|
||||
|
||||
def update_qto_name(self):
|
||||
if self.settings["Name"]:
|
||||
self.settings["qto"].Name = self.settings["Name"]
|
||||
|
||||
def load_qto_template(self):
|
||||
self.qto_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(self.settings["qto"].Name)
|
||||
|
||||
def update_existing_properties(self):
|
||||
for prop in self.settings["qto"].Quantities or []:
|
||||
self.update_existing_property(prop)
|
||||
|
||||
def update_existing_property(self, prop):
|
||||
if prop.Name not in self.settings["Properties"]:
|
||||
return
|
||||
value = self.settings["Properties"][prop.Name]
|
||||
if prop.is_a("IfcPhysicalSimpleQuantity"):
|
||||
prop[3] = float(value) if value else None
|
||||
del self.settings["Properties"][prop.Name]
|
||||
|
||||
def add_new_properties(self):
|
||||
properties = []
|
||||
for name, value in self.settings["Properties"].items():
|
||||
if value is None:
|
||||
continue
|
||||
property_type = self.get_canonical_property_type(name)
|
||||
properties.append(
|
||||
self.file.create_entity(
|
||||
"IfcQuantity{}".format(property_type),
|
||||
**{"Name": name, "{}Value".format(property_type): value},
|
||||
)
|
||||
)
|
||||
return properties
|
||||
|
||||
def extend_qto_with_new_properties(self, new_properties):
|
||||
props = list(self.settings["qto"].Quantities) if self.settings["qto"].Quantities else []
|
||||
props.extend(new_properties)
|
||||
self.settings["qto"].Quantities = props
|
||||
|
||||
def get_canonical_property_type(self, name):
|
||||
if not self.qto_template:
|
||||
return "Length"
|
||||
for prop_template in self.qto_template.HasPropertyTemplates:
|
||||
if prop_template.Name != name:
|
||||
continue
|
||||
return prop_template.TemplateType[2:].lower().capitalize()
|
||||
return "Length"
|
||||
|
||||
def get_primary_measure_type(self, name, previous_value=None):
|
||||
if not self.qto_template:
|
||||
return previous_value.is_a() if previous_value else "IfcLabel"
|
||||
for prop_template in self.qto_template.HasPropertyTemplates:
|
||||
if prop_template.Name != name:
|
||||
continue
|
||||
return prop_template.PrimaryMeasureType or "IfcLabel"
|
||||
return previous_value.is_a() if previous_value else "IfcLabel"
|
||||
@@ -0,0 +1,27 @@
|
||||
import blenderbim.bim.schema # TODO: refactor
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, settings=None):
|
||||
self.file = file
|
||||
self.settings = {"product": None, "pset": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
to_purge = []
|
||||
should_remove_pset = True
|
||||
for inverse in self.file.get_inverse(self.settings["pset"]):
|
||||
if inverse.is_a("IfcRelDefinesByProperties"):
|
||||
if not inverse.RelatedObjects or len(inverse.RelatedObjects) == 1:
|
||||
to_purge.append(inverse)
|
||||
else:
|
||||
related_objects = list(inverse.RelatedObjects)
|
||||
related_objects.remove(self.settings["product"])
|
||||
inverse.RelatedObjects = related_objects
|
||||
should_remove_pset = False
|
||||
if should_remove_pset:
|
||||
self.file.remove(self.settings["pset"])
|
||||
for element in to_purge:
|
||||
self.file.remove(element)
|
||||
# TODO: implement deep purging
|
||||
Reference in New Issue
Block a user