Skip to content

Instantly share code, notes, and snippets.

@Moult

Moult/qtofix.py Secret

Created February 10, 2023 11:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Moult/d25e4fe1c295d5fdafe1c3b2f1adeab1 to your computer and use it in GitHub Desktop.
Save Moult/d25e4fe1c295d5fdafe1c3b2f1adeab1 to your computer and use it in GitHub Desktop.
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.geom
import ifcopenshell.util.unit
import ifcopenshell.util.shape
class QtoFix:
def execute(self):
self.file = ifcopenshell.open("model.ifc")
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
gross_settings = ifcopenshell.geom.settings()
gross_settings.set(gross_settings.DISABLE_OPENING_SUBTRACTIONS, True)
net_settings = ifcopenshell.geom.settings()
for element in self.file.by_type("IfcDoor"):
ifcopenshell.api.run("root.remove_product", self.file, product=element)
for element in self.file.by_type("IfcWall"):
self.quantities = {}
qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True)
qto = qtos["Qto_WallBaseQuantities"]
gross_shape = ifcopenshell.geom.create_shape(gross_settings, element)
net_shape = ifcopenshell.geom.create_shape(net_settings, element)
length = ifcopenshell.util.shape.get_x(gross_shape.geometry) / unit_scale
width = ifcopenshell.util.shape.get_y(gross_shape.geometry) / unit_scale
height = ifcopenshell.util.shape.get_z(gross_shape.geometry) / unit_scale
gross_side_area = ifcopenshell.util.shape.get_side_area(gross_shape.geometry)
net_volume = ifcopenshell.util.shape.get_volume(net_shape.geometry)
self.verify_quantity(element, qto, "Length", length)
self.verify_quantity(element, qto, "Width", width)
self.verify_quantity(element, qto, "Height", height)
self.verify_quantity(element, qto, "GrossSideArea", gross_side_area)
self.verify_quantity(element, qto, "NetVolume", net_volume)
if self.quantities:
ifcopenshell.api.run("pset.edit_qto", self.file, qto=self.file.by_id(qto["id"]), properties=self.quantities)
for element in self.file.by_type("IfcSlab"):
self.quantities = {}
qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True)
qto = qtos["Qto_SlabBaseQuantities"]
gross_shape = ifcopenshell.geom.create_shape(gross_settings, element)
length = ifcopenshell.util.shape.get_x(gross_shape.geometry) / unit_scale
width = ifcopenshell.util.shape.get_y(gross_shape.geometry) / unit_scale
depth = ifcopenshell.util.shape.get_z(gross_shape.geometry) / unit_scale
perimeter = ifcopenshell.util.shape.get_footprint_perimeter(gross_shape.geometry) / unit_scale
gross_area = ifcopenshell.util.shape.get_footprint_area(gross_shape.geometry)
gross_volume = ifcopenshell.util.shape.get_volume(gross_shape.geometry)
self.verify_quantity(element, qto, "Length", length)
self.verify_quantity(element, qto, "Width", width)
self.verify_quantity(element, qto, "Depth", depth)
self.verify_quantity(element, qto, "Perimeter", perimeter)
self.verify_quantity(element, qto, "GrossArea", gross_area)
self.verify_quantity(element, qto, "GrossVolume", gross_volume)
if self.quantities:
ifcopenshell.api.run("pset.edit_qto", self.file, qto=self.file.by_id(qto["id"]), properties=self.quantities)
for element in self.file.by_type("IfcColumn"):
self.quantities = {}
qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True)
qto = qtos["Qto_ColumnBaseQuantities"]
gross_shape = ifcopenshell.geom.create_shape(gross_settings, element)
length = ifcopenshell.util.shape.get_z(gross_shape.geometry) / unit_scale
outer_surface_area = ifcopenshell.util.shape.get_outer_surface_area(gross_shape.geometry)
gross_volume = ifcopenshell.util.shape.get_volume(gross_shape.geometry)
self.verify_quantity(element, qto, "Length", length)
self.verify_quantity(element, qto, "OuterSurfaceArea", outer_surface_area)
self.verify_quantity(element, qto, "GrossVolume", gross_volume)
if self.quantities:
ifcopenshell.api.run("pset.edit_qto", self.file, qto=self.file.by_id(qto["id"]), properties=self.quantities)
for element in self.file.by_type("IfcBeam"):
self.quantities = {}
qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True)
qto = qtos["Qto_BeamBaseQuantities"]
gross_shape = ifcopenshell.geom.create_shape(gross_settings, element)
length = ifcopenshell.util.shape.get_z(gross_shape.geometry) / unit_scale
outer_surface_area = ifcopenshell.util.shape.get_outer_surface_area(gross_shape.geometry)
gross_volume = ifcopenshell.util.shape.get_volume(gross_shape.geometry)
self.verify_quantity(element, qto, "Length", length)
self.verify_quantity(element, qto, "OuterSurfaceArea", outer_surface_area)
self.verify_quantity(element, qto, "GrossVolume", gross_volume)
if self.quantities:
ifcopenshell.api.run("pset.edit_qto", self.file, qto=self.file.by_id(qto["id"]), properties=self.quantities)
for element in self.file.by_type("IfcOpeningElement"):
self.quantities = {}
qto = self.get_qto(element, "Qto_OpeningElementBaseQuantities")
gross_shape = ifcopenshell.geom.create_shape(gross_settings, element)
height = ifcopenshell.util.shape.get_x(gross_shape.geometry) / unit_scale
width = ifcopenshell.util.shape.get_y(gross_shape.geometry) / unit_scale
depth = ifcopenshell.util.shape.get_z(gross_shape.geometry) / unit_scale
area = ifcopenshell.util.shape.get_side_area(gross_shape.geometry)
volume = ifcopenshell.util.shape.get_volume(gross_shape.geometry)
self.verify_quantity(element, qto, "Height", height)
self.verify_quantity(element, qto, "Width", width)
self.verify_quantity(element, qto, "Depth", depth)
self.verify_quantity(element, qto, "Area", area)
self.verify_quantity(element, qto, "Volume", volume)
if self.quantities:
ifcopenshell.api.run("pset.edit_qto", self.file, qto=self.file.by_id(qto["id"]), properties=self.quantities)
self.file.write("model-qto.ifc")
def get_qto(self, element, name):
qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True)
if name in qtos:
return qtos[name]
qto = ifcopenshell.api.run("pset.add_qto", self.file, product=element, name=name)
return {"id": qto.id()}
def verify_quantity(self, element, qto, name, value):
if name not in qto:
self.quantities[name] = value
print(f"Quantity {name} added: {value} for {element}")
elif not ifcopenshell.util.shape.is_x(qto[name], value):
print(f"Quantity {name} fixed: {qto[name]} -> {value} for {element}")
self.quantities[name] = value
QtoFix().execute()
@elisemunche
Copy link

Hi, great code!
This code works to find the lengths for some of my IFC files, but for others the length may be found as get_x instead of get_z. Do you have a suggestion as to what this may be due and if it is a way to fix it so the code works generically?

For example is you see the output below, the length of the beam corresponds to x and not to z for the specific file:

psets BEAM: {'BaseQuantities': {'Length': 105.000000001222, 'OuterSurfaceArea': 0.00621171657618607, 'NetVolume': 2.10148755905985e-05, 'NetWeight': 0.164966773386198, 'id': 2900}}
x 104.99999726451682
y: 23.9999999999709
z: 23.9999999991269

Thank you!

@Moult
Copy link
Author

Moult commented Apr 14, 2023 via email

@elisemunche
Copy link

Okei thanks, that may work!

Just so I don't misunderstand, it is more correct to work further with the lengths provided by the get_z (or x) functions, instead of the pset function?

I am also struggling to find the profiles of the elements, do you know how I can find them?

Also, if I wish to have the cross section areas, can I just calculate this by multiplying the width and height (the y and z in the output over)? (This of course will only work if the profile is a rectangle)

Many questions at once, but I am a little lost. Thank you again!

@Moult
Copy link
Author

Moult commented Apr 14, 2023

Using the shape util created shapes will get you exactly what the geometry is, which may or may not be more correct than what already exists in the existing qtos. In my opinion, it is almost always more correct because the qto method is known as opposed to a black box. But your project may be special and have special calculation rules.

The cross section area may be calculated either by using get_side_area and specifying the axis of the side you want (which you can work out via analysis of the get_x/y/z function you used to get the length axis), which is a generic way to get it for any tessellated shape. Otherwise, you can (and should) first check for the representation type, which you'd use the util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") to get the body representation, resolve mapped representations using util.representation.resolve_representation then if it is a representationtype of sweptsolid, you can check the extrusion profile then use get_area.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment