From c2684f4fbf84cd561fb91b39a9b2574ff4b60164 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 27 May 2021 16:33:34 +1000 Subject: [PATCH] New split wall tool --- src/blenderbim/blenderbim/bim/handler.py | 18 ++-- .../blenderbim/bim/module/model/__init__.py | 1 + .../blenderbim/bim/module/model/operator.py | 101 ++++++++++++++++-- .../blenderbim/bim/module/model/ui.py | 1 + .../blenderbim/bim/module/model/workspace.py | 1 + 5 files changed, 105 insertions(+), 17 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/handler.py b/src/blenderbim/blenderbim/bim/handler.py index 9720353ce6..cb15a0ab81 100644 --- a/src/blenderbim/blenderbim/bim/handler.py +++ b/src/blenderbim/blenderbim/bim/handler.py @@ -55,6 +55,15 @@ def name_callback(obj, data): AttributeData.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id) +def active_object_callback(): + obj = bpy.context.active_object + for obj in bpy.context.selected_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + if IfcStore.id_map[obj.BIMObjectProperties.ifc_definition_id] != obj: + bpy.ops.bim.copy_class(obj=obj.name) + + def subscribe_to(object, data_path, callback): subscribe_to = object.path_resolve(data_path, False) bpy.msgbus.subscribe_rna( @@ -176,15 +185,6 @@ def create_application_organisation(ifc): ) -def active_object_callback(): - obj = bpy.context.active_object - for obj in bpy.context.selected_objects: - if not obj.BIMObjectProperties.ifc_definition_id: - continue - if IfcStore.id_map[obj.BIMObjectProperties.ifc_definition_id] != obj: - bpy.ops.bim.copy_class(obj=obj.name) - - @persistent def setDefaultProperties(scene): global global_subscription_owner diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py index 56555ac77e..a0328588ae 100644 --- a/src/blenderbim/blenderbim/bim/module/model/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py @@ -7,6 +7,7 @@ classes = ( operator.JoinWall, operator.AlignWall, operator.FlipWall, + operator.SplitWall, prop.BIMModelProperties, ui.BIM_PT_authoring, ui.BIM_PT_authoring_architectural, diff --git a/src/blenderbim/blenderbim/bim/module/model/operator.py b/src/blenderbim/blenderbim/bim/module/model/operator.py index 8ffcfb69de..f102f68b5f 100644 --- a/src/blenderbim/blenderbim/bim/module/model/operator.py +++ b/src/blenderbim/blenderbim/bim/module/model/operator.py @@ -1,11 +1,12 @@ import bpy +import bmesh import math import ifcopenshell import ifcopenshell.util.type import ifcopenshell.util.unit import mathutils.geometry from blenderbim.bim.ifc import IfcStore -from math import pi +from math import pi, degrees from mathutils import Vector, Matrix from ifcopenshell.api.material.data import Data as MaterialData @@ -144,6 +145,20 @@ class FlipWall(bpy.types.Operator): return {"FINISHED"} +class SplitWall(bpy.types.Operator): + bl_idname = "bim.split_wall" + bl_label = "Split Wall" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + selected_objs = context.selected_objects + if len(selected_objs) == 0: + return {"FINISHED"} + for obj in selected_objs: + DumbWallSplitter(obj, bpy.context.scene.cursor.location).split() + return {"FINISHED"} + + def recalculate_dumb_wall_origin(wall, new_origin=None): if new_origin is None: new_origin = wall.matrix_world @ Vector(wall.bound_box[0]) @@ -157,6 +172,68 @@ def recalculate_dumb_wall_origin(wall, new_origin=None): wall.matrix_world.translation = new_origin +class DumbWallSplitter: + def __init__(self, wall, point): + self.wall = wall + self.point = point + + def split(self): + recalculate_dumb_wall_origin(self.wall) + self.point = self.determine_split_point() + if not self.point: + return + new_wall = self.duplicate_wall() + self.snap_end_face_to_point(self.wall, "max") + self.snap_end_face_to_point(new_wall, "min") + + def determine_split_point(self): + start = self.wall.matrix_world @ Vector(self.wall.bound_box[0]) + end = self.wall.matrix_world @ Vector(self.wall.bound_box[4]) + point, distance = mathutils.geometry.intersect_point_line(self.point, start, end) + if round(distance, 2) <= 0 or round(distance, 2) >= 1: + return # The split point is not on the wall + return point + + def duplicate_wall(self): + new = self.wall.copy() + self.wall.users_collection[0].objects.link(new) + bpy.ops.bim.copy_class(obj=new.name) + return new + + def snap_end_face_to_point(self, wall, which_end): + bm = bmesh.new() + bm.from_mesh(wall.data) + bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) + min_face, max_face = self.get_wall_end_faces(wall, bm) + face = min_face if which_end == "min" else max_face + local_point = wall.matrix_world.inverted() @ self.point + for vert in face.verts: + vert.co.x = local_point.x + bm.to_mesh(wall.data) + wall.data.update() + bm.free() + IfcStore.edited_objs.add(wall) + + # An end face is a quad that is on one end of the wall or the other. It must + # have at least one vertex on either extreme X-axis, and a non-insignificant + # X component of its face normal + def get_wall_end_faces(self, wall, bm): + min_face = None + max_face = None + min_x = min([v[0] for v in wall.bound_box]) + max_x = max([v[0] for v in wall.bound_box]) + bm.faces.ensure_lookup_table() + for f in bm.faces: + for v in f.verts: + if v.co.x == min_x and abs(f.normal.x) > 0.1: + min_face = f + elif v.co.x == max_x and abs(f.normal.x) > 0.1: + max_face = f + if min_face and max_face: + break + return min_face, max_face + + class DumbWallFlipper: # A flip switches the origin from the min XY corner to the max XY corner, and rotates the origin by 180. def __init__(self, wall): @@ -194,7 +271,7 @@ class DumbWallAligner: reference_width = (Vector(self.reference_wall.bound_box[3]) - Vector(self.reference_wall.bound_box[0])).y if self.is_rotation_flipped(): - offset = self.wall.matrix_world.to_quaternion() @ Vector((0, - (reference_width / 2) - (width / 2), 0)) + offset = self.wall.matrix_world.to_quaternion() @ Vector((0, -(reference_width / 2) - (width / 2), 0)) else: offset = self.wall.matrix_world.to_quaternion() @ Vector((0, (reference_width / 2) - (width / 2), 0)) @@ -243,14 +320,22 @@ class DumbWallAligner: self.wall.matrix_world.translation[1] = new_origin[1] def align_rotation(self): - reference_rotation = self.reference_wall.rotation_euler[2] % (2 * pi) - self.wall.rotation_euler[2] = (pi * round(self.wall.rotation_euler[2] / pi)) + reference_rotation + 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() + angle = reference.angle_signed(wall) + if round(degrees(angle) % 360) in (0, 180): + return + elif angle > (pi / 2): + self.wall.rotation_euler[2] -= pi - angle + else: + self.wall.rotation_euler[2] += angle + bpy.context.view_layer.update() def is_rotation_flipped(self): - pi2 = pi * 2 - wall_delta_to_nearest_360 = abs((pi2 * round(self.wall.rotation_euler[2] / pi2)) - self.wall.rotation_euler[2]) - reference_delta_to_nearest_360 = abs((pi2 * round(self.reference_wall.rotation_euler[2] / pi2)) - self.reference_wall.rotation_euler[2]) - return abs(reference_delta_to_nearest_360 - wall_delta_to_nearest_360) > 0.1 + 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() + angle = reference.angle_signed(wall) + return round(degrees(angle) % 360) == 180 class DumbWallJoiner: diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index c5ea06053f..0eae3f114e 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -38,6 +38,7 @@ class BIM_PT_authoring_architectural(Panel): row.operator("bim.align_wall", icon="ANCHOR_BOTTOM", text="Int.").align_type = "INTERIOR" row = self.layout.row(align=True) row.operator("bim.flip_wall", icon="ORIENTATION_NORMAL", text="Flip") + row.operator("bim.split_wall", icon="MOD_PHYSICS", text="Split") class BIM_PT_misc_utilities(Panel): diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index fdc39bad02..a29fa7fceb 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -20,6 +20,7 @@ class WallTool(WorkSpaceTool): ("bim.join_wall", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("join_type", "L")]}), ("bim.join_wall", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("join_type", "V")]}), ("bim.flip_wall", {"type": "F", "value": "PRESS", "shift": True}, {"properties": []}), + ("bim.split_wall", {"type": "S", "value": "PRESS", "shift": True}, {"properties": []}), ( "bim.align_wall", {"type": "X", "value": "PRESS", "shift": True},