From ef24bb52a7b474438cc891c5a7d2cf47171f3cb9 Mon Sep 17 00:00:00 2001 From: Robin Quint Date: Tue, 19 Aug 2025 15:53:45 +0200 Subject: [PATCH] Make Door / Window Tool place doors on the correct side of a wall (#6986) --- src/bonsai/bonsai/bim/module/model/opening.py | 55 +++++++++++++++++-- .../bonsai/bim/module/model/polyline.py | 17 +++++- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/opening.py b/src/bonsai/bonsai/bim/module/model/opening.py index 54e2228248..32acaae5a5 100644 --- a/src/bonsai/bonsai/bim/module/model/opening.py +++ b/src/bonsai/bonsai/bim/module/model/opening.py @@ -40,7 +40,7 @@ import bonsai.core.geometry import bonsai.bim.import_ifc as import_ifc from collections import defaultdict from math import pi, radians -from mathutils import Vector, Matrix +from mathutils import Vector, Matrix, Euler from bpy.types import Operator from bpy.types import SpaceView3D from bpy.props import FloatProperty @@ -92,11 +92,19 @@ class FilledOpeningGenerator: layers = tool.Model.get_material_layer_parameters(element) if layers["layer_set_direction"] == "AXIS2": opening_thickness_si = layers["thickness"] * 2 - axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"] + axes = tool.Model.get_wall_axis(voided_obj, layers=layers) + axis_base = axes["base"] + axis_side = axes["side"] new_matrix = voided_obj.matrix_world.copy() - point_on_axis = tool.Cad.point_on_edge(target, axis) - new_matrix.translation.x = point_on_axis.x - new_matrix.translation.y = point_on_axis.y + point_on_base_axis = tool.Cad.point_on_edge(target, axis_base) + point_on_side_axis = tool.Cad.point_on_edge(target, axis_side) + if (point_on_base_axis - target).length <= (point_on_side_axis - target).length: + new_matrix.translation.x = point_on_base_axis.x + new_matrix.translation.y = point_on_base_axis.y + else: + new_matrix.translation.x = point_on_side_axis.x + new_matrix.translation.y = point_on_side_axis.y + new_matrix = new_matrix @ Matrix.Rotation(radians(180.0), 4, "Z") if should_set_z_level: if filling.is_a("IfcDoor"): @@ -433,7 +441,44 @@ class FlipFill(bpy.types.Operator, tool.Ifc.Operator): element = tool.Ifc.get_entity(obj) if not element or not element.FillsVoids: continue + + filled_opening = element.FillsVoids[0].RelatingOpeningElement + filled_element = filled_opening.VoidsElements[0].RelatingBuildingElement + filled_object = tool.Ifc.get_object(filled_element) + + if filled_element.is_a() in ["IfcWall", "IfcWallStandardCase"]: + # if the filled element is a wall, move the filling in such a way + # that it will have the same relative position, but to the other + # side of the wall + # + # For example, if a door frame protudes 1cm out of the wall, + # it will produde 1cm out of the other side of the wall. + + layers = tool.Model.get_material_layer_parameters(filled_element) + axes = tool.Model.get_wall_axis(filled_object, layers=layers) + + center_axis = [(axes["base"][0] + axes["side"][0]) * 0.5, (axes["base"][1] + axes["side"][1]) * 0.5] + + original_pos = obj.matrix_world.translation + bb = tool.Blender.get_object_bounding_box(obj) + min_y = min(bb["min_y"], 0) + max_y = max(bb["max_y"], 0) + + point_on_center_axis = tool.Cad.point_on_edge(original_pos, center_axis) + offset_to_center_axis = point_on_center_axis - original_pos + offset_to_center_axis.z = 0 + depth_offset = max_y + min_y + depth_correction_vec = offset_to_center_axis.normalized() * depth_offset + + mirrored_point = original_pos + offset_to_center_axis * 2.0 - depth_correction_vec + + obj.matrix_world.translation = mirrored_point + bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj) + tool.Geometry.flip_object(obj, "XY") + ifcopenshell.api.geometry.edit_object_placement(tool.Ifc.get(), filled_opening, obj.matrix_world) + tool.Geometry.reload_representation(filled_object) + return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/model/polyline.py b/src/bonsai/bonsai/bim/module/model/polyline.py index 0d5314aba9..29b0a5c4c3 100644 --- a/src/bonsai/bonsai/bim/module/model/polyline.py +++ b/src/bonsai/bonsai/bim/module/model/polyline.py @@ -541,8 +541,21 @@ def get_generic_product_preview_data(context, relating_type): snap_obj = bpy.data.objects.get(snap_prop.snap_object) snap_element = tool.Ifc.get_entity(snap_obj) rot_mat = Quaternion() - if snap_element and snap_element.is_a("IfcWall"): - rot_mat = snap_obj.matrix_world.to_quaternion() + if relating_type.is_a() in ["IfcDoorType", "IfcWindowType"] and snap_element and snap_element.is_a("IfcWall"): + layers = tool.Model.get_material_layer_parameters(snap_element) + axes = tool.Model.get_wall_axis(snap_obj, layers=layers) + axis_base = axes["base"] + axis_side = axes["side"] + point_on_base_axis = tool.Cad.point_on_edge(mouse_point, axis_base) + point_on_side_axis = tool.Cad.point_on_edge(mouse_point, axis_side) + if (point_on_base_axis - mouse_point).length_squared <= (point_on_side_axis - mouse_point).length_squared: + # mouse is snapped to the base axis, the preview looks exactly like the placed door / window + rot_mat = snap_obj.matrix_world.to_quaternion() + else: + # mouse is snapped to the side axis, the preview is inverted, rotate it now and correct x position later + rot_mat = snap_obj.matrix_world.to_quaternion() @ Quaternion(Vector((0, 0, 1)), radians(180)) + + mouse_point.z = snap_obj.matrix_world.translation.z obj_type = tool.Ifc.get_object(relating_type) if obj_type.data: