This commit is contained in:
Andrej730
2024-04-23 17:05:25 +05:00
parent d03b47e1e6
commit db31574103
6 changed files with 56 additions and 33 deletions
@@ -18,10 +18,11 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.util.unit import ifcopenshell.util.unit
from typing import Optional
class Usecase: class Usecase:
def __init__(self, file, name="foot", conversion_offset=None): def __init__(self, file: ifcopenshell.file, name: str = "foot", conversion_offset: Optional[float] = None):
"""Add a conversion based unit """Add a conversion based unit
If you're in one of those countries who don't use SI units, you're If you're in one of those countries who don't use SI units, you're
@@ -41,7 +42,7 @@ class Usecase:
that this is just an example and you don't actually need to specify that this is just an example and you don't actually need to specify
that for fahrenheit as it's built into this API function. For that for fahrenheit as it's built into this API function. For
advanced users only. advanced users only.
:type conversion_offset: float :type conversion_offset: float, optional
:return: The new IfcConversionBasedUnit or :return: The new IfcConversionBasedUnit or
IfcConversionBasedUnitWithOffset IfcConversionBasedUnitWithOffset
:rtype: ifcopenshell.entity_instance.entity_instance :rtype: ifcopenshell.entity_instance.entity_instance
@@ -60,7 +61,7 @@ class Usecase:
self.file = file self.file = file
self.settings = {"name": name, "conversion_offset": conversion_offset} self.settings = {"name": name, "conversion_offset": conversion_offset}
def execute(self): def execute(self) -> ifcopenshell.entity_instance:
unit_type = ifcopenshell.util.unit.imperial_types.get(self.settings["name"], "USERDEFINED") unit_type = ifcopenshell.util.unit.imperial_types.get(self.settings["name"], "USERDEFINED")
dimensions = ifcopenshell.util.unit.named_dimensions[unit_type] dimensions = ifcopenshell.util.unit.named_dimensions[unit_type]
exponents = self.file.createIfcDimensionalExponents(*dimensions) exponents = self.file.createIfcDimensionalExponents(*dimensions)
@@ -17,10 +17,11 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.unit import ifcopenshell.util.unit
from typing import Optional
class Usecase: class Usecase:
def __init__(self, file, unit_type="LENGTHUNIT", prefix=None): def __init__(self, file: ifcopenshell.file, unit_type: str = "LENGTHUNIT", prefix: Optional[str] = None):
"""Add a new SI unit """Add a new SI unit
The supported types are ABSORBEDDOSEUNIT, AMOUNTOFSUBSTANCEUNIT, The supported types are ABSORBEDDOSEUNIT, AMOUNTOFSUBSTANCEUNIT,
@@ -59,7 +60,7 @@ class Usecase:
self.file = file self.file = file
self.settings = {"unit_type": unit_type, "prefix": prefix} self.settings = {"unit_type": unit_type, "prefix": prefix}
def execute(self): def execute(self) -> ifcopenshell.entity_instance:
name = ifcopenshell.util.unit.si_type_names.get(self.settings["unit_type"], None) name = ifcopenshell.util.unit.si_type_names.get(self.settings["unit_type"], None)
return self.file.create_entity( return self.file.create_entity(
"IfcSIUnit", UnitType=self.settings["unit_type"], Name=name, Prefix=self.settings["prefix"] "IfcSIUnit", UnitType=self.settings["unit_type"], Name=name, Prefix=self.settings["prefix"]
@@ -18,10 +18,18 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.util.unit import ifcopenshell.util.unit
from typing import Optional
class Usecase: class Usecase:
def __init__(self, file, units=None, length=None, area=None, volume=None): def __init__(
self,
file: ifcopenshell.file,
units: Optional[list[ifcopenshell.entity_instance]] = None,
length: Optional[dict] = None,
area: Optional[dict] = None,
volume: Optional[dict] = None,
):
"""Assign default project units """Assign default project units
Whenever a unitised quantity is specified, such as a length, area, Whenever a unitised quantity is specified, such as a length, area,
@@ -67,7 +75,7 @@ class Usecase:
self.settings["area"] = area or {"is_metric": True, "raw": "METERS"} self.settings["area"] = area or {"is_metric": True, "raw": "METERS"}
self.settings["volume"] = volume or {"is_metric": True, "raw": "METERS"} self.settings["volume"] = volume or {"is_metric": True, "raw": "METERS"}
def execute(self): def execute(self) -> ifcopenshell.entity_instance:
# We're going to refactor this to split unit creation and assignment # We're going to refactor this to split unit creation and assignment
if self.settings["units"]: if self.settings["units"]:
units = self.settings["units"] units = self.settings["units"]
@@ -84,7 +92,7 @@ class Usecase:
self.assign_units(unit_assignment, units) self.assign_units(unit_assignment, units)
return unit_assignment return unit_assignment
def get_unit_assignment(self): def get_unit_assignment(self) -> ifcopenshell.entity_instance:
unit_assignment = self.file.by_type("IfcUnitAssignment") unit_assignment = self.file.by_type("IfcUnitAssignment")
if unit_assignment: if unit_assignment:
unit_assignment = unit_assignment[0] unit_assignment = unit_assignment[0]
@@ -97,13 +105,15 @@ class Usecase:
self.file.by_type("IfcContext")[0].UnitsInContext = unit_assignment self.file.by_type("IfcContext")[0].UnitsInContext = unit_assignment
return unit_assignment return unit_assignment
def assign_units(self, unit_assignment, new_units): def assign_units(
self, unit_assignment: ifcopenshell.entity_instance, new_units: list[ifcopenshell.entity_instance]
) -> None:
units = set(unit_assignment.Units or []) units = set(unit_assignment.Units or [])
for unit in new_units: for unit in new_units:
units.add(unit) units.add(unit)
unit_assignment.Units = list(units) unit_assignment.Units = list(units)
def create_metric_unit(self, unit_type, data): def create_metric_unit(self, unit_type: str, data: dict) -> ifcopenshell.entity_instance:
type_prefix = "" type_prefix = ""
if unit_type == "area": if unit_type == "area":
type_prefix = "SQUARE_" type_prefix = "SQUARE_"
@@ -116,7 +126,7 @@ class Usecase:
type_prefix + ifcopenshell.util.unit.get_unit_name(data["raw"]), type_prefix + ifcopenshell.util.unit.get_unit_name(data["raw"]),
) )
def create_imperial_unit(self, unit_type, data): def create_imperial_unit(self, unit_type: str, data: dict) -> ifcopenshell.entity_instance:
if unit_type == "length": if unit_type == "length":
dimensional_exponents = self.file.createIfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0) dimensional_exponents = self.file.createIfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0)
name_prefix = "" name_prefix = ""
@@ -15,10 +15,12 @@
# #
# 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 typing import Optional
class Usecase: class Usecase:
def __init__(self, file, units=None): def __init__(self, file: ifcopenshell.file, units: Optional[list[ifcopenshell.entity_instance]] = None):
"""Unassigns units as default units for the project """Unassigns units as default units for the project
:param units: A list of units to assign as project defaults. :param units: A list of units to assign as project defaults.
@@ -21,6 +21,7 @@ from math import pi
from typing import Iterable, Any, Union, Literal, Optional from typing import Iterable, Any, Union, Literal, Optional
import ifcopenshell import ifcopenshell
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
import ifcopenshell.api import ifcopenshell.api
prefixes = { prefixes = {
@@ -353,7 +354,7 @@ def get_prefix_multiplier(text):
return 1 return 1
def get_unit_name(text): def get_unit_name(text: str) -> Union[str, None]:
text = text.upper().replace("METER", "METRE") text = text.upper().replace("METER", "METRE")
for name in unit_names: for name in unit_names:
if name.replace("_", " ") in text: if name.replace("_", " ") in text:
@@ -368,13 +369,13 @@ def get_named_dimensions(name):
return named_dimensions.get(name, (0, 0, 0, 0, 0, 0, 0)) return named_dimensions.get(name, (0, 0, 0, 0, 0, 0, 0))
def get_unit_assignment(ifc_file): def get_unit_assignment(ifc_file: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None]:
unit_assignments = ifc_file.by_type("IfcUnitAssignment") unit_assignments = ifc_file.by_type("IfcUnitAssignment")
if unit_assignments: if unit_assignments:
return unit_assignments[0] return unit_assignments[0]
def get_project_unit(ifc_file, unit_type): def get_project_unit(ifc_file: ifcopenshell.file, unit_type: str) -> Union[ifcopenshell.entity_instance, None]:
"""Get the default project unit of a particular unit type """Get the default project unit of a particular unit type
:param ifc_file: The IFC file. :param ifc_file: The IFC file.
@@ -384,7 +385,7 @@ def get_project_unit(ifc_file, unit_type):
:type unit_type: str :type unit_type: str
:return: The IFC unit entity, or nothing if there is no default project unit :return: The IFC unit entity, or nothing if there is no default project unit
defined. defined.
:rtype: ifcopenshell.entity_instance,None :rtype: Union[ifcopenshell.entity_instance, None]
""" """
unit_assignment = get_unit_assignment(ifc_file) unit_assignment = get_unit_assignment(ifc_file)
if unit_assignment: if unit_assignment:
@@ -393,7 +394,9 @@ def get_project_unit(ifc_file, unit_type):
return unit return unit
def get_property_unit(prop, ifc_file): def get_property_unit(
prop: ifcopenshell.entity_instance, ifc_file: ifcopenshell.file
) -> Union[ifcopenshell.entity_instance, None]:
unit = getattr(prop, "Unit", None) unit = getattr(prop, "Unit", None)
if unit: if unit:
return unit return unit
@@ -446,14 +449,14 @@ def get_property_unit(prop, ifc_file):
return units[0] return units[0]
def get_unit_measure_class(unit_type): def get_unit_measure_class(unit_type: str) -> str:
if unit_type == "USERDEFINED": if unit_type == "USERDEFINED":
# See https://github.com/buildingSMART/IFC4.3.x-development/issues/71 # See https://github.com/buildingSMART/IFC4.3.x-development/issues/71
return "IfcNumericMeasure" return "IfcNumericMeasure"
return "Ifc" + unit_type[0:-4].lower().capitalize() + "Measure" return "Ifc" + unit_type[0:-4].lower().capitalize() + "Measure"
def get_measure_unit_type(measure_class): def get_measure_unit_type(measure_class: str) -> str:
if measure_class == "IfcNumericMeasure": if measure_class == "IfcNumericMeasure":
# See https://github.com/buildingSMART/IFC4.3.x-development/issues/71 # See https://github.com/buildingSMART/IFC4.3.x-development/issues/71
return "USERDEFINED" return "USERDEFINED"
@@ -462,7 +465,7 @@ def get_measure_unit_type(measure_class):
return measure_class.upper() + "UNIT" return measure_class.upper() + "UNIT"
def get_symbol_measure_class(symbol): def get_symbol_measure_class(symbol: Optional[str] = None) -> str:
# Dumb, but everybody gets it, unlike regex golf # Dumb, but everybody gets it, unlike regex golf
if not symbol: if not symbol:
return "IfcNumericMeasure" return "IfcNumericMeasure"
@@ -480,7 +483,7 @@ def get_symbol_measure_class(symbol):
return "IfcNumericMeasure" return "IfcNumericMeasure"
def get_symbol_quantity_class(symbol): def get_symbol_quantity_class(symbol: Optional[str] = None) -> str:
# Dumb, but everybody gets it, unlike regex golf # Dumb, but everybody gets it, unlike regex golf
if not symbol: if not symbol:
return "IfcQuantityCount" return "IfcQuantityCount"
@@ -498,7 +501,7 @@ def get_symbol_quantity_class(symbol):
return "IfcQuantityCount" return "IfcQuantityCount"
def get_unit_symbol(unit): def get_unit_symbol(unit: ifcopenshell.entity_instance) -> str:
symbol = "" symbol = ""
if unit.is_a("IfcSIUnit"): if unit.is_a("IfcSIUnit"):
symbol += prefix_symbols.get(unit.Prefix, "") symbol += prefix_symbols.get(unit.Prefix, "")
@@ -508,7 +511,7 @@ def get_unit_symbol(unit):
return symbol return symbol
def convert_unit(value, from_unit, to_unit): def convert_unit(value: float, from_unit: ifcopenshell.entity_instance, to_unit: ifcopenshell.entity_instance) -> float:
"""Convert from one unit to another unit """Convert from one unit to another unit
:param value: The numeric value you want to convert :param value: The numeric value you want to convert
@@ -668,9 +671,9 @@ def format_length(
def is_attr_type( def is_attr_type(
content_type: Union[ifcopenshell.ifcopenshell_wrapper.named_type, ifcopenshell.ifcopenshell_wrapper.type_declaration], content_type: ifcopenshell_wrapper.parameter_type,
ifc_unit_type_name: str, ifc_unit_type_name: str,
) -> Union[ifcopenshell.ifcopenshell_wrapper.type_declaration, None]: ) -> Union[ifcopenshell_wrapper.type_declaration, None]:
cur_decl = content_type cur_decl = content_type
while hasattr(cur_decl, "declared_type") is True: while hasattr(cur_decl, "declared_type") is True:
cur_decl = cur_decl.declared_type() cur_decl = cur_decl.declared_type()
@@ -679,7 +682,7 @@ def is_attr_type(
if cur_decl.name() == ifc_unit_type_name: if cur_decl.name() == ifc_unit_type_name:
return cur_decl return cur_decl
if isinstance(cur_decl, ifcopenshell.ifcopenshell_wrapper.aggregation_type): if isinstance(cur_decl, ifcopenshell_wrapper.aggregation_type):
res = cur_decl.type_of_element() res = cur_decl.type_of_element()
cur_decl = res.declared_type() cur_decl = res.declared_type()
if hasattr(cur_decl, "name") and cur_decl.name() == ifc_unit_type_name: if hasattr(cur_decl, "name") and cur_decl.name() == ifc_unit_type_name:
@@ -696,13 +699,13 @@ def is_attr_type(
def iter_element_and_attributes_per_type( def iter_element_and_attributes_per_type(
ifc_file: ifcopenshell.file, attr_type_name: str ifc_file: ifcopenshell.file, attr_type_name: str
) -> Iterable[tuple[ifcopenshell.entity_instance, ifcopenshell.ifcopenshell_wrapper.attribute, Any, str]]: ) -> Iterable[tuple[ifcopenshell.entity_instance, ifcopenshell_wrapper.attribute, Any]]:
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(ifc_file.schema) schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(ifc_file.schema)
for element in ifc_file: for element in ifc_file:
entity = schema.declaration_by_name(element.is_a()) entity = schema.declaration_by_name(element.is_a())
attrs = entity.all_attributes() attrs = entity.all_attributes()
for i, (attr, val, is_derived) in enumerate(zip(attrs, list(element), entity.derived())): for attr, val, is_derived in zip(attrs, list(element), entity.derived()):
if is_derived: if is_derived:
continue continue
@@ -725,7 +728,7 @@ def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str) ->
# Copy all elements from the original file to the patched file # Copy all elements from the original file to the patched file
file_patched = ifcopenshell.file.from_string(ifc_file.wrapped_data.to_string()) file_patched = ifcopenshell.file.from_string(ifc_file.wrapped_data.to_string())
unit_assignment = ifcopenshell.util.unit.get_unit_assignment(file_patched) unit_assignment = get_unit_assignment(file_patched)
old_length = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == "LENGTHUNIT"][0] 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) new_length = ifcopenshell.api.run("unit.add_si_unit", file_patched, unit_type="LENGTHUNIT", prefix=prefix)
@@ -733,10 +736,10 @@ def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str) ->
# Traverse all elements and their nested attributes in the file and convert them # Traverse all elements and their nested attributes in the file and convert them
for element, attr, val in iter_element_and_attributes_per_type(file_patched, "IfcLengthMeasure"): for element, attr, val in iter_element_and_attributes_per_type(file_patched, "IfcLengthMeasure"):
if isinstance(val, tuple): if isinstance(val, tuple):
new_value = [ifcopenshell.util.unit.convert_unit(v, old_length, new_length) for v in val] new_value = [convert_unit(v, old_length, new_length) for v in val]
setattr(element, attr.name(), tuple(new_value)) setattr(element, attr.name(), tuple(new_value))
else: else:
new_value = ifcopenshell.util.unit.convert_unit(val, old_length, new_length) new_value = convert_unit(val, old_length, new_length)
setattr(element, attr.name(), new_value) setattr(element, attr.name(), new_value)
file_patched.remove(old_length) file_patched.remove(old_length)
@@ -26,7 +26,13 @@ from logging import Logger
class Patcher: class Patcher:
def __init__(self, src: str, file: ifcopenshell.file, logger: Logger, unit: str = "METERS"): def __init__(
self,
src: str,
file: ifcopenshell.file,
logger: Logger,
unit: str = "METERS",
):
"""Converts the length unit of a model to the specified unit """Converts the length unit of a model to the specified unit
Allowed metric units include METERS, MILLIMETERS, CENTIMETERS, etc. Allowed metric units include METERS, MILLIMETERS, CENTIMETERS, etc.