diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index d815105578..ed2df412bc 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -199,7 +199,7 @@ class MaterialCreator: class IfcImporter: - def __init__(self, ifc_import_settings): + def __init__(self, ifc_import_settings: IfcImportSettings): self.ifc_import_settings = ifc_import_settings self.diff = None self.file: ifcopenshell.file = None @@ -722,7 +722,7 @@ class IfcImporter: if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False): return True - def apply_blender_offset_to_matrix_world(self, obj, matrix): + def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix: props = bpy.context.scene.BIMGeoreferenceProperties if props.has_blender_offset: if obj.data and obj.data.get("has_cartesian_point_offset", None): @@ -950,7 +950,9 @@ class IfcImporter: self.create_product(element, mesh=mesh) def create_products( - self, products, settings: Optional[ifcopenshell.geom.main.settings] = None + self, + products: set[ifcopenshell.entity_instance], + settings: Optional[ifcopenshell.geom.main.settings] = None, ) -> set[ifcopenshell.entity_instance]: results = set() if not products: @@ -1804,7 +1806,7 @@ class IfcImporter: if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING": return rel.RelatingGroup - def get_element_matrix(self, element: ifcopenshell.entity_instance) -> np.array: + def get_element_matrix(self, element: ifcopenshell.entity_instance) -> np.ndarray: if isinstance(element, ifcopenshell.sqlite_entity): result = self.geometry_cache["shapes"][element.id()]["matrix"] else: @@ -1950,14 +1952,14 @@ class IfcImporter: print(traceback.format_exc()) - def a2p(self, o, z, x): + def a2p(self, o: mathutils.Vector, z: mathutils.Vector, x: mathutils.Vector) -> mathutils.Matrix: y = z.cross(x) r = mathutils.Matrix((x, y, z, o)) r.resize_4x4() r.transpose() return r - def get_axis2placement(self, plc): + def get_axis2placement(self, plc: ifcopenshell.entity_instance) -> mathutils.Matrix: if plc.is_a("IfcAxis2Placement3D"): z = mathutils.Vector(plc.Axis.DirectionRatios if plc.Axis else (0, 0, 1)) x = mathutils.Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1, 0, 0)) @@ -1977,7 +1979,7 @@ class IfcImporter: o = plc.LocalOrigin.Coordinates return self.a2p(o, z, x) - def get_local_placement(self, plc): + def get_local_placement(self, plc: Optional[ifcopenshell.entity_instance] = None) -> mathutils.Matrix: if plc is None: return mathutils.Matrix() if plc.PlacementRelTo is None: @@ -1992,11 +1994,11 @@ class IfcImporter: bpy.context.scene.BIMRootProperties.contexts = str(subcontext.id()) break - def link_element(self, element, obj): + def link_element(self, element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None: self.added_data[element.id()] = obj tool.Ifc.link(element, obj) - def set_matrix_world(self, obj, matrix_world): + def set_matrix_world(self, obj: bpy.types.Object, matrix_world: mathutils.Matrix) -> None: obj.matrix_world = matrix_world tool.Geometry.record_object_position(obj) diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 0a3ff34fdf..7acd2429ed 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -622,7 +622,7 @@ class Geometry(blenderbim.core.tool.Geometry): obj.data.BIMMeshProperties.material_checksum = str([s.id() for s in cls.get_styles(obj) if s]) @classmethod - def record_object_position(cls, obj): + def record_object_position(cls, obj: bpy.types.Object) -> None: # These are recorded separately because they have different numerical tolerances obj.BIMObjectProperties.location_checksum = repr(np.array(obj.matrix_world.translation).tobytes()) obj.BIMObjectProperties.rotation_checksum = repr(np.array(obj.matrix_world.to_3x3()).tobytes()) diff --git a/src/ifcopenshell-python/ifcopenshell/__init__.py b/src/ifcopenshell-python/ifcopenshell/__init__.py index f23d64d933..fb984b7176 100644 --- a/src/ifcopenshell-python/ifcopenshell/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/__init__.py @@ -39,6 +39,7 @@ import sys import tempfile import zipfile from pathlib import Path +from typing import Optional import ifcopenshell.util.file @@ -197,12 +198,14 @@ def register_schema(schema): register_schema_attributes(schema.schema) -def schema_by_name(schema=None, schema_version=None): +def schema_by_name( + schema: Optional[str] = None, schema_version: Optional[tuple[int, ...]] = None +) -> ifcopenshell_wrapper.schema_definition: """Returns an object allowing you to query the IFC schema itself :param schema: Which IFC schema to use, chosen from "IFC2X3", "IFC4", or "IFC4X3". These refer to the ISO approved versions of IFC. - :type schema: string + :type schema: string, optional :param schema_version: If you want to specify an exact version of IFC that may not be an ISO approved version, use this argument instead of ``schema``. IFC versions on technical.buildingsmart.org are @@ -211,7 +214,9 @@ def schema_by_name(schema=None, schema_version=None): ADD2 TC1, which is the official version approved by ISO when people refer to "IFC4". Generally you should not use this argument unless you are testing non-ISO IFC releases. - :type schema_version: tuple[int] + :type schema_version: tuple[int, ...], optional + :return: Schema definition object. + :rtype: ifocpenshell_wrapper.schema_definition """ if schema_version: prefixes = ("IFC", "X", "_ADD", "_TC") diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py index 4aed2fd050..68b5d59bbb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py @@ -17,12 +17,19 @@ # along with IfcOpenShell. If not, see . import datetime +import ifcopenshell.util.constraint import ifcopenshell.util.date import ifcopenshell.util.sequence +from typing import Any, Optional class Usecase: - def __init__(self, file, task_time=None, attributes=None): + def __init__( + self, + file: ifcopenshell.file, + task_time: ifcopenshell.entity_instance, + attributes: Optional[dict[str, Any]] = None, + ): """Edits the attributes of an IfcTaskTime For more information about the attributes and data types of an @@ -55,7 +62,7 @@ class Usecase: self.file = file self.settings = {"task_time": task_time, "attributes": attributes or {}} - def execute(self): + def execute(self) -> None: self.task = self.get_task() self.calendar = ifcopenshell.util.sequence.derive_calendar(self.task) @@ -169,12 +176,12 @@ class Usecase: duration, "IfcDuration" ) - def get_task(self): - return [ + def get_task(self) -> ifcopenshell.entity_instance: + return next( e for e in self.file.get_inverse(self.settings["task_time"]) if e.is_a("IfcTask") - ][0] + ) def handle_resource_calculation(self): resources = ifcopenshell.util.sequence.get_task_resources(self.task, is_deep=False) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_time.py index 76a0521478..4512789fdb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_time.py @@ -17,10 +17,16 @@ # along with IfcOpenShell. If not, see . import ifcopenshell.util.date +from typing import Any, Optional class Usecase: - def __init__(self, file, work_time=None, attributes=None): + def __init__( + self, + file: ifcopenshell.file, + work_time: ifcopenshell.entity_instance, + attributes: Optional[dict[str, Any]] = None, + ): """Edits the attributes of an IfcWorkTime For more information about the attributes and data types of an @@ -53,13 +59,15 @@ class Usecase: self.file = file self.settings = {"work_time": work_time, "attributes": attributes or {}} - def execute(self): + def execute(self) -> None: for name, value in self.settings["attributes"].items(): if name in ("Start", "StartDate"): value = ifcopenshell.util.date.datetime2ifc(value, "IfcDate") + # 4 IfcWorktime Start self.settings["work_time"][4] = value elif name in ("Finish", "FinishDate"): value = ifcopenshell.util.date.datetime2ifc(value, "IfcDate") + # 5 IfcWorktime Finish self.settings["work_time"][5] = value else: setattr(self.settings["work_time"], name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index 8c7914a8b0..d5446d731e 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -28,7 +28,7 @@ import numbers import zipfile import functools from pathlib import Path -from typing import List, Optional +from typing import Optional, Any import ifcopenshell.util.element import ifcopenshell.util.file @@ -52,7 +52,7 @@ class Transaction: self.batch_delete_ids = set() self.batch_inverses = [] - def serialise_entity_instance(self, element): + def serialise_entity_instance(self, element: ifcopenshell.entity_instance) -> dict[str, Any]: info = element.get_info() for key, value in info.items(): info[key] = self.serialise_value(element, value) @@ -103,7 +103,7 @@ class Transaction: } ) - def store_delete(self, element): + def store_delete(self, element: ifcopenshell.entity_instance) -> None: inverses = {} if self.is_batched: if element.id() not in self.batch_delete_ids: diff --git a/src/ifcopenshell-python/ifcopenshell/util/sequence.py b/src/ifcopenshell-python/ifcopenshell/util/sequence.py index 5b254d973c..db98be456f 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/sequence.py +++ b/src/ifcopenshell-python/ifcopenshell/util/sequence.py @@ -20,7 +20,20 @@ import datetime import ifcopenshell.util.date from math import floor from functools import lru_cache -from collections import namedtuple +from typing import Union, Literal, Optional, Iterator + + +DURATION_TYPE = Literal["ELAPSEDTIME", "WORKTIME", "NOTDEFINED"] +RECURRENCE_TYPE = Literal[ + "BY_DAY_COUNT", + "BY_WEEKDAY_COUNT", + "DAILY", + "MONTHLY_BY_DAY_OF_MONTH", + "MONTHLY_BY_POSITION", + "WEEKLY", + "YEARLY_BY_DAY_OF_MONTH", + "YEARLY_BY_POSITION", +] def derive_date(task, attribute_name, date=None, is_earliest=False, is_latest=False): @@ -49,7 +62,7 @@ def derive_date(task, attribute_name, date=None, is_earliest=False, is_latest=Fa return date -def derive_calendar(task): +def derive_calendar(task: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: calendar = get_calendar(task) if calendar: return calendar @@ -57,7 +70,7 @@ def derive_calendar(task): return derive_calendar(rel.RelatingObject) -def get_calendar(task): +def get_calendar(task: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: calendar = [ rel.RelatingControl for rel in task.HasAssignments or [] @@ -68,7 +81,7 @@ def get_calendar(task): return calendar[0] -def count_working_days(start, finish, calendar): +def count_working_days(start, finish, calendar: ifcopenshell.entity_instance) -> int: result = 0 if start == finish: return 0 @@ -88,7 +101,11 @@ def count_working_days(start, finish, calendar): def get_start_or_finish_date( - start, duration, duration_type, calendar, date_type="FINISH" + start, + duration, + duration_type: DURATION_TYPE, + calendar: ifcopenshell.entity_instance, + date_type: Literal["START", "FINISH"] = "FINISH", ): if not duration.days: # Typically a milestone will have zero duration, so the start == finish @@ -107,7 +124,7 @@ def get_start_or_finish_date( return datetime.datetime.combine(result, datetime.time(17)) -def offset_date(start, duration, duration_type, calendar): +def offset_date(start, duration, duration_type: DURATION_TYPE, calendar: ifcopenshell.entity_instance): current_date = start months = getattr(duration, "months", 0) years = getattr(duration, "years", 0) @@ -129,7 +146,7 @@ def offset_date(start, duration, duration_type, calendar): return current_date -def get_soonest_working_day(start, duration_type, calendar): +def get_soonest_working_day(start, duration_type: DURATION_TYPE, calendar: ifcopenshell.entity_instance): if duration_type == "ELAPSEDTIME" or not is_calendar_applicable(start, calendar): return start while not is_working_day(start, calendar): @@ -139,7 +156,7 @@ def get_soonest_working_day(start, duration_type, calendar): return start -def get_recent_working_day(start, duration_type, calendar): +def get_recent_working_day(start, duration_type: DURATION_TYPE, calendar: ifcopenshell.entity_instance): if duration_type == "ELAPSEDTIME" or not is_calendar_applicable(start, calendar): return start while not is_working_day(start, calendar): @@ -150,7 +167,7 @@ def get_recent_working_day(start, duration_type, calendar): @lru_cache(maxsize=None) -def is_working_day(day, calendar): +def is_working_day(day, calendar: ifcopenshell.entity_instance) -> bool: is_working_day = False for work_time in calendar.WorkingTimes or []: if is_work_time_applicable_to_day(work_time, day): @@ -166,7 +183,7 @@ def is_working_day(day, calendar): @lru_cache(maxsize=None) -def is_calendar_applicable(day, calendar): +def is_calendar_applicable(day, calendar: ifcopenshell.entity_instance) -> bool: if not calendar or not calendar.WorkingTimes: return False is_applicable = False @@ -177,7 +194,7 @@ def is_calendar_applicable(day, calendar): return is_applicable -def is_day_in_work_time(day, work_time): +def is_day_in_work_time(day, work_time: ifcopenshell.entity_instance) -> bool: is_day_in_work_time = True if isinstance(day, datetime.datetime): day = datetime.date(day.year, day.month, day.day) @@ -198,7 +215,7 @@ def is_day_in_work_time(day, work_time): return is_day_in_work_time -def is_work_time_applicable_to_day(work_time, day): +def is_work_time_applicable_to_day(work_time: ifcopenshell.entity_instance, day) -> bool: if not is_day_in_work_time(day, work_time): return False if not work_time.RecurrencePattern: @@ -249,7 +266,7 @@ def is_work_time_applicable_to_day(work_time, day): return False # TODO -def get_task_work_schedule(task): +def get_task_work_schedule(task: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: parent_task = get_parent_task(task) if parent_task: return get_task_work_schedule(parent_task) or get_task_work_schedule(task) @@ -262,23 +279,23 @@ def get_task_work_schedule(task): return None -def get_nested_tasks(task): +def get_nested_tasks(task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: return [object for rel in task.IsNestedBy or [] for object in rel.RelatedObjects] -def get_parent_task(task): +def get_parent_task(task: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: nests = task.Nests if nests and (obj := nests[0].RelatingObject).is_a("IfcTask"): return obj -def get_all_nested_tasks(task): +def get_all_nested_tasks(task: ifcopenshell.entity_instance) -> Iterator[ifcopenshell.entity_instance]: for nested_task in get_nested_tasks(task): yield nested_task yield from get_all_nested_tasks(nested_task) -def get_work_schedule_tasks(work_schedule): +def get_work_schedule_tasks(work_schedule: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: tasks = [] for root_task in get_root_tasks(work_schedule): nested_tasks = get_all_nested_tasks(root_task) @@ -286,7 +303,7 @@ def get_work_schedule_tasks(work_schedule): return tasks -def get_root_tasks(work_schedule): +def get_root_tasks(work_schedule: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: return [ obj for rel in work_schedule.Controls @@ -295,7 +312,7 @@ def get_root_tasks(work_schedule): ] -def get_root_tasks_ids(work_schedule): +def get_root_tasks_ids(work_schedule: ifcopenshell.entity_instance) -> list[int]: return [ obj.id() for rel in work_schedule.Controls @@ -304,7 +321,7 @@ def get_root_tasks_ids(work_schedule): ] -def guess_date_range(work_schedule): +def guess_date_range(work_schedule: ifcopenshell.entity_instance): earliest = None latest = None root_tasks = get_root_tasks(work_schedule) @@ -326,7 +343,7 @@ def guess_date_range(work_schedule): return earliest, latest -def get_direct_task_outputs(task): +def get_direct_task_outputs(task: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: return [ rel.RelatingProduct for rel in task.HasAssignments @@ -334,7 +351,7 @@ def get_direct_task_outputs(task): ] -def get_task_outputs(task, is_deep=False): +def get_task_outputs(task: ifcopenshell.entity_instance, is_deep=False): if not is_deep: return get_direct_task_outputs(task) else: @@ -345,7 +362,7 @@ def get_task_outputs(task, is_deep=False): ] -def get_task_inputs(task, is_deep=False): +def get_task_inputs(task: ifcopenshell.entity_instance, is_deep=False): if not is_deep: return [ object @@ -368,7 +385,7 @@ def get_task_inputs(task, is_deep=False): ] -def get_task_resources(task, is_deep=False): +def get_task_resources(task: ifcopenshell.entity_instance, is_deep=False): if not is_deep: return [ object @@ -391,15 +408,17 @@ def get_task_resources(task, is_deep=False): ] -def has_task_outputs(task): +def has_task_outputs(task: ifcopenshell.entity_instance) -> bool: return len(get_task_outputs(task)) > 0 -def has_task_inputs(task): +def has_task_inputs(task: ifcopenshell.entity_instance) -> bool: return len(get_task_inputs(task)) > 0 -def get_tasks_for_product(product, schedule=None): +def get_tasks_for_product( + product: ifcopenshell.entity_instance, schedule: Optional[ifcopenshell.entity_instance] = None +) -> tuple[list[ifcopenshell.entity_instance], list[ifcopenshell.entity_instance]]: """ Get all tasks assigned to or referenced by the given product. @@ -441,7 +460,7 @@ def get_tasks_for_product(product, schedule=None): return inputs, outputs -def get_sequence_assignment(task, sequence="successor"): +def get_sequence_assignment(task: ifcopenshell.entity_instance, sequence="successor"): if sequence == "successor": relationship_attr = "IsPredecessorTo" elif sequence == "predecessor":