diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py
index 1c22ea0f01..9865e7cd28 100644
--- a/src/blenderbim/blenderbim/bim/module/search/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/search/operator.py
@@ -20,6 +20,7 @@ import re
import bpy
import json
import ifcopenshell
+import ifcopenshell.api
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.selector
diff --git a/src/ifcopenshell-python/ifcopenshell/api/boundary/assign_connection_geometry.py b/src/ifcopenshell-python/ifcopenshell/api/boundary/assign_connection_geometry.py
index 90994d8499..a5693324e4 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/boundary/assign_connection_geometry.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/boundary/assign_connection_geometry.py
@@ -89,7 +89,6 @@ def assign_connection_geometry(
usecase.axis = axis
usecase.ref_direction = ref_direction
usecase.unit_scale = unit_scale
- usecase.ifc_vertices = []
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py b/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py
index 95a0929ab6..38c79d20f2 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/context/add_context.py
@@ -16,8 +16,17 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+import ifcopenshell
+from typing import Optional
-def add_context(file, context_type=None, context_identifier=None, target_view=None, parent=None) -> None:
+
+def add_context(
+ file: ifcopenshell.file,
+ context_type: str,
+ context_identifier: Optional[str] = None,
+ target_view: Optional[str] = None,
+ parent: Optional[ifcopenshell.entity_instance] = None,
+) -> ifcopenshell.entity_instance:
"""Adds a new geometric representation context
In IFC, physical objects may have zero, one, or multiple geometric
@@ -104,7 +113,7 @@ def add_context(file, context_type=None, context_identifier=None, target_view=No
:type parent: ifcopenshell.entity_instance, optional
:return: the newly created IfcGeometricRepresentationContext or
IfcGeometricRepresentationSubContext entity
- :rtype: ifcopenshell.entity_instance, optional
+ :rtype: ifcopenshell.entity_instance
Example:
diff --git a/src/ifcopenshell-python/ifcopenshell/api/context/edit_context.py b/src/ifcopenshell-python/ifcopenshell/api/context/edit_context.py
index 30f6d642b1..5bef4c9956 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/context/edit_context.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/context/edit_context.py
@@ -16,8 +16,11 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+import ifcopenshell
+from typing import Any
-def edit_context(file, context, attributes) -> None:
+
+def edit_context(file: ifcopenshell.file, context: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
"""Edits the attributes of an IfcGeometricRepresentationContext
For more information about the attributes and data types of an
@@ -26,7 +29,7 @@ def edit_context(file, context, attributes) -> None:
:param context: The IfcGeometricRepresentationContext entity you want to edit
:type context: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
- :type attributes: dict, optional
+ :type attributes: dict
:return: None
:rtype: None
@@ -44,7 +47,7 @@ def edit_context(file, context, attributes) -> None:
ifcopenshell.api.run("context.edit_context", model,
context=body, attributes={"ContextIdentifier": "Body"})
"""
- settings = {"context": context, "attributes": attributes or {}}
+ settings = {"context": context, "attributes": attributes}
for name, value in settings["attributes"].items():
setattr(settings["context"], name, value)
diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_schedule.py
index 65c95ca0d1..cb8e678420 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_schedule.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/cost/add_cost_schedule.py
@@ -22,7 +22,9 @@ from datetime import datetime
from typing import Optional
-def add_cost_schedule(file: ifcopenshell.file, name: Optional[str] = None, predefined_type="NOTDEFINED") -> None:
+def add_cost_schedule(
+ file: ifcopenshell.file, name: Optional[str] = None, predefined_type: str = "NOTDEFINED"
+) -> ifcopenshell.entity_instance:
"""Add a new cost schedule
A cost schedule is a group of cost items which typically represent a
diff --git a/src/ifcopenshell-python/ifcopenshell/api/document/edit_information.py b/src/ifcopenshell-python/ifcopenshell/api/document/edit_information.py
index 478c1c11da..0c9ef1cce0 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/document/edit_information.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/document/edit_information.py
@@ -16,13 +16,13 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
import ifcopenshell
-from typing import Any, Optional
+from typing import Any
def edit_information(
file: ifcopenshell.file,
information: ifcopenshell.entity_instance,
- attributes: Optional[dict[str, Any]] = None,
+ attributes: dict[str, Any],
) -> None:
"""Edits the attributes of an IfcDocumentInformation
@@ -32,7 +32,7 @@ def edit_information(
:param reference: The IfcDocumentInformation entity you want to edit
:type reference: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
- :type attributes: dict, optional
+ :type attributes: dict
:return: None
:rtype: None
@@ -46,7 +46,7 @@ def edit_information(
attributes={"Identification": "A-GA-6100", "Name": "Overall Plan",
"Location": "A-GA-6100 - Overall Plan.pdf"})
"""
- settings = {"information": information, "attributes": attributes or {}}
+ settings = {"information": information, "attributes": attributes}
for name, value in settings["attributes"].items():
setattr(settings["information"], name, value)
diff --git a/src/ifcopenshell-python/ifcopenshell/api/document/edit_reference.py b/src/ifcopenshell-python/ifcopenshell/api/document/edit_reference.py
index fb705fbbc2..3ba210cef1 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/document/edit_reference.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/document/edit_reference.py
@@ -16,13 +16,13 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
import ifcopenshell
-from typing import Any, Optional
+from typing import Any
def edit_reference(
file: ifcopenshell.file,
reference: ifcopenshell.entity_instance,
- attributes: Optional[dict[str, Any]] = None,
+ attributes: dict[str, Any],
) -> None:
"""Edits the attributes of an IfcDocumentReference
@@ -32,7 +32,7 @@ def edit_reference(
:param reference: The IfcDocumentReference entity you want to edit
:type reference: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
- :type attributes: dict, optional
+ :type attributes: dict
:return: None
:rtype: None
@@ -49,7 +49,7 @@ def edit_reference(
ifcopenshell.api.run("document.edit_reference", model,
reference=reference, attributes={"Identification": "2.1.15"})
"""
- settings = {"reference": reference, "attributes": attributes or {}}
+ settings = {"reference": reference, "attributes": attributes}
for name, value in settings["attributes"].items():
setattr(settings["reference"], name, value)
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_axis_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_axis_representation.py
index d8180d287f..0c78866ccc 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_axis_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_axis_representation.py
@@ -17,9 +17,14 @@
# along with IfcOpenShell. If not, see .
import ifcopenshell.util.unit
+from typing import Union
+
+COORD = Union[tuple[float, float], tuple[float, float, float]]
-def add_axis_representation(file, context=None, axis=None) -> None:
+def add_axis_representation(
+ file: ifcopenshell.file, context: ifcopenshell.entity_instance, axis: tuple[COORD, COORD]
+) -> ifcopenshell.entity_instance:
"""Adds a new axis representation
Certain objects are typically "axis-based", such as walls, beams,
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_boolean.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_boolean.py
index 3ec590a6f3..026e118805 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_boolean.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_boolean.py
@@ -16,27 +16,51 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+from __future__ import annotations
import ifcopenshell.util.unit
import numpy as np
+import numpy.typing as npt
+from typing import Optional, TYPE_CHECKING, Literal
+
+if TYPE_CHECKING:
+ import bpy.types
-def add_boolean(file, **usecase_settings) -> None:
+NPArrayOfFloats = npt.NDArray[np.float64]
+
+
+def add_boolean(
+ file: ifcopenshell.file,
+ representation: ifcopenshell.entity_instance,
+ # A matrix to define a clipping Ifchalfspacesolid.
+ # The XY plane is the clipping boundary and +Z is removed.
+ operator: str = "DIFFERENCE",
+ # IfcHalfSpaceSolid, Mesh
+ type: Literal["IfcHalfSpaceSolid", "Mesh"] = "IfcHalfSpaceSolid",
+ matrix: Optional[NPArrayOfFloats] = None,
+ # A Blender OBJ to define the voided OBJ for a "Mesh" type
+ blender_obj: Optional[bpy.types.Object] = None,
+ # A Blender OBJ to define the void OBJ for a "Mesh" type
+ blender_void: Optional[bpy.types.Object] = None,
+ should_force_faceted_brep: bool = False,
+ should_force_triangulation: bool = False,
+) -> list[ifcopenshell.entity_instance]:
+ """For `type` values:
+ - "IfcHalfSpaceSolid" - `matrix` is not optional.
+ - "Mesh" - `blender_obj` and `blender_void` are not optional
+ """
usecase = Usecase()
usecase.file = file
usecase.settings = {
- "representation": None,
- "operator": "DIFFERENCE",
- # IfcHalfSpaceSolid, Mesh
- "type": "IfcHalfSpaceSolid",
- # The XY plane is the clipping boundary and +Z is removed.
- "matrix": None, # A matrix to define a clipping Ifchalfspacesolid.
- "blender_obj": None, # A Blender OBJ to define the voided OBJ for a "Mesh" type
- "blender_void": None, # A Blender OBJ to define the void OBJ for a "Mesh" type
- "should_force_faceted_brep": False,
- "should_force_triangulation": False,
+ "representation": representation,
+ "operator": operator,
+ "type": type,
+ "matrix": matrix,
+ "blender_obj": blender_obj,
+ "blender_void": blender_void,
+ "should_force_faceted_brep": should_force_faceted_brep,
+ "should_force_triangulation": should_force_triangulation,
}
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_door_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_door_representation.py
index da7678aac4..73feca9326 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_door_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_door_representation.py
@@ -16,13 +16,15 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+from __future__ import annotations
+import collections.abc
import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import ShapeBuilder, V
from ifcopenshell.api.geometry.add_window_representation import create_ifc_window
from mathutils import Vector
from math import cos, radians
-
-import collections
+from typing import Any, Optional, Literal, Union
+import dataclasses
SUPPORTED_DOOR_TYPES = (
@@ -38,9 +40,14 @@ SUPPORTED_DOOR_TYPES = (
)
+def mm(x: float) -> float:
+ """mm to meters shortcut for readability"""
+ return x / 1000
+
+
def create_ifc_door_lining(
builder: ShapeBuilder, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()
-):
+) -> ifcopenshell.entity_instance:
"""`thickness` of the profile is defined as list in the following order: `(SIDE, TOP)`
`thickness` can be also defined just as 1 float value.
@@ -69,80 +76,212 @@ def create_ifc_door_lining(
return door_lining
-def create_ifc_box(builder: ShapeBuilder, size: Vector, position: Vector = V(0, 0, 0).freeze()):
+def create_ifc_box(
+ builder: ShapeBuilder, size: Vector, position: Vector = V(0, 0, 0).freeze()
+) -> ifcopenshell.entity_instance:
rect = builder.rectangle(size.xy)
box = builder.extrude(rect, size.z, position=position, extrusion_vector=V(0, 0, 1))
return box
-def add_door_representation(file, **usecase_settings) -> None:
- """units in usecase_settings expected to be in ifc project units"""
+# we use dataclass as we need default values for arguments
+# it's okay to use slots since we don't need dynamic attributes
+@dataclasses.dataclass(slots=True)
+class DoorLiningProperties:
+ LiningDepth: Optional[float] = None
+ """Optional, defaults to 50mm."""
+
+ LiningThickness: Optional[float] = None
+ """Optional, defaults to 50mm."""
+
+ LiningOffset: Optional[float] = None
+ """Offset from the outer side of the wall (by Y-axis). Optional, defaults to 0.0."""
+
+ LiningToPanelOffsetX: Optional[float] = None
+ """Offset from the wall. Optional, defaults to 25mm."""
+
+ LiningToPanelOffsetY: Optional[float] = None
+ """Offset from the X-axis (unlike windows). Optional, defaults to 25mm."""
+
+ TransomThickness: Optional[float] = None
+ """Vertical distance between door and window panels. Optional, defaults to 0.0."""
+
+ TransomOffset: Optional[float] = None
+ """Distance from the bottom door opening
+ to the beginning of the transom
+ unlike windows TransomOffset which goes to the center of the transom.
+ Optional, defaults 1.525m."""
+
+ ShapeAspectStyle: None = None
+ """Optional. Deprecated argument."""
+
+ CasingDepth: Optional[float] = None
+ """Casing cover wall faces around the opening
+ on the left, right and upper sides
+ Casing should be either on both sides of the wall or no casing
+ If `LiningOffset` is present then therefore casing is not possible on outer wall
+ therefore there will be no casing on inner wall either. Optional, defaults to 5mm."""
+
+ CasingThickness: Optional[float] = None
+ """Casing thickness by Z-axis. Optional, defaults to 75mm."""
+
+ ThresholdDepth: Optional[float] = None
+ """Threshold covers the bottom side of the opening. Optional, defaults to 100mm."""
+
+ ThresholdThickness: Optional[float] = None
+ """Theshold thickness by Z-axis. Optional, defaults to 25mm."""
+
+ ThresholdOffset: Optional[float] = None
+ """Threshold offset by Y-axis. Optional, defaults to 0.0."""
+
+ def initialize_properties(self, unit_scale: float) -> None:
+ # in meters
+ # fmt: off
+ default_values: dict[str, float] = dict(
+ LiningDepth = mm(50),
+ LiningThickness = mm(50),
+ LiningOffset = 0.0,
+ LiningToPanelOffsetX = mm(25),
+ LiningToPanelOffsetY = mm(25),
+ TransomThickness = 0.0,
+ TransomOffset = mm(1525),
+ CasingDepth = mm(5),
+ CasingThickness = mm(75),
+ ThresholdDepth = mm(100),
+ ThresholdThickness = mm(25),
+ ThresholdOffset = 0.0,
+ )
+ # fmt: on
+
+ si_conversion = 1 / unit_scale
+ for attr, default_value in default_values.items():
+ if getattr(self, attr) is not None:
+ continue
+ setattr(self, attr, default_value * si_conversion)
+
+
+@dataclasses.dataclass(slots=True)
+class DoorPanelProperties:
+ PanelDepth: Optional[float] = None
+ """Frame thickness by Y axis. Optional, defaults to 35 mm."""
+
+ PanelWidth: float = 1.0
+ """Ratio to the clear door opening. Optional, defaults to 1.0."""
+
+ FrameDepth: Optional[float] = None
+ """Frame thickness by Y axis. Optional, defaults to 35 mm."""
+
+ FrameThickness: Optional[float] = None
+ """Frame thickness by X axis. Optional, defaults to 35 mm."""
+
+ PanelPosition: None = None
+ """Optional, value is never used"""
+
+ PanelOperation: None = None
+ """Optional, value is never used.
+ Defines the basic ways to describe how door panels operate."""
+
+ ShapeAspectStyle: None = None
+ """Optional. Deprecated argument."""
+
+ def initialize_properties(self, unit_scale: float) -> None:
+ # in meters
+ # fmt: off
+ default_values: dict[str, float] = dict(
+ PanelDepth = mm(35),
+ FrameDepth = mm(35),
+ FrameThickness = mm(35),
+ )
+ # fmt: on
+
+ si_conversion = 1 / unit_scale
+ for attr, default_value in default_values.items():
+ if getattr(self, attr) is not None:
+ continue
+ setattr(self, attr, default_value * si_conversion)
+
+
+def add_door_representation(
+ file: ifcopenshell.file,
+ *, # keywords only as this API implementation is probably not final
+ context: ifcopenshell.entity_instance,
+ overall_height: Optional[float] = None,
+ overall_width: Optional[float] = None,
+ # door type
+ # http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorTypeOperationEnum.htm
+ operation_type: Literal[
+ "SINGLE_SWING_LEFT",
+ "SINGLE_SWING_RIGHT",
+ "DOUBLE_SWING_RIGHT",
+ "DOUBLE_SWING_LEFT",
+ "DOUBLE_DOOR_SINGLE_SWING",
+ "DOUBLE_DOOR_DOUBLE_SWING",
+ "SLIDING_TO_LEFT",
+ "SLIDING_TO_RIGHT",
+ "DOUBLE_DOOR_SLIDING",
+ ] = "SINGLE_SWING_LEFT",
+ lining_properties: Optional[Union[DoorLiningProperties, dict[str, Any]]] = None,
+ panel_properties: Optional[Union[DoorPanelProperties, dict[str, Any]]] = None,
+ unit_scale: Optional[float] = None,
+) -> ifcopenshell.entity_instance:
+ """units in usecase_settings expected to be in ifc project units
+
+ :param context: IfcGeometricRepresentationContext for the representation.
+ :type context: ifcopenshell.entity_instance
+ :param overall_height: Overall door height. Defaults to 2m.
+ :type overall_height: float, optional
+ :param overall_width: Overall door width. Defaults to 0.9m.
+ :type overall_width: float, optional
+ :param operation_type: Type of the door. Defaults to SINGLE_SWING_LEFT.
+ :type operation_type: str, optional
+ :param lining_properties: DoorLiningProperties or a dictionary to create one.
+ See DoorLiningProperties description for details.
+ :type lining_properties: Union[DoorLiningProperties, dict[str, Any]]]
+ :param panel_properties: DoorPanelProperties or a dictionary to create one.
+ See DoorPanelProperties description for details.
+ :type panel_properties: Union[DoorPanelProperties, dict[str, Any]]]
+ :param unit_scale: The unit scale as calculated by
+ ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
+ will be automatically calculated for you.
+ :type unit_scale: float, optional
+ :return: IfcShapeRepresentation for a door.
+ :rtype: ifcopenshell.entity_instance
+
+ """
usecase = Usecase()
usecase.file = file
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoor.htm
- # http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorTypeOperationEnum.htm
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorLiningProperties.htm
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorPanelProperties.htm
- usecase.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(usecase.file)}
- usecase.settings.update(
+ # define unit_scale first as it's going to be used setting default arguments
+ unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file) if unit_scale is None else unit_scale
+ settings: dict[str, Any] = {"unit_scale": unit_scale}
+
+ if lining_properties is None:
+ lining_properties = DoorLiningProperties()
+ elif not isinstance(lining_properties, DoorLiningProperties):
+ lining_properties = DoorLiningProperties(**lining_properties)
+ lining_properties.initialize_properties(unit_scale)
+ lining_properties = dataclasses.asdict(lining_properties)
+
+ if panel_properties is None:
+ panel_properties = DoorPanelProperties()
+ elif not isinstance(panel_properties, DoorPanelProperties):
+ panel_properties = DoorPanelProperties(**panel_properties)
+ panel_properties.initialize_properties(unit_scale)
+ panel_properties = dataclasses.asdict(panel_properties)
+
+ settings.update(
{
- "context": None, # IfcGeometricRepresentationContext
- "overall_height": usecase.convert_si_to_unit(2.0),
- "overall_width": usecase.convert_si_to_unit(0.9),
- # DOUBLE_DOOR_DOUBLE_SWING, DOUBLE_DOOR_FOLDING, DOUBLE_DOOR_LIFTING_VERTICAL,
- # DOUBLE_DOOR_SINGLE_SWING, DOUBLE_DOOR_SINGLE_SWING_OPPOSITE_LEFT,
- # DOUBLE_DOOR_SINGLE_SWING_OPPOSITE_RIGHT, DOUBLE_DOOR_SLIDING,
- # DOUBLE_SWING_LEFT, DOUBLE_SWING_RIGHT, FOLDING_TO_LEFT,
- # FOLDING_TO_RIGHT, LIFTING_HORIZONTAL, LIFTING_VERTICAL_LEFT,
- # LIFTING_VERTICAL_RIGHT, REVOLVING, REVOLVING_VERTICAL,
- # ROLLINGUP, SINGLE_SWING_LEFT, SINGLE_SWING_RIGHT, SLIDING_TO_LEFT,
- # SLIDING_TO_RIGHT, SWING_FIXED_LEFT, SWING_FIXED_RIGHT
- "operation_type": "SINGLE_SWING_LEFT", # door type
- "lining_properties": {
- "LiningDepth": usecase.convert_si_to_unit(0.050),
- "LiningThickness": usecase.convert_si_to_unit(0.050),
- # offset from the outer side of the wall (by Y-axis)
- "LiningOffset": usecase.convert_si_to_unit(0.0),
- # offset from the wall
- "LiningToPanelOffsetX": usecase.convert_si_to_unit(0.025),
- # offset from the X-axis (unlike windows)
- "LiningToPanelOffsetY": usecase.convert_si_to_unit(0.025),
- # transom - vertical distance between door and window panels
- "TransomThickness": usecase.convert_si_to_unit(0.000),
- # TransomOffset - distance from the bottom door opening
- # to the beginning of the transom
- # unlike windows TransomOffset which goes to the center of the transom
- "TransomOffset": usecase.convert_si_to_unit(1.525),
- "ShapeAspectStyle": None, # DEPRECATED
- # Casing cover wall faces around the opening
- # on the left, right and upper sides
- # Casing should be either on both sides of the wall or no casing
- # If `LiningOffset` is present then therefore casing is not possible on outer wall
- # therefore there will be no casing on inner wall either
- "CasingDepth": usecase.convert_si_to_unit(0.005),
- "CasingThickness": usecase.convert_si_to_unit(0.075), # by Z-axis
- # Threshold covers the bottom side of the opening
- "ThresholdDepth": usecase.convert_si_to_unit(0.1),
- "ThresholdThickness": usecase.convert_si_to_unit(0.025), # by Z-axis
- # offset by Y-axis
- "ThresholdOffset": usecase.convert_si_to_unit(0.000),
- },
- "panel_properties": {
- "PanelDepth": usecase.convert_si_to_unit(0.035), # by Y
- "PanelWidth": 1.0, # as ratio to the clear door opening
- "FrameDepth": usecase.convert_si_to_unit(0.035), # by Y
- "FrameThickness": usecase.convert_si_to_unit(0.035), # by X
- # LEFT, MIDDLE, RIGHT, NOTDEFINED
- "PanelPosition": ..., # NEVER USED
- # defines the basic ways to describe how door panels operate
- # basically how it opens
- "PanelOperation": None, # NEVER USED
- "ShapeAspectStyle": None, # DEPRECATED
- },
+ "context": context,
+ "overall_height": overall_height if overall_height is not None else usecase.convert_si_to_unit(2.0),
+ "overall_width": overall_width if overall_width is not None else usecase.convert_si_to_unit(0.9),
+ "operation_type": operation_type,
+ "lining_properties": lining_properties,
+ "panel_properties": panel_properties,
}
)
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
+ usecase.settings = settings
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_footprint_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_footprint_representation.py
index 976b48e5ce..6b48845e4e 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_footprint_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_footprint_representation.py
@@ -19,13 +19,17 @@
import ifcopenshell.util.unit
-def add_footprint_representation(file, **usecase_settings) -> None:
+def add_footprint_representation(
+ file,
+ # IfcGeometricRepresentationContext
+ context: ifcopenshell.entity_instance,
+ # A list of IFC curves to include in the curve set
+ curves: list[ifcopenshell.entity_instance],
+) -> ifcopenshell.entity_instance:
settings = {
- "context": None, # IfcGeometricRepresentationContext
- "curves": [], # A list of IFC curves to include in the curve set
+ "context": context,
+ "curves": curves,
}
- for key, value in usecase_settings.items():
- settings[key] = value
return file.createIfcShapeRepresentation(
settings["context"],
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_mesh_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_mesh_representation.py
index fbe42063d9..e26c5c39b8 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_mesh_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_mesh_representation.py
@@ -17,26 +17,43 @@
# along with IfcOpenShell. If not, see .
import ifcopenshell.util.unit
+from typing import Optional
+
+COORD_3D = tuple[float, float, float]
-def add_mesh_representation(file: ifcopenshell.file, **usecase_settings) -> None:
+def add_mesh_representation(
+ file: ifcopenshell.file,
+ # IfcGeometricRepresentationContext
+ context: ifcopenshell.entity_instance,
+ # Vertices, edges, and faces are given in the form of: [item1, item2, item3, ...]
+ # A list of coordinates
+ # ... where itemN = [(0., 0., 0.), (1., 1., 1.), (x, y, z), ...]
+ vertices: list[COORD_3D],
+ # A list of edges, represented by vertex index pairs
+ # ... where itemN = [(0, 1), (1, 2), (v1, v2), ...]
+ edges: list[tuple[int, int]],
+ # A list of polygons, represented by vertex indices
+ # ... where itemN = [(0, 1, 2), (5, 4, 2, 3), (v1, v2, v3, ... vN), ...]
+ faces: list[list[int]],
+ # Optionally apply a vector offset to all coordinates
+ cooridnate_offset: Optional[COORD_3D] = None,
+ # A scale factor to apply for all vectors in case the unit is different
+ unit_scale: Optional[float] = None,
+ # Force using IfcFacetedBreps instead of IfcPolygonalFaceSets
+ force_faceted_brep: bool = False,
+) -> ifcopenshell.entity_instance:
usecase = Usecase()
usecase.file = file
usecase.settings = {
- "context": None, # IfcGeometricRepresentationContext
- # Vertices, edges, and faces are given in the form of: [item1, item2, item3, ...]
- # ... where itemN = [(0., 0., 0.), (1., 1., 1.), (x, y, z), ...]
- "vertices": None, # A list of coordinates
- # ... where itemN = [(0, 1), (1, 2), (v1, v2), ...]
- "edges": None, # A list of edges, represented by vertex index pairs
- # ... where itemN = [(0, 1, 2), (5, 4, 2, 3), (v1, v2, v3, ... vN), ...]
- "faces": None, # A list of polygons, represented by vertex indices
- "coordinate_offset": None, # Optionally apply a vector offset to all coordinates
- "unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
- "force_faceted_brep": False, # Force using IfcFacetedBreps instead of IfcPolygonalFaceSets
+ "context": context,
+ "vertices": vertices,
+ "edges": edges,
+ "faces": faces,
+ "coordinate_offset": cooridnate_offset,
+ "unit_scale": unit_scale,
+ "force_faceted_brep": force_faceted_brep,
}
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_profile_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_profile_representation.py
index 025f09f0fc..18dffbc6b3 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_profile_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_profile_representation.py
@@ -19,23 +19,35 @@
import ifcopenshell.geom
import ifcopenshell.util.unit
from ifcopenshell.util.data import Clipping
+from typing import Any, Union, Optional, Literal
+
+VECTOR_3D = tuple[float, float, float]
-def add_profile_representation(file, **usecase_settings) -> None:
+def add_profile_representation(
+ file: ifcopenshell.file,
+ # IfcGeometricRepresentationContext
+ context: ifcopenshell.entity_instance,
+ profile: ifcopenshell.entity_instance,
+ # in meters
+ depth: float = 1.0,
+ cardinal_point: Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] = 5,
+ # A list of planes that define clipping half space solids
+ # Planes are defined either by Clipping objects
+ # or by dictionaries of arguments for `Clipping.parse`
+ clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None,
+ placement_zx_axes: tuple[Union[VECTOR_3D, None], Union[VECTOR_3D, None]] = (None, None),
+) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {
- "context": None, # IfcGeometricRepresentationContext
- "profile": None,
- "depth": 1.0,
- "cardinal_point": 5,
- # Planes are defined either by Clipping objects
- # or by dictionaries of arguments for `Clipping.parse`
- "clippings": [], # A list of planes that define clipping half space solids
- "placement_zx_axes": (None, None),
+ "context": context,
+ "profile": profile,
+ "depth": depth,
+ "cardinal_point": cardinal_point,
+ "clippings": clippings if clippings is not None else [],
+ "placement_zx_axes": placement_zx_axes,
}
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_railing_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_railing_representation.py
index 9de884c8c2..b6dc8653d8 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_railing_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_railing_representation.py
@@ -22,46 +22,100 @@ from itertools import chain
from mathutils import Vector, Matrix
import collections
import mathutils
-from pprint import pprint
from math import pi, cos, sin, tan, radians
+from typing import Literal, Optional, Any
-def mm(x):
+def mm(x: float) -> float:
"""mm to meters shortcut for readability"""
return x / 1000
-def add_railing_representation(file, **usecase_settings) -> None:
+def add_railing_representation(
+ file: ifcopenshell.file,
+ *, # keywords only as this API implementation is probably not final
+ # IfcGeometricRepresentationContext
+ context: ifcopenshell.entity_instance,
+ railing_type: Literal["WALL_MOUNTED_HANDRAIL"] = "WALL_MOUNTED_HANDRAIL",
+ railing_path: list[Vector],
+ use_manual_supports: bool = False,
+ support_spacing: Optional[float] = None,
+ railing_diameter: Optional[float] = None,
+ clear_width: Optional[float] = None,
+ terminal_type: Literal[
+ "180",
+ "TO_END_POST",
+ "TO_WALL",
+ "TO_FLOOR",
+ "TO_END_POST_AND_FLOOR",
+ ] = "180",
+ height: Optional[float] = None,
+ looped_path: bool = False,
+ unit_scale: Optional[float] = None,
+) -> ifcopenshell.entity_instance:
"""
- units in usecase_settings expected to be in ifc project units
+ Units are expected to be in IFC project units.
- `railing_path` is a list of point coordinates for the railing path,
- coordinates are expected to be at the top of the railing, not at the center
+ :param context: IfcGeometricRepresentationContext for the representation.
+ :type context: ifcopenshell.entity_instance
+ :param railing_type: Type of the railing. Defaults to "WALL_MOUNTED_HANDRAIL".
+ :type railing_type: Literal["WALL_MOUNTED_HANDRAIL"], optional
+ :param railing_path: A list of points coordinates for the railing path,
+ coordinates are expected to be at the top of the railing, not at the center.
+ If not provided, default path [(0, 0, 1), (1, 0, 1), (2, 0, 1)] (in meters) will be used
+ :type railing_path: list[Vector], optional.
+ :param use_manual_supports: If enabled, supports are added on every vertex on the edges of the railing path.
+ If disabled, supports are added automatically based on the support spacing. Default to False.
+ :type use_manual_supports: bool, optional
+ :param support_spacing: Distance between supports if automatic supports are used. Defaults to 1m.
+ :type support_spacing: float, optional
+ :param railing_diameter: Railing diameter. Defaults to 50mm.
+ :type railing_diameter: float, optional
+ :param clear_width: Clear width between the railing and the wall. Defaults to 40mm.
+ :type clear_width: float, optional
+ :param terminal_type: type of the cap. Defaults to "180".
+ :type terminal_type: Literal["180","TO_END_POST","TO_WALL","TO_FLOOR","TO_END_POST_AND_FLOOR"], optional
+ :param height: defaults to 1m
+ :type height: float, optional
+ :param looped_path: Whether to end the railing on the first point of `railing_path`. Defaults to False.
+ :type looped_path: bool, optional
+ :param unit_scale: The unit scale as calculated by
+ ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
+ will be automatically calculated for you.
+ :type unit_scale: float, optional
+ :return: IfcShapeRepresentation for a railing.
+ :rtype: ifcopenshell.entity_instance
- `railing_path` is expected to be a list of Vector objects
"""
usecase = Usecase()
usecase.file = file
- usecase.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(usecase.file)}
- usecase.settings.update(
+ # define unit_scale first as it's going to be used setting default arguments
+ settings: dict[str, Any] = {
+ "unit_scale": ifcopenshell.util.unit.calculate_unit_scale(file) if unit_scale is None else unit_scale,
+ }
+ settings.update(
{
- "context": None, # IfcGeometricRepresentationContext
- "railing_type": "WALL_MOUNTED_HANDRAIL",
- "railing_path": usecase.path_si_to_units([V(0, 0, 1), V(1, 0, 1), V(2, 0, 1)]),
- "use_manual_supports": False,
- "support_spacing": usecase.convert_si_to_unit(mm(1000)),
- "railing_diameter": usecase.convert_si_to_unit(mm(50)),
- "clear_width": usecase.convert_si_to_unit(mm(40)),
- "terminal_type": "180",
- "height": usecase.convert_si_to_unit(mm(1000)),
- "looped_path": False,
+ "context": context,
+ "railing_type": railing_path,
+ "railing_path": (
+ railing_path
+ if railing_path is not None
+ else usecase.path_si_to_units([V(0, 0, 1), V(1, 0, 1), V(2, 0, 1)])
+ ),
+ "use_manual_supports": use_manual_supports,
+ "support_spacing": support_spacing if support_spacing is not None else usecase.convert_si_to_unit(mm(1000)),
+ "railing_diameter": (
+ railing_diameter if railing_diameter is not None else usecase.convert_si_to_unit(mm(50))
+ ),
+ "clear_width": clear_width if clear_width is not None else usecase.convert_si_to_unit(mm(40)),
+ "terminal_type": terminal_type,
+ "height": height if height is not None else usecase.convert_si_to_unit(mm(1000)),
+ "looped_path": looped_path,
}
)
+ usecase.settings = settings
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
-
- if usecase.settings["railing_type"] != "WALL_MOUNTED_HANDRAIL":
+ if railing_type != "WALL_MOUNTED_HANDRAIL":
raise Exception('Only "WALL_MOUNTED_HANDRAIL" railing_type is supported at the moment.')
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py
index aa98a15f3c..9693e46340 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py
@@ -16,11 +16,12 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
from __future__ import annotations
-import bpy
+import bpy.types
import math
import bmesh
import ifcopenshell.util.unit
from mathutils import Vector, Matrix
+from typing import Union, Optional, Literal
Z_AXIS = Vector((0, 0, 1))
@@ -28,7 +29,44 @@ X_AXIS = Vector((1, 0, 0))
EPSILON = 1e-6
-def add_representation(file: ifcopenshell.file, **usecase_settings) -> ifcopenshell.entity_instance:
+def add_representation(
+ file: ifcopenshell.file,
+ *, # keywords only as this API implementation is probably not final
+ # IfcGeometricRepresentationContext
+ context: ifcopenshell.entity_instance,
+ # This is (currently) a Blender object, hence this depends on Blender now
+ blender_object: bpy.types.Object,
+ # This is (currently) a Blender data object, hence this depends on Blender now
+ geometry: Union[bpy.types.Mesh, bpy.types.Curve],
+ # Optionally apply a vector offset to all coordinates
+ coordinate_offset: Optional[Vector] = None,
+ # How many representation items to create
+ total_items: int = 1,
+ # A scale factor to apply for all vectors in case the unit is different
+ unit_scale: Optional[float] = None,
+ # If we should force faceted breps for meshes
+ should_force_faceted_brep: bool = False,
+ # If we should force triangulation for meshes
+ should_force_triangulation: bool = False,
+ # If UV coordinates should also be generated
+ should_generate_uvs: bool = False,
+ # Whether to cast a mesh into a particular class
+ ifc_representation_class: Optional[
+ Literal[
+ "IfcExtrudedAreaSolid/IfcRectangleProfileDef",
+ "IfcExtrudedAreaSolid/IfcCircleProfileDef",
+ "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef",
+ "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids",
+ "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage",
+ "IfcGeometricCurveSet/IfcTextLiteral",
+ "IfcTextLiteral",
+ ]
+ ] = None,
+ # The material profile set if the extrusion requires it
+ profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
+ # The text literal if the representation requires it
+ text_literal: Optional[ifcopenshell.entity_instance] = None,
+) -> ifcopenshell.entity_instance:
# lazy import Helper to avoid circular import
if "Helper" not in globals():
from blenderbim.bim.module.geometry.helper import Helper
@@ -37,30 +75,20 @@ def add_representation(file: ifcopenshell.file, **usecase_settings) -> ifcopensh
# TODO: This usecase currently depends on Blender's data model
usecase.file = file
usecase.settings = {
- "context": None, # IfcGeometricRepresentationContext
- "blender_object": None, # This is (currently) a Blender object, hence this depends on Blender now
- "geometry": None, # This is (currently) a Blender data object, hence this depends on Blender now
- "coordinate_offset": None, # Optionally apply a vector offset to all coordinates
- "total_items": 1, # How many representation items to create
- "unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
- "should_force_faceted_brep": False, # If we should force faceted breps for meshes
- "should_force_triangulation": False, # If we should force triangulation for meshes
- "should_generate_uvs": False, # If UV coordinates should also be generated
- # Possible IFC representation classes:
- # IfcExtrudedAreaSolid/IfcRectangleProfileDef
- # IfcExtrudedAreaSolid/IfcCircleProfileDef
- # IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef
- # IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids
- # IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage
- # IfcGeometricCurveSet/IfcTextLiteral
- # IfcTextLiteral
- "ifc_representation_class": None, # Whether to cast a mesh into a particular class
- "profile_set_usage": None, # The material profile set if the extrusion requires it
- "text_literal": None, # The text literal if the representation requires it
+ "context": context,
+ "blender_object": blender_object,
+ "geometry": geometry,
+ "coordinate_offset": coordinate_offset,
+ "total_items": total_items,
+ "unit_scale": unit_scale,
+ "should_force_faceted_brep": should_force_faceted_brep,
+ "should_force_triangulation": should_force_triangulation,
+ "should_generate_uvs": should_generate_uvs,
+ "ifc_representation_class": ifc_representation_class,
+ "profile_set_usage": profile_set_usage,
+ "text_literal": text_literal,
}
usecase.ifc_vertices = []
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py
index 7514ade38f..5a46ba0f49 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py
@@ -17,22 +17,32 @@
# along with IfcOpenShell. If not, see .
import ifcopenshell.util.unit
+from ifcopenshell.util.data import Clipping
from math import sin, cos
+from typing import Any, Optional, Union
-def add_slab_representation(file, **usecase_settings) -> None:
+def add_slab_representation(
+ file,
+ # IfcGeometricRepresentationContext
+ context: ifcopenshell.entity_instance,
+ # in meters
+ depth: float = 0.2,
+ # in radians
+ x_angle: float = 0.0,
+ # A list of planes that define clipping half space solids
+ # Planes are defined either by Clipping objects
+ # or by dictionaries of arguments for `Clipping.parse`
+ clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None,
+) -> ifcopenshell.entity_instance:
usecase = Usecase()
usecase.file = file
usecase.settings = {
- "context": None, # IfcGeometricRepresentationContext
- "depth": 0.2,
- "x_angle": 0, # Radians
- # Planes are defined either by Clipping objects
- # or by dictionaries of arguments for `Clipping.parse`
- "clippings": [], # A list of planes that define clipping half space solids
+ "context": context,
+ "depth": depth,
+ "x_angle": x_angle,
+ "clippings": clippings if clippings is not None else [],
}
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py
index 504a89078b..945400fcfa 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py
@@ -18,27 +18,39 @@
import ifcopenshell.util.unit
from math import sin, cos
+from typing import Optional, Union, Any
from ifcopenshell.util.data import Clipping
-def add_wall_representation(file, **usecase_settings) -> None:
+def add_wall_representation(
+ file: ifcopenshell.file,
+ context: ifcopenshell.entity_instance, # IfcGeometricRepresentationContext
+ # all lengths are in meters
+ length: float = 1.0,
+ height: float = 3.0,
+ offset: float = 0.0,
+ thickness: float = 0.2,
+ # Sloped walls along the wall's X axis, provided in radians
+ x_angle: float = 0.0,
+ # A list of planes that define clipping half space solids
+ # Planes are defined either by Clipping objects
+ # or by dictionaries of arguments for `Clipping.parse`
+ clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None,
+ # Any existing IfcBooleanResults
+ booleans: Optional[list[ifcopenshell.entity_instance]] = None,
+) -> ifcopenshell.entity_instance:
usecase = Usecase()
usecase.file = file
usecase.settings = {
- "context": None, # IfcGeometricRepresentationContext
- "length": 1.0,
- "height": 3.0,
- "offset": 0.0,
- "thickness": 0.2,
- # Sloped walls along the wall's X axis, provided in radians
- "x_angle": 0,
- # Planes are defined either by Clipping objects
- # or by dictionaries of arguments for `Clipping.parse`
- "clippings": [], # A list of planes that define clipping half space solids
- "booleans": [], # Any existing IfcBooleanResults
+ "context": context,
+ "length": length,
+ "height": height,
+ "offset": offset,
+ "thickness": thickness,
+ "x_angle": x_angle,
+ "clippings": clippings if clippings is not None else [],
+ "booleans": booleans if booleans is not None else [],
}
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py
index b0a46e69ab..73a9c255a2 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_window_representation.py
@@ -16,11 +16,14 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+from __future__ import annotations
+import collections.abc
import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import ShapeBuilder, V
from itertools import chain
from mathutils import Vector
-import collections
+import dataclasses
+from typing import Any, Optional, Literal, Union
# SCHEMAS describe panels setup
@@ -42,6 +45,11 @@ DEFAULT_PANEL_SCHEMAS = {
}
+def mm(x: float) -> float:
+ """mm to meters shortcut for readability"""
+ return x / 1000
+
+
def create_ifc_window_frame_simple(
builder: ShapeBuilder, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()
):
@@ -210,71 +218,209 @@ def create_ifc_window(
return output_items
-def add_window_representation(file, **usecase_settings) -> None:
- """units in usecase_settings expected to be in ifc project units"""
+# we use dataclass as we need default values for arguments
+# it's okay to use slots since we don't need dynamic attributes
+@dataclasses.dataclass(slots=True)
+class WindowLiningProperties:
+ LiningDepth: Optional[float] = None
+ """Optional, defaults to 50mm."""
+
+ LiningThickness: Optional[float] = None
+ """Optional, defaults to 50mm."""
+
+ LiningOffset: Optional[float] = None
+ """Offset to the wall. Optional, defaults to 50mm."""
+
+ LiningToPanelOffsetX: Optional[float] = None
+ """Offset from the wall. Optional, defaults to 25mm."""
+
+ # that way it allows you to define overall_depth constant between all panels
+ # and still have panels with different size:
+ # overall_depth = lining_depth + offset_y
+ # full offset from X axis = overall_depth - frame_depth.
+ LiningToPanelOffsetY: Optional[float] = None
+ """Offset from the lining. Optional, defaults to 25mm."""
+
+ MullionThickness: Optional[float] = None
+ """Mullion thickness (horizontal distance between panels).
+
+ Applies to windows of types: DoublePanelVertical, TriplePanelBottom, TriplePanelTop,
+ TriplePanelLeft, TriplePanelRight.
+
+ Optional, defaults to 50mm."""
+
+ FirstMullionOffset: Optional[float] = None
+ """Distance from the first lining to the mullion center. Optional, defaults to 300mm."""
+
+ SecondMullionOffset: Optional[float] = None
+ """Distance from the first lining to the second mullion center.
+
+ Applies to windows of type: TriplePanelVertical.
+
+ Optional, defaults to 450mm."""
+
+ TransomThickness: Optional[float] = None
+ """Transom thickness (vertical distance between panels), works similar way to mullions.
+
+ Applies to windows of types:DoublePanelHorizontal, TriplePanelBottom, TriplePanelTop,
+ TriplePanelLeft, TriplePanelRight.
+
+ Optional, defaults to 50mm."""
+
+ FirstTransomOffset: Optional[float] = None
+ """Optional, defaults to 300mm."""
+
+ SecondTransomOffset: Optional[float] = None
+ """
+ Applies to windows of type: TriplePanelHorizontal.
+ Optional, defaults to 600mm."""
+
+ ShapeAspectStyle: None = None
+ """Optional. Deprecated argument."""
+
+ def initialize_properties(self, unit_scale: float) -> None:
+ # in meters
+ # fmt: off
+ default_values: dict[str, float] = dict(
+ LiningDepth = mm(50),
+ LiningThickness = mm(50),
+ LiningOffset = mm(50),
+ LiningToPanelOffsetX = mm(25),
+ LiningToPanelOffsetY = mm(25),
+ MullionThickness = mm(50),
+ FirstMullionOffset = mm(300),
+ SecondMullionOffset = mm(450),
+ TransomThickness = mm(50),
+ FirstTransomOffset = mm(300),
+ SecondTransomOffset = mm(600),
+ )
+ # fmt: on
+
+ si_conversion = 1 / unit_scale
+ for attr, default_value in default_values.items():
+ if getattr(self, attr) is not None:
+ continue
+ setattr(self, attr, default_value * si_conversion)
+
+
+@dataclasses.dataclass(slots=True)
+class WindowPanelProperties:
+ FrameDepth: Optional[float] = None
+ """Frame thickness by Y axis. Optional, defaults to 35 mm."""
+
+ FrameThickness: Optional[float] = None
+ """Frame thickness by X axis. Optional, defaults to 35 mm."""
+
+ PanelPosition: None = None
+ """Optional, value is never used"""
+
+ PanelOperation: None = None
+ """Optional, value is never used.
+ Defines the basic ways to describe how window panels operate."""
+
+ ShapeAspectStyle: None = None
+ """Optional. Deprecated argument."""
+
+ def initialize_properties(self, unit_scale: float) -> None:
+ # in meters
+ # fmt: off
+ default_values: dict[str, float] = dict(
+ FrameDepth = mm(35),
+ FrameThickness = mm(35),
+ )
+ # fmt: on
+
+ si_conversion = 1 / unit_scale
+ for attr, default_value in default_values.items():
+ if getattr(self, attr) is not None:
+ continue
+ setattr(self, attr, default_value * si_conversion)
+
+
+def add_window_representation(
+ file: ifcopenshell.file,
+ *, # keywords only as this API implementation is probably not final
+ context: ifcopenshell.entity_instance,
+ overall_height: Optional[float] = None,
+ overall_width: Optional[float] = None,
+ partition_type: Literal[
+ "SINGLE_PANEL",
+ "DOUBLE_PANEL_HORIZONTAL",
+ "DOUBLE_PANEL_VERTICAL",
+ "TRIPLE_PANEL_BOTTOM",
+ "TRIPLE_PANEL_HORIZONTAL",
+ "TRIPLE_PANEL_LEFT",
+ "TRIPLE_PANEL_RIGHT",
+ "TRIPLE_PANEL_TOP",
+ "TRIPLE_PANEL_VERTICAL",
+ ] = "SINGLE_PANEL",
+ lining_properties: Optional[Union[WindowLiningProperties, dict[str, Any]]] = None,
+ panel_properties: Optional[list[Union[WindowPanelProperties, dict[str, Any]]]] = None,
+ unit_scale: Optional[float] = None,
+) -> ifcopenshell.entity_instance:
+ """units in usecase_settings expected to be in ifc project units
+
+ :param context: IfcGeometricRepresentationContext for the representation.
+ :type context: ifcopenshell.entity_instance
+ :param overall_height: Overall window height. Defaults to 0.9m.
+ :type overall_height: float, optional
+ :param overall_width: Overall window width. Defaults to 0.6m.
+ :type overall_width: float, optional
+ :param partition_type: Type of the window. Defaults to SINGLE_PANEL.
+ :type partition_type: str, optional
+ :param lining_properties: WindowLiningProperties or a dictionary to create one.
+ See WindowLiningProperties description for details.
+ :type lining_properties: Union[WindowLiningProperties, dict[str, Any]]]
+ :param panel_properties: A list of WindowPanelProperties or dictionaries to create one.
+ See WindowPanelProperties description for details.
+ :type panel_properties: list[Union[WindowPanelProperties, dict[str, Any]]]]
+ :param unit_scale: The unit scale as calculated by
+ ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
+ will be automatically calculated for you.
+ :type unit_scale: float, optional
+ :return: IfcShapeRepresentation for a window.
+ :rtype: ifcopenshell.entity_instance
+
+ """
usecase = Usecase()
usecase.file = file
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindow.htm
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowTypePartitioningEnum.htm
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowLiningProperties.htm
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcWindowPanelProperties.htm
- usecase.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(usecase.file)}
- usecase.settings.update(
+ # define unit_scale first as it's going to be used setting default arguments
+ unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file) if unit_scale is None else unit_scale
+ settings: dict[str, Any] = {"unit_scale": unit_scale}
+
+ if lining_properties is None:
+ lining_properties = WindowLiningProperties()
+ elif not isinstance(lining_properties, WindowLiningProperties):
+ lining_properties = WindowLiningProperties(**lining_properties)
+ lining_properties.initialize_properties(unit_scale)
+ lining_properties = dataclasses.asdict(lining_properties)
+
+ if panel_properties is None:
+ panel_properties = [WindowPanelProperties()]
+
+ for i in range(len(panel_properties)):
+ properties = panel_properties[i]
+ if not isinstance(properties, WindowPanelProperties):
+ properties = WindowPanelProperties(**properties)
+ properties.initialize_properties(unit_scale)
+ panel_properties[i] = dataclasses.asdict(properties)
+
+ settings.update(
{
- "context": None, # IfcGeometricRepresentationContext
- # SINGLE_PANEL, DOUBLE_PANEL_HORIZONTAL, DOUBLE_PANEL_VERTICAL,
- # TRIPLE_PANEL_BOTTOM, TRIPLE_PANEL_HORIZONTAL, TRIPLE_PANEL_LEFT,
- # TRIPLE_PANEL_RIGHT, TRIPLE_PANEL_TOP, TRIPLE_PANEL_VERTICAL
- "partition_type": "SINGLE_PANEL",
- "overall_height": usecase.convert_si_to_unit(0.9),
- "overall_width": usecase.convert_si_to_unit(0.6),
- "lining_properties": {
- "LiningDepth": usecase.convert_si_to_unit(0.050),
- "LiningThickness": usecase.convert_si_to_unit(0.050),
- "LiningOffset": usecase.convert_si_to_unit(0.050), # offset to the wall
- # offset from the wall
- "LiningToPanelOffsetX": usecase.convert_si_to_unit(0.025),
- # offset from the lining
- # that way it allows you to define overall_depth constant between all panels
- # and still have panels with different size:
- # overall_depth = lining_depth + offset_y
- # full offset from X axis = overall_depth - frame_depth
- "LiningToPanelOffsetY": usecase.convert_si_to_unit(0.025),
- # applies to DoublePanelVertical, TriplePanelBottom, TriplePanelTop,
- # TriplePanelLeft, TriplePanelRight
- # mullion - horizontal distance between panels
- "MullionThickness": usecase.convert_si_to_unit(0.050),
- # distance from the first lining to the mullion center
- "FirstMullionOffset": usecase.convert_si_to_unit(0.3),
- # applies to TriplePanelVertical
- # distance from the first lining to the second mullion center
- "SecondMullionOffset": usecase.convert_si_to_unit(0.45),
- # applies to DoublePanelHorizontal, TriplePanelBottom, TriplePanelTop,
- # TriplePanelLeft, TriplePanelRight
- # works similar way to mullion
- "TransomThickness": usecase.convert_si_to_unit(0.050),
- "FirstTransomOffset": usecase.convert_si_to_unit(0.3),
- # applies to TriplePanelHorizontal
- "SecondTransomOffset": usecase.convert_si_to_unit(0.6),
- "ShapeAspectStyle": None, # DEPRECATED
- },
- "panel_properties": [
- {
- "FrameDepth": usecase.convert_si_to_unit(0.035), # by Y
- "FrameThickness": usecase.convert_si_to_unit(0.035), # by X
- # BOTTOM, LEFT, MIDDLE, RIGHT, TOP
- "PanelPosition": ..., # NEVER USED
- # defines the basic ways to describe how window panels operate
- # how it's hanged, how it opens
- "OperationType": None, # NEVER USED
- "ShapeAspectStyle": None, # DEPRECATED
- },
- ],
+ "context": context,
+ "overall_height": overall_height if overall_height is not None else usecase.convert_si_to_unit(0.9),
+ "overall_width": overall_width if overall_width is not None else usecase.convert_si_to_unit(0.6),
+ "partition_type": partition_type,
+ "lining_properties": lining_properties,
+ "panel_properties": panel_properties,
}
)
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
+ usecase.settings = settings
usecase.settings["panel_schema"] = DEFAULT_PANEL_SCHEMAS[usecase.settings["partition_type"]]
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/assign_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/assign_representation.py
index 1df964d845..c89c6fd819 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/assign_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/assign_representation.py
@@ -20,12 +20,12 @@ import ifcopenshell.api
import ifcopenshell.util.element
-def assign_representation(file, **usecase_settings) -> None:
+def assign_representation(
+ file: ifcopenshell.file, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
+) -> None:
usecase = Usecase()
usecase.file = file
- usecase.settings = {"product": None, "representation": None}
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
+ usecase.settings = {"product": product, "representation": representation}
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_element.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_element.py
index d86e2bbca3..87d0149407 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_element.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_element.py
@@ -20,16 +20,20 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
import ifcopenshell.util.element
+from typing import Optional
-def connect_element(file, **usecase_settings) -> None:
+def connect_element(
+ file: ifcopenshell.file,
+ relating_element: ifcopenshell.entity_instance,
+ related_element: ifcopenshell.entity_instance,
+ description: Optional[str] = None,
+) -> ifcopenshell.entity_instance:
settings = {
- "relating_element": None,
- "related_element": None,
- "description": None,
+ "relating_element": relating_element,
+ "related_element": related_element,
+ "description": description,
}
- for key, value in usecase_settings.items():
- settings[key] = value
incompatible_connections = []
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py
index bab818ee64..9e18ca0f18 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py
@@ -20,18 +20,24 @@ import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
import ifcopenshell.util.element
+from typing import Optional
-def connect_path(file, **usecase_settings) -> None:
+def connect_path(
+ file: ifcopenshell.file,
+ relating_element: ifcopenshell.entity_instance,
+ related_element: ifcopenshell.entity_instance,
+ relating_connection: str = "NOTDEFINED",
+ related_connection: str = "NOTDEFINED",
+ description: Optional[str] = None,
+) -> ifcopenshell.entity_instance:
settings = {
- "relating_element": None,
- "related_element": None,
- "relating_connection": "NOTDEFINED",
- "related_connection": "NOTDEFINED",
- "description": None,
+ "relating_element": relating_element,
+ "related_element": related_element,
+ "relating_connection": relating_connection,
+ "related_connection": related_connection,
+ "description": description,
}
- for key, value in usecase_settings.items():
- settings[key] = value
incompatible_connections = []
for rel in settings["relating_element"].ConnectedTo:
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/create_2pt_wall.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/create_2pt_wall.py
index cd508d4a3e..74c487b47e 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/create_2pt_wall.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/create_2pt_wall.py
@@ -22,8 +22,16 @@ import ifcopenshell.util.unit
def create_2pt_wall(
- file, element=None, context=None, p1=None, p2=None, elevation=None, height=None, thickness=None, is_si=True
-) -> None:
+ file: ifcopenshell.file,
+ element: ifcopenshell.entity_instance,
+ context: ifcopenshell.entity_instance,
+ p1: tuple[float, float],
+ p2: tuple[float, float],
+ elevation: float,
+ height: float,
+ thickness: float,
+ is_si: bool = True,
+) -> ifcopenshell.entity_instance:
usecase = Usecase()
usecase.file = file
usecase.settings = {
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_element.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_element.py
index 1e1eaaa82b..666d3d35b4 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_element.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_element.py
@@ -20,30 +20,31 @@ import ifcopenshell
import ifcopenshell.util.element
-def disconnect_element(file, **usecase_settings) -> None:
- settings = {
- "relating_element": None,
- "related_element": None,
- }
- for key, value in usecase_settings.items():
- settings[key] = value
-
+def disconnect_element(
+ file: ifcopenshell.file,
+ relating_element: ifcopenshell.entity_instance,
+ related_element: ifcopenshell.entity_instance,
+) -> None:
+ # TODO: arguments relating_element, related_element probably
+ # should be renamed to element1, element2
+ # as api call doesn't really treat them as "relating" and "related"
+ # and just purging all connections between them
incompatible_connections = []
- for rel in settings["relating_element"].ConnectedTo:
- if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == settings["related_element"]:
+ for rel in relating_element.ConnectedTo:
+ if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == related_element:
incompatible_connections.append(rel)
- for rel in settings["relating_element"].ConnectedFrom:
- if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == settings["related_element"]:
+ for rel in relating_element.ConnectedFrom:
+ if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == related_element:
incompatible_connections.append(rel)
- for rel in settings["related_element"].ConnectedTo:
- if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == settings["relating_element"]:
+ for rel in related_element.ConnectedTo:
+ if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == relating_element:
incompatible_connections.append(rel)
- for rel in settings["related_element"].ConnectedFrom:
- if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == settings["relating_element"]:
+ for rel in related_element.ConnectedFrom:
+ if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == relating_element:
incompatible_connections.append(rel)
if incompatible_connections:
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_path.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_path.py
index 14bbaf9e7c..787f7a5633 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_path.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_path.py
@@ -19,33 +19,36 @@
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element
+from typing import Optional
-def disconnect_path(file, **usecase_settings) -> None:
- settings = {
- "relating_element": None,
- "related_element": None,
- "element": None,
- "connection_type": None,
- }
- for key, value in usecase_settings.items():
- settings[key] = value
-
- if settings["connection_type"] and settings["element"]:
+def disconnect_path(
+ file: ifcopenshell.file,
+ element: Optional[ifcopenshell.entity_instance] = None,
+ connection_type: Optional[str] = None,
+ relating_element: Optional[ifcopenshell.entity_instance] = None,
+ related_element: Optional[ifcopenshell.entity_instance] = None,
+) -> None:
+ """There are two options to use this API method:
+ - provide `element` (connected from) and `connection_type` that should be disconnected.
+ - provide connected elements to disconnect explicitly:
+ `relating_element` (connected from) and `related_element` (connected to)
+ """
+ if connection_type and element:
connections = [
r
- for r in settings["element"].ConnectedTo
- if r.is_a("IfcRelConnectsPathElements") and r.RelatingConnectionType == settings["connection_type"]
+ for r in element.ConnectedTo
+ if r.is_a("IfcRelConnectsPathElements") and r.RelatingConnectionType == connection_type
] + [
r
- for r in settings["element"].ConnectedFrom
- if r.is_a("IfcRelConnectsPathElements") and r.RelatedConnectionType == settings["connection_type"]
+ for r in element.ConnectedFrom
+ if r.is_a("IfcRelConnectsPathElements") and r.RelatedConnectionType == connection_type
]
- else:
+ elif related_element:
connections = [
r
- for r in settings["relating_element"].ConnectedTo
- if r.is_a("IfcRelConnectsPathElements") and r.RelatedElement == settings["related_element"]
+ for r in relating_element.ConnectedTo
+ if r.is_a("IfcRelConnectsPathElements") and r.RelatedElement == related_element
]
for connection in set(connections):
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py
index 768468e03a..c71d803ce7 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py
@@ -31,8 +31,8 @@ def edit_object_placement(
file: ifcopenshell.file,
product: ifcopenshell.entity_instance,
matrix: Optional[NPArrayOfFloats] = None,
- is_si=True,
- should_transform_children=False,
+ is_si: bool = True,
+ should_transform_children: bool = False,
) -> ifcopenshell.entity_instance:
usecase = Usecase()
usecase.file = file
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/map_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/map_representation.py
index 83e1e1e821..597463a3e5 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/map_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/map_representation.py
@@ -16,14 +16,15 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+import ifcopenshell
-def map_representation(file, **usecase_settings) -> None:
+
+def map_representation(
+ file: ifcopenshell.file, representation: ifcopenshell.entity_instance
+) -> ifcopenshell.entity_instance:
usecase = Usecase()
usecase.file = file
- usecase.settings = {"representation": None}
- usecase.ifc_vertices = []
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
+ usecase.settings = {"representation": representation}
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_boolean.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_boolean.py
index 5d81203a5d..c24bcdbc1b 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_boolean.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_boolean.py
@@ -19,12 +19,10 @@
import ifcopenshell.util.element
-def remove_boolean(file, **usecase_settings) -> None:
+def remove_boolean(file: ifcopenshell.file, item: ifcopenshell.entity_instance) -> None:
usecase = Usecase()
usecase.file = file
- usecase.settings = {"item": None}
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
+ usecase.settings = {"item": item}
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/unassign_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/unassign_representation.py
index 83b1570ac6..435ea5207c 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/unassign_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/unassign_representation.py
@@ -20,12 +20,12 @@ import ifcopenshell.api
import ifcopenshell.util.element
-def unassign_representation(file, **usecase_settings) -> None:
+def unassign_representation(
+ file: ifcopenshell.file, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
+) -> None:
usecase = Usecase()
usecase.file = file
- usecase.settings = {"product": None, "representation": None}
- for key, value in usecase_settings.items():
- usecase.settings[key] = value
+ usecase.settings = {"product": product, "representation": representation}
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py
index 5da8819e47..8b6ed48337 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py
@@ -16,8 +16,10 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+import ifcopenshell
-def add_georeferencing(file) -> None:
+
+def add_georeferencing(file: ifcopenshell.file) -> None:
"""Add empty georeferencing entities to a model
By default, models are not georeferenced. Georeferencing requires two
diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py
index f4118d96e1..08d7361935 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/edit_georeferencing.py
@@ -16,8 +16,16 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+import ifcopenshell
+from typing import Optional, Any
-def edit_georeferencing(file, map_conversion=None, projected_crs=None, true_north=None) -> None:
+
+def edit_georeferencing(
+ file: ifcopenshell.file,
+ map_conversion: Optional[dict[str, Any]] = None,
+ projected_crs: Optional[dict[str, Any]] = None,
+ true_north: Optional[tuple[float, float]] = None,
+) -> None:
"""Edits the attributes of a map conversion, projected CRS, and true north
Setting the correct georeferencing parameters is a complex topic and
@@ -47,7 +55,7 @@ def edit_georeferencing(file, map_conversion=None, projected_crs=None, true_nort
names and values you want to edit.
:type projected_crs: dict, optional
:param true_north: A unitised 2D vector, where each ordinate is a float
- :type true_north: list[float]
+ :type true_north: tuple[float, float], optional
:return: None
:rtype: None
@@ -101,7 +109,7 @@ class Usecase:
self.set_true_north()
def set_true_north(self):
- if self.settings["true_north"] == []:
+ if self.settings["true_north"] == None:
return
for context in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.TrueNorth:
@@ -111,6 +119,8 @@ class Usecase:
context.TrueNorth = self.file.create_entity("IfcDirection")
direction = context.TrueNorth
if self.settings["true_north"] is None:
+ # TODO: code will never be executed since None value
+ # is substituted by an empty list
context.TrueNorth = self.settings["true_north"]
elif context.CoordinateSpaceDimension == 2:
direction.DirectionRatios = self.settings["true_north"][0:2]
diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/remove_georeferencing.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/remove_georeferencing.py
index 3d3941ed7c..bac0f6253d 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/georeference/remove_georeferencing.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/remove_georeferencing.py
@@ -16,8 +16,10 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+import ifcopenshell
-def remove_georeferencing(file) -> None:
+
+def remove_georeferencing(file: ifcopenshell.file) -> None:
"""Remove georeferencing data
All georeferencing parameters such as projected CRS and map conversion
diff --git a/src/ifcopenshell-python/ifcopenshell/api/grid/create_axis_curve.py b/src/ifcopenshell-python/ifcopenshell/api/grid/create_axis_curve.py
index 2f3520c662..abb2848205 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/grid/create_axis_curve.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/grid/create_axis_curve.py
@@ -16,13 +16,18 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+from __future__ import annotations
import ifcopenshell
+import ifcopenshell.util.element
import ifcopenshell.util.unit
import ifcopenshell.util.placement
from mathutils import Matrix # For now, we depend on Blender
+import bpy.types
-def create_axis_curve(file, axis_curve=None, grid_axis=None) -> None:
+def create_axis_curve(
+ file: ifcopenshell.file, axis_curve: bpy.types.Object, grid_axis: ifcopenshell.entity_instance
+) -> None:
"""Adds curve geometry to a grid axis to represent the axis extents
This currently depends on the Blender geometry kernel to function.
diff --git a/src/ifcopenshell-python/ifcopenshell/api/grid/create_grid_axis.py b/src/ifcopenshell-python/ifcopenshell/api/grid/create_grid_axis.py
index f089b43b98..b91d86e259 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/grid/create_grid_axis.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/grid/create_grid_axis.py
@@ -15,9 +15,17 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+import ifcopenshell
+from typing import Optional, Literal
-def create_grid_axis(file, axis_tag=None, same_sense=None, uvw_axes=None, grid=None) -> None:
+def create_grid_axis(
+ file: ifcopenshell.file,
+ grid: ifcopenshell.entity_instance,
+ axis_tag: str = "A",
+ same_sense: bool = True,
+ uvw_axes: Literal["UAxes", "VAxes", "WAxes"] = "UAxes",
+) -> ifcopenshell.entity_instance:
"""Adds a new grid axis to a grid
An IFC grid will typically have a minimum of two axes which will be
@@ -66,17 +74,9 @@ def create_grid_axis(file, axis_tag=None, same_sense=None, uvw_axes=None, grid=N
axis_1 = ifcopenshell.api.run("grid.create_grid_axis", model,
axis_tag="1", uvw_axes="VAxes", grid=grid)
"""
- settings = {
- "axis_tag": axis_tag or "A",
- "same_sense": same_sense or True,
- "uvw_axes": uvw_axes or "UAxes", # Choose which axes
- "grid": grid,
- }
- element = file.create_entity(
- "IfcGridAxis", **{"AxisTag": settings["axis_tag"], "SameSense": settings["same_sense"]}
- )
- axes = list(getattr(settings["grid"], settings["uvw_axes"]) or [])
+ element = file.create_entity("IfcGridAxis", **{"AxisTag": axis_tag, "SameSense": same_sense})
+ axes = list(getattr(grid, uvw_axes) or [])
axes.append(element)
- setattr(settings["grid"], settings["uvw_axes"], axes)
+ setattr(grid, uvw_axes, axes)
return element
diff --git a/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py b/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py
index 51032ed52f..2e3ff1b005 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py
@@ -19,7 +19,7 @@
import ifcopenshell.util.element
-def remove_grid_axis(file, axis=None) -> None:
+def remove_grid_axis(file: ifcopenshell.file, axis: ifcopenshell.entity_instance) -> None:
"""Removes a grid axis from a grid
:param axis: The IfcGridAxis you want to remove.
@@ -43,9 +43,8 @@ def remove_grid_axis(file, axis=None) -> None:
# Let's remove it!
ifcopenshell.api.run("grid.remove_grid_axis", model, axis=axis_2)
"""
- settings = {"axis": axis}
-
- if len(file.get_inverse(settings["axis"].AxisCurve)) == 1:
- ifcopenshell.util.element.remove_deep(file, settings["axis"].AxisCurve)
- file.remove(settings["axis"].AxisCurve)
- file.remove(settings["axis"])
+ axis_curve = axis.AxisCurve
+ if len(file.get_inverse(axis_curve)) == 1:
+ ifcopenshell.util.element.remove_deep(file, axis_curve)
+ file.remove(axis_curve)
+ file.remove(axis)
diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/add_group.py b/src/ifcopenshell-python/ifcopenshell/api/group/add_group.py
index 7a233bdcbf..44553bddcf 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/group/add_group.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/group/add_group.py
@@ -19,9 +19,12 @@
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.guid
+from typing import Optional
-def add_group(file, Name="Unnamed", Description=None) -> None:
+def add_group(
+ file: ifcopenshell.file, Name: str = "Unnamed", Description: Optional[str] = None
+) -> ifcopenshell.entity_instance:
"""Adds a new group
An IFC group is an arbitrary collection of products, which are typically
diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/edit_group.py b/src/ifcopenshell-python/ifcopenshell/api/group/edit_group.py
index 1eb0c8d6f4..912b3c30c9 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/group/edit_group.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/group/edit_group.py
@@ -15,9 +15,11 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see .
+import ifcopenshell
+from typing import Any
-def edit_group(file, group=None, attributes=None) -> None:
+def edit_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
"""Edits the attributes of an IfcGroup
For more information about the attributes and data types of an
@@ -26,7 +28,7 @@ def edit_group(file, group=None, attributes=None) -> None:
:param group: The IfcGroup entity you want to edit
:type group: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
- :type attributes: dict, optional
+ :type attributes: dict
:return: None
:rtype: None
@@ -38,7 +40,7 @@ def edit_group(file, group=None, attributes=None) -> None:
ifcopenshell.api.run("group.edit_group", model,
group=group, attributes={"Description": "All furniture and joinery included in the unit"})
"""
- settings = {"group": group, "attributes": attributes or {}}
+ settings = {"group": group, "attributes": attributes}
for name, value in settings["attributes"].items():
setattr(settings["group"], name, value)
diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/remove_group.py b/src/ifcopenshell-python/ifcopenshell/api/group/remove_group.py
index 05e85f3fc0..d17806d6c3 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/group/remove_group.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/group/remove_group.py
@@ -21,7 +21,7 @@ import ifcopenshell.api
import ifcopenshell.util.element
-def remove_group(file, group=None) -> None:
+def remove_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance) -> None:
"""Removes a group
All products assigned to the group will remain, but the relationship to
diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/update_group_products.py b/src/ifcopenshell-python/ifcopenshell/api/group/update_group_products.py
index 74ef54c0bb..c68992f7a3 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/group/update_group_products.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/group/update_group_products.py
@@ -21,7 +21,9 @@ import ifcopenshell.api
import ifcopenshell.guid
-def update_group_products(file, group=None, products=None) -> None:
+def update_group_products(
+ file: ifcopenshell.file, group: ifcopenshell.entity_instance, products: list[ifcopenshell.entity_instance]
+) -> ifcopenshell.entity_instance:
"""Sets a group products to be an explicit list of products
Any previous products assigned to that group will have their assignment
diff --git a/src/ifcopenshell-python/ifcopenshell/api/library/add_library.py b/src/ifcopenshell-python/ifcopenshell/api/library/add_library.py
index fdd21c3424..00af1708e4 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/library/add_library.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/library/add_library.py
@@ -21,7 +21,7 @@ import ifcopenshell.util.schema
import ifcopenshell.util.date
-def add_library(file: ifcopenshell.entity_instance, name: str) -> ifcopenshell.entity_instance:
+def add_library(file: ifcopenshell.file, name: str) -> ifcopenshell.entity_instance:
"""Adds a new library to the project
A library is an external data source that is related to the project. It
diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py
index 37a57825f5..f584c341ca 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile_usage.py
@@ -36,7 +36,7 @@ def edit_profile_usage(
:param usage: The IfcMaterialProfileSetUsage entity you want to edit
:type usage: ifcopenshell.entity_instance
:param attributes: a dictionary of attribute names and values.
- :type attributes: dict, optional
+ :type attributes: dict
:return: None
:rtype: None
@@ -93,7 +93,7 @@ def edit_profile_usage(
usecase = Usecase()
usecase.file = file
- usecase.settings = {"usage": usage, "attributes": attributes or {}}
+ usecase.settings = {"usage": usage, "attributes": attributes}
return usecase.execute()
diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_product.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_product.py
index e492707c44..a033030530 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_product.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_product.py
@@ -22,7 +22,7 @@ import ifcopenshell.guid
def assign_product(
- file: ifcopenshell.entity_instance,
+ file: ifcopenshell.file,
relating_product: ifcopenshell.entity_instance,
related_object: ifcopenshell.entity_instance,
) -> ifcopenshell.entity_instance:
diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py
index 79ef8b90de..5179073e70 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py
@@ -21,7 +21,7 @@ from typing import Any
def edit_work_schedule(
- file: ifcopenshell.entity_instance, work_schedule: ifcopenshell.entity_instance, attributes: dict[str, Any]
+ file: ifcopenshell.file, work_schedule: ifcopenshell.entity_instance, attributes: dict[str, Any]
) -> None:
"""Edits the attributes of an IfcWorkSchedule
diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py
index 22a19e5051..05ec24adff 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py
@@ -20,7 +20,7 @@ import ifcopenshell
import ifcopenshell.util.element
-def remove_work_plan(file: ifcopenshell.entity_instance, work_plan: ifcopenshell.entity_instance) -> None:
+def remove_work_plan(file: ifcopenshell.file, work_plan: ifcopenshell.entity_instance) -> None:
"""Removes a work plan
Note that schedules that are grouped under the work plan are not
diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py
index f6c8c3b0c3..48583984aa 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
def add_surface_textures(
- file: ifcopenshell.entity_instance,
+ file: ifcopenshell.file,
material: Optional[bpy.types.Material] = None,
textures: Optional[list[dict]] = None,
uv_maps: Optional[list[ifcopenshell.entity_instance]] = None,
diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py
index ee421256c5..53513cfb05 100644
--- a/src/ifcopenshell-python/ifcopenshell/file.py
+++ b/src/ifcopenshell-python/ifcopenshell/file.py
@@ -24,7 +24,7 @@ import zipfile
import functools
import ifcopenshell
from pathlib import Path
-from typing import Optional, Any
+from typing import Optional, Any, Union, Callable
from . import ifcopenshell_wrapper
from .entity_instance import entity_instance
@@ -379,7 +379,7 @@ class file:
return e
- def __getattr__(self, attr):
+ def __getattr__(self, attr) -> Union[Any, Callable[..., ifcopenshell.entity_instance]]:
if attr[0:6] == "create":
return functools.partial(self.create_entity, attr[6:])
elif attr == "schema":
diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py
index 2430a2553a..60ac89044c 100644
--- a/src/ifcopenshell-python/ifcopenshell/util/element.py
+++ b/src/ifcopenshell-python/ifcopenshell/util/element.py
@@ -979,7 +979,7 @@ def get_decomposition(element: ifcopenshell.entity_instance, is_recursive=True)
def get_grouped_by(element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
"""Retrieves all subelements of an element based on the group.
- :param element: The IFC element
+ :param element: IfcGroup entity
:type element: ifcopenshell.entity_instance
:return: All subelements of the group
:rtype: list[ifcopenshell.entity_instance]
diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py
index 29ed257cc8..81ec583419 100644
--- a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py
+++ b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py
@@ -26,7 +26,7 @@ import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.unit
from math import cos, sin, pi, tan, radians, degrees, atan, sqrt, ceil
-from typing import List, Tuple, Type, Union
+from typing import Union, Optional, Literal, Any
from itertools import chain
from mathutils import Vector, Matrix
@@ -34,7 +34,7 @@ V = lambda *x: Vector([float(i) for i in x])
sign = lambda x: x and (1, -1)[x < 0]
PRECISION = 1.0e-5
-VectorTuple = Type[Tuple[float, float, float]]
+VectorTuple = type[tuple[float, float, float]]
"tuple of 3 `float` values"
@@ -59,13 +59,17 @@ class ShapeBuilder:
self.file = ifc_file
def polyline(
- self, points: List[Vector], closed: bool = False, position_offset: Vector = None, arc_points: List[int] = []
+ self,
+ points: list[Vector],
+ closed: bool = False,
+ position_offset: Optional[Vector] = None,
+ arc_points: list[int] = [],
) -> ifcopenshell.entity_instance:
"""
Generate an IfcIndexedPolyCurve based on the provided points.
:param points: List of 2d or 3d points
- :type points: List[Vector]
+ :type points: list[Vector]
:param closed: Whether polyline should be closed. Default is `False`
:type closed: bool, optional
:param position_offset: offset to be applied to all points
@@ -73,7 +77,7 @@ class ShapeBuilder:
:param arc_points: Indices of the middle points for arcs. For creating an arc segment,
provide 3 points: `arc_start`, `arc_middle` and `arc_end` to `points` and add the `arc_middle`
point's index to `arc_points`
- :type arc_points: List[int], optional
+ :type arc_points: list[int], optional
:return: IfcIndexedPolyCurve
:rtype: ifcopenshell.entity_instance
@@ -155,7 +159,9 @@ class ShapeBuilder:
ifc_curve = self.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
return ifc_curve
- def get_rectangle_coords(self, size: Vector = Vector((1.0, 1.0)).freeze(), position: Vector = None) -> List[Vector]:
+ def get_rectangle_coords(
+ self, size: Vector = Vector((1.0, 1.0)).freeze(), position: Optional[Vector] = None
+ ) -> list[Vector]:
"""
Get rectangle coords arranged as below:
@@ -248,7 +254,7 @@ class ShapeBuilder:
# TODO: explain points order for the curve_between_two_points
# because the order is important and defines the center of the curve
# currently it seems like the first point shifted by x-axis defines the center
- def curve_between_two_points(self, points):
+ def curve_between_two_points(self, points: tuple[Vector, Vector]) -> ifcopenshell.entity_instance:
# > points - list of 2 Vectors
"""Simple circle based curve between two points
Good for creating curves and fillets, won't work for continuous ellipse shapes.
@@ -268,7 +274,13 @@ class ShapeBuilder:
curve = self.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=[seg])
return curve
- def get_trim_points_from_mask(self, x_axis_radius, y_axis_radius, trim_points_mask, position_offset=None):
+ def get_trim_points_from_mask(
+ self,
+ x_axis_radius: float,
+ y_axis_radius: float,
+ trim_points_mask: list[int],
+ position_offset: Optional[Vector] = None,
+ ) -> list[Vector]:
"""Handy way to get edge points of the ellipse like shape of a given radiuses.
Mask points are numerated from 0 to 3 ccw starting from (x_axis_radius/2; 0).
@@ -289,13 +301,13 @@ class ShapeBuilder:
def create_ellipse_curve(
self,
- x_axis_radius,
- y_axis_radius,
+ x_axis_radius: float,
+ y_axis_radius: float,
position=Vector((0.0, 0.0)).freeze(),
- trim_points=[],
- ref_x_direction=Vector((1.0, 0.0)),
- trim_points_mask=[],
- ):
+ trim_points: list[Vector] = (),
+ ref_x_direction: Vector = Vector((1.0, 0.0)),
+ trim_points_mask: list[int] = (),
+ ) -> ifcopenshell.entity_instance:
"""
Ellipse trimming points should be specified in counter clockwise order.
@@ -329,7 +341,13 @@ class ShapeBuilder:
)
return trim_ellipse
- def profile(self, outer_curve, name=None, inner_curves=[], profile_type="AREA"):
+ def profile(
+ self,
+ outer_curve: ifcopenshell.entity_instance,
+ name: Optional[str] = None,
+ inner_curves: list[ifcopenshell.entity_instance] = (),
+ profile_type: str = "AREA",
+ ) -> ifcopenshell.entity_instance:
# > inner_curves - list of IfcCurve;
# inner_curves could be used as a tool for boolean operation
# but if any point of inner curve will go outside the outer curve
@@ -369,7 +387,12 @@ class ShapeBuilder:
profile = self.file.create_entity("IfcArbitraryClosedProfileDef", **kwargs)
return profile
- def translate(self, curve_or_item, translation: Vector, create_copy=False):
+ def translate(
+ self,
+ curve_or_item: Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]],
+ translation: Vector,
+ create_copy: bool = False,
+ ) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
# > curve_or_item - could be a list of curves or items or representations
# < returns translated object
@@ -413,7 +436,7 @@ class ShapeBuilder:
def rotate_2d_point(
self, point_2d: Vector, angle=90, pivot_point: Vector = Vector((0.0, 0.0)).freeze(), counter_clockwise=False
- ):
+ ) -> Vector:
# > angle - in degrees
# < rotated Vector
@@ -425,12 +448,12 @@ class ShapeBuilder:
def rotate(
self,
- curve_or_item,
- angle=90,
+ curve_or_item: Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]],
+ angle: float = 90,
pivot_point: Vector = Vector((0.0, 0.0)).freeze(),
- counter_clockwise=False,
- create_copy=False,
- ):
+ counter_clockwise: bool = False,
+ create_copy: bool = False,
+ ) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
# > curve_or_item - could be a list of curves or items
# > angle - in degrees
# < returns rotated object
@@ -479,7 +502,7 @@ class ShapeBuilder:
point_2d: Vector,
mirror_axes: Vector = Vector((1.0, 1.0)).freeze(),
mirror_point: Vector = Vector((0.0, 0.0)).freeze(),
- ):
+ ) -> Vector:
"""mirror_axes - along which axes mirror will be applied"""
base = point_2d # prevent mutating the argument
mirror_axes = Vector([-1 if i > 0 else 1 for i in mirror_axes])
@@ -558,12 +581,12 @@ class ShapeBuilder:
def mirror(
self,
- curve_or_item,
+ curve_or_item: Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]],
mirror_axes: Vector = Vector((1.0, 1.0)).freeze(),
mirror_point: Vector = Vector((0.0, 0.0)).freeze(),
- create_copy=False,
- placement_matrix=None,
- ):
+ create_copy: bool = False,
+ placement_matrix: Optional[Matrix] = None,
+ ) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
"""mirror_axes - along which axes mirror will be applied
For example, mirroring `A(1,0)` by axis `(1,0)` will result in `A'(-1,0)`
@@ -687,14 +710,14 @@ class ShapeBuilder:
def extrude(
self,
- profile_or_curve,
- magnitude=1.0,
+ profile_or_curve: ifcopenshell.entity_instance,
+ magnitude: float = 1.0,
position: Vector = Vector([0.0, 0.0, 0.0]).freeze(),
extrusion_vector: Vector = Vector((0.0, 0.0, 1.0)).freeze(),
position_z_axis: Vector = Vector((0.0, 0.0, 1.0)).freeze(),
position_x_axis: Vector = Vector((1.0, 0.0, 0.0)).freeze(),
- position_y_axis: Vector = None,
- ):
+ position_y_axis: Optional[Vector] = None,
+ ) -> ifcopenshell.entity_instance:
"""Extrude profile or curve to get IfcExtrudedAreaSolid.
REMEMBER when handling custom axes - IFC is using RIGHT handed coordinate system.
@@ -730,7 +753,9 @@ class ShapeBuilder:
)
return extruded_area
- def create_swept_disk_solid(self, path_curve, radius):
+ def create_swept_disk_solid(
+ self, path_curve: ifcopenshell.entity_instance, radius: float
+ ) -> ifcopenshell.entity_instance:
"""Create IfcSweptDiskSolid from `path_curve` (must be 3D) and `radius`"""
if path_curve.Dim != 3:
raise Exception(
@@ -741,16 +766,22 @@ class ShapeBuilder:
disk_solid = self.file.createIfcSweptDiskSolid(Directrix=path_curve, Radius=radius)
return disk_solid
- def get_representation(self, context, items, representation_type: str = None) -> ifcopenshell.entity_instance:
+ def get_representation(
+ self,
+ context: ifcopenshell.entity_instance,
+ items: Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]],
+ representation_type: Optional[str] = None,
+ ) -> ifcopenshell.entity_instance:
"""Create IFC representation for the specified context and items.
:param context: IfcGeometricRepresentationSubContext
+ :type context: ifcopenshell.entity_instance
:param items: could be a list or single curve/IfcExtrudedAreaSolid
:param representation_type: Explicitly specified RepresentationType, defaults to `None`.
If not provided it will be guessed from the items types
:type representation_type: str, optional
- :return: IfcRepresentation
+ :return: IfcShapeRepresentation
:rtype: ifcopenshell.entity_instance
"""
if not isinstance(items, collections.abc.Iterable):
@@ -779,11 +810,11 @@ class ShapeBuilder:
)
return representation
- def deep_copy(self, element):
+ def deep_copy(self, element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
return ifcopenshell.util.element.copy_deep(self.file, element)
# UTILITIES
- def extrude_kwargs(self, axis):
+ def extrude_kwargs(self, axis: Literal["Y", "X", "Z"]) -> dict[str, Vector]:
"""Shortcut to get kwargs for `ShapeBuilder.extrude` to extrude by some axis.
It assumes you have 2D profile in:
@@ -814,7 +845,9 @@ class ShapeBuilder:
"extrusion_vector": Vector((0, 0, 1)),
}
- def rotate_extrusion_kwargs_by_z(self, kwargs, angle, counter_clockwise=False):
+ def rotate_extrusion_kwargs_by_z(
+ self, kwargs: dict[str, Any], angle: float, counter_clockwise: bool = False
+ ) -> dict[str, Vector]:
"""shortcut to rotate extrusion kwargs by z axis
`kwargs` expected to have `position_x_axis` and `position_z_axis` keys
@@ -829,7 +862,7 @@ class ShapeBuilder:
kwargs["position_z_axis"].rotate(rot)
return kwargs
- def get_polyline_coords(self, polyline):
+ def get_polyline_coords(self, polyline: ifcopenshell.entity_instance) -> list[Vector]:
"""polyline should be either `IfcIndexedPolyCurve` or `IfcPolyline`"""
coords = None
if polyline.is_a("IfcIndexedPolyCurve"):
@@ -838,7 +871,7 @@ class ShapeBuilder:
coords = [p.Coordinates for p in polyline.Points]
return coords
- def set_polyline_coords(self, polyline, coords):
+ def set_polyline_coords(self, polyline: ifcopenshell.entity_instance, coords: list[Vector]) -> None:
"""polyline should be either `IfcIndexedPolyCurve` or `IfcPolyline`"""
if polyline.is_a("IfcIndexedPolyCurve"):
polyline.Points.CoordList = coords
@@ -846,7 +879,14 @@ class ShapeBuilder:
for i, co in enumerate(coords):
polyline.Points[i].Coordinates = co
- def get_simple_2dcurve_data(self, coords, fillets=[], fillet_radius=[], closed=True, create_ifc_curve=None):
+ def get_simple_2dcurve_data(
+ self,
+ coords: list[Vector],
+ fillets: list[int] = (),
+ fillet_radius: list[float] = (),
+ closed: bool = True,
+ create_ifc_curve: bool = False,
+ ) -> tuple[list[Vector], list[tuple[int, int], Union[ifcopenshell.entity_instance, None]]]:
"""
Creates simple 2D curve from set of 2d coords and list of points with fillets.
Simple curve means that all fillets are based on 90 degree angle.
@@ -957,8 +997,14 @@ class ShapeBuilder:
return (points, segments, ifc_curve)
def create_z_profile_lips_curve(
- self, FirstFlangeWidth, SecondFlangeWidth, Depth, Girth, WallThickness, FilletRadius
- ):
+ self,
+ FirstFlangeWidth: float,
+ SecondFlangeWidth: float,
+ Depth: float,
+ Girth: float,
+ WallThickness: float,
+ FilletRadius: float,
+ ) -> ifcopenshell.entity_instance:
x1 = FirstFlangeWidth
x2 = SecondFlangeWidth
y = Depth / 2
@@ -996,7 +1042,9 @@ class ShapeBuilder:
return ifc_curve
- def create_transition_arc_ifc(self, width, height, create_ifc_curve=False):
+ def create_transition_arc_ifc(
+ self, width: str, height: str, create_ifc_curve: bool = False
+ ) -> tuple[list[Vector], list[tuple[int, int], Union[ifcopenshell.entity_instance, None]]]:
# create an arc in the rectangle with specified width and height
# if it's not possible to make a complete arc
# it will create arc with longest radius possible
@@ -1028,7 +1076,7 @@ class ShapeBuilder:
)
return points, segments, transition_arc
- def polygonal_face_set(self, points, faces):
+ def polygonal_face_set(self, points: list[Vector], faces: list[[list[int]]]) -> ifcopenshell.entity_instance:
"""
> `points` - list of points
@@ -1048,8 +1096,14 @@ class ShapeBuilder:
return face_set
def extrude_face_set(
- self, points, magnitude: float, extrusion_vector=V(0, 0, 1).freeze(), offset=None, start_cap=True, end_cap=True
- ):
+ self,
+ points: list[Vector],
+ magnitude: float,
+ extrusion_vector: Vector = V(0, 0, 1).freeze(),
+ offset: Optional[Vector] = None,
+ start_cap: bool = True,
+ end_cap: bool = True,
+ ) -> ifcopenshell.entity_instance:
"""
Method to extrude by creating face sets rather than creating IfcExtrudedAreaSolid.
@@ -1057,18 +1111,20 @@ class ShapeBuilder:
to assure CorrectItemsForType.
:param points: list of points, assuming they form consecutive closed polyline.
+ :type points: list[Vector]
:param magnitude: extrusion magnitude
- :param type: float
+ :type magnitude: float
:param extrusion_vector: extrusion direction, by default it's extruding by Z+ axis
- :param type: Vector, optional
+ :type extrusion_vector: Vector, optional
:param offset: offset from the points
- :param type: Vector, optional
+ :type offset: Vector, optional
:param start_cap: if True, create start cap, by default it's True
- :param type: bool, optional
+ :type start_cap: bool, optional
:param end_cap: if True, create end cap, by default it's True
- :param type: bool, optional
+ :type end_cap: bool, optional
:return: IfcPolygonalFaceSet
+ :rtype: ifcopenshell.entity_instance
"""
# prevent mutating arguments, deepcopy doesn't work
diff --git a/src/ifcopenshell-python/test/api/pset/test_remove_pset.py b/src/ifcopenshell-python/test/api/pset/test_remove_pset.py
index 1685f158e8..134bf1b9a7 100644
--- a/src/ifcopenshell-python/test/api/pset/test_remove_pset.py
+++ b/src/ifcopenshell-python/test/api/pset/test_remove_pset.py
@@ -18,6 +18,7 @@
import test.bootstrap
import ifcopenshell.api
+import ifcopenshell.util.element
class TestRemovePset(test.bootstrap.IFC4):