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