mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 01:11:40 +00:00
typing
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -16,8 +16,17 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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:
|
||||
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
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)
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
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)
|
||||
|
||||
@@ -17,9 +17,14 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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,
|
||||
|
||||
@@ -16,27 +16,51 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -17,26 +17,43 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@@ -17,22 +17,32 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,14 +16,15 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
||||
@@ -16,8 +16,16 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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]
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
||||
@@ -16,13 +16,18 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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.
|
||||
|
||||
@@ -15,9 +15,17 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import test.bootstrap
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class TestRemovePset(test.bootstrap.IFC4):
|
||||
|
||||
Reference in New Issue
Block a user