Files
IfcOpenShell/src/bonsai/scripts/get_all_qtos.py
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

124 lines
5.4 KiB
Python
Raw Normal View History

2025-03-13 17:17:37 +05:00
"""Update ifc5d json files with qtos from the provided pset templates path."""
import json
import ifcopenshell.util.pset
import ifcopenshell.util.type
2025-03-13 17:17:37 +05:00
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())}
2025-03-13 17:17:37 +05:00
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"
)
2025-03-13 17:17:37 +05:00
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
2025-03-13 17:17:37 +05:00
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()