Initial attempt at a qto module in Ifc5D

This is a more flexible, non-Blender approach to doing QTO which allows:

 - Doing QTO without Blender
 - Doing QTO using query filters
 - Using config files for QTOs
 - Choosing your calculation engine, either built-in or creating your own
This commit is contained in:
Dion Moult
2024-05-28 17:32:51 +10:00
parent 54951d2102
commit 5159e2daee
2 changed files with 152 additions and 0 deletions
@@ -0,0 +1,17 @@
{
"name": "IFC4 Base Quantities",
"description": "This ruleset quantifies every single possible standardised base quantity in IFC4",
"calculators": {
"IOSTriangulation": {
"IfcWall": {
"Qto_WallBaseQuantities": {
"Length": "net_get_x",
"Width": "net_get_y",
"Height": "net_get_z",
"NetSideArea": "net_get_side_area",
"NetVolume": "net_get_volume"
}
}
}
}
}
+135
View File
@@ -0,0 +1,135 @@
# Ifc5D - IFC costing utility
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Ifc5D.
#
# Ifc5D is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ifc5D is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Ifc5D. If not, see <http://www.gnu.org/licenses/>.
import os
import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.pset
import ifcopenshell.util.selector
import multiprocessing
from typing import Optional
def get_rules(name: str):
cwd = os.path.dirname(os.path.realpath(__file__))
with open(os.path.join(cwd, name + ".json"), "r") as f:
return json.load(f)
def quantify(ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_instance], rules: dict):
results = {}
for calculator, queries in rules["calculators"].items():
calculator = calculators[calculator]
for query, qtos in queries.items():
filtered_elements = ifcopenshell.util.selector.filter_elements(ifc_file, query, elements)
calculator.calculate(ifc_file, filtered_elements, qtos, results)
return results
def edit_qtos(ifc_file, results):
for element, qtos in results.items():
for name, quantities in qtos.items():
qto = ifcopenshell.util.element.get_pset(element, name, should_inherit=False)
if qto:
qto = ifc_file.by_id(qto["id"])
else:
qto = ifcopenshell.api.pset.add_qto(ifc_file, element, name)
ifcopenshell.api.pset.edit_qto(ifc_file, qto=qto, properties=quantities)
class IOSTriangulation:
@staticmethod
def calculate(
ifc_file: ifcopenshell.file,
elements: set[ifcopenshell.entity_instance],
qtos: dict,
results: Optional[dict] = None,
):
import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.util.shape
if results is None:
results = {}
formula_functions = {}
gross_settings = ifcopenshell.geom.settings()
gross_settings.set(gross_settings.DISABLE_OPENING_SUBTRACTIONS, True)
net_settings = ifcopenshell.geom.settings()
gross_qtos = {}
net_qtos = {}
for name, quantities in qtos.items():
for quantity, formula in quantities.items():
if formula.startswith("gross_"):
formula = formula[6:]
gross_qtos.setdefault(name, {})[quantity] = formula
formula_functions[formula] = getattr(ifcopenshell.util.shape, formula)
elif formula.startswith("net_"):
formula = formula[4:]
net_qtos.setdefault(name, {})[quantity] = formula
formula_functions[formula] = getattr(ifcopenshell.util.shape, formula)
tasks = []
if gross_qtos:
tasks.append((IOSTriangulation.create_iterator(ifc_file, gross_settings, elements), gross_qtos))
if net_qtos:
tasks.append((IOSTriangulation.create_iterator(ifc_file, net_settings, elements), net_qtos))
for iterator, qtos in tasks:
if iterator.initialize():
while True:
shape = iterator.get()
element = ifc_file.by_id(shape.id)
results.setdefault(element, {})
for name, quantities in qtos.items():
results[element].setdefault(name, {})
for quantity, formula in quantities.items():
results[element][name][quantity] = formula_functions[formula](shape.geometry)
if not iterator.next():
break
return results
@staticmethod
def create_iterator(ifc_file, settings, elements):
return ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=elements)
class Blender:
@staticmethod
def calculate(ifc_file, elements, qtos):
import blenderbim.tool as tool
for element in elements:
obj = tool.Ifc.get_object(element)
if not obj:
continue
for name, quantities in qtos.items():
for quantity, formula in quantities.items():
getattr(tool.Qto, formula)
# TODO
calculators = {"Blender": Blender, "IOSTriangulation": IOSTriangulation}