mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 02:23:34 +00:00
124 lines
5.4 KiB
Python
124 lines
5.4 KiB
Python
"""Update ifc5d json files with qtos from the provided pset templates path."""
|
|
|
|
import json
|
|
import ifcopenshell.util.pset
|
|
import ifcopenshell.util.type
|
|
import ifc5d
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
from typing import Union
|
|
|
|
|
|
def order_dict(dictionary):
|
|
# https://stackoverflow.com/questions/22721579/sorting-a-nested-ordereddict-by-key-recursively
|
|
return {k: order_dict(v) if isinstance(v, dict) else v for k, v in sorted(dictionary.items())}
|
|
|
|
|
|
PSET_TEMPLATES_FOLDER = Path(ifcopenshell.util.__file__).parent / "schema"
|
|
JSON_FOLDER = Path(ifc5d.__file__).parent
|
|
QueriesData = dict[str, dict[str, dict[str, Union[str, None]]]]
|
|
|
|
|
|
def main() -> None:
|
|
# Update IFC4X3 json files from pset templates.
|
|
update_json_with_qtos_from_template_file(
|
|
JSON_FOLDER / "IFC4X3QtoBaseQuantities.json", PSET_TEMPLATES_FOLDER / "Pset_IFC4X3.ifc"
|
|
)
|
|
update_json_with_qtos_from_template_file(
|
|
JSON_FOLDER / "IFC4X3QtoBaseQuantitiesBlender.json", PSET_TEMPLATES_FOLDER / "Pset_IFC4X3.ifc"
|
|
)
|
|
# Reuse methods defined in IFC4 in IFC4X3 calculators.
|
|
reuse_methods_from_other_json_file(
|
|
JSON_FOLDER / "IFC4QtoBaseQuantities.json", JSON_FOLDER / "IFC4X3QtoBaseQuantities.json"
|
|
)
|
|
reuse_methods_from_other_json_file(
|
|
JSON_FOLDER / "IFC4QtoBaseQuantitiesBlender.json", JSON_FOLDER / "IFC4X3QtoBaseQuantitiesBlender.json"
|
|
)
|
|
|
|
|
|
def update_json_with_qtos_from_template_file(json_filepath: Path, ifc_filepath: Path) -> None:
|
|
"""Add missing qtos and properties to the json file from the provided pset template file."""
|
|
qto_templates: dict[str, list[ifcopenshell.entity_instance]] = defaultdict(list)
|
|
qto_props: dict[str, set[str]] = defaultdict(set)
|
|
ifc_file: ifcopenshell.file
|
|
ifc_file = ifcopenshell.open(ifc_filepath)
|
|
for template in ifc_file.by_type("IfcPropertySetTemplate"):
|
|
template_name = template.Name
|
|
if not template_name.startswith("Qto_"):
|
|
continue
|
|
qto_templates[template_name].append(template)
|
|
for prop in template.HasPropertyTemplates:
|
|
qto_props[template_name].add(prop.Name)
|
|
|
|
qto_base_quantities_data = json.loads(json_filepath.read_text())
|
|
|
|
calculator_queries_data: QueriesData
|
|
added_qtos: set[str] = set()
|
|
|
|
for calculator, calculator_queries_data in qto_base_quantities_data["calculators"].items():
|
|
# Gather all supported QTOs.
|
|
calculator_supported_qtos: set[str] = set()
|
|
for selector_query, query_qtos_data in calculator_queries_data.items():
|
|
for qto_name in query_qtos_data:
|
|
calculator_supported_qtos.add(qto_name)
|
|
|
|
# Check for missing QTOs.
|
|
for template_name in qto_templates:
|
|
template = qto_templates[template_name][0]
|
|
|
|
applicable_entity_value: str
|
|
applicable_entity_value = template.ApplicableEntity
|
|
applicable_entities = ifcopenshell.util.pset.parse_applicable_entity(applicable_entity_value)
|
|
|
|
selector_query = ifcopenshell.util.pset.convert_applicable_entities_to_query(applicable_entities)
|
|
# Add missing selector queries and qtos.
|
|
query_qtos_data = calculator_queries_data.setdefault(selector_query, {})
|
|
qto_props_data = query_qtos_data.setdefault(template_name, {})
|
|
|
|
# Add missing properties.
|
|
for prop_name in qto_props[template_name]:
|
|
if prop_name in qto_props_data:
|
|
continue
|
|
qto_props_data[prop_name] = None
|
|
added_qtos.add(template_name)
|
|
|
|
if not added_qtos:
|
|
print(f"No Qtos updates for calculator '{calculator}'.")
|
|
continue
|
|
|
|
# Sort dict alphabetically to keep it looking nice.
|
|
# Don't sort the entire json to keep the header structure.
|
|
qto_base_quantities_data["calculators"][calculator] = order_dict(calculator_queries_data)
|
|
print(f"Added Qtos for {calculator}: {added_qtos}")
|
|
json_filepath.write_text(json.dumps(qto_base_quantities_data, indent=4) + "\n")
|
|
|
|
|
|
def reuse_methods_from_other_json_file(json_source_filepath: Path, json_target_filepath: Path) -> None:
|
|
json_source_data = json.loads(json_source_filepath.read_text())
|
|
json_target_data = json.loads(json_target_filepath.read_text())
|
|
queries_data: QueriesData
|
|
queries_data_target: QueriesData
|
|
for calculator, queries_data in json_source_data["calculators"].items():
|
|
for selector_query, query_qtos_data in queries_data.items():
|
|
queries_data_target = json_target_data["calculators"][calculator]
|
|
# Don't match exactly since queries between IFC4 and IFC4X3 are not in sync currently.
|
|
target_query = next((q for q in queries_data_target if q.startswith(selector_query)), None)
|
|
if target_query is None:
|
|
continue
|
|
query_qtos_data_target = queries_data_target[target_query]
|
|
for qto_name in query_qtos_data:
|
|
if qto_name not in query_qtos_data_target:
|
|
continue
|
|
props_data = query_qtos_data[qto_name]
|
|
props_data_target = query_qtos_data_target[qto_name]
|
|
for prop_name in props_data:
|
|
if prop_name not in props_data_target:
|
|
continue
|
|
props_data_target[prop_name] = props_data[prop_name]
|
|
|
|
json_target_filepath.write_text(json.dumps(json_target_data, indent=4) + "\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|