mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-07 07:41:06 +00:00
The slicing frame of a belt object started at its lowest vertex, but the belt under the leading end of an overhang lies below that, by the overhang's length times the tilt's shear. Every support generator works in layers at z >= 0, so none of them could reach it: normal supports stopped at the object's own lowest layer, and the two tree generators each carried a stack of hacks to extend themselves below it (a post-hoc copy of the lowest base area in TreeSupport, "virtual belt raft layers" in TreeSupport3D/TreeModelVolumes), sized from the pre-rotation bbox and capped at global z = 0, which is only right for the trailing half of the belt. Start the frame at the lowest belt-floor point under the footprint instead, less a 10 mm margin along the belt for the base of a support column (BeltSliceStrategy::apply_preslice_transforms and BeltTransformPipeline::compute_belt_height_and_floor agree on it). The layers between it and the first vertex come out empty, which belt slicing already tolerates, and the generators need no extension at all: - normal supports: the generator anchors its layer grid at the frame origin, so run it in the object frame and shift the global belt Z offset onto the result afterwards, as organic supports already did. With the offset on the object layers a top contact at negative z turned the intermediate-layer count negative and the generator allocated layers until the kernel killed it (any overhang in the leading half of the belt). Drop the first-layer flange expansion on a belt: the first support layer is the leading tip of the support, not a flange, and inflating it put lines in the air ahead of the belt. - classic tree: a node now keeps dropping until its whole circle is in the belt, so the branch tapers to a tip on the belt instead of stopping, a radius above it, when its centre crosses. - organic: the belt is no longer a support blocker. A blocker is a collision, and a branch descending onto one slides off it, down the tilted belt and ahead of the part; the belt is where branches end, which the per-layer m_belt_floor clipping already does. The belt brim is generated after the parallel support step instead of inside it: belt_brim_obstacles() reads every object's layers and support layers, which another object's support step rebuilds (and, now, shifts) at the same time. This is the race behind the Windows arm64 segfault in "Belt brim of each object precedes its perimeters on its own filament". Also: the belt tilt axis moves to Developer mode as its own row (a shared line is shown by its first option's mode), first_layer_plane band thickness, belt_support_floor_mode, belt_preslice_global and gcode_back_transform are retired and presumed on, the gravity arrow is a plain line along the up direction, and the "Show raw G-code (belt only)" preview toggle is gone. Regression test: "Belt supports reach the belt under a leading overhang" slices a cube with a fin whose underside is parallel to the layers, 20 mm ahead of the cube and up to 41 mm of slicing Z above the belt, for normal, organic and classic tree supports, and checks that the lowest support layer sits on the belt beneath its own lines. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
84 lines
3.8 KiB
C++
84 lines
3.8 KiB
C++
#include "BeltSliceStrategy.hpp"
|
|
#include "Model.hpp"
|
|
#include "BeltTransform.hpp"
|
|
#include "Point.hpp"
|
|
#include "PrintConfig.hpp"
|
|
|
|
#include <limits>
|
|
#include <algorithm>
|
|
|
|
namespace Slic3r {
|
|
|
|
void BeltSliceStrategy::apply_preslice_transforms(Transform3d &trafo,
|
|
const PrintConfig &config,
|
|
const ModelVolumePtrs &model_volumes,
|
|
double *out_belt_min_z)
|
|
{
|
|
// 1. Belt rotation — the sole mesh-side belt transform (matching
|
|
// BeltTransformPipeline::build_forward_transform). Only active in
|
|
// belt-printer mode.
|
|
bool has_rotation = false;
|
|
if (config.belt_printer.value) {
|
|
const Matrix3d rot = BeltTransformPipeline::build_rotation_matrix(config, &has_rotation);
|
|
if (has_rotation) {
|
|
Transform3d belt_xform = Transform3d::Identity();
|
|
belt_xform.linear() = rot;
|
|
trafo = belt_xform * trafo;
|
|
}
|
|
}
|
|
|
|
if (!has_rotation)
|
|
return;
|
|
|
|
// 2. Z-shift — detect if the mesh clips below the build plate after the
|
|
// transforms and lift it. Each mesh vertex must be brought into object space
|
|
// via mv->get_matrix() before applying the full trafo (which is in object
|
|
// space). Missing this on assemblies (where per-volume get_matrix() positions
|
|
// each volume within the object) would compute min_z against mesh-local vertex
|
|
// coordinates rather than object-space coordinates, so volumes translated along
|
|
// the slicer's Z axis would be silently excluded from the bound check.
|
|
|
|
//
|
|
// The lift is measured to the lowest point of the SUPPORT region, not of the
|
|
// mesh: the belt floor (z = shear * u in this rotated frame, u the from-axis
|
|
// coordinate) runs below every vertex, and under the leading end of an
|
|
// overhang it lies below the lowest vertex by up to the overhang's length
|
|
// times the shear. Supports have to reach that floor, and every support
|
|
// generator works in layers at z >= 0, so z = 0 has to be the lowest floor
|
|
// point under the footprint. The layers between it and the first vertex
|
|
// come out empty, which belt slicing already tolerates (the bottom corner
|
|
// of a tilted part is a point). Vertices on the belt have z == floor, so
|
|
// for a part resting on the belt this is simply the floor at its leading
|
|
// extreme, less the frame margin (see BeltTransformPipeline::frame_margin).
|
|
BeltTransformPipeline::BeltFloorParams floor;
|
|
const bool has_floor = BeltTransformPipeline::floor_shear(config, floor);
|
|
double min_z = std::numeric_limits<double>::max();
|
|
for (const ModelVolume *mv : model_volumes) {
|
|
if (!mv->is_model_part()) continue;
|
|
Transform3d vol_trafo = trafo * mv->get_matrix();
|
|
const auto &its = mv->mesh().its;
|
|
for (const stl_vertex &v : its.vertices) {
|
|
Vec3d vm = v.cast<double>();
|
|
Vec3d pt = vol_trafo * vm;
|
|
min_z = std::min(min_z, pt.z());
|
|
if (has_floor)
|
|
min_z = std::min(min_z, floor.shear_factor * (floor.from_axis == 0 ? pt.x() : pt.y()));
|
|
}
|
|
}
|
|
if (has_floor && min_z != std::numeric_limits<double>::max())
|
|
min_z -= BeltTransformPipeline::frame_margin(floor);
|
|
const double z_shift_val = (min_z < 0. && min_z != std::numeric_limits<double>::max()) ? -min_z : 0.;
|
|
if (z_shift_val > 0.) {
|
|
Transform3d z_shift = Transform3d::Identity();
|
|
z_shift.matrix()(2, 3) = z_shift_val;
|
|
trafo = z_shift * trafo;
|
|
}
|
|
// out_belt_min_z is only meaningful in belt mode; the standalone-remap path
|
|
// never reported it.
|
|
if (out_belt_min_z && config.belt_printer.value) {
|
|
*out_belt_min_z = (min_z != std::numeric_limits<double>::max()) ? min_z : 0.;
|
|
}
|
|
}
|
|
|
|
} // namespace Slic3r
|