Bonsai: orient new fillings by the wall's direction sense

Fixes #6588

FilledOpeningGenerator.generate decided a new door/window's facing
purely from which wall axis (base or side) the insertion point snapped
to: base kept the wall's orientation, side rotated the filling 180
degrees. That convention encodes the wall body lying on the +Y side of
the base axis, which is only true for DirectionSense POSITIVE. On a
flipped wall (NEGATIVE), the body lies on -Y, so every inserted filling
faced out of the wall instead of into it, with the lining protruding
outside the body, and the user had to run Flip Fill manually after
every insertion.

Fold the direction sense into the decision: the filling faces the wall
body from whichever face was clicked, so NEGATIVE inverts which snap
target needs the 180 degree turn. The ProductPreviewDecorator ghost
preview uses the same base/side test, and is updated identically so
what the user sees while hovering matches what gets placed. POSITIVE
walls are byte-for-byte unchanged, and the opening void extrusion is
centered on the filling plane, so the cut is unaffected by the turn.

Verified live headless (Blender 5.2): doors and windows on a NEGATIVE
wall now face into the body from either clicked face, mirroring the
POSITIVE result; opening still cuts the wall in all four
direction-sense x clicked-face combinations.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Petru Conduraru
2026-07-20 00:51:17 +03:00
parent 89523999b3
commit 5ca5341ff5
2 changed files with 20 additions and 3 deletions
@@ -1554,16 +1554,26 @@ class ProductDecorator(tool.Blender.ViewportDecorator):
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)
# Match FilledOpeningGenerator.generate: the filling faces the
# wall body from whichever face is snapped, so a NEGATIVE
# direction sense inverts which face needs the 180 degree turn.
flipped_wall = layers["direction_sense"] == "NEGATIVE"
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
# mouse is snapped to the base axis
rotate_filling = flipped_wall
else:
# mouse is snapped to the side axis, the preview is inverted, rotate it now and correct x position later
# mouse is snapped to the side axis
rotate_filling = not flipped_wall
if rotate_filling:
# 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)))
.to_matrix()
.to_4x4()
)
else:
# the preview looks exactly like the placed door / window
rot_mat = snap_obj.matrix_world
mouse_point.z = snap_obj.matrix_world.translation.z
@@ -303,12 +303,19 @@ class FilledOpeningGenerator:
new_matrix = voided_obj.matrix_world.copy()
point_on_base_axis = tool.Cad.point_on_edge(target, axis_base)
point_on_side_axis = tool.Cad.point_on_edge(target, axis_side)
# The filling faces the wall body from whichever face was
# clicked, so a NEGATIVE direction sense (body on the wall's
# local -Y) inverts which face needs the 180 degree turn.
flipped_wall = layers["direction_sense"] == "NEGATIVE"
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
rotate_filling = flipped_wall
else:
new_matrix.translation.x = point_on_side_axis.x
new_matrix.translation.y = point_on_side_axis.y
rotate_filling = not flipped_wall
if rotate_filling:
new_matrix = new_matrix @ Matrix.Rotation(radians(180.0), 4, "Z")
if should_set_z_level: