Refactor slab addition to support obtuse x_angle. See #5938

To support obtuse x_angle a refactoring had to be made, which helped
improve the general code for slab addition.
This is challenging because there are a few features that interact
with each other to create slabs, like `depth`, `direction_sense`, `offset`
and `x_angle`. In addition, these interactions can happen in different parts
of the code. This refactor improves the coherence between those different parts.
Files changed:
- `api/geometry/add_slab_representation.py`
- `model/slab.py` - inside the function `change_thickness()`
File to be changed in a following commit:
- `model/wall.py` - inside the operator `ChangeExtrusionXAngle` - To-do
This commit is contained in:
Bruno Perdigão
2025-02-27 20:36:56 -03:00
parent 7a69d6f20e
commit 1855ac421c
3 changed files with 57 additions and 50 deletions
+32 -20
View File
@@ -31,7 +31,7 @@ import bonsai.core.geometry
import bonsai.core.root import bonsai.core.root
import bonsai.tool as tool import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore from bonsai.bim.ifc import IfcStore
from math import cos from math import cos, pi
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator
from bonsai.bim.module.model.polyline import PolylineOperator from bonsai.bim.module.model.polyline import PolylineOperator
@@ -135,7 +135,7 @@ class DumbSlabGenerator:
matrix_world.translation.z = self.container_obj.location.z matrix_world.translation.z = self.container_obj.location.z
else: else:
matrix_world.translation.z matrix_world.translation.z
obj.matrix_world = matrix_world @ Matrix.Rotation(self.x_angle, 4, "X") obj.matrix_world = matrix_world
bpy.context.view_layer.update() bpy.context.view_layer.update()
element = bonsai.core.root.assign_class( element = bonsai.core.root.assign_class(
@@ -170,6 +170,7 @@ class DumbSlabGenerator:
is_global=True, is_global=True,
should_sync_changes_first=False, should_sync_changes_first=False,
) )
obj.matrix_world = obj.matrix_world @ Matrix.Rotation(self.x_angle, 4, "X")
if self.footprint_context: if self.footprint_context:
extrusion = tool.Model.get_extrusion(representation) extrusion = tool.Model.get_extrusion(representation)
@@ -294,28 +295,39 @@ class DumbSlabPlaner:
if representation: if representation:
extrusion = tool.Model.get_extrusion(representation) extrusion = tool.Model.get_extrusion(representation)
if extrusion: if extrusion:
x, y, z = extrusion.ExtrudedDirection.DirectionRatios
existing_x_angle = tool.Model.get_existing_x_angle(extrusion) existing_x_angle = tool.Model.get_existing_x_angle(extrusion)
perpendicular_depth = thickness * (1 / cos(existing_x_angle)) existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, 0, tolerance=0.001) else existing_x_angle
perpendicular_offset = layer_params["offset"] * (1 / cos(existing_x_angle)) existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, pi, tolerance=0.001) else existing_x_angle
offset_vector = Vector((0.0, 0.0, perpendicular_offset / self.unit_scale)) direction_ratios = Vector(extrusion.ExtrudedDirection.DirectionRatios)
if layer_params["direction_sense"] == "POSITIVE": offset_direction = direction_ratios.copy()
y = abs(y) if existing_x_angle > 0 else -abs(y) perpendicular_depth = thickness * abs(1 / cos(existing_x_angle))
z = abs(z) perpendicular_offset = layer_params["offset"] * abs(1 / cos(existing_x_angle)) / self.unit_scale
elif layer_params["direction_sense"] == "NEGATIVE":
y = -abs(y) if existing_x_angle > 0 else abs(y) # Check angle and z direction to determine whether the extrusion direction is positive or negative
z = -abs(z) if (existing_x_angle < (pi / 2) and direction_ratios.z > 0) or (
extrusion.ExtrudedDirection.DirectionRatios = (x, y, z) existing_x_angle > (pi / 2) and direction_ratios.z < 0
):
# The extrusion direction is positive. If the layer_parameter is set to negative,
# then the we change the extrusion direction.
# The offset direction must always be positive, so we keep it.
if layer_params["direction_sense"] == "NEGATIVE":
direction_ratios *= -1
elif (existing_x_angle > (pi / 2) and direction_ratios.z > 0) or (
existing_x_angle < (pi / 2) and direction_ratios.z < 0
):
# The extrusion direction is negative. If the layer_parameter is set to positive,
# then the we change the extrusion direction.
# The offset direction must always be positive, so we change it too.
if layer_params["direction_sense"] == "POSITIVE":
direction_ratios *= -1
offset_direction *= -1
extrusion.ExtrudedDirection.DirectionRatios = tuple(direction_ratios)
extrusion.Depth = perpendicular_depth extrusion.Depth = perpendicular_depth
if perpendicular_offset != 0.0 and not extrusion.Position: if perpendicular_offset != 0.0 and not extrusion.Position:
tool.Model.add_extrusion_position(extrusion, perpendicular_offset) position = offset_direction * perpendicular_offset
tool.Model.add_extrusion_position(extrusion, position)
# Update the extrusion's location based on its current rotation angle and offset
if extrusion.Position:
rot_matrix = Matrix.Rotation(existing_x_angle, 4, "X")
rot_offset = offset_vector @ rot_matrix
extrusion.Position.Location.Coordinates = tuple(rot_offset)
else: else:
props = tool.Model.get_model_props() props = tool.Model.get_model_props()
+4 -15
View File
@@ -2008,33 +2008,22 @@ class Model(bonsai.core.tool.Model):
FilledOpeningGenerator().generate(filling_obj, voided_obj) FilledOpeningGenerator().generate(filling_obj, voided_obj)
@classmethod @classmethod
def add_extrusion_position(cls, extrusion: ifcopenshell.entity_instance, offset: float) -> None: def add_extrusion_position(cls, extrusion: ifcopenshell.entity_instance, position: tuple) -> None:
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
position = ifc_file.createIfcAxis2Placement3D( new_position = ifc_file.createIfcAxis2Placement3D(
ifc_file.createIfcCartesianPoint((0.0, 0.0, offset)), ifc_file.createIfcCartesianPoint(position),
ifc_file.createIfcDirection((0.0, 0.0, 1.0)), ifc_file.createIfcDirection((0.0, 0.0, 1.0)),
ifc_file.createIfcDirection((1.0, 0.0, 0.0)), ifc_file.createIfcDirection((1.0, 0.0, 0.0)),
) )
extrusion.Position = position extrusion.Position = new_position
@classmethod @classmethod
def get_existing_x_angle(cls, extrusion: ifcopenshell.entity_instance) -> float: def get_existing_x_angle(cls, extrusion: ifcopenshell.entity_instance) -> float:
x, y, z = extrusion.ExtrudedDirection.DirectionRatios x, y, z = extrusion.ExtrudedDirection.DirectionRatios
vector = Vector((0, 1)) vector = Vector((0, 1))
x_angle = vector.angle_signed(Vector((y, z))) x_angle = vector.angle_signed(Vector((y, z)))
# The extrusion direction is changed by the layer direction change
# So we have to adapt the values of y, z and vector accordingly
if z < 0 and y < 0:
y = abs(y)
z = abs(z)
if z < 0 and y >= 0:
vector = Vector((0, -1))
x_angle = vector.angle_signed(Vector((y, z)))
return x_angle return x_angle
@classmethod @classmethod
@@ -85,31 +85,37 @@ class Usecase:
points = ((0.0, 0.0), (size, 0.0), (size, size), (0.0, size), (0.0, 0.0)) points = ((0.0, 0.0), (size, 0.0), (size, size), (0.0, size), (0.0, 0.0))
if self.settings["polyline"]: if self.settings["polyline"]:
points = [ points = [
(self.convert_si_to_unit(p[0]), self.convert_si_to_unit(p[1] * (1 / cos(self.settings["x_angle"])))) (self.convert_si_to_unit(p[0]), self.convert_si_to_unit(p[1] * abs(1 / cos(self.settings["x_angle"]))))
for p in self.settings["polyline"] for p in self.settings["polyline"]
] ]
if self.file.schema == "IFC2X3": if self.file.schema == "IFC2X3":
curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points]) curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points])
else: else:
curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList2D(points)) curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList2D(points))
if self.settings["x_angle"]:
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))
if self.settings["x_angle"]:
direction_ratios = (0.0, sin(self.settings["x_angle"]), cos(self.settings["x_angle"]))
else:
direction_ratios = (0.0, 0.0, 1.0)
offset_direction = direction_ratios # offset direction doesn't change if direction_sense is negative
extrusion_direction = self.file.createIfcDirection(direction_ratios)
if self.settings["direction_sense"] == "NEGATIVE":
direction_ratios = tuple((-n for n in direction_ratios))
extrusion_direction = self.file.createIfcDirection(direction_ratios)
perpendicular_offset = self.convert_si_to_unit(self.settings["offset"]) * abs(1 / cos(self.settings["x_angle"]))
perpendicular_depth = self.convert_si_to_unit(self.settings["depth"]) * abs(1 / cos(self.settings["x_angle"]))
position = None position = None
# default position for IFC2X3 where .Position is not optional # default position for IFC2X3 where .Position is not optional
if self.file.schema == "IFC2X3" or self.settings["offset"] != 0: if self.file.schema == "IFC2X3" or self.settings["offset"] != 0:
position_vector = (
offset_direction[0] * perpendicular_offset,
offset_direction[1] * perpendicular_offset,
offset_direction[2] * perpendicular_offset,
)
position = self.file.createIfcAxis2Placement3D( position = self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint((0.0, 0.0, self.convert_si_to_unit(self.settings["offset"]))), self.file.createIfcCartesianPoint(position_vector),
self.file.createIfcDirection((0.0, 0.0, 1.0)), self.file.createIfcDirection((0.0, 0.0, 1.0)),
self.file.createIfcDirection((1.0, 0.0, 0.0)), self.file.createIfcDirection((1.0, 0.0, 0.0)),
) )
@@ -118,7 +124,7 @@ class Usecase:
self.file.createIfcArbitraryClosedProfileDef("AREA", None, curve), self.file.createIfcArbitraryClosedProfileDef("AREA", None, curve),
position, position,
extrusion_direction, extrusion_direction,
self.convert_si_to_unit(self.settings["depth"]) * 1 / cos(self.settings["x_angle"]), perpendicular_depth,
) )
if self.settings["clippings"]: if self.settings["clippings"]:
return self.apply_clippings(extrusion) return self.apply_clippings(extrusion)