This commit is contained in:
Andrej730
2024-03-18 16:09:35 +05:00
parent e2a06e10b6
commit b67b5e8d9f
5 changed files with 63 additions and 33 deletions
+24 -7
View File
@@ -29,6 +29,7 @@ import ifcopenshell.util.selector
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.schema import ifcopenshell.util.schema
from statistics import mean from statistics import mean
from typing import Optional, Union
try: try:
from odf.namespaces import OFFICENS from odf.namespaces import OFFICENS
@@ -269,7 +270,7 @@ class IfcCsv:
elif not include_global_id: elif not include_global_id:
self.results = sorted(self.results, key=lambda x: x[0]) self.results = sorted(self.results, key=lambda x: x[0])
def export_csv(self, output, delimiter=None): def export_csv(self, output: str, delimiter: Optional[str] = None) -> None:
with open(output, "w", newline="", encoding="utf-8") as f: with open(output, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f, delimiter=delimiter) writer = csv.writer(f, delimiter=delimiter)
writer.writerow(self.headers) writer.writerow(self.headers)
@@ -387,8 +388,16 @@ class IfcCsv:
self.import_xlsx(ifc_file, table, attributes, null, empty, bool_true, bool_false) self.import_xlsx(ifc_file, table, attributes, null, empty, bool_true, bool_false)
def import_csv( def import_csv(
self, ifc_file, table, attributes=None, delimiter=",", null="-", empty="", bool_true="YES", bool_false="NO" self,
): ifc_file: ifcopenshell.file,
table: str,
attributes: Optional[list[Union[str, None]]] = None,
delimiter: str = ",",
null: str = "-",
empty: str = "",
bool_true: str = "YES",
bool_false: str = "NO",
) -> None:
with open(table, newline="", encoding="utf-8") as f: with open(table, newline="", encoding="utf-8") as f:
reader = csv.reader(f, delimiter=delimiter) reader = csv.reader(f, delimiter=delimiter)
headers = [] headers = []
@@ -421,7 +430,17 @@ class IfcCsv:
for _, row in df.iterrows(): for _, row in df.iterrows():
self.process_row(ifc_file, row.tolist(), headers, attributes, null, empty, bool_true, bool_false) self.process_row(ifc_file, row.tolist(), headers, attributes, null, empty, bool_true, bool_false)
def process_row(self, ifc_file, row, headers, attributes, null, empty, bool_true, bool_false): def process_row(
self,
ifc_file: ifcopenshell.file,
row: list[str],
headers: list[str],
attributes: list[Union[str, None]],
null: str,
empty: str,
bool_true: str,
bool_false: str,
) -> None:
try: try:
element = ifc_file.by_guid(row[0]) element = ifc_file.by_guid(row[0])
except: except:
@@ -448,9 +467,7 @@ if __name__ == "__main__":
parser.add_argument("-s", "--spreadsheet", type=str, default="data.csv", help="The spreadsheet file") parser.add_argument("-s", "--spreadsheet", type=str, default="data.csv", help="The spreadsheet file")
parser.add_argument("-f", "--format", type=str, default="csv", help="The format, chosen from csv, ods, or xlsx") parser.add_argument("-f", "--format", type=str, default="csv", help="The format, chosen from csv, ods, or xlsx")
parser.add_argument("-d", "--delimiter", type=str, default=",", help="The delimiter in CSV. Defaults to a comma.") parser.add_argument("-d", "--delimiter", type=str, default=",", help="The delimiter in CSV. Defaults to a comma.")
parser.add_argument( parser.add_argument("-n", "--null", type=str, default="N/A", help="How to represent null values. Defaults to N/A.")
"-n", "--null", type=str, default="N/A", help="How to represent null values. Defaults to N/A."
)
parser.add_argument( parser.add_argument(
"-e", "--empty", type=str, default="-", help="How to represent empty strings. Defaults to a hyphen." "-e", "--empty", type=str, default="-", help="How to represent empty strings. Defaults to a hyphen."
) )
@@ -17,14 +17,15 @@
# 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.element import ifcopenshell.util.element
from typing import Optional
def get_references(element, should_inherit=True): def get_references(element: ifcopenshell.entity_instance, should_inherit=True) -> set[ifcopenshell.entity_instance]:
results = set() results = set()
if not element.is_a("IfcRoot"): if not element.is_a("IfcRoot"):
if hasattr(element, "HasExternalReferences"): if hasattr(element, "HasExternalReferences"):
return {r.RelatingReference for r in element.HasExternalReferences or []} return {r.RelatingReference for r in element.HasExternalReferences or []}
elif hasattr(element, "HasExternalReference"): # Seriously, IFC? elif hasattr(element, "HasExternalReference"): # Seriously, IFC?
return {r.RelatingReference for r in element.HasExternalReference or []} return {r.RelatingReference for r in element.HasExternalReference or []}
if should_inherit: if should_inherit:
element_type = ifcopenshell.util.element.get_type(element) element_type = ifcopenshell.util.element.get_type(element)
@@ -50,13 +51,13 @@ def get_references(element, should_inherit=True):
return occurrence_results return occurrence_results
def get_classification(reference): def get_classification(reference: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
if reference.is_a("IfcClassification"): if reference.is_a("IfcClassification"):
return reference return reference
return get_classification(reference.ReferencedSource) return get_classification(reference.ReferencedSource)
def get_inherited_references(reference): def get_inherited_references(reference: Optional[ifcopenshell.entity_instance]) -> list[ifcopenshell.entity_instance]:
results = [] results = []
while True: while True:
if not reference or reference.is_a("IfcClassification"): if not reference or reference.is_a("IfcClassification"):
@@ -64,4 +65,3 @@ def get_inherited_references(reference):
results.append(reference) results.append(reference)
reference = reference.ReferencedSource reference = reference.ReferencedSource
return results return results
@@ -18,9 +18,10 @@
import numpy as np import numpy as np
import ifcopenshell import ifcopenshell
from typing import Literal, Iterable
def a2p(o, z, x): def a2p(o: Iterable[float], z: Iterable[float], x: Iterable[float]) -> np.array:
"""Converts a location, X, and Z axis vector to a 4x4 transformation matrix """Converts a location, X, and Z axis vector to a 4x4 transformation matrix
IFC uses a right-handed coordinate system, so it is not necessary to IFC uses a right-handed coordinate system, so it is not necessary to
@@ -73,7 +74,7 @@ def get_axis2placement(placement: ifcopenshell.entity_instance) -> np.array:
return a2p(o, z, x) return a2p(o, z, x)
def get_local_placement(placement): def get_local_placement(placement: ifcopenshell.entity_instance) -> np.array:
"""Parse a local placement into a 4x4 transformation matrix """Parse a local placement into a 4x4 transformation matrix
This is typically used to find the location and rotation of an element. The This is typically used to find the location and rotation of an element. The
@@ -107,7 +108,7 @@ def get_local_placement(placement):
return np.dot(parent, get_axis2placement(placement.RelativePlacement)) return np.dot(parent, get_axis2placement(placement.RelativePlacement))
def get_cartesiantransformationoperator3d(inst): def get_cartesiantransformationoperator3d(inst: ifcopenshell.entity_instance) -> np.array:
"""Parses an IfcCartesianTransformationOperator into a 4x4 transformation matrix """Parses an IfcCartesianTransformationOperator into a 4x4 transformation matrix
Note that in general you will not need to call this directly. See Note that in general you will not need to call this directly. See
@@ -152,7 +153,7 @@ def get_cartesiantransformationoperator3d(inst):
return m4 return m4
def get_mappeditem_transformation(item): def get_mappeditem_transformation(item: ifcopenshell.entity_instance) -> np.array:
"""Parse an IfcMappedItem into a 4x4 transformation matrix """Parse an IfcMappedItem into a 4x4 transformation matrix
Mapped items take a representation with an origin and transform them with a Mapped items take a representation with an origin and transform them with a
@@ -170,7 +171,7 @@ def get_mappeditem_transformation(item):
return get_cartesiantransformationoperator3d(item.MappingTarget) @ m4 return get_cartesiantransformationoperator3d(item.MappingTarget) @ m4
def get_storey_elevation(storey): def get_storey_elevation(storey: ifcopenshell.entity_instance) -> float:
"""Get the Z elevation in project units of a buildling storey """Get the Z elevation in project units of a buildling storey
Building storeys store elevation in two possible locations: the Z value of Building storeys store elevation in two possible locations: the Z value of
@@ -187,7 +188,7 @@ def get_storey_elevation(storey):
return getattr(storey, "Elevation", 0.0) or 0.0 return getattr(storey, "Elevation", 0.0) or 0.0
def rotation(angle, axis, is_degrees=True): def rotation(angle: float, axis: Literal["X", "Y", "Z"], is_degrees=True) -> np.array:
"""Create a 4x4 numpy matrix representing an euler rotation """Create a 4x4 numpy matrix representing an euler rotation
:param angle: The angle of rotation :param angle: The angle of rotation
@@ -21,6 +21,7 @@ import json
import time import time
import ifcopenshell import ifcopenshell
import ifcopenshell.util.attribute import ifcopenshell.util.attribute
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
# This is highly experimental and incomplete, however, it may work for simple datasets. # This is highly experimental and incomplete, however, it may work for simple datasets.
# In this simple implementation, we only support 2X3<->4 right now # In this simple implementation, we only support 2X3<->4 right now
@@ -28,7 +29,7 @@ import ifcopenshell.util.attribute
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
def get_fallback_schema(version): def get_fallback_schema(version: str) -> str:
"""fallback to the schema version we do have docs and mapping for, """fallback to the schema version we do have docs and mapping for,
needed to support IFC versions like 4X3_RC1, 4X1 etc""" needed to support IFC versions like 4X3_RC1, 4X1 etc"""
if version.startswith("IFC4X3"): if version.startswith("IFC4X3"):
@@ -38,7 +39,7 @@ def get_fallback_schema(version):
return version return version
def is_a(entity, ifc_class): def is_a(entity: ifcopenshell.entity_instance, ifc_class: str) -> bool:
ifc_class = ifc_class.upper() ifc_class = ifc_class.upper()
if entity.name_uc() == ifc_class: if entity.name_uc() == ifc_class:
return True return True
@@ -59,7 +60,9 @@ def get_subtypes(entity):
return get_classes(entity) return get_classes(entity)
def reassign_class(ifc_file, element, new_class): def reassign_class(
ifc_file: ifcopenshell.file, element: ifcopenshell.entity_instance, new_class: str
) -> ifcopenshell.entity_instance:
""" """
Attempts to change the class (entity name) of `element` to `new_class` by Attempts to change the class (entity name) of `element` to `new_class` by
removing element and recreating a similar instance of type `new_class` removing element and recreating a similar instance of type `new_class`
@@ -74,7 +77,7 @@ def reassign_class(ifc_file, element, new_class):
It's unlikely that this affects real-world usage of this function. It's unlikely that this affects real-world usage of this function.
""" """
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(ifc_file.schema) schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(ifc_file.schema)
try: try:
declaration = schema.declaration_by_name(new_class) declaration = schema.declaration_by_name(new_class)
except: except:
@@ -116,11 +119,11 @@ def reassign_class(ifc_file, element, new_class):
class BatchReassignClass: class BatchReassignClass:
def __init__(self, file): def __init__(self, file: ifcopenshell.file):
self.file = file self.file = file
self.purge() self.purge()
def reassign(self, element, new_class): def reassign(self, element: ifcopenshell.entity_instance, new_class: str) -> ifcopenshell.entity_instance:
try: try:
new_element = self.file.create_entity(new_class) new_element = self.file.create_entity(new_class)
except: except:
@@ -150,9 +153,12 @@ class BatchReassignClass:
self.file.remove(element) self.file.remove(element)
self.purge() self.purge()
def purge(self): def purge(self) -> None:
self.replacements = {} # mapping {inverse: {attribute_index: {old_element: new_element} } }
self.to_delete = set() self.replacements: dict[
ifcopenshell.entity_instance, dict[int, dict[ifcopenshell.entity_instance, ifcopenshell.entity_instance]]
] = {}
self.to_delete: set[ifcopenshell.entity_instance] = set()
class Migrator: class Migrator:
@@ -217,7 +223,7 @@ class Migrator:
"User": None, "User": None,
} }
def migrate(self, element, new_file): def migrate(self, element: ifcopenshell.entity_instance, new_file: ifcopenshell.file) -> ifcopenshell.entity_instance:
if element.id() == 0: if element.id() == 0:
return new_file.create_entity(element.is_a(), element.wrappedValue) return new_file.create_entity(element.is_a(), element.wrappedValue)
try: try:
@@ -25,8 +25,9 @@ import ifcopenshell.util.element
import ifcopenshell.util.placement import ifcopenshell.util.placement
import ifcopenshell.util.geolocation import ifcopenshell.util.geolocation
import ifcopenshell.util.classification import ifcopenshell.util.classification
import ifcopenshell.util.schema
from decimal import Decimal from decimal import Decimal
from typing import Optional from typing import Optional, Any, Union
filter_elements_grammar = lark.Lark( filter_elements_grammar = lark.Lark(
@@ -258,8 +259,8 @@ def format(query):
return FormatTransformer().transform(format_grammar.parse(query)) return FormatTransformer().transform(format_grammar.parse(query))
def get_element_value(element, query): def get_element_value(element: ifcopenshell.entity_instance, query: str) -> Any:
keys = GetElementTransformer().transform(get_element_grammar.parse(query)) keys: list[str] = GetElementTransformer().transform(get_element_grammar.parse(query))
return Selector.get_element_value(element, keys) return Selector.get_element_value(element, keys)
@@ -309,7 +310,12 @@ def filter_elements(
return transformer.elements return transformer.elements
def set_element_value(ifc_file, element, query, value): def set_element_value(
ifc_file: ifcopenshell.file,
element: ifcopenshell.entity_instance,
query: Union[str, list[str]],
value: Optional[str],
) -> Union[ifcopenshell.entity_instance, None]:
if isinstance(query, (list, tuple)): if isinstance(query, (list, tuple)):
keys = query keys = query
else: else:
@@ -885,7 +891,7 @@ class Selector:
return {"keys": keys, "is_regex": is_regex} return {"keys": keys, "is_regex": is_regex}
@classmethod @classmethod
def get_element_value(cls, element, keys): def get_element_value(cls, element: ifcopenshell.entity_instance, keys: list[str]) -> Any:
value = element value = element
for key in keys: for key in keys:
if value is None: if value is None: