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.tool as tool
from bonsai.bim.ifc import IfcStore
from math import cos
from math import cos, pi
from mathutils import Vector, Matrix
from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator
from bonsai.bim.module.model.polyline import PolylineOperator
@@ -135,7 +135,7 @@ class DumbSlabGenerator:
matrix_world.translation.z = self.container_obj.location.z
else:
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()
element = bonsai.core.root.assign_class(
@@ -170,6 +170,7 @@ class DumbSlabGenerator:
is_global=True,
should_sync_changes_first=False,
)
obj.matrix_world = obj.matrix_world @ Matrix.Rotation(self.x_angle, 4, "X")
if self.footprint_context:
extrusion = tool.Model.get_extrusion(representation)
@@ -294,28 +295,39 @@ class DumbSlabPlaner:
if representation:
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))
perpendicular_offset = layer_params["offset"] * (1 / cos(existing_x_angle))
offset_vector = Vector((0.0, 0.0, perpendicular_offset / self.unit_scale))
if layer_params["direction_sense"] == "POSITIVE":
y = abs(y) if existing_x_angle > 0 else -abs(y)
z = abs(z)
elif layer_params["direction_sense"] == "NEGATIVE":
y = -abs(y) if existing_x_angle > 0 else abs(y)
z = -abs(z)
extrusion.ExtrudedDirection.DirectionRatios = (x, y, z)
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, 0, tolerance=0.001) else existing_x_angle
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, pi, tolerance=0.001) else existing_x_angle
direction_ratios = Vector(extrusion.ExtrudedDirection.DirectionRatios)
offset_direction = direction_ratios.copy()
perpendicular_depth = thickness * abs(1 / cos(existing_x_angle))
perpendicular_offset = layer_params["offset"] * abs(1 / cos(existing_x_angle)) / self.unit_scale
# Check angle and z direction to determine whether the extrusion direction is positive or negative
if (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 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
if perpendicular_offset != 0.0 and not extrusion.Position:
tool.Model.add_extrusion_position(extrusion, perpendicular_offset)
# 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)
position = offset_direction * perpendicular_offset
tool.Model.add_extrusion_position(extrusion, position)
else:
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)
@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()
position = ifc_file.createIfcAxis2Placement3D(
ifc_file.createIfcCartesianPoint((0.0, 0.0, offset)),
new_position = ifc_file.createIfcAxis2Placement3D(
ifc_file.createIfcCartesianPoint(position),
ifc_file.createIfcDirection((0.0, 0.0, 1.0)),
ifc_file.createIfcDirection((1.0, 0.0, 0.0)),
)
extrusion.Position = position
extrusion.Position = new_position
@classmethod
def get_existing_x_angle(cls, extrusion: ifcopenshell.entity_instance) -> float:
x, y, z = extrusion.ExtrudedDirection.DirectionRatios
vector = Vector((0, 1))
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
@classmethod
@@ -85,31 +85,37 @@ class Usecase:
points = ((0.0, 0.0), (size, 0.0), (size, size), (0.0, size), (0.0, 0.0))
if self.settings["polyline"]:
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"]
]
if self.file.schema == "IFC2X3":
curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points])
else:
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
# default position for IFC2X3 where .Position is not optional
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(
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((1.0, 0.0, 0.0)),
)
@@ -118,7 +124,7 @@ class Usecase:
self.file.createIfcArbitraryClosedProfileDef("AREA", None, curve),
position,
extrusion_direction,
self.convert_si_to_unit(self.settings["depth"]) * 1 / cos(self.settings["x_angle"]),
perpendicular_depth,
)
if self.settings["clippings"]:
return self.apply_clippings(extrusion)