mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 14:31:39 +00:00
initial proposal for updating units conversion
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import ifcopenshell
|
||||||
from math import pi
|
from math import pi
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
|
|
||||||
@@ -214,12 +215,10 @@ si_conversions = {
|
|||||||
"fahrenheit": 1.8,
|
"fahrenheit": 1.8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
si_offsets = {
|
si_offsets = {
|
||||||
"fahrenheit": -459.67,
|
"fahrenheit": -459.67,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
imperial_types = {
|
imperial_types = {
|
||||||
"thou": "LENGTHUNIT",
|
"thou": "LENGTHUNIT",
|
||||||
"inch": "LENGTHUNIT",
|
"inch": "LENGTHUNIT",
|
||||||
@@ -260,7 +259,6 @@ imperial_types = {
|
|||||||
"fahrenheit": "THERMODYNAMICTEMPERATUREUNIT",
|
"fahrenheit": "THERMODYNAMICTEMPERATUREUNIT",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
prefix_symbols = {
|
prefix_symbols = {
|
||||||
"EXA": "E",
|
"EXA": "E",
|
||||||
"PETA": "P",
|
"PETA": "P",
|
||||||
@@ -558,13 +556,13 @@ def calculate_unit_scale(ifc_file):
|
|||||||
|
|
||||||
|
|
||||||
def format_length(
|
def format_length(
|
||||||
value,
|
value,
|
||||||
precision,
|
precision,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
suppress_zero_inches=True,
|
suppress_zero_inches=True,
|
||||||
unit_system="imperial",
|
unit_system="imperial",
|
||||||
input_unit="foot",
|
input_unit="foot",
|
||||||
output_unit="foot",
|
output_unit="foot",
|
||||||
):
|
):
|
||||||
"""Formats a length for readability and imperial formatting
|
"""Formats a length for readability and imperial formatting
|
||||||
|
|
||||||
@@ -624,3 +622,82 @@ def format_length(
|
|||||||
elif unit_system == "metric":
|
elif unit_system == "metric":
|
||||||
rounded_val = round(value / precision) * precision
|
rounded_val = round(value / precision) * precision
|
||||||
return f"{rounded_val:.{decimal_places}f}"
|
return f"{rounded_val:.{decimal_places}f}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_base_type_name(
|
||||||
|
content_type: ifcopenshell.ifcopenshell_wrapper.named_type | ifcopenshell.ifcopenshell_wrapper.type_declaration) -> ifcopenshell.ifcopenshell_wrapper.type_declaration | None:
|
||||||
|
cur_decl = content_type
|
||||||
|
while hasattr(cur_decl, "declared_type") is True:
|
||||||
|
cur_decl = cur_decl.declared_type()
|
||||||
|
if hasattr(cur_decl, "name") is False:
|
||||||
|
continue
|
||||||
|
if cur_decl.name() == "IfcLengthMeasure":
|
||||||
|
return cur_decl
|
||||||
|
|
||||||
|
if isinstance(cur_decl, ifcopenshell.ifcopenshell_wrapper.aggregation_type):
|
||||||
|
res = cur_decl.type_of_element()
|
||||||
|
cur_decl = res.declared_type()
|
||||||
|
if hasattr(cur_decl, "name") and cur_decl.name() == "IfcLengthMeasure":
|
||||||
|
return cur_decl
|
||||||
|
while hasattr(cur_decl, "declared_type") is True:
|
||||||
|
cur_decl = cur_decl.declared_type()
|
||||||
|
if hasattr(cur_decl, "name") is False:
|
||||||
|
continue
|
||||||
|
if cur_decl.name() == "IfcLengthMeasure":
|
||||||
|
return cur_decl
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def convert_file_units(ifc_file: ifcopenshell.file, target_units: str) -> ifcopenshell.file:
|
||||||
|
"""Converts all units in an IFC file to the specified target units. Returns a new file."""
|
||||||
|
prefix = "MILLI" if target_units == "MILLIMETERS" else None
|
||||||
|
|
||||||
|
file_patched = ifcopenshell.api.run("project.create_file", version=ifc_file.schema)
|
||||||
|
if ifc_file.schema == "IFC2X3":
|
||||||
|
user = file_patched.add(ifc_file.by_type("IfcProject")[0].OwnerHistory.OwningUser)
|
||||||
|
application = file_patched.add(ifc_file.by_type("IfcProject")[0].OwnerHistory.OwningApplication)
|
||||||
|
old_get_user = ifcopenshell.api.owner.settings.get_user
|
||||||
|
old_get_application = ifcopenshell.api.owner.settings.get_application
|
||||||
|
ifcopenshell.api.owner.settings.get_user = lambda ifc: user
|
||||||
|
ifcopenshell.api.owner.settings.get_application = lambda ifc: application
|
||||||
|
|
||||||
|
# Copy all elements from the original file to the patched file
|
||||||
|
for el in ifc_file:
|
||||||
|
file_patched.add(el)
|
||||||
|
|
||||||
|
unit_assignment = ifcopenshell.util.unit.get_unit_assignment(file_patched)
|
||||||
|
|
||||||
|
old_length = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == "LENGTHUNIT"][0]
|
||||||
|
new_length = ifcopenshell.api.run("unit.add_si_unit", file_patched, unit_type="LENGTHUNIT", prefix=prefix)
|
||||||
|
|
||||||
|
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(ifc_file.schema)
|
||||||
|
# Traverse all elements and their nested attributes in the file and convert them
|
||||||
|
for element in file_patched:
|
||||||
|
entity = schema.declaration_by_name(element.is_a())
|
||||||
|
attrs = entity.all_attributes()
|
||||||
|
for i, (attr, val, is_derived) in enumerate(zip(attrs, list(element), entity.derived())):
|
||||||
|
if is_derived:
|
||||||
|
continue
|
||||||
|
# Get all methods and attributes of the element
|
||||||
|
attr_type = attr.type_of_attribute()
|
||||||
|
base_type = get_base_type_name(attr_type)
|
||||||
|
if base_type is None:
|
||||||
|
continue
|
||||||
|
if val is None:
|
||||||
|
continue
|
||||||
|
if isinstance(val, tuple):
|
||||||
|
new_el = [ifcopenshell.util.unit.convert_unit(v, old_length, new_length) for v in val]
|
||||||
|
setattr(element, attr.name(), tuple(new_el))
|
||||||
|
else:
|
||||||
|
new_el = ifcopenshell.util.unit.convert_unit(val, old_length, new_length)
|
||||||
|
# set the new value
|
||||||
|
setattr(element, attr.name(), new_el)
|
||||||
|
|
||||||
|
file_patched.remove(old_length)
|
||||||
|
|
||||||
|
if ifc_file.schema == "IFC2X3":
|
||||||
|
ifcopenshell.api.owner.settings.get_user = old_get_user
|
||||||
|
ifcopenshell.api.owner.settings.get_application = old_get_application
|
||||||
|
|
||||||
|
return file_patched
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import ifcopenshell.api
|
|||||||
import ifcopenshell.api.owner.settings
|
import ifcopenshell.api.owner.settings
|
||||||
import ifcopenshell.util.pset
|
import ifcopenshell.util.pset
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
|
import ifcopenshell.util.unit
|
||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher:
|
||||||
@@ -47,44 +48,7 @@ class Patcher:
|
|||||||
self.file = file
|
self.file = file
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
|
self.file_patched: ifcopenshell.file = None
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
unit = {"is_metric": "METERS" in self.unit, "raw": self.unit}
|
self.file_patched = ifcopenshell.util.unit.convert_file_units(self.file, self.unit)
|
||||||
self.file_patched = ifcopenshell.api.run("project.create_file", version=self.file.schema)
|
|
||||||
if self.file.schema == "IFC2X3":
|
|
||||||
user = self.file_patched.add(self.file.by_type("IfcProject")[0].OwnerHistory.OwningUser)
|
|
||||||
application = self.file_patched.add(self.file.by_type("IfcProject")[0].OwnerHistory.OwningApplication)
|
|
||||||
old_get_user = ifcopenshell.api.owner.settings.get_user
|
|
||||||
old_get_application = ifcopenshell.api.owner.settings.get_application
|
|
||||||
ifcopenshell.api.owner.settings.get_user = lambda ifc: user
|
|
||||||
ifcopenshell.api.owner.settings.get_application = lambda ifc: application
|
|
||||||
project = ifcopenshell.api.run("root.create_entity", self.file_patched, ifc_class="IfcProject")
|
|
||||||
unit_assignment = ifcopenshell.api.run("unit.assign_unit", self.file_patched, **{"length": unit})
|
|
||||||
|
|
||||||
# Is there a better way?
|
|
||||||
for element in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
|
||||||
element.Precision = 1e-8
|
|
||||||
|
|
||||||
# If we don't add openings first, they don't get converted
|
|
||||||
for element in self.file.by_type("IfcOpeningElement"):
|
|
||||||
self.file_patched.add(element)
|
|
||||||
|
|
||||||
for element in self.file:
|
|
||||||
self.file_patched.add(element)
|
|
||||||
|
|
||||||
new_length = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == "LENGTHUNIT"][0]
|
|
||||||
old_length = [
|
|
||||||
u
|
|
||||||
for u in self.file_patched.by_type("IfcProject")[1].UnitsInContext.Units
|
|
||||||
if getattr(u, "UnitType", None) == "LENGTHUNIT"
|
|
||||||
][0]
|
|
||||||
|
|
||||||
for inverse in self.file_patched.get_inverse(old_length):
|
|
||||||
ifcopenshell.util.element.replace_attribute(inverse, old_length, new_length)
|
|
||||||
|
|
||||||
self.file_patched.remove(old_length)
|
|
||||||
self.file_patched.remove(project)
|
|
||||||
|
|
||||||
if self.file.schema == "IFC2X3":
|
|
||||||
ifcopenshell.api.owner.settings.get_user = old_get_user
|
|
||||||
ifcopenshell.api.owner.settings.get_application = old_get_application
|
|
||||||
|
|||||||
Reference in New Issue
Block a user