mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
IFC4X3/IFC2X3 pset templates library
Generated from xmls from the ISO release of IFC4X3. Based on https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/annex/annex-a/general-usage/IFC4_ADD2.ifc Also generated pset templates library for IFC2X3 (also based on similar xmls) but IFC2X3 ifc pset library is using... IFC4X3 schema because IfcPropertySetTemplate and IfcRelDeclares are not available in IFC2X3. Which is okay for our purposes for now. Will need to update IFC4X3 library if one of the issues will be resolved: https://github.com/buildingSMART/IFC4.3.x-development/issues/587 https://github.com/buildingSMART/IFC4.3.x-development/issues/586
This commit is contained in:
@@ -26,6 +26,7 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.bim.schema
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.project as core
|
||||
import blenderbim.core.context
|
||||
@@ -54,6 +55,7 @@ class CreateProject(bpy.types.Operator):
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMProjectProperties
|
||||
template = None if props.template_file == "0" else props.template_file
|
||||
blenderbim.bim.schema.reload(props.export_schema)
|
||||
core.create_project(tool.Ifc, tool.Project, schema=props.export_schema, template=template)
|
||||
|
||||
def rollback(self, data):
|
||||
@@ -561,6 +563,7 @@ class LoadProjectElements(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
self.file = IfcStore.get_file()
|
||||
blenderbim.bim.schema.reload(self.file.schema)
|
||||
start = time.time()
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
path_log = os.path.join(context.scene.BIMProperties.data_dir, "process.log")
|
||||
|
||||
@@ -22,6 +22,7 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import blenderbim.bim.schema
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -223,7 +224,7 @@ class SavePsetTemplateFile(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.schema.reload()
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,12 @@ cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
class IfcSchema:
|
||||
def __init__(self):
|
||||
def __init__(self, schema_name="IFC4"):
|
||||
schema_name = schema_name.upper()
|
||||
if schema_name not in ("IFC2X3", "IFC4", "IFC4X3"):
|
||||
schema_name = "IFC4"
|
||||
self.schema_name = schema_name
|
||||
|
||||
self.schema_dir = Path(cwd).joinpath("schema")
|
||||
self.data_dir = Path(cwd).joinpath("data")
|
||||
# TODO: Make it less troublesome
|
||||
@@ -51,8 +56,7 @@ class IfcSchema:
|
||||
|
||||
def load_pset_templates(self):
|
||||
property_paths = self.data_dir.joinpath("pset").glob("*.ifc")
|
||||
# TODO: add IFC2X3 PsetQto template support
|
||||
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
|
||||
self.psetqto = ifcopenshell.util.pset.get_template(self.schema_name)
|
||||
# Keep only the first template, which is the official buildingSMART one
|
||||
self.psetqto.templates = self.psetqto.templates[0:1]
|
||||
self.psetqto.get_applicable.cache_clear()
|
||||
@@ -62,6 +66,7 @@ class IfcSchema:
|
||||
self.psetqto.templates.append(ifcopenshell.open(path))
|
||||
|
||||
def load(self):
|
||||
# TODO: need to update for ifc4x3?
|
||||
for product in self.products:
|
||||
with open(os.path.join(self.schema_dir, f"{product}_IFC4.json")) as f:
|
||||
setattr(self, product, json.load(f))
|
||||
@@ -99,6 +104,6 @@ class IfcSchema:
|
||||
ifc = IfcSchema()
|
||||
|
||||
|
||||
def reload():
|
||||
def reload(schema_name):
|
||||
global ifc
|
||||
ifc = IfcSchema()
|
||||
ifc = IfcSchema(schema_name)
|
||||
|
||||
@@ -197,7 +197,7 @@ class DocExtractor:
|
||||
raise Exception(
|
||||
f'Docs for IFC 2.3.0.1 expected to be in folder "{IFC2x3_DOCS_LOCATION.resolve()}\\"\n'
|
||||
"For doc extraction please either setup docs as described above \n"
|
||||
"or change IFC2x3_DOCS_LOCATION in doc.py accordingly. \n"
|
||||
"or change IFC2x3_DOCS_LOCATION in the script accordingly.\n"
|
||||
"You can download docs from the repository: \n"
|
||||
"https://github.com/buildingSMART/IFC/tree/Ifc2.3.0.1"
|
||||
)
|
||||
@@ -506,7 +506,7 @@ class DocExtractor:
|
||||
raise Exception(
|
||||
f'Docs for Ifc4.0.2.1 expected to be in folder "{IFC4_DOCS_LOCATION.resolve()}\\"\n'
|
||||
"For doc extraction please either setup docs as described above \n"
|
||||
"or change IFC4_DOCS_LOCATION in doc.py accordingly."
|
||||
"or change IFC4_DOCS_LOCATION in the script accordingly.\n"
|
||||
"You can download docs from the repository: \n"
|
||||
"https://github.com/buildingSMART/IFC/tree/Ifc4.0.2.1"
|
||||
)
|
||||
@@ -845,7 +845,7 @@ class DocExtractor:
|
||||
raise Exception(
|
||||
f'Specs development repository for Ifc4.3.0.1 expected to be in folder "{IFC4x3_DEV_LOCATION.resolve()}\\"\n'
|
||||
"For doc extraction please either setup docs as described above \n"
|
||||
"or change IFC4x3_DEV_LOCATION in doc.py accordingly."
|
||||
"or change IFC4x3_DEV_LOCATION in the script accordingly.\n"
|
||||
"You can download docs from the repository: \n"
|
||||
"https://github.com/buildingSMART/IFC4.3.x-development"
|
||||
)
|
||||
@@ -853,7 +853,7 @@ class DocExtractor:
|
||||
raise Exception(
|
||||
f'Formal release for Ifc4.3.0.1 expected to be in folder "{IFC4x3_HTML_LOCATION.resolve()}\\"\n'
|
||||
"For doc extraction please either setup docs as described above \n"
|
||||
"or change IFC4x3_HTML_LOCATION in doc.py accordingly."
|
||||
"or change IFC4x3_HTML_LOCATION in the script accordingly.\n"
|
||||
"You can download docs from the repository: \n"
|
||||
"https://github.com/buildingsmart/ifc4.3-html"
|
||||
)
|
||||
@@ -932,7 +932,7 @@ class DocExtractor:
|
||||
description = self.clean_description(parsed_predefined_types_data[v])
|
||||
predefined_types[v] = description
|
||||
continue
|
||||
|
||||
|
||||
# attributes
|
||||
if attr_name not in parsed_attributes_data:
|
||||
print(
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.attribute
|
||||
from pathlib import Path
|
||||
from lxml import etree
|
||||
import zipfile
|
||||
import shutil
|
||||
import glob
|
||||
|
||||
BASE_MODULE_PATH = Path(__file__).parent
|
||||
IFC4x3_HTML_LOCATION = BASE_MODULE_PATH / "IFC4.3-html-iso-release"
|
||||
IFC4x3_OUTPUT_PATH = BASE_MODULE_PATH / "schema/Pset_IFC4X3.ifc"
|
||||
IFC2x3_HTML_ZIP_LOCATION = BASE_MODULE_PATH / "IFC2x3_TC1_HTML_distribution-pset_errata.zip"
|
||||
IFC2x3_OUTPUT_PATH = BASE_MODULE_PATH / "schema/Pset_IFC2X3.ifc"
|
||||
|
||||
PROPERTY_TYPES_DICT = {
|
||||
"TypePropertySingleValue": ("P_SINGLEVALUE", "type"),
|
||||
# in IFC2X3 weirdly xmls have TypeSimpleProperty
|
||||
# no difference with TypePropertySingleValue though
|
||||
"TypeSimpleProperty": ("P_SINGLEVALUE", "type"),
|
||||
"TypePropertyListValue": ("P_LISTVALUE", "type"),
|
||||
"TypePropertyBoundedValue": ("P_BOUNDEDVALUE", "type"),
|
||||
"TypePropertyReferenceValue": ("P_REFERENCEVALUE", "reftype"),
|
||||
"TypePropertyEnumeratedValue": ("P_ENUMERATEDVALUE", ""),
|
||||
"TypePropertyTableValue": ("P_TABLEVALUE", "type"),
|
||||
"TypeComplexProperty": ("P_COMPLEX", ""),
|
||||
}
|
||||
|
||||
|
||||
class PsetTemplatesGenerator:
|
||||
def parse_ifc4x3_data(self):
|
||||
print("Starting parsing data for IFC4X3...")
|
||||
if not IFC4x3_HTML_LOCATION.is_dir():
|
||||
raise Exception(
|
||||
f'ISO release for Ifc4.3.0.1 expected to be in folder "{IFC4x3_HTML_LOCATION.resolve()}\\"\n'
|
||||
"For doc extraction please either setup docs as described above \n"
|
||||
"or change IFC4x3_HTML_LOCATION in the script accordingly.\n"
|
||||
"You can download docs from the repository: \n"
|
||||
"https://github.com/buildingSMART/IFC4.3-html/releases/tag/sep-13-release"
|
||||
)
|
||||
# unzip the data
|
||||
pset_data_zip = IFC4x3_HTML_LOCATION / "IFC/RELEASE/IFC4x3/HTML/annex-a-psd.zip"
|
||||
pset_data_location = BASE_MODULE_PATH / "temp/annex-a-psd"
|
||||
with zipfile.ZipFile(pset_data_zip, "r") as fi_zip:
|
||||
fi_zip.extractall(pset_data_location)
|
||||
|
||||
pset_data_glob = f"{pset_data_location}/*.xml"
|
||||
self.parse_psets_data("IFC4X3", pset_data_glob, str(IFC4x3_OUTPUT_PATH))
|
||||
shutil.rmtree(pset_data_location)
|
||||
|
||||
def parse_ifc2x3_data(self):
|
||||
print("Starting parsing data for IFC2X3...")
|
||||
if not IFC2x3_HTML_ZIP_LOCATION.is_file():
|
||||
raise Exception(
|
||||
f'ISO release for IFC2x3 TC1 expected to be in folder "{IFC2x3_HTML_ZIP_LOCATION.resolve()}\\"\n'
|
||||
"For doc extraction please either setup docs as described above \n"
|
||||
"or change IFC2x3_HTML_LOCATION in the script accordingly.\n"
|
||||
"You can download docs from the url: \n"
|
||||
"https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/IFC2x3_TC1_HTML_distribution-pset_errata.zip"
|
||||
)
|
||||
|
||||
# unzip the data
|
||||
pset_data_location = BASE_MODULE_PATH / "temp/ifc2x3_html"
|
||||
with zipfile.ZipFile(IFC2x3_HTML_ZIP_LOCATION, "r") as fi_zip:
|
||||
fi_zip.extractall(pset_data_location)
|
||||
|
||||
pset_data_glob = f"{pset_data_location}/**/pset*.xml"
|
||||
# we're using IFC4X3 since IfcPropertySetTemplate and IfcRelDeclares
|
||||
# are not available in IFC2X3
|
||||
self.parse_psets_data("IFC4X3", pset_data_glob, str(IFC2x3_OUTPUT_PATH))
|
||||
shutil.rmtree(pset_data_location)
|
||||
|
||||
def ifc_entity(self, entity_name, **kwargs):
|
||||
entity = self.ifc_file.create_entity(entity_name, **kwargs)
|
||||
return entity
|
||||
|
||||
def parse_psets_data(self, schema_name, pset_data_glob, ifc_output_path):
|
||||
schema_name = schema_name.upper()
|
||||
self.ifc_file = ifcopenshell.api.run("project.create_file", version=schema_name)
|
||||
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name)
|
||||
|
||||
self.ifc_derived_unit_enum = (
|
||||
schema.declaration_by_name("IfcDerivedUnitEnum").as_enumeration_type().enumeration_items()
|
||||
)
|
||||
self.ifc_unit_enum = schema.declaration_by_name("IfcUnitEnum").as_enumeration_type().enumeration_items()
|
||||
|
||||
self.ifc_entity("IfcProject", Name=f"{schema_name} Property Set Templates")
|
||||
psets_list = []
|
||||
rel = self.ifc_entity("IfcRelDeclares", RelatedDefinitions=psets_list) if schema_name != "IFC2X3" else None
|
||||
|
||||
# in IFC4 there is also
|
||||
# IFCLIBRARYREFERENCE after each (sometimes multiple of them if there are several languages involved)
|
||||
# 1) enumeration item (also with IFCRELASSOCIATESLIBRARY)
|
||||
# 2) enumeration
|
||||
# 3) property set (also with IFCRELASSOCIATESLIBRARY)
|
||||
# 4) property definition (also with IFCRELASSOCIATESLIBRARY)
|
||||
# but in ifc4x3 there is no data for those library refernces
|
||||
# TODO: need to add it to .ifc for IFC4X3 too
|
||||
# if https://github.com/buildingSMART/IFC4.3.x-development/issues/587 is resolved
|
||||
|
||||
# iterate through all xmls files
|
||||
for pset_path in glob.iglob(pset_data_glob, recursive=True):
|
||||
pset_path = Path(pset_path)
|
||||
|
||||
with open(pset_path, "r", encoding="utf-8") as fi:
|
||||
root_xml = etree.fromstring(fi.read())
|
||||
pset_name = root_xml.find("Name").text
|
||||
# pset / qset
|
||||
pset_type = True if pset_name.split("_")[0] == "Pset" else False
|
||||
|
||||
# TODO: if guids provided in xml should use them instead
|
||||
pset_guid = ifcopenshell.guid.new()
|
||||
applicable_entities = [i.text for i in root_xml.find("ApplicableClasses").findall("ClassName")]
|
||||
pset = self.ifc_entity(
|
||||
"IfcPropertySetTemplate",
|
||||
GlobalId=pset_guid,
|
||||
TemplateType=root_xml.get("templatetype"),
|
||||
Name=pset_name,
|
||||
Description=root_xml.find("Definition").text,
|
||||
ApplicableEntity=",".join(applicable_entities),
|
||||
)
|
||||
|
||||
# NOTE: there is also Applicability tag
|
||||
# but it's seems always empty in ifc4 and ifc4x3
|
||||
# in ifc2x3 it's present but contains just applicability string description
|
||||
# e.g. "IfcElectricMotorType, IfcFlowMovingDeviceType entities." for "Pset_ElectricMotorTypeCommon"
|
||||
|
||||
pdef_entities = []
|
||||
for pdef in root_xml.find("PropertyDefs" if pset_type else "QtoDefs").getchildren():
|
||||
pset_property = self.get_property_from_pdef(pset_name, pset_type, pdef)
|
||||
if not pset_property:
|
||||
continue
|
||||
pdef_entities.append(pset_property)
|
||||
pset.HasPropertyTemplates = pdef_entities
|
||||
psets_list.append(pset)
|
||||
|
||||
print(f"{len(psets_list)} psets parsed.")
|
||||
if rel:
|
||||
rel.RelatedDefinitions = psets_list
|
||||
self.ifc_file.write(ifc_output_path)
|
||||
|
||||
def get_property_from_pdef(self, pset_name, pset_type, pdef):
|
||||
pset_prop_guid = ifcopenshell.guid.new()
|
||||
pset_property_name = pdef.find("Name").text
|
||||
|
||||
# figuring pset property types and related values
|
||||
if not pset_type:
|
||||
property_type_tag = None
|
||||
else:
|
||||
try:
|
||||
assert len(pdef.find("PropertyType").getchildren()) == 1, "Not implemented case"
|
||||
except AssertionError:
|
||||
warning_message = (
|
||||
f"WARNING: met not handles case with multiple property types "
|
||||
f"- {pset_name}/{pset_property_name}."
|
||||
"This property may have incorrect data parsed."
|
||||
)
|
||||
print(warning_message)
|
||||
|
||||
property_type_tag = pdef.find("PropertyType").getchildren()[0].tag
|
||||
if property_type_tag not in PROPERTY_TYPES_DICT:
|
||||
warning_message = (
|
||||
f"WARNING: not implemented property type "
|
||||
f"{property_type_tag} ({pset_name}/{pset_property_name}). "
|
||||
"This property will be skipped. Need to rework the code to support that type of property."
|
||||
)
|
||||
print(warning_message)
|
||||
return
|
||||
|
||||
# NOTE: there is also ValueDef tag
|
||||
# which could have two subtags - MinValue and MaxValue
|
||||
# but it's seems to be present only in ifc2x3 and everywhere values are either "", "0", "?"
|
||||
# so just ignoring it
|
||||
|
||||
# create pset property
|
||||
if not pset_type or property_type_tag != "TypeComplexProperty":
|
||||
pset_property = self.ifc_entity("IfcSimplePropertyTemplate")
|
||||
# tested on IFC4_ADD2.ifc - all props are READWRITE by default
|
||||
pset_property.AccessState = "READWRITE"
|
||||
else:
|
||||
pset_property = self.ifc_entity("IfcComplexPropertyTemplate")
|
||||
complex_prop_xml = pdef.find("PropertyType/TypeComplexProperty")
|
||||
child_properties = [
|
||||
self.get_property_from_pdef(pset_name, pset_type, child_pdef)
|
||||
for child_pdef in complex_prop_xml.findall("PropertyDef")
|
||||
]
|
||||
pset_property.UsageName = complex_prop_xml.get("name")
|
||||
pset_property.HasPropertyTemplates = child_properties
|
||||
|
||||
pset_property.GlobalId = pset_prop_guid
|
||||
pset_property.Description = pdef.find("Definition").text
|
||||
pset_property.Name = pset_property_name
|
||||
|
||||
self.add_prop_type_params_to_prop(pset_type, pdef, pset_property, property_type_tag)
|
||||
return pset_property
|
||||
|
||||
def add_prop_type_params_to_prop(self, pset_type, pdef, pset_property, property_type_tag):
|
||||
if not pset_type:
|
||||
property_type = pdef.find("QtoType").text
|
||||
else:
|
||||
property_type, property_type_parse = PROPERTY_TYPES_DICT[property_type_tag]
|
||||
pset_property.TemplateType = property_type
|
||||
|
||||
if not pset_type or property_type == "P_COMPLEX":
|
||||
# qsets and complex props don't have measure types
|
||||
return
|
||||
|
||||
property_type_path = f"PropertyType/{property_type_tag}"
|
||||
property_type_xml = pdef.find(property_type_path)
|
||||
|
||||
# usually it's only for P_TABLEVALUE
|
||||
expression_xml = property_type_xml.find("Expression")
|
||||
if expression_xml is not None and expression_xml.text != "-":
|
||||
pset_property.Expression = expression_xml.text
|
||||
|
||||
# figure measure type for property sets
|
||||
if property_type == "P_ENUMERATEDVALUE":
|
||||
# always create new enumeration for the new properties
|
||||
# even if the same enumeration was already used before
|
||||
# example of reoccuring enumeration in .ifc for ifc4 - PEnum_ElementStatus
|
||||
enum_items = [i.text for i in property_type_xml.findall(f"EnumList/EnumItem")]
|
||||
enum_items = [self.ifc_entity("IfcLabel", wrappedValue=i) for i in enum_items]
|
||||
prop_enumeration = self.ifc_entity(
|
||||
"IfcPropertyEnumeration",
|
||||
Name=property_type_xml.find("EnumList").get("name"),
|
||||
EnumerationValues=enum_items,
|
||||
)
|
||||
pset_property.Enumerators = prop_enumeration
|
||||
pset_property.PrimaryMeasureType = "IfcLabel"
|
||||
|
||||
elif property_type == "P_TABLEVALUE":
|
||||
pset_property.PrimaryMeasureType = property_type_xml.find("DefiningValue/DataType").get("type")
|
||||
pset_property.SecondaryMeasureType = property_type_xml.find("DefinedValue/DataType").get("type")
|
||||
else:
|
||||
if property_type == "P_REFERENCEVALUE":
|
||||
pset_property.PrimaryMeasureType = property_type_xml.get(property_type_parse)
|
||||
# TODO: ifc4add2 seems to have some secondary measure types
|
||||
# need to add it in ifc4x3 too
|
||||
# if https://github.com/buildingSMART/IFC4.3.x-development/issues/586 is resolved
|
||||
else:
|
||||
if property_type == "P_LISTVALUE":
|
||||
property_type_node = "ListValue"
|
||||
if property_type in ("P_SINGLEVALUE", "P_BOUNDEDVALUE"):
|
||||
property_type_node = "DataType"
|
||||
|
||||
# only used only in ifc2x3, omitted in ifc4 and ifc4x3
|
||||
unit_type_xml = property_type_xml.find("UnitType")
|
||||
if unit_type_xml is not None:
|
||||
unit_type = unit_type_xml.get(property_type_parse).strip()
|
||||
if unit_type.lower().startswith("ifc"):
|
||||
unit_entity = self.ifc_entity(unit_type)
|
||||
else:
|
||||
if unit_type in self.ifc_derived_unit_enum:
|
||||
unit_entity = self.ifc_entity("IfcDerivedUnit", UnitType=unit_type)
|
||||
elif unit_type in self.ifc_unit_enum:
|
||||
unit_entity = self.ifc_entity("IfcNamedUnit", UnitType=unit_type)
|
||||
else:
|
||||
print("WARNING. Wasn't able to find units {unit_type} in schema.")
|
||||
pset_property.PrimaryUnit = unit_entity
|
||||
|
||||
pset_property.PrimaryMeasureType = property_type_xml.find(property_type_node).get(property_type_parse)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
templates_generator = PsetTemplatesGenerator()
|
||||
templates_generator.parse_ifc4x3_data()
|
||||
templates_generator.parse_ifc2x3_data()
|
||||
@@ -37,7 +37,9 @@ def get_template(schema):
|
||||
|
||||
class PsetQto:
|
||||
templates_path = {
|
||||
"IFC2X3": "Pset_IFC2X3.ifc",
|
||||
"IFC4": "Pset_IFC4_ADD2.ifc",
|
||||
"IFC4X3": "Pset_IFC4X3.ifc"
|
||||
}
|
||||
|
||||
def __init__(self, schema: str, templates=None) -> None:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user