This commit is contained in:
Andrej730
2024-10-04 12:10:15 +05:00
parent 2994f5c170
commit bf9d84e738
8 changed files with 141 additions and 88 deletions
@@ -23,11 +23,12 @@ import bmesh
import bonsai.tool as tool
import ifcopenshell.util.element
from mathutils import Vector, Matrix
from typing import Optional
class Annotator:
@staticmethod
def add_text(related_element=None):
def add_text(related_element: Optional[ifcopenshell.entity_instance] = None) -> bpy.types.Object:
curve = bpy.data.curves.new(type="FONT", name="Text")
curve.body = "TEXT"
obj = bpy.data.objects.new("Text", curve)
@@ -53,7 +54,7 @@ class Annotator:
return obj
@staticmethod
def resize_text(text_obj):
def resize_text(text_obj: bpy.types.Object) -> None:
camera = None
group = tool.Drawing.get_drawing_group(tool.Ifc.get_entity(text_obj))
for element in tool.Drawing.get_drawing_elements(group):
@@ -71,7 +72,9 @@ class Annotator:
text_obj.data.size = font_size
@staticmethod
def add_line_to_annotation(obj, co1=None, co2=None):
def add_line_to_annotation(
obj: bpy.types.Object, co1: Optional[Vector] = None, co2: Optional[Vector] = None
) -> bpy.types.Object:
if co1 is None:
co1, co2, _, _ = Annotator.get_placeholder_coords()
co1 = obj.matrix_world.inverted() @ co1
@@ -93,7 +96,7 @@ class Annotator:
return obj
@staticmethod
def add_vertex_to_annotation(obj):
def add_vertex_to_annotation(obj: bpy.types.Object) -> bpy.types.Object:
verts_world_space = Annotator.get_placeholder_coords()
vert_local = obj.matrix_world.inverted() @ verts_world_space[0]
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
@@ -102,7 +105,7 @@ class Annotator:
return obj
@staticmethod
def add_plane_to_annotation(obj, remove_face=False):
def add_plane_to_annotation(obj: bpy.types.Object, remove_face: bool = False) -> bpy.types.Object:
# default order = bot left, top left, bot right, top right
# therefore we redefine the order
face_verts = [0, 2, 3, 1]
@@ -119,7 +122,7 @@ class Annotator:
return obj
@staticmethod
def get_annotation_obj(drawing, object_type, data_type):
def get_annotation_obj(drawing: ifcopenshell.entity_instance, object_type: str, data_type: str) -> bpy.types.Object:
camera = tool.Ifc.get_object(drawing)
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
matrix_world = tool.Drawing.get_camera_matrix(camera)
@@ -162,7 +165,7 @@ class Annotator:
return obj
@staticmethod
def get_placeholder_coords(camera=None):
def get_placeholder_coords(camera: Optional[bpy.types.Object] = None) -> tuple[Vector, Vector, Vector, Vector]:
if not camera:
camera = bpy.context.scene.camera
+23 -12
View File
@@ -35,6 +35,7 @@ import bonsai.core.root
from bonsai.bim.module.model.window import create_bm_window, create_bm_box
from mathutils import Vector, Matrix
from typing import Union, Any, Optional
import json
import collections
@@ -169,7 +170,9 @@ def update_door_modifier_representation(context: bpy.types.Context) -> None:
# TODO: move it out to tools
def bm_sort_out_geom(geom_data):
def bm_sort_out_geom(
geom_data: list[Union[bmesh.types.BMVert, bmesh.types.BMEdge, bmesh.types.BMFace]]
) -> dict[str, Any]:
geom_dict = {"verts": [], "edges": [], "faces": []}
for el in geom_data:
@@ -183,8 +186,12 @@ def bm_sort_out_geom(geom_data):
def bm_mirror(
bm, verts, mirror_axes: Vector = V(1, 0, 0).freeze(), mirror_point: Vector = V(0, 0, 0).freeze(), create_copy=False
):
bm: bmesh.types.BMesh,
verts: list[bmesh.types.BMVert],
mirror_axes: Vector = V(1, 0, 0).freeze(),
mirror_point: Vector = V(0, 0, 0).freeze(),
create_copy: bool = False,
) -> list[bmesh.types.BMVert]:
matrix = Matrix.Translation(mirror_point)
for i, v in enumerate(mirror_axes):
if not v:
@@ -208,14 +215,14 @@ def bm_mirror(
def create_bm_extruded_profile(
bm,
points,
edges=None,
faces=None,
bm: bmesh.types.BMesh,
points: list[Vector],
edges: Optional[list[tuple[int, int]]] = None,
faces: Optional[list[list[int]]] = None,
position: Vector = V(0, 0, 0).freeze(),
magnitude=1.0,
magnitude: float = 1.0,
extrusion_vector: Vector = V(0, 0, 1).freeze(),
):
) -> list[bmesh.types.BMVert]:
bm.verts.index_update()
bm.edges.index_update()
bm.faces.ensure_lookup_table()
@@ -242,7 +249,9 @@ def create_bm_extruded_profile(
return new_verts + extruded_verts
def create_bm_door_lining(bm, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()):
def create_bm_door_lining(
bm: bmesh.types.BMesh, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()
) -> list[bmesh.types.BMVert]:
"""`thickness` of the profile is defined as list in the following order: `(SIDE, TOP)`
`thickness` can be also defined just as 1 float value.
@@ -303,7 +312,7 @@ def create_bm_door_lining(bm, size: Vector, thickness: list, position: Vector =
return new_verts + translate_verts
def update_door_modifier_bmesh(context):
def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
obj = context.active_object
props = obj.BIMDoorProperties
@@ -400,7 +409,9 @@ def update_door_modifier_bmesh(context):
inner_casing_verts = create_bm_door_lining(bm, casing_size, inner_casing_thickness, inner_casing_position)
casing_verts.extend(inner_casing_verts)
def create_bm_door_panel(panel_size, panel_position, door_swing_type):
def create_bm_door_panel(
panel_size: Vector, panel_position: Vector, door_swing_type: str
) -> list[bmesh.types.BMVert]:
door_verts = []
# add door panel
door_verts.extend(create_bm_box(bm, panel_size, panel_position))
@@ -33,13 +33,14 @@ from bonsai.bim.module.model.decorator import ProfileDecorator
from mathutils import Vector
import json
from typing import Any
# reference:
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailing.htm
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailingType.htm
def bm_split_edge_at_offset(edge, offset):
def bm_split_edge_at_offset(edge: bmesh.types.BMEdge, offset: float) -> dict[str, Any]:
v0, v1 = edge.verts
offset = offset / 2
@@ -51,7 +52,7 @@ def bm_split_edge_at_offset(edge, offset):
return new_geometry
def update_railing_modifier_ifc_data(context):
def update_railing_modifier_ifc_data(context: bpy.types.Context) -> None:
"""should be called after new geometry settled
since it's going to update ifc representation
"""
@@ -113,7 +114,7 @@ def update_railing_modifier_ifc_data(context):
tool.Ifc.edit(obj)
def update_bbim_railing_pset(element, railing_data):
def update_bbim_railing_pset(element: ifcopenshell.entity_instance, railing_data: dict[str, Any]) -> None:
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
if not pset:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Railing")
@@ -121,7 +122,7 @@ def update_bbim_railing_pset(element, railing_data):
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": railing_data})
def update_railing_modifier_bmesh(context):
def update_railing_modifier_bmesh(context: bpy.types.Context) -> None:
"""before using should make sure that Data contains up-to-date information.
If BBIM Pset just changed should call refresh() before updating bmesh
"""
@@ -153,7 +154,7 @@ def update_railing_modifier_bmesh(context):
if props.railing_type != "FRAMELESS_PANEL":
return
def generate_frameless_panel_railing():
def generate_frameless_panel_railing() -> None:
# generating FRAMELESS_PANEL railing
height = props.height
thickness = props.thickness
@@ -214,7 +215,7 @@ def update_railing_modifier_bmesh(context):
generate_frameless_panel_railing()
def get_path_data(obj):
def get_path_data(obj: bpy.types.Object) -> dict[str, Any]:
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
@@ -468,7 +469,7 @@ class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
def cancel_editing_railing_path(context):
def cancel_editing_railing_path(context: bpy.types.Context) -> set[str]:
obj = context.active_object
props = obj.BIMRailingProperties
+26 -17
View File
@@ -37,13 +37,14 @@ import mathutils.geometry
from bpypolyskel import bpypolyskel
import shapely
from pprint import pprint
from typing import Literal, Union, Any
# reference:
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoof.htm
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoofType.htm
def bm_mesh_clean_up(bm):
def bm_mesh_clean_up(bm: bmesh.types.BMesh) -> None:
# remove internal edges and faces
# adding missing faces so we could rely on `e.is_boundary` later
bmesh.ops.contextual_create(bm, geom=bm.edges[:])
@@ -94,7 +95,7 @@ class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
def is_valid_roof_footprint(bm):
def is_valid_roof_footprint(bm: bmesh.types.BMesh) -> tuple[set[str], str]:
# should be bmesh to support edit mode
bm.verts.ensure_lookup_table()
base_z = bm.verts[0].co.z
@@ -108,8 +109,14 @@ def is_valid_roof_footprint(bm):
def generate_hiped_roof_bmesh(
bm, mode="ANGLE", height=1.0, roof_thickness=0.1, angle=pi / 18, rafter_edge_angle=pi / 2, mutate_current_bmesh=True
):
bm: bmesh.types.BMesh,
mode: Literal["ANGLE", "HEIGHT"] = "ANGLE",
height: float = 1.0,
roof_thickness: float = 0.1,
angle: float = pi / 18,
rafter_edge_angle: float = pi / 2,
mutate_current_bmesh: float = True,
) -> bmesh.types.BMesh:
"""return bmesh with gable roof geometry
`mutate_current_bmesh` is a flag to indicate whether the input bmesh
@@ -199,16 +206,16 @@ def generate_hiped_roof_bmesh(
new_edges = [bm.edges.new([new_verts[vi] for vi in edge]) for edge in edges]
new_faces = [bm.faces.new([new_verts[vi] for vi in face]) for face in faces]
def find_identical_new_vert(co):
def find_identical_new_vert(co: Vector) -> Union[bmesh.types.BMVert, None]:
for v in bm.verts:
if tool.Cad.is_x((co - v.co).length, 0):
return v
def find_other_polygon_verts(edge):
def find_other_polygon_verts(edge: bmesh.types.BMEdge) -> list[bmesh.types.BMVert]:
polygon = edge.link_faces[0]
return [v for v in polygon.verts if v not in edge.verts]
def change_angle(projected_vert_co, edge_verts, new_angle):
def change_angle(projected_vert_co: Vector, edge_verts: list[bmesh.types.BMVert], new_angle: float) -> Vector:
A, B = [v.co for v in edge_verts]
P = projected_vert_co
@@ -232,10 +239,10 @@ def generate_hiped_roof_bmesh(
verts_to_rip = []
bottom_chords_to_remove = []
def is_footprint_vert(v):
def is_footprint_vert(v: bmesh.types.BMVert) -> bool:
return tool.Cad.is_x(v.co.z - footprint_z, 0)
def is_footprint_edge(edge):
def is_footprint_edge(edge: bmesh.types.BMEdge) -> bool:
return all(is_footprint_vert(v) for v in edge.verts)
# find footprint edges
@@ -279,7 +286,9 @@ def generate_hiped_roof_bmesh(
if defined_angle >= pi / 2:
bottom_chords_to_remove.append(identical_edge)
def separate_vert(bm, vert, edge, new_co):
def separate_vert(
bm: bmesh.types.BMesh, vert: bmesh.types.BMVert, edge: bmesh.types.BMEdge, new_co: Vector
) -> bmesh.types.BMVert:
face = next(f for f in vert.link_faces if edge in f.edges)
new_v = bmesh.utils.face_vert_separate(face, vert)
new_v.co = new_co
@@ -378,11 +387,11 @@ def generate_hiped_roof_bmesh(
return bm
def bm_get_indices(sequence):
def bm_get_indices(sequence) -> list[int]:
return [i.index for i in sequence]
def update_roof_modifier_ifc_data(context):
def update_roof_modifier_ifc_data(context: bpy.types.Context) -> None:
"""should be called after new geometry settled
since it's going to update ifc representation
"""
@@ -390,7 +399,7 @@ def update_roof_modifier_ifc_data(context):
props = obj.BIMRoofProperties
element = tool.Ifc.get_entity(obj)
def roof_is_gabled():
def roof_is_gabled() -> bool:
pset_data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof")
path_data = pset_data["data_dict"]["path_data"]
angle_layer = path_data.get("gable_roof_angles", None)
@@ -411,7 +420,7 @@ def update_roof_modifier_ifc_data(context):
tool.Ifc.edit(obj)
def update_bbim_roof_pset(element, roof_data):
def update_bbim_roof_pset(element: ifcopenshell.entity_instance, roof_data: dict[str, Any]) -> None:
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
if not pset:
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Roof")
@@ -419,7 +428,7 @@ def update_bbim_roof_pset(element, roof_data):
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": roof_data})
def update_roof_modifier_bmesh(context):
def update_roof_modifier_bmesh(context: bpy.types.Context) -> None:
"""before using should make sure that Data contains up-to-date information.
If BBIM Pset just changed should call refresh() before updating bmesh
"""
@@ -466,7 +475,7 @@ def update_roof_modifier_bmesh(context):
tool.Blender.apply_bmesh(obj.data, bm)
def get_path_data(obj):
def get_path_data(obj: bpy.types.Object) -> Union[dict[str, Any], None]:
"""get path data for current mesh, path data is cleaned up"""
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
@@ -699,7 +708,7 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
def cancel_editing_roof_path(context):
def cancel_editing_roof_path(context: bpy.types.Context) -> set[str]:
obj = context.active_object
props = obj.BIMRoofProperties
+3 -3
View File
@@ -33,7 +33,7 @@ from bpy.props import FloatProperty, IntProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
def regenerate_stair_mesh(context):
def regenerate_stair_mesh(context: bpy.types.Context) -> None:
obj = context.active_object
props_kwargs = obj.BIMStairProperties.get_props_kwargs()
vertices, edges, faces = tool.Model.generate_stair_2d_profile(**props_kwargs)
@@ -65,7 +65,7 @@ def regenerate_stair_mesh(context):
obj.data.update()
def update_ifc_stair_props(obj):
def update_ifc_stair_props(obj: bpy.types.Object) -> None:
"""should be called after new geometry settled
since it's going to update ifc representation
"""
@@ -114,7 +114,7 @@ def update_ifc_stair_props(obj):
tool.Ifc.edit(obj)
# update related annotation objects
def get_elements_from_product(product):
def get_elements_from_product(product: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
elements = []
for rel in product.ReferencedBy:
if not rel.is_a("IfcRelAssignsToProduct"):
+14 -9
View File
@@ -34,9 +34,10 @@ import ifcopenshell.util.unit
from ifcopenshell.util.shape_builder import V
from bmesh.types import BMVert
from mathutils import Vector
from typing import Optional
def update_window_modifier_representation(context):
def update_window_modifier_representation(context: bpy.types.Context) -> None:
obj = context.active_object
element = tool.Ifc.get_entity(obj)
props = obj.BIMWindowProperties
@@ -129,7 +130,9 @@ def update_window_modifier_representation(context):
tool.Model.update_simple_openings(element)
def create_bm_window_frame(bm, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()):
def create_bm_window_frame(
bm: bmesh.types.BMesh, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()
) -> list[bmesh.types.BMVert]:
"""`thickness` of the profile is defined as list in the following order:
`(LEFT, TOP, RIGHT, BOTTOM)`
@@ -194,7 +197,9 @@ def create_bm_window_frame(bm, size: Vector, thickness: list, position: Vector =
return new_verts + translate_verts
def create_bm_box(bm, size: Vector = V(1, 1, 1).freeze(), position: Vector = V(0, 0, 0).freeze()):
def create_bm_box(
bm: bmesh.types.BMesh, size: Vector = V(1, 1, 1).freeze(), position: Vector = V(0, 0, 0).freeze()
) -> list[bmesh.types.BMVert]:
"""create a box of `size`, position box first vertex at `position`"""
box_verts = bmesh.ops.create_cube(bm, size=1)["verts"]
bmesh.ops.translate(bm, vec=-box_verts[0].co, verts=box_verts)
@@ -204,17 +209,17 @@ def create_bm_box(bm, size: Vector = V(1, 1, 1).freeze(), position: Vector = V(0
def create_bm_window(
bm,
bm: bmesh.types.BMesh,
lining_size: Vector,
lining_thickness: list,
lining_to_panel_offset_x,
lining_to_panel_offset_y_full,
frame_size: Vector,
frame_thickness,
glass_thickness,
frame_thickness: float,
glass_thickness: float,
position: Vector,
x_offsets: list = None,
):
x_offsets: Optional[list] = None,
) -> tuple[list[bmesh.types.BMVert], list[bmesh.types.BMVert], list[bmesh.types.BMVert]]:
"""`lining_thickness` and `x_offsets` are expected to be defined as a list,
similarly to `create_bm_window_frame` `thickness` argument"""
@@ -241,7 +246,7 @@ def create_bm_window(
return (window_lining_verts, frame_verts, glass_verts)
def update_window_modifier_bmesh(context):
def update_window_modifier_bmesh(context: bpy.types.Context) -> None:
obj = context.active_object
props = obj.BIMWindowProperties
panel_schema = DEFAULT_PANEL_SCHEMAS[props.window_type]
+5 -5
View File
@@ -35,32 +35,32 @@ from bpy.props import (
)
def get_export_schema(self, context):
def get_export_schema(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not ProjectData.is_loaded:
ProjectData.load()
return ProjectData.data["export_schema"]
def get_template_file(self, context):
def get_template_file(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not ProjectData.is_loaded:
ProjectData.load()
return ProjectData.data["template_file"]
def get_library_file(self, context):
def get_library_file(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not ProjectData.is_loaded:
ProjectData.load()
return ProjectData.data["library_file"]
def update_library_file(self, context):
def update_library_file(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
if self.library_file != "0":
bpy.ops.bim.select_library_file(
filepath=os.path.join(bpy.context.scene.BIMProperties.data_dir, "libraries", self.library_file)
)
def update_filter_mode(self, context):
def update_filter_mode(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
self.filter_categories.clear()
if self.filter_mode == "NONE":
return
+52 -28
View File
@@ -248,7 +248,12 @@ class Model(bonsai.core.tool.Model):
return obj
@classmethod
def import_profile(cls, profile, obj=None, position=None):
def import_profile(
cls,
profile: ifcopenshell.entity_instance,
obj: Optional[bpy.types.Object] = None,
position: Optional[Matrix] = None,
) -> bpy.types.Object:
"""Creates new profile mesh and assigns it to `obj`,
if `obj` is `None` then new "Profile" object will be created.
@@ -297,7 +302,9 @@ class Model(bonsai.core.tool.Model):
return obj
@classmethod
def import_surface(cls, surface, obj=None):
def import_surface(
cls, surface: ifcopenshell.entity_instance, obj: Optional[bpy.types.Object] = None
) -> bpy.types.Object:
cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
cls.vertices = []
@@ -333,7 +340,7 @@ class Model(bonsai.core.tool.Model):
return obj
@classmethod
def import_curve(cls, obj, position, curve):
def import_curve(cls, obj: bpy.types.Object, position: Matrix, curve: ifcopenshell.entity_instance) -> None:
offset = len(cls.vertices)
if curve.is_a("IfcPolyline"):
@@ -402,7 +409,7 @@ class Model(bonsai.core.tool.Model):
cls.edges.append((offset, offset + 1))
@classmethod
def import_rectangle(cls, obj, position, profile):
def import_rectangle(cls, obj: bpy.types.Object, position: Matrix, profile: ifcopenshell.entity_instance) -> None:
if profile.Position:
p_position = Matrix(ifcopenshell.util.placement.get_axis2placement(profile.Position).tolist())
p_position.translation *= cls.unit_scale
@@ -424,7 +431,7 @@ class Model(bonsai.core.tool.Model):
cls.edges[-1] = (len(cls.vertices) - 1, 0) # Close the loop
@classmethod
def load_openings(cls, openings):
def load_openings(cls, openings: list[ifcopenshell.entity_instance]) -> Iterable[bpy.types.Object]:
if not openings:
return []
ifc_import_settings = import_ifc.IfcImportSettings.factory()
@@ -476,7 +483,11 @@ class Model(bonsai.core.tool.Model):
}
@classmethod
def get_booleans(cls, element=None, representation=None) -> list[ifcopenshell.entity_instance]:
def get_booleans(
cls,
element: Optional[ifcopenshell.entity_instance] = None,
representation: Optional[ifcopenshell.entity_instance] = None,
) -> list[ifcopenshell.entity_instance]:
if representation is None:
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
@@ -491,7 +502,9 @@ class Model(bonsai.core.tool.Model):
return booleans
@classmethod
def get_manual_booleans(cls, element, representation=None) -> list[ifcopenshell.entity_instance]:
def get_manual_booleans(
cls, element: ifcopenshell.entity_instance, representation: Optional[ifcopenshell.entity_instance] = None
) -> list[ifcopenshell.entity_instance]:
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean")
if not pset:
return []
@@ -521,7 +534,7 @@ class Model(bonsai.core.tool.Model):
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
@classmethod
def unmark_manual_booleans(cls, element, boolean_ids):
def unmark_manual_booleans(cls, element: ifcopenshell.entity_instance, boolean_ids: list[int]) -> None:
# NOTE: we use use boolean_ids instead of boolean entities
# so it will be possible to unmark manual booleans after they already was deleted
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean")
@@ -538,12 +551,14 @@ class Model(bonsai.core.tool.Model):
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
@classmethod
def get_flow_segment_axis(cls, obj):
def get_flow_segment_axis(cls, obj: bpy.types.Object) -> tuple[Vector, Vector]:
z_values = [v[2] for v in obj.bound_box]
return (obj.matrix_world @ Vector((0, 0, min(z_values))), obj.matrix_world @ Vector((0, 0, max(z_values))))
@classmethod
def get_flow_segment_profile(cls, element):
def get_flow_segment_profile(
cls, element: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
if material and material.is_a("IfcMaterialProfileSet") and len(material.MaterialProfiles) == 1:
return material.MaterialProfiles[0].Profile
@@ -579,7 +594,7 @@ class Model(bonsai.core.tool.Model):
return "PROFILE"
@classmethod
def get_wall_axis(cls, obj, layers=None):
def get_wall_axis(cls, obj: bpy.types.Object, layers: Optional[dict[str, Any]] = None) -> dict[str, Any]:
x_values = [v[0] for v in obj.bound_box]
min_x = min(x_values)
max_x = max(x_values)
@@ -602,7 +617,9 @@ class Model(bonsai.core.tool.Model):
return axes
@classmethod
def handle_array_on_copied_element(cls, element, array_data=None):
def handle_array_on_copied_element(
cls, element: ifcopenshell.entity_instance, array_data: Optional[dict[str, Any]] = None
) -> None:
"""if no `array_data` is provided then an array will be removed from the element"""
if array_data is None:
@@ -647,7 +664,9 @@ class Model(bonsai.core.tool.Model):
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
@classmethod
def regenerate_array(cls, parent_obj, data, array_layers_to_apply=tuple()):
def regenerate_array(
cls, parent_obj: bpy.types.Object, data: list[dict[str, Any]], array_layers_to_apply: Iterable[int] = tuple()
) -> None:
"""`array_layers_to_apply` - list of array layer indices to apply"""
tool.Blender.Modifier.Array.remove_constraints(tool.Ifc.get_entity(parent_obj))
@@ -744,7 +763,12 @@ class Model(bonsai.core.tool.Model):
bpy.context.view_layer.update()
@classmethod
def replace_object_ifc_representation(cls, ifc_context, obj, new_representation):
def replace_object_ifc_representation(
cls,
ifc_context: ifcopenshell.entity_instance,
obj: bpy.types.Object,
new_representation: ifcopenshell.entity_instance,
) -> None:
ifc_file = tool.Ifc.get()
ifc_element = tool.Ifc.get_entity(obj)
old_representation = ifcopenshell.util.representation.get_representation(
@@ -771,7 +795,7 @@ class Model(bonsai.core.tool.Model):
)
@classmethod
def update_thumbnail_for_element(cls, element, refresh=False):
def update_thumbnail_for_element(cls, element: ifcopenshell.entity_instance, refresh: bool = False) -> None:
if bpy.app.background:
return
@@ -877,7 +901,7 @@ class Model(bonsai.core.tool.Model):
)
@classmethod
def get_modeling_bbim_pset_data(cls, object, pset_name):
def get_modeling_bbim_pset_data(cls, object: bpy.types.Object, pset_name: str) -> Union[dict[str, Any], None]:
"""get modelling BBIM pset data (eg, BBIM_Roof) and loads it's `Data` as json to `data_dict`"""
element = tool.Ifc.get_entity(object)
if not element:
@@ -890,7 +914,7 @@ class Model(bonsai.core.tool.Model):
return pset_data
@classmethod
def edit_element_placement(cls, element, matrix):
def edit_element_placement(cls, element: ifcopenshell.entity_instance, matrix: Matrix) -> None:
"""Useful for moving objects like ports or openings -
the method will ensure it will be moved in blender scene too if it exists"""
obj = tool.Ifc.get_object(element)
@@ -900,13 +924,13 @@ class Model(bonsai.core.tool.Model):
tool.Ifc.run("geometry.edit_object_placement", product=element, matrix=matrix, is_si=True)
@classmethod
def sync_object_ifc_position(cls, obj):
def sync_object_ifc_position(cls, obj: bpy.types.Object) -> None:
"""make sure IFC position will be in sync with the Blender object position, if object was moved in Blender"""
if tool.Ifc.is_moved(obj):
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
@classmethod
def get_element_matrix(cls, element, keep_local=False):
def get_element_matrix(cls, element: ifcopenshell.entity_instance, keep_local: bool = False) -> Matrix:
placement = element.ObjectPlacement
if keep_local:
placement = ifcopenshell.util.placement.get_axis2placement(placement.RelativePlacement)
@@ -947,23 +971,23 @@ class Model(bonsai.core.tool.Model):
)
@classmethod
def is_parametric_roof_active(cls):
return (RoofData.is_loaded or not RoofData.load()) and RoofData.data["pset_data"]
def is_parametric_roof_active(cls) -> bool:
return bool((RoofData.is_loaded or not RoofData.load()) and RoofData.data["pset_data"])
@classmethod
def is_parametric_railing_active(cls):
return (RailingData.is_loaded or not RailingData.load()) and RailingData.data["pset_data"]
def is_parametric_railing_active(cls) -> bool:
return bool((RailingData.is_loaded or not RailingData.load()) and RailingData.data["pset_data"])
@classmethod
def is_parametric_window_active(cls):
return (WindowData.is_loaded or not WindowData.load()) and WindowData.data["pset_data"]
def is_parametric_window_active(cls) -> bool:
return bool((WindowData.is_loaded or not WindowData.load()) and WindowData.data["pset_data"])
@classmethod
def is_parametric_door_active(cls):
return (DoorData.is_loaded or not DoorData.load()) and DoorData.data["pset_data"]
def is_parametric_door_active(cls) -> bool:
return bool((DoorData.is_loaded or not DoorData.load()) and DoorData.data["pset_data"])
@classmethod
def get_active_stair_calculated_params(cls, pset_data=None):
def get_active_stair_calculated_params(cls, pset_data: Optional[dict[str, Any]] = None) -> dict[str, Any]:
props = bpy.context.active_object.BIMStairProperties
if props.is_editing: