This commit is contained in:
Andrej730
2024-10-22 11:13:59 +05:00
parent 9eaecdb0a4
commit 71c5e83fa0
10 changed files with 60 additions and 27 deletions
@@ -45,8 +45,8 @@ class CoveringTool(WorkSpaceTool):
CoveringToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type) CoveringToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
def add_layout_hotkey(layout, text, hotkey, description): def add_layout_hotkey(layout: bpy.types.UILayout, text: str, hotkey: str, description: str) -> None:
args = ["covering", layout, text, hotkey, description] args = ("covering", layout, text, hotkey, description)
tool.Blender.add_layout_hotkey_operator(*args) tool.Blender.add_layout_hotkey_operator(*args)
@@ -19,6 +19,9 @@
import bpy import bpy
import numpy as np import numpy as np
import ifcopenshell import ifcopenshell
import ifcopenshell.util.geolocation
import ifcopenshell.util.placement
import ifcopenshell.util.unit
import bonsai.tool as tool import bonsai.tool as tool
import bonsai.core.misc as core import bonsai.core.misc as core
import bonsai.core.geometry as core_geometry import bonsai.core.geometry as core_geometry
@@ -519,7 +519,7 @@ class AlignProduct(bpy.types.Operator):
obj.matrix_world = Matrix.Translation(active_y_axis * -y_distances[i]) @ obj.matrix_world obj.matrix_world = Matrix.Translation(active_y_axis * -y_distances[i]) @ obj.matrix_world
return {"FINISHED"} return {"FINISHED"}
def get_axis_distances(self, point, axis, context): def get_axis_distances(self, point: Vector, axis: Vector, context: bpy.types.Context) -> list[float]:
results = [] results = []
for obj in context.selected_objects: for obj in context.selected_objects:
if self.align_type == "CENTERLINE": if self.align_type == "CENTERLINE":
+8 -8
View File
@@ -412,11 +412,11 @@ class DumbWallAligner:
# An alignment shifts the origin of all walls to the closest point on the # An alignment shifts the origin of all walls to the closest point on the
# local X axis of the reference wall. In addition, the Z rotation is copied. # local X axis of the reference wall. In addition, the Z rotation is copied.
# Z translations are ignored for alignment. # Z translations are ignored for alignment.
def __init__(self, wall, reference_wall): def __init__(self, wall: bpy.types.Object, reference_wall: bpy.types.Object):
self.wall = wall self.wall = wall
self.reference_wall = reference_wall self.reference_wall = reference_wall
def align_centerline(self): def align_centerline(self) -> None:
self.align_rotation() self.align_rotation()
l_start = Vector(self.reference_wall.bound_box[0]).lerp(Vector(self.reference_wall.bound_box[3]), 0.5) l_start = Vector(self.reference_wall.bound_box[0]).lerp(Vector(self.reference_wall.bound_box[3]), 0.5)
@@ -434,7 +434,7 @@ class DumbWallAligner:
new_origin = point - offset new_origin = point - offset
self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy
def align_last_layer(self): def align_last_layer(self) -> None:
self.align_rotation() self.align_rotation()
if self.is_rotation_flipped(): if self.is_rotation_flipped():
@@ -457,7 +457,7 @@ class DumbWallAligner:
new_origin = point - offset new_origin = point - offset
self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy
def align_first_layer(self): def align_first_layer(self) -> None:
self.align_rotation() self.align_rotation()
if self.is_rotation_flipped(): if self.is_rotation_flipped():
@@ -480,7 +480,7 @@ class DumbWallAligner:
new_origin = point - offset new_origin = point - offset
self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy self.wall.matrix_world.translation[0], self.wall.matrix_world.translation[1] = new_origin.xy
def align_rotation(self): def align_rotation(self) -> None:
reference = (self.reference_wall.matrix_world.to_quaternion() @ Vector((1, 0, 0))).to_2d() reference = (self.reference_wall.matrix_world.to_quaternion() @ Vector((1, 0, 0))).to_2d()
wall = (self.wall.matrix_world.to_quaternion() @ Vector((1, 0, 0))).to_2d() wall = (self.wall.matrix_world.to_quaternion() @ Vector((1, 0, 0))).to_2d()
angle = reference.angle_signed(wall) angle = reference.angle_signed(wall)
@@ -492,7 +492,7 @@ class DumbWallAligner:
self.wall.rotation_euler[2] += angle self.wall.rotation_euler[2] += angle
bpy.context.view_layer.update() bpy.context.view_layer.update()
def is_rotation_flipped(self): def is_rotation_flipped(self) -> bool:
reference = (self.reference_wall.matrix_world.to_quaternion() @ Vector((1, 0, 0))).to_2d() reference = (self.reference_wall.matrix_world.to_quaternion() @ Vector((1, 0, 0))).to_2d()
wall = (self.wall.matrix_world.to_quaternion() @ Vector((1, 0, 0))).to_2d() wall = (self.wall.matrix_world.to_quaternion() @ Vector((1, 0, 0))).to_2d()
angle = reference.angle_signed(wall) angle = reference.angle_signed(wall)
@@ -835,7 +835,7 @@ class DumbWallJoiner:
body = copy.deepcopy(axis1["reference"]) body = copy.deepcopy(axis1["reference"])
self.recreate_wall(element1, wall1, axis, body) self.recreate_wall(element1, wall1, axis, body)
def split(self, wall1, target): def split(self, wall1: bpy.types.Object, target: Vector) -> None:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
element1 = tool.Ifc.get_entity(wall1) element1 = tool.Ifc.get_entity(wall1)
@@ -895,7 +895,7 @@ class DumbWallJoiner:
self.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"]) self.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"])
self.recreate_wall(element2, wall2, axis2["reference"], axis2["reference"]) self.recreate_wall(element2, wall2, axis2["reference"], axis2["reference"])
def flip(self, wall1): def flip(self, wall1: bpy.types.Object) -> None:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
if tool.Ifc.is_moved(wall1): if tool.Ifc.is_moved(wall1):
@@ -49,8 +49,8 @@ class SpatialTool(WorkSpaceTool):
SpatialToolUI.draw(context, layout) SpatialToolUI.draw(context, layout)
def add_layout_hotkey(layout, text, hotkey, description): def add_layout_hotkey(layout: bpy.types.UILayout, text: str, hotkey: str, description: str) -> None:
args = ["spatial", layout, text, hotkey, description] args = ("spatial", layout, text, hotkey, description)
tool.Blender.add_layout_hotkey_operator(*args) tool.Blender.add_layout_hotkey_operator(*args)
@@ -41,8 +41,8 @@ class StructuralTool(WorkSpaceTool):
StructuralToolUI.draw(context, layout) StructuralToolUI.draw(context, layout)
def add_layout_hotkey(layout, text, hotkey, description): def add_layout_hotkey(layout: bpy.types.UILayout, text: str, hotkey: str, description: str) -> None:
args = ["structural", layout, text, hotkey, description] args = ("structural", layout, text, hotkey, description)
tool.Blender.add_layout_hotkey_operator(*args) tool.Blender.add_layout_hotkey_operator(*args)
+9 -1
View File
@@ -16,8 +16,16 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
def resize_to_storey(misc, obj=None, total_storeys=None): if TYPE_CHECKING:
import bpy
import ifcopenshell
import bonsai.tool as tool
def resize_to_storey(misc: tool.Misc, obj: bpy.types.Object, total_storeys: int) -> None:
storey = misc.get_object_storey(obj) storey = misc.get_object_storey(obj)
if not storey: if not storey:
return return
+18 -9
View File
@@ -19,28 +19,33 @@
import bpy import bpy
import bmesh import bmesh
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.unit
import bonsai.core.tool import bonsai.core.tool
import bonsai.core.root import bonsai.core.root
import bonsai.tool as tool import bonsai.tool as tool
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from bonsai.bim.ifc import IfcStore from typing import Union
class Misc(bonsai.core.tool.Misc): class Misc(bonsai.core.tool.Misc):
@classmethod @classmethod
def get_object_storey(cls, obj): def get_object_storey(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
storey = ifcopenshell.util.element.get_container(tool.Ifc.get_entity(obj)) element = tool.Ifc.get_entity(obj)
assert element
storey = ifcopenshell.util.element.get_container(element)
if storey and storey.is_a("IfcBuildingStorey"): if storey and storey.is_a("IfcBuildingStorey"):
return storey return storey
@classmethod @classmethod
def get_storey_elevation_in_si(cls, storey): def get_storey_elevation_in_si(cls, storey: ifcopenshell.entity_instance) -> float:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
elevation = ifcopenshell.util.placement.get_storey_elevation(storey) elevation = ifcopenshell.util.placement.get_storey_elevation(storey)
return elevation * unit_scale return elevation * unit_scale
@classmethod @classmethod
def get_storey_height_in_si(cls, storey, total_storeys): def get_storey_height_in_si(cls, storey: ifcopenshell.entity_instance, total_storeys: int) -> Union[float, None]:
building = ifcopenshell.util.element.get_aggregate(storey) building = ifcopenshell.util.element.get_aggregate(storey)
related_objects = [] related_objects = []
for rel in building.IsDecomposedBy: for rel in building.IsDecomposedBy:
@@ -60,11 +65,12 @@ class Misc(bonsai.core.tool.Misc):
return (next_storey_elevation - storey_elevation) * unit_scale return (next_storey_elevation - storey_elevation) * unit_scale
@classmethod @classmethod
def set_object_origin_to_bottom(cls, obj): def set_object_origin_to_bottom(cls, obj: bpy.types.Object) -> None:
absolute_bound_box = [obj.matrix_world @ Vector(c) for c in obj.bound_box] absolute_bound_box = [obj.matrix_world @ Vector(c) for c in obj.bound_box]
min_z = min([c[2] for c in absolute_bound_box]) min_z = min([c[2] for c in absolute_bound_box])
new_origin = obj.matrix_world.translation.copy() new_origin = obj.matrix_world.translation.copy()
new_origin[2] = min_z new_origin[2] = min_z
assert isinstance(obj.data, bpy.types.Mesh)
obj.data.transform( obj.data.transform(
Matrix.Translation( Matrix.Translation(
(obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin)) (obj.matrix_world.inverted().to_quaternion() @ (obj.matrix_world.translation - new_origin))
@@ -73,15 +79,15 @@ class Misc(bonsai.core.tool.Misc):
obj.matrix_world.translation = new_origin obj.matrix_world.translation = new_origin
@classmethod @classmethod
def move_object_to_elevation(cls, obj, elevation): def move_object_to_elevation(cls, obj: bpy.types.Object, elevation: float) -> None:
obj.matrix_world.translation[2] = elevation obj.matrix_world.translation[2] = elevation
@classmethod @classmethod
def run_root_copy_class(cls, obj=None): def run_root_copy_class(cls, obj: bpy.types.Object) -> ifcopenshell.entity_instance:
return bonsai.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj) return bonsai.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
@classmethod @classmethod
def scale_object_to_height(cls, obj, height): def scale_object_to_height(cls, obj: bpy.types.Object, height: float) -> None:
absolute_bound_box = [obj.matrix_world @ Vector(c) for c in obj.bound_box] absolute_bound_box = [obj.matrix_world @ Vector(c) for c in obj.bound_box]
max_z = max([c[2] for c in absolute_bound_box]) max_z = max([c[2] for c in absolute_bound_box])
min_z = min([c[2] for c in absolute_bound_box]) min_z = min([c[2] for c in absolute_bound_box])
@@ -101,6 +107,7 @@ class Misc(bonsai.core.tool.Misc):
cls, objs: list[bpy.types.Object], cutter: bpy.types.Object cls, objs: list[bpy.types.Object], cutter: bpy.types.Object
) -> list[bpy.types.Object]: ) -> list[bpy.types.Object]:
cutter_mesh = cutter.data cutter_mesh = cutter.data
assert isinstance(cutter_mesh, bpy.types.Mesh)
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(cutter_mesh) bm.from_mesh(cutter_mesh)
@@ -119,6 +126,7 @@ class Misc(bonsai.core.tool.Misc):
collection.objects.link(new_obj) collection.objects.link(new_obj)
mod = new_obj.modifiers.new(type="BOOLEAN", name="Boolean") mod = new_obj.modifiers.new(type="BOOLEAN", name="Boolean")
assert isinstance(mod, bpy.types.BooleanModifier)
mod.object = cutter mod.object = cutter
with bpy.context.temp_override(object=new_obj): with bpy.context.temp_override(object=new_obj):
bpy.ops.object.modifier_apply(modifier="Boolean") bpy.ops.object.modifier_apply(modifier="Boolean")
@@ -126,6 +134,7 @@ class Misc(bonsai.core.tool.Misc):
bm_flipped.to_mesh(cutter_mesh) bm_flipped.to_mesh(cutter_mesh)
mod = obj.modifiers.new(type="BOOLEAN", name="Boolean") mod = obj.modifiers.new(type="BOOLEAN", name="Boolean")
assert isinstance(mod, bpy.types.BooleanModifier)
mod.object = cutter mod.object = cutter
with bpy.context.temp_override(object=obj): with bpy.context.temp_override(object=obj):
bpy.ops.object.modifier_apply(modifier="Boolean") bpy.ops.object.modifier_apply(modifier="Boolean")
+13
View File
@@ -762,6 +762,12 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod @classmethod
def get_x_y_z_h_mat_from_obj(cls, obj: bpy.types.Object) -> tuple[float, float, float, float, Matrix]: def get_x_y_z_h_mat_from_obj(cls, obj: bpy.types.Object) -> tuple[float, float, float, float, Matrix]:
"""
`x`, `y` - object's center XY in world space;\n
`z` - object's local Z- in world space;\n
`h` - object's Z dimension;\n
`mat` - object's matrix
"""
mat = obj.matrix_world mat = obj.matrix_world
local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector()) local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector())
global_bbox_center = mat @ local_bbox_center global_bbox_center = mat @ local_bbox_center
@@ -774,6 +780,12 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod @classmethod
def get_x_y_z_h_mat_from_cursor(cls) -> tuple[float, float, float, float, Matrix]: def get_x_y_z_h_mat_from_cursor(cls) -> tuple[float, float, float, float, Matrix]:
"""
`x`, `y` - from cursor;\n
`z` - from default container Z location (if set);\n
`h` - default value of 3;\n
`mat` - identity matrix
"""
x, y, z = bpy.context.scene.cursor.location.xyz x, y, z = bpy.context.scene.cursor.location.xyz
if container := tool.Root.get_default_container(): if container := tool.Root.get_default_container():
if container_obj := tool.Ifc.get_object(container): if container_obj := tool.Ifc.get_object(container):
@@ -940,6 +952,7 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod @classmethod
def edit_active_space_obj_from_mesh(cls, mesh: bpy.types.Mesh) -> None: def edit_active_space_obj_from_mesh(cls, mesh: bpy.types.Mesh) -> None:
active_obj = bpy.context.active_object active_obj = bpy.context.active_object
assert active_obj and isinstance(active_obj.data, bpy.types.Mesh)
mesh.name = active_obj.data.name mesh.name = active_obj.data.name
mesh.BIMMeshProperties.ifc_definition_id = active_obj.data.BIMMeshProperties.ifc_definition_id mesh.BIMMeshProperties.ifc_definition_id = active_obj.data.BIMMeshProperties.ifc_definition_id
tool.Geometry.change_object_data(active_obj, mesh, is_global=True) tool.Geometry.change_object_data(active_obj, mesh, is_global=True)
+2 -2
View File
@@ -50,8 +50,8 @@ HEADER_FIELDS = {
class Transaction: class Transaction:
def __init__(self, ifc_file): def __init__(self, ifc_file: file):
self.file = ifc_file self.file: file = ifc_file
self.operations = [] self.operations = []
self.is_batched = False self.is_batched = False
self.batch_delete_index = 0 self.batch_delete_index = 0