fix(imex): rotate the prime-tower hull before checking it against carriage zones

imex_wipe_tower_hull() took the axis-aligned box estimate_wipe_tower_polygon()
returns and compared it to the IMEX collision zones as-is. The real tower is
rotated about its anchor corner before placement -- first_layer_wipe_tower_corners
builds the box in tower-local coordinates, rotates about the local origin, then
translates by wipe_tower_x/y -- so a rotated tower's true footprint fell outside
the hull and the placement check passed on a tower that intrudes into a
carriage's reserved space.

The gap was documented in place and previously harmless, because
wipe_tower_rotation_angle was read from the project config after it had been
moved to the print preset, so the setting did nothing. Upstream repaired that
read ("Fix prime tower rotation angle setting not working"), which makes the
angle reachable and the stale hull wrong.

Rotation is applied to the hull alone. estimate_wipe_tower_polygon() still
returns an unrotated box and still leaves ArrangePolygon::rotation unset, since
the arranger consumes that field separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clifford Garwood
2026-08-23 01:09:38 -04:00
co-authored by Claude Opus 5
parent 90d1481463
commit 8edf6b9d4f
+10 -2
View File
@@ -1478,12 +1478,20 @@ Polygon PartPlate::imex_wipe_tower_hull() const
// the real tower height (m_wipe_tower_data.height), likewise post-slice;
// approximating it from object height over-reserved space and blocked legal
// placements. The default rib wall prints no cone.
// - wipe_tower_rotation_angle is ignored (estimate_wipe_tower_polygon has it
// commented out), so a rotated tower's true footprint differs from this hull.
// See also the unvalidated object brim / skirt / support gaps, which affect every
// print.
Polygon hull = ap.poly.contour;
// estimate_wipe_tower_polygon() builds an axis-aligned box; the real tower is
// rotated about its anchor corner before placement (first_layer_wipe_tower_corners
// rotates the tower-local box about the local origin, then translates it by
// wipe_tower_x/y). Without this a rotated tower's footprint escapes the hull and
// the zone check passes on a tower that intrudes.
if (const auto* rot = print_cfg.option<ConfigOptionFloat>("wipe_tower_rotation_angle");
rot && rot->value != 0.)
hull.rotate(Geometry::deg2rad(rot->value),
Point(scaled(wt_pos.x()), scaled(wt_pos.y())));
// estimate_wipe_tower_polygon() works in plate-local mm; the zones and the
// instance hulls it is compared against are in plate-list coordinates.
hull.translate(Point(scaled(m_origin.x()), scaled(m_origin.y())));