From c72f17068c2fc8431d94312a759f0f15356bc890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Mon, 20 Jan 2025 19:00:44 -0300 Subject: [PATCH] Add support for direction sense and offset in slabs with `x_angle` --- src/bonsai/bonsai/bim/module/model/slab.py | 18 ++++++-- src/bonsai/bonsai/bim/module/model/wall.py | 46 ++++++++++++++++--- src/bonsai/bonsai/tool/model.py | 17 ++++++- .../api/geometry/add_slab_representation.py | 6 +++ 4 files changed, 74 insertions(+), 13 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/slab.py b/src/bonsai/bonsai/bim/module/model/slab.py index 29ba2211c8..335320d5e9 100644 --- a/src/bonsai/bonsai/bim/module/model/slab.py +++ b/src/bonsai/bonsai/bim/module/model/slab.py @@ -30,6 +30,7 @@ import bonsai.core.type import bonsai.core.geometry import bonsai.core.root import bonsai.tool as tool +from math import cos, radians from mathutils import Vector, Matrix from bonsai.bim.module.geometry.helper import Helper from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator @@ -134,7 +135,7 @@ class DumbSlabGenerator: matrix_world.translation.z = self.container_obj.location.z else: matrix_world.translation.z - obj.matrix_world = Matrix.Rotation(self.x_angle, 4, "X") @ matrix_world + obj.matrix_world = matrix_world @ Matrix.Rotation(self.x_angle, 4, "X") bpy.context.view_layer.update() element = bonsai.core.root.assign_class( @@ -305,13 +306,15 @@ class DumbSlabPlaner: extrusion = tool.Model.get_extrusion(representation) if extrusion: x, y, z = extrusion.ExtrudedDirection.DirectionRatios + existing_x_angle = tool.Model.get_existing_x_angle(extrusion) + perpendicular_depth = thickness * (1 / cos(existing_x_angle)) offset = layer_params["offset"] offset_vector = Vector((0.0, 0.0, offset / self.unit_scale)) if layer_params["direction_sense"] == "POSITIVE": - y = abs(y) + y = abs(y) if existing_x_angle > 0 else -abs(y) z = abs(z) - if layer_params["direction_sense"] == "NEGATIVE": - y = -abs(y) + elif layer_params["direction_sense"] == "NEGATIVE": + y = -abs(y) if existing_x_angle > 0 else abs(y) z = -abs(z) offset_vector = -offset_vector extrusion.ExtrudedDirection.DirectionRatios = (x, y, z) @@ -319,8 +322,12 @@ class DumbSlabPlaner: if offset != 0.0 and not extrusion.Position: tool.Model.add_extrusion_position(extrusion, offset) + + # Update the extrusion's location based on its current rotation angle and offset if extrusion.Position: - extrusion.Position.Location.Coordinates = tuple(offset_vector) + rot_matrix = Matrix.Rotation(existing_x_angle, 4, "X") + rot_offset = offset_vector @ rot_matrix + extrusion.Position.Location.Coordinates = tuple(rot_offset) else: @@ -373,6 +380,7 @@ class DumbSlabPlaner: ) + class EnableEditingSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.enable_editing_sketch_extrusion_profile" bl_label = "Enable Editing Sketch Extrusion Profile" diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index dc1887d2cc..5d1608196c 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -36,7 +36,7 @@ import bonsai.core.geometry import bonsai.core.model as core import bonsai.tool as tool from bonsai.bim.ifc import IfcStore -from math import pi, sin, cos, degrees +from math import pi, sin, cos, degrees, radians from mathutils import Vector, Matrix from bonsai.bim.module.model.opening import FilledOpeningGenerator from bonsai.bim.module.model.decorator import PolylineDecorator, ProductDecorator @@ -235,13 +235,43 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator): if not extrusion: return x, y, z = extrusion.ExtrudedDirection.DirectionRatios - existing_x_angle = Vector((0, 1)).angle_signed(Vector((y, z))) + existing_x_angle = tool.Model.get_existing_x_angle(extrusion) perpendicular_depth = extrusion.Depth / (1 / cos(existing_x_angle)) extrusion.Depth = perpendicular_depth * (1 / cos(x_angle)) extrusion.ExtrudedDirection.DirectionRatios = (0.0, sin(x_angle), cos(x_angle)) if tool.Model.get_usage_type(element) == "LAYER2": layer2_objs.append(obj) else: + if tool.Model.get_usage_type(element) == "LAYER3": + # Reset the transformation and returns to the original points with 0 degrees + extrusion.SweptArea.OuterCurve.Points.CoordList = [(p[0], p[1] * (cos(existing_x_angle))) for p in extrusion.SweptArea.OuterCurve.Points.CoordList] + + # Apply the transformation for the new x_angle + extrusion.SweptArea.OuterCurve.Points.CoordList = [(p[0], p[1] * (1 / cos(x_angle))) for p in extrusion.SweptArea.OuterCurve.Points.CoordList] + + # The extrusion direction calculated previously default to the positive direction + # Here we set the extrusion direction to negative it that's the case + x, y, z = extrusion.ExtrudedDirection.DirectionRatios + layer_params = tool.Model.get_material_layer_parameters(element) + offset = layer_params["offset"] + offset_vector = Vector((0.0, 0.0, offset / unit_scale)) + + if layer_params["direction_sense"] == "NEGATIVE": + y = -abs(y) if x_angle > 0 else abs(y) + z = -abs(z) + offset_vector = -offset_vector + extrusion.ExtrudedDirection.DirectionRatios = (x, y, z) + + if offset != 0.0 and not extrusion.Position: + tool.Model.add_extrusion_position(extrusion, offset) + + # Update the extrusion's location based on its current rotation angle + if extrusion.Position: + rot_matrix = Matrix.Rotation(x_angle, 4, "X") + rot_offset = offset_vector @ rot_matrix + extrusion.Position.Location.Coordinates = tuple(rot_offset) + + bonsai.core.geometry.switch_representation( tool.Ifc, tool.Geometry, @@ -252,11 +282,13 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator): should_sync_changes_first=False, ) - euler = obj.matrix_world.to_euler() - euler.x = x_angle - new_matrix = euler.to_matrix().to_4x4() - new_matrix.translation = obj.matrix_world.translation - obj.matrix_world = new_matrix + # Object rotation + local_rot_mat = obj.rotation_euler.to_matrix() + rot_mat = mathutils.Matrix.Rotation(x_angle - existing_x_angle, 4, 'X') + new_rot_mat = local_rot_mat.to_4x4() @ rot_mat + new_rot_euler = new_rot_mat.to_euler() + obj.rotation_euler = new_rot_euler + if layer2_objs: DumbWallRecalculator().recalculate(layer2_objs) return {"FINISHED"} diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 240d00d495..65423195c5 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -36,7 +36,7 @@ import bonsai.core.geometry import bonsai.core.tool import bonsai.tool as tool import bonsai.core.geometry as geometry -from math import atan, degrees +from math import atan, degrees, radians from mathutils import Matrix, Vector from copy import deepcopy from functools import partial @@ -2057,3 +2057,18 @@ class Model(bonsai.core.tool.Model): extrusion.Position = position + @classmethod + def get_existing_x_angle(cls, extrusion): + x, y, z = extrusion.ExtrudedDirection.DirectionRatios + # The existing angle result can change when the direction sense in Negative because the DirectionRatios may have negative y and z. + # For instance, a 30 degree angled slab, with negative direction will show as -150 degrees. To prevent that we do the following transformations + existing_x_angle = Vector((0, 1)).angle_signed(Vector((y, z))) + existing_x_angle = ( + existing_x_angle + radians(180) if existing_x_angle < -radians(90) else existing_x_angle + ) + existing_x_angle = ( + existing_x_angle - radians(180) if existing_x_angle > radians(90) else existing_x_angle + ) + + return existing_x_angle + diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py index 9016c1ae12..fffd7703dd 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_slab_representation.py @@ -93,8 +93,14 @@ class Usecase: extrusion_direction = self.file.createIfcDirection( (0.0, sin(self.settings["x_angle"]), cos(self.settings["x_angle"])) ) + if self.settings["direction_sense"] == "NEGATIVE": + extrusion_direction = self.file.createIfcDirection( + (0.0, -sin(self.settings["x_angle"]), -cos(self.settings["x_angle"])) + ) else: extrusion_direction = self.file.createIfcDirection((0.0, 0.0, 1.0)) + if self.settings["direction_sense"] == "NEGATIVE": + extrusion_direction = self.file.createIfcDirection((0.0, 0.0, -1.0)) position = None # default position for IFC2X3 where .Position is not optional