mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-16 07:22:10 +00:00
Rotate instead of shear for slicing stage (#30)
* initial commit * fix upper bounds for assemblies * significantly less Z shift issues, still not quite tamped down yet though * add instrumentation to logs * finally found the issue * update printer defaults
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Custom Printer",
|
||||
"version": "02.03.02.67",
|
||||
"version": "02.03.02.68",
|
||||
"force_update": "0",
|
||||
"description": "My configurations",
|
||||
"machine_model_list": [
|
||||
|
||||
@@ -89,9 +89,9 @@
|
||||
"1"
|
||||
],
|
||||
"belt_printer": "1",
|
||||
"belt_shear_z": "pos_tan",
|
||||
"belt_shear_z_angle": "45",
|
||||
"belt_scale_y": "inv_cos",
|
||||
"belt_slice_rotation": "x",
|
||||
"belt_slice_rotation_angle": "45",
|
||||
"belt_slice_rotation_global": "1",
|
||||
"gcode_shear_z": "pos_tan",
|
||||
"gcode_scale_y": "inv_cos",
|
||||
"build_plate_tilt_x": "45",
|
||||
|
||||
@@ -53,6 +53,10 @@ void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print)
|
||||
file.write_format("; belt_scale_y_angle = %.1f\n", print.config().belt_scale_y_angle.value);
|
||||
file.write_format("; belt_scale_z = %s\n", full_cfg.opt_serialize("belt_scale_z").c_str());
|
||||
file.write_format("; belt_scale_z_angle = %.1f\n", print.config().belt_scale_z_angle.value);
|
||||
// Slicing rotation configs
|
||||
file.write_format("; belt_slice_rotation = %s\n", full_cfg.opt_serialize("belt_slice_rotation").c_str());
|
||||
file.write_format("; belt_slice_rotation_angle = %.1f\n", print.config().belt_slice_rotation_angle.value);
|
||||
file.write_format("; belt_slice_rotation_global = %d\n", print.config().belt_slice_rotation_global.value ? 1 : 0);
|
||||
file.write_format("; belt_mesh_transform_order = %s\n", full_cfg.opt_serialize("belt_mesh_transform_order").c_str());
|
||||
// Pre-slice remap configs
|
||||
file.write_format("; preslice_remap_x = %s\n", full_cfg.opt_serialize("preslice_remap_x").c_str());
|
||||
@@ -104,7 +108,10 @@ void BeltGCode::on_set_origin(const PrintObject *obj, const Point &inst_shift)
|
||||
|| (m_config.preslice_remap_global.value
|
||||
&& BeltTransformPipeline::has_preslice_remap(m_config))
|
||||
|| (m_config.belt_shear_z_global.value
|
||||
&& m_config.belt_shear_z.value != BeltShearMode::None);
|
||||
&& m_config.belt_shear_z.value != BeltShearMode::None)
|
||||
|| (m_config.belt_slice_rotation_global.value
|
||||
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None
|
||||
&& std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON);
|
||||
if (use_global && m_config.belt_printer.value) {
|
||||
auto *belt_writer = dynamic_cast<BeltGCodeWriter*>(m_writer.get());
|
||||
if (belt_writer) {
|
||||
|
||||
@@ -57,17 +57,39 @@ Vec3d BeltGCodeWriter::to_cartesian(const Vec3d &pos) const
|
||||
Vec3d BeltGCodeWriter::to_machine_coords(const Vec3d &pos) const
|
||||
{
|
||||
// Step 1+2: To Cartesian (back_transform + axis_remap).
|
||||
Vec3d result = to_cartesian(pos);
|
||||
Vec3d after_back = m_belt_back_transform.apply(pos);
|
||||
Vec3d result = apply_axis_remap(after_back);
|
||||
Vec3d after_remap = result;
|
||||
// Step 3: Per-axis origin snap (computed in the Cartesian frame).
|
||||
for (int i = 0; i < 3; ++i)
|
||||
if (m_origin_snap[i])
|
||||
result[i] -= (m_origin_bbox_min[i] - m_origin_offset[i]);
|
||||
Vec3d after_snap = result;
|
||||
// Step 4: Machine-frame transform (gcode_shear / gcode_scale / post_gcode_remap)
|
||||
// applied LAST so it acts as a global linear transform on the placed coords.
|
||||
// Order matters: putting it before origin_snap would feed sheared bbox corners
|
||||
// into the snap's per-object min calculation, mis-normalizing non-cubic geometries
|
||||
// (the corners of the original bbox aren't extreme points of the sheared shape).
|
||||
return m_machine_frame_transform.apply(result);
|
||||
Vec3d final = m_machine_frame_transform.apply(result);
|
||||
|
||||
// [BELT-DEBUG] One-shot log per layer transition (i.e. when the input Z
|
||||
// crosses an integer mm boundary) to keep the log volume manageable while
|
||||
// still capturing one sample per ~5 layers. Shows the full pipeline so
|
||||
// Case A vs Case B can be compared step-by-step.
|
||||
static thread_local int s_last_logged_z = std::numeric_limits<int>::min();
|
||||
int z_bucket = static_cast<int>(std::floor(pos.z() * 5.0)); // every 0.2mm
|
||||
if (z_bucket != s_last_logged_z) {
|
||||
s_last_logged_z = z_bucket;
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] to_machine_coords"
|
||||
<< " slicer_in=(" << pos.x() << "," << pos.y() << "," << pos.z() << ")"
|
||||
<< " after_back=(" << after_back.x() << "," << after_back.y() << "," << after_back.z() << ")"
|
||||
<< " after_remap=(" << after_remap.x() << "," << after_remap.y() << "," << after_remap.z() << ")"
|
||||
<< " after_snap=(" << after_snap.x() << "," << after_snap.y() << "," << after_snap.z() << ")"
|
||||
<< " final=(" << final.x() << "," << final.y() << "," << final.z() << ")"
|
||||
<< " mft_active=" << m_machine_frame_transform.is_active()
|
||||
<< " back_active=" << m_belt_back_transform.is_active();
|
||||
}
|
||||
return final;
|
||||
}
|
||||
|
||||
// ---- Overridden movement methods ------------------------------------------
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "BeltSliceStrategy.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -14,9 +17,10 @@ std::unique_ptr<BeltSliceStrategy> BeltSliceStrategy::create(const PrintConfig &
|
||||
|
||||
BeltSliceStrategy::BeltSliceStrategy(const PrintConfig &config)
|
||||
{
|
||||
m_shear = BeltTransformPipeline::build_shear_matrix(config, &m_has_shear);
|
||||
m_scale = BeltTransformPipeline::build_scale_matrix(config, &m_has_scale);
|
||||
m_order = config.belt_mesh_transform_order.value;
|
||||
m_shear = BeltTransformPipeline::build_shear_matrix(config, &m_has_shear);
|
||||
m_scale = BeltTransformPipeline::build_scale_matrix(config, &m_has_scale);
|
||||
m_rotation = BeltTransformPipeline::build_rotation_matrix(config, &m_has_rotation);
|
||||
m_order = config.belt_mesh_transform_order.value;
|
||||
}
|
||||
|
||||
void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo,
|
||||
@@ -26,27 +30,83 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo,
|
||||
{
|
||||
// ScaleThenShear: applied to a point, scale runs first then shear (m_shear * m_scale).
|
||||
// ShearThenScale: applied to a point, shear runs first then scale (m_scale * m_shear).
|
||||
if (m_has_shear || m_has_scale) {
|
||||
Transform3d belt_xform = Transform3d::Identity();
|
||||
belt_xform.linear() = (m_order == BeltTransformOrder::ScaleThenShear)
|
||||
// Rotation (if active) is applied AFTER shear/scale, matching build_forward_transform.
|
||||
if (m_has_shear || m_has_scale || m_has_rotation) {
|
||||
Matrix3d shear_scale = (m_order == BeltTransformOrder::ScaleThenShear)
|
||||
? Matrix3d(m_shear * m_scale)
|
||||
: Matrix3d(m_scale * m_shear);
|
||||
Transform3d belt_xform = Transform3d::Identity();
|
||||
belt_xform.linear() = Matrix3d(m_rotation * shear_scale);
|
||||
trafo = belt_xform * trafo;
|
||||
}
|
||||
|
||||
// Z-shift — detect if mesh clips below build plate after transforms.
|
||||
if (has_remap || m_has_shear || m_has_scale) {
|
||||
// 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
|
||||
// step on assemblies (where per-volume get_matrix() positions each volume
|
||||
// within the object) causes min_z to be computed against mesh-local vertex
|
||||
// coordinates rather than object-space coordinates, so volumes translated
|
||||
// along the slicer's Z axis are silently excluded from the bound check.
|
||||
if (has_remap || m_has_shear || m_has_scale || m_has_rotation) {
|
||||
// [BELT-DEBUG] Capture the incoming trafo for diagnostic logging.
|
||||
// This is the slicer-frame transform AFTER belt_xform but BEFORE z_shift.
|
||||
const Transform3d trafo_pre_shift = trafo;
|
||||
auto log_mat = [](const Matrix3d &m) {
|
||||
std::ostringstream ss;
|
||||
ss << std::fixed << std::setprecision(4);
|
||||
ss << "[[" << m(0,0) << "," << m(0,1) << "," << m(0,2) << "],"
|
||||
<< "[" << m(1,0) << "," << m(1,1) << "," << m(1,2) << "],"
|
||||
<< "[" << m(2,0) << "," << m(2,1) << "," << m(2,2) << "]]";
|
||||
return ss.str();
|
||||
};
|
||||
auto log_vec3 = [](const Vec3d &v) {
|
||||
std::ostringstream ss;
|
||||
ss << std::fixed << std::setprecision(4);
|
||||
ss << "(" << v.x() << "," << v.y() << "," << v.z() << ")";
|
||||
return ss.str();
|
||||
};
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] apply_to_trafo enter"
|
||||
<< " has_shear=" << m_has_shear
|
||||
<< " has_scale=" << m_has_scale
|
||||
<< " has_rotation=" << m_has_rotation
|
||||
<< " has_remap=" << has_remap
|
||||
<< " trafo.linear=" << log_mat(trafo_pre_shift.linear())
|
||||
<< " trafo.translation=" << log_vec3(trafo_pre_shift.translation())
|
||||
<< " volumes=" << model_volumes.size();
|
||||
|
||||
double min_z = std::numeric_limits<double>::max();
|
||||
int vol_idx = 0;
|
||||
for (const ModelVolume *mv : model_volumes) {
|
||||
if (!mv->is_model_part()) continue;
|
||||
for (const stl_vertex &v : mv->mesh().its.vertices) {
|
||||
Vec3d pt = trafo * v.cast<double>();
|
||||
if (!mv->is_model_part()) { ++vol_idx; continue; }
|
||||
Transform3d vol_trafo = trafo * mv->get_matrix();
|
||||
// [BELT-DEBUG] Per-volume bbox in mesh-frame and post-trafo slicer-frame.
|
||||
const auto &its = mv->mesh().its;
|
||||
Vec3d mesh_min(std::numeric_limits<double>::max(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
|
||||
Vec3d mesh_max(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest());
|
||||
Vec3d slicer_min(std::numeric_limits<double>::max(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
|
||||
Vec3d slicer_max(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest());
|
||||
double vol_min_z = std::numeric_limits<double>::max();
|
||||
for (const stl_vertex &v : its.vertices) {
|
||||
Vec3d vm = v.cast<double>();
|
||||
mesh_min = mesh_min.cwiseMin(vm);
|
||||
mesh_max = mesh_max.cwiseMax(vm);
|
||||
Vec3d pt = vol_trafo * vm;
|
||||
slicer_min = slicer_min.cwiseMin(pt);
|
||||
slicer_max = slicer_max.cwiseMax(pt);
|
||||
vol_min_z = std::min(vol_min_z, pt.z());
|
||||
min_z = std::min(min_z, pt.z());
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] vol[" << vol_idx
|
||||
<< "] id=" << mv->id().id << " name='" << mv->name << "'"
|
||||
<< " mesh_bbox_min=" << log_vec3(mesh_min) << " mesh_bbox_max=" << log_vec3(mesh_max)
|
||||
<< " get_matrix.translation=" << log_vec3(mv->get_matrix().translation())
|
||||
<< " slicer_bbox_min=" << log_vec3(slicer_min) << " slicer_bbox_max=" << log_vec3(slicer_max)
|
||||
<< " vol_min_z=" << vol_min_z;
|
||||
++vol_idx;
|
||||
}
|
||||
double belt_z_shift_val = (min_z < 0. && min_z != std::numeric_limits<double>::max()) ? -min_z : 0.;
|
||||
BOOST_LOG_TRIVIAL(warning) << "Belt Z-shift: min_z=" << min_z
|
||||
<< " z_shift=" << belt_z_shift_val;
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] combined min_z=" << min_z
|
||||
<< " z_shift_val=" << belt_z_shift_val;
|
||||
if (belt_z_shift_val > 0.) {
|
||||
Transform3d z_shift = Transform3d::Identity();
|
||||
z_shift.matrix()(2, 3) = belt_z_shift_val;
|
||||
@@ -54,10 +114,13 @@ void BeltSliceStrategy::apply_to_trafo(Transform3d &trafo,
|
||||
}
|
||||
if (out_belt_min_z) {
|
||||
double new_val = (min_z != std::numeric_limits<double>::max()) ? min_z : 0.;
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_min_z tid=" << std::this_thread::get_id()
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] write m_belt_min_z tid=" << std::this_thread::get_id()
|
||||
<< " target=" << out_belt_min_z << " old=" << *out_belt_min_z << " new=" << new_val;
|
||||
*out_belt_min_z = new_val;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] apply_to_trafo exit"
|
||||
<< " final_trafo.linear=" << log_mat(trafo.linear())
|
||||
<< " final_trafo.translation=" << log_vec3(trafo.translation());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,11 +34,13 @@ public:
|
||||
private:
|
||||
explicit BeltSliceStrategy(const PrintConfig &config);
|
||||
|
||||
bool m_has_shear = false;
|
||||
bool m_has_scale = false;
|
||||
Matrix3d m_shear = Matrix3d::Identity();
|
||||
Matrix3d m_scale = Matrix3d::Identity();
|
||||
BeltTransformOrder m_order = BeltTransformOrder::ScaleThenShear;
|
||||
bool m_has_shear = false;
|
||||
bool m_has_scale = false;
|
||||
bool m_has_rotation = false;
|
||||
Matrix3d m_shear = Matrix3d::Identity();
|
||||
Matrix3d m_scale = Matrix3d::Identity();
|
||||
Matrix3d m_rotation = Matrix3d::Identity();
|
||||
BeltTransformOrder m_order = BeltTransformOrder::ScaleThenShear;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -95,23 +95,47 @@ Matrix3d BeltTransformPipeline::build_scale_matrix(const PrintConfig &config, bo
|
||||
return scale;
|
||||
}
|
||||
|
||||
Matrix3d BeltTransformPipeline::build_rotation_matrix(const PrintConfig &config, bool *has_rot_out)
|
||||
{
|
||||
BeltRotationAxis axis = config.belt_slice_rotation.value;
|
||||
double angle_deg = config.belt_slice_rotation_angle.value;
|
||||
bool active = axis != BeltRotationAxis::None && std::abs(angle_deg) > EPSILON;
|
||||
if (has_rot_out) *has_rot_out = active;
|
||||
if (!active)
|
||||
return Matrix3d::Identity();
|
||||
double angle_rad = Geometry::deg2rad(angle_deg);
|
||||
Vec3d unit_axis;
|
||||
switch (axis) {
|
||||
case BeltRotationAxis::X: unit_axis = Vec3d::UnitX(); break;
|
||||
case BeltRotationAxis::Y: unit_axis = Vec3d::UnitY(); break;
|
||||
case BeltRotationAxis::Z: unit_axis = Vec3d::UnitZ(); break;
|
||||
default: return Matrix3d::Identity();
|
||||
}
|
||||
return Eigen::AngleAxisd(angle_rad, unit_axis).toRotationMatrix();
|
||||
}
|
||||
|
||||
Transform3d BeltTransformPipeline::build_forward_transform(const PrintConfig &config)
|
||||
{
|
||||
Transform3d pre_remap = build_preslice_remap(config);
|
||||
Transform3d pre_remap = build_preslice_remap(config);
|
||||
bool shear_active = false;
|
||||
Matrix3d shear = build_shear_matrix(config, &shear_active);
|
||||
bool scale_active = false;
|
||||
Matrix3d scale = build_scale_matrix(config, &scale_active);
|
||||
bool rot_active = false;
|
||||
Matrix3d rot = build_rotation_matrix(config, &rot_active);
|
||||
|
||||
// Match the mesh-side ordering selected by belt_mesh_transform_order so
|
||||
// BeltBackTransform inverts the same composition that BeltSliceStrategy
|
||||
// applied to the mesh.
|
||||
// ScaleThenShear: applied to p, scale runs first then shear (shear * scale).
|
||||
// ShearThenScale: applied to p, shear runs first then scale (scale * shear).
|
||||
Transform3d combined = Transform3d::Identity();
|
||||
combined.linear() = (config.belt_mesh_transform_order.value == BeltTransformOrder::ScaleThenShear)
|
||||
// Rotation is applied AFTER shear/scale: rot * shear_scale * pre_remap.
|
||||
Matrix3d shear_scale = (config.belt_mesh_transform_order.value == BeltTransformOrder::ScaleThenShear)
|
||||
? Matrix3d(shear * scale)
|
||||
: Matrix3d(scale * shear);
|
||||
|
||||
Transform3d combined = Transform3d::Identity();
|
||||
combined.linear() = Matrix3d(rot * shear_scale);
|
||||
combined = combined * pre_remap;
|
||||
return combined;
|
||||
}
|
||||
@@ -166,7 +190,7 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
|
||||
BeltTransformPipeline::BeltHeightResult result;
|
||||
result.object_height = original_height;
|
||||
|
||||
// Extract Z-axis shear/scale + per-axis scale + transform order from config.
|
||||
// Extract Z-axis shear/scale + per-axis scale + transform order + rotation from config.
|
||||
BeltShearMode z_shear_mode;
|
||||
double z_shear_angle;
|
||||
BeltScaleMode z_scale_mode;
|
||||
@@ -175,6 +199,8 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
|
||||
BeltScaleMode from_scale_mode; // scale on the shear's source axis
|
||||
double from_scale_angle;
|
||||
BeltTransformOrder order;
|
||||
BeltRotationAxis rot_axis;
|
||||
double rot_angle;
|
||||
|
||||
if constexpr (std::is_same_v<Config, PrintConfig>) {
|
||||
z_shear_mode = config.belt_shear_z.value;
|
||||
@@ -183,6 +209,8 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
|
||||
z_scale_angle = config.belt_scale_z_angle.value;
|
||||
z_shear_from = int(config.belt_shear_z_from.value);
|
||||
order = config.belt_mesh_transform_order.value;
|
||||
rot_axis = config.belt_slice_rotation.value;
|
||||
rot_angle = config.belt_slice_rotation_angle.value;
|
||||
if (z_shear_from == 0) {
|
||||
from_scale_mode = config.belt_scale_x.value;
|
||||
from_scale_angle = config.belt_scale_x_angle.value;
|
||||
@@ -212,12 +240,18 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
|
||||
auto *opt = config.template option<ConfigOptionEnum<BeltTransformOrder>>(key);
|
||||
return opt ? opt->value : BeltTransformOrder::ScaleThenShear;
|
||||
};
|
||||
auto get_rot_axis = [&](const char *key) {
|
||||
auto *opt = config.template option<ConfigOptionEnum<BeltRotationAxis>>(key);
|
||||
return opt ? opt->value : BeltRotationAxis::None;
|
||||
};
|
||||
z_shear_mode = get_shear("belt_shear_z");
|
||||
z_shear_angle = get_float("belt_shear_z_angle");
|
||||
z_scale_mode = get_scale("belt_scale_z");
|
||||
z_scale_angle = get_float("belt_scale_z_angle");
|
||||
z_shear_from = get_axis("belt_shear_z_from");
|
||||
order = get_order("belt_mesh_transform_order");
|
||||
rot_axis = get_rot_axis("belt_slice_rotation");
|
||||
rot_angle = get_float("belt_slice_rotation_angle");
|
||||
if (z_shear_from == 0) {
|
||||
from_scale_mode = get_scale("belt_scale_x");
|
||||
from_scale_angle = get_float("belt_scale_x_angle");
|
||||
@@ -227,10 +261,11 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
|
||||
}
|
||||
}
|
||||
|
||||
bool has_z_shear = z_shear_mode != BeltShearMode::None;
|
||||
bool has_z_scale = z_scale_mode != BeltScaleMode::None;
|
||||
bool has_z_shear = z_shear_mode != BeltShearMode::None;
|
||||
bool has_z_scale = z_scale_mode != BeltScaleMode::None;
|
||||
bool has_rotation = rot_axis != BeltRotationAxis::None && std::abs(rot_angle) > EPSILON;
|
||||
|
||||
if (!has_z_shear && !has_z_scale)
|
||||
if (!has_z_shear && !has_z_scale && !has_rotation)
|
||||
return result;
|
||||
|
||||
double shear_factor = has_z_shear
|
||||
@@ -267,6 +302,51 @@ BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
|
||||
result.floor_params.shear_factor = effective_shear;
|
||||
result.floor_params.from_axis = from;
|
||||
result.floor_params.z_shift = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.);
|
||||
} else if (has_rotation) {
|
||||
// Rotation-only path (no Z-shear): sweep 8 bbox corners through R.
|
||||
double angle_rad = Geometry::deg2rad(rot_angle);
|
||||
Vec3d unit_axis;
|
||||
switch (rot_axis) {
|
||||
case BeltRotationAxis::X: unit_axis = Vec3d::UnitX(); break;
|
||||
case BeltRotationAxis::Y: unit_axis = Vec3d::UnitY(); break;
|
||||
case BeltRotationAxis::Z: unit_axis = Vec3d::UnitZ(); break;
|
||||
default: unit_axis = Vec3d::UnitX(); break;
|
||||
}
|
||||
Matrix3d R = Eigen::AngleAxisd(angle_rad, unit_axis).toRotationMatrix();
|
||||
double min_rz = std::numeric_limits<double>::max();
|
||||
double max_rz = std::numeric_limits<double>::lowest();
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
Vec3d c((i & 1) ? bb.max.x() : bb.min.x(),
|
||||
(i & 2) ? bb.max.y() : bb.min.y(),
|
||||
(i & 4) ? bb.max.z() : bb.min.z());
|
||||
double z = (R * c).z();
|
||||
min_rz = std::min(min_rz, z);
|
||||
max_rz = std::max(max_rz, z);
|
||||
}
|
||||
// Optional Z-scale still applies multiplicatively if both are set.
|
||||
result.object_height = (max_rz - min_rz) * (has_z_scale ? scale_z : 1.0);
|
||||
|
||||
// Belt floor in slicer-frame is the image of z_machine = 0 under R.
|
||||
// R(+α, X): point (·, y, 0) → (·, cos α · y, sin α · y) ⇒ z = tan(α) · y_s
|
||||
// R(+α, Y): point (x, ·, 0) → (cos α · x, ·, -sin α · x) ⇒ z = -tan(α) · x_s
|
||||
// R(+α, Z): point (·, ·, 0) → (·, ·, 0); no tilt → no floor
|
||||
double sin_a = std::sin(angle_rad), cos_a = std::cos(angle_rad);
|
||||
switch (rot_axis) {
|
||||
case BeltRotationAxis::X:
|
||||
result.floor_params.shear_factor = (std::abs(cos_a) > EPSILON) ? sin_a / cos_a : 0.;
|
||||
result.floor_params.from_axis = 1; // Y
|
||||
break;
|
||||
case BeltRotationAxis::Y:
|
||||
result.floor_params.shear_factor = (std::abs(cos_a) > EPSILON) ? -sin_a / cos_a : 0.;
|
||||
result.floor_params.from_axis = 0; // X
|
||||
break;
|
||||
case BeltRotationAxis::Z:
|
||||
default:
|
||||
result.floor_params.shear_factor = 0.0;
|
||||
result.floor_params.from_axis = 1;
|
||||
break;
|
||||
}
|
||||
result.floor_params.z_shift = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.);
|
||||
} else {
|
||||
result.object_height = original_height * scale_z;
|
||||
}
|
||||
|
||||
@@ -92,6 +92,12 @@ public:
|
||||
std::abs(sz - 1.) > EPSILON;
|
||||
}
|
||||
|
||||
static bool has_rotation(const PrintConfig &config)
|
||||
{
|
||||
return config.belt_slice_rotation.value != BeltRotationAxis::None &&
|
||||
std::abs(config.belt_slice_rotation_angle.value) > EPSILON;
|
||||
}
|
||||
|
||||
// ---- Matrix builders --------------------------------------------------
|
||||
|
||||
// Build the pre-slice axis remap transform (includes Rev-mode translation).
|
||||
@@ -105,6 +111,11 @@ public:
|
||||
// Also sets has_scale_out if non-null.
|
||||
static Matrix3d build_scale_matrix(const PrintConfig &config, bool *has_scale_out = nullptr);
|
||||
|
||||
// Build the 3x3 rotation matrix from belt_slice_rotation* config.
|
||||
// Returns Identity if rotation axis is None or angle is ~0.
|
||||
// Also sets has_rot_out if non-null.
|
||||
static Matrix3d build_rotation_matrix(const PrintConfig &config, bool *has_rot_out = nullptr);
|
||||
|
||||
// Combined forward transform. Shear/scale order is selected by
|
||||
// belt_mesh_transform_order so the result matches what BeltSliceStrategy
|
||||
// applied to the mesh (BeltBackTransform inverts this).
|
||||
|
||||
@@ -84,12 +84,12 @@ FirstLayerPlane::FirstLayerPlane(const PrintConfig &config)
|
||||
// -------- Resolve Auto -------------------------------------------------
|
||||
FirstLayerPlaneMode mode = config.first_layer_plane.value;
|
||||
if (mode == FirstLayerPlaneMode::Auto) {
|
||||
if (config.belt_printer.value &&
|
||||
config.belt_shear_z.value != BeltShearMode::None) {
|
||||
mode = FirstLayerPlaneMode::BeltShear;
|
||||
} else {
|
||||
mode = FirstLayerPlaneMode::XY;
|
||||
}
|
||||
bool belt_affine_active = config.belt_printer.value &&
|
||||
(config.belt_shear_z.value != BeltShearMode::None ||
|
||||
(config.belt_slice_rotation.value != BeltRotationAxis::None &&
|
||||
std::abs(config.belt_slice_rotation_angle.value) > EPSILON));
|
||||
mode = belt_affine_active ? FirstLayerPlaneMode::BeltAffine
|
||||
: FirstLayerPlaneMode::XY;
|
||||
}
|
||||
m_mode = mode;
|
||||
|
||||
@@ -130,7 +130,7 @@ FirstLayerPlane::FirstLayerPlane(const PrintConfig &config)
|
||||
m_active = true;
|
||||
return;
|
||||
|
||||
case FirstLayerPlaneMode::BeltShear: {
|
||||
case FirstLayerPlaneMode::BeltAffine: {
|
||||
// Compute the slicing-frame plane that maps to machine_Z = user_offset
|
||||
// under the gcode axis remap (and optional back-transform).
|
||||
MachineZAffine mz = compute_machine_z_affine(config);
|
||||
|
||||
@@ -15,9 +15,12 @@ bool BeltBackTransform::init_from_config(const PrintConfig &config)
|
||||
bool has_global_shear = config.belt_shear_x_global.value ||
|
||||
config.belt_shear_y_global.value ||
|
||||
config.belt_shear_z_global.value;
|
||||
bool has_global_rotation = config.belt_slice_rotation_global.value
|
||||
&& config.belt_slice_rotation.value != BeltRotationAxis::None;
|
||||
bool has_preslice_global = config.belt_preslice_global.value
|
||||
|| config.preslice_remap_global.value;
|
||||
if (!has_global_shear && !has_preslice_global && !BeltTransformPipeline::has_preslice_remap(config))
|
||||
if (!has_global_shear && !has_global_rotation && !has_preslice_global
|
||||
&& !BeltTransformPipeline::has_preslice_remap(config))
|
||||
return false;
|
||||
|
||||
// Build the forward pipeline (scale * shear * pre_remap) and store its inverse.
|
||||
|
||||
@@ -1329,6 +1329,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"belt_shear_y", "belt_shear_y_angle", "belt_shear_y_from", "belt_shear_y_global",
|
||||
"belt_shear_z", "belt_shear_z_angle", "belt_shear_z_from", "belt_shear_z_global",
|
||||
"belt_scale_x", "belt_scale_x_angle", "belt_scale_y", "belt_scale_y_angle", "belt_scale_z", "belt_scale_z_angle",
|
||||
"belt_slice_rotation", "belt_slice_rotation_angle", "belt_slice_rotation_global",
|
||||
"belt_mesh_transform_order",
|
||||
"preslice_remap_x", "preslice_remap_y", "preslice_remap_z", "preslice_remap_global",
|
||||
"gcode_remap_x", "gcode_remap_y", "gcode_remap_z", "gcode_back_transform",
|
||||
|
||||
@@ -1536,6 +1536,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
// so each gets independent layer Z values.
|
||||
bool belt_force_separate = m_config.belt_printer.value && (
|
||||
(m_config.belt_shear_z_global.value && m_config.belt_shear_z.value != BeltShearMode::None)
|
||||
|| (m_config.belt_slice_rotation_global.value
|
||||
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None
|
||||
&& std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON)
|
||||
|| m_config.belt_preslice_global.value
|
||||
|| (m_config.preslice_remap_global.value && BeltTransformPipeline::has_preslice_remap(m_config)));
|
||||
model_object_status.print_instances = print_objects_from_model_object(*model_object, this->shrinkage_compensation(), belt_force_separate);
|
||||
@@ -1629,6 +1632,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
if (belt_instances_shifted
|
||||
&& m_config.belt_printer.value
|
||||
&& ((m_config.belt_shear_z_global.value && m_config.belt_shear_z.value != BeltShearMode::None)
|
||||
|| (m_config.belt_slice_rotation_global.value
|
||||
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None
|
||||
&& std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON)
|
||||
|| m_config.belt_preslice_global.value
|
||||
|| (m_config.preslice_remap_global.value && BeltTransformPipeline::has_preslice_remap(m_config)))) {
|
||||
for (PrintObject *object : m_objects)
|
||||
|
||||
@@ -326,6 +326,14 @@ static t_config_enum_values s_keys_map_BeltAxis {
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltAxis)
|
||||
|
||||
static t_config_enum_values s_keys_map_BeltRotationAxis {
|
||||
{ "none", int(BeltRotationAxis::None) },
|
||||
{ "x", int(BeltRotationAxis::X) },
|
||||
{ "y", int(BeltRotationAxis::Y) },
|
||||
{ "z", int(BeltRotationAxis::Z) },
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltRotationAxis)
|
||||
|
||||
static t_config_enum_values s_keys_map_BeltTransformOrder {
|
||||
{ "scale_then_shear", int(BeltTransformOrder::ScaleThenShear) },
|
||||
{ "shear_then_scale", int(BeltTransformOrder::ShearThenScale) },
|
||||
@@ -374,7 +382,10 @@ static t_config_enum_values s_keys_map_FirstLayerPlaneMode {
|
||||
{ "xy", int(FirstLayerPlaneMode::XY) },
|
||||
{ "yz", int(FirstLayerPlaneMode::YZ) },
|
||||
{ "xz", int(FirstLayerPlaneMode::XZ) },
|
||||
{ "belt_shear", int(FirstLayerPlaneMode::BeltShear) },
|
||||
{ "belt_affine", int(FirstLayerPlaneMode::BeltAffine) },
|
||||
// Back-compat alias: pre-rotation builds serialised this mode as
|
||||
// "belt_shear". Accept it on parse so old 3MFs / presets keep loading.
|
||||
{ "belt_shear", int(FirstLayerPlaneMode::BeltAffine) },
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FirstLayerPlaneMode)
|
||||
|
||||
@@ -6429,9 +6440,9 @@ void PrintConfigDef::init_fff_params()
|
||||
add_belt_axis_enum ("belt_shear_y_from", "From", "Source axis for Y shear.", BeltAxis::Z, comExpert);
|
||||
add_belt_shear_global("belt_shear_y_global", "Global");
|
||||
|
||||
add_belt_shear_mode ("belt_shear_z", "Function", BeltShearMode::PosTan);
|
||||
add_belt_shear_angle("belt_shear_z_angle", "Angle");
|
||||
add_belt_axis_enum ("belt_shear_z_from", "From", "Source axis for Z shear.", BeltAxis::Y);
|
||||
add_belt_shear_mode ("belt_shear_z", "Function", BeltShearMode::None, comExpert);
|
||||
add_belt_shear_angle("belt_shear_z_angle", "Angle", comExpert);
|
||||
add_belt_axis_enum ("belt_shear_z_from", "From", "Source axis for Z shear.", BeltAxis::Y, comExpert);
|
||||
add_belt_shear_global("belt_shear_z_global", "Global", true);
|
||||
|
||||
// Per-axis scale controls for belt printer
|
||||
@@ -6463,12 +6474,47 @@ void PrintConfigDef::init_fff_params()
|
||||
add_belt_scale_mode ("belt_scale_x", "Function", BeltScaleMode::None, comExpert);
|
||||
add_belt_scale_angle("belt_scale_x_angle", "Angle", comExpert);
|
||||
|
||||
add_belt_scale_mode ("belt_scale_y", "Function", BeltScaleMode::None);
|
||||
add_belt_scale_angle("belt_scale_y_angle", "Angle");
|
||||
add_belt_scale_mode ("belt_scale_y", "Function", BeltScaleMode::None, comExpert);
|
||||
add_belt_scale_angle("belt_scale_y_angle", "Angle", comExpert);
|
||||
|
||||
add_belt_scale_mode ("belt_scale_z", "Function", BeltScaleMode::None, comExpert);
|
||||
add_belt_scale_angle("belt_scale_z_angle", "Angle", comExpert);
|
||||
|
||||
// Global slicing rotation (alternative to per-axis shear+scale).
|
||||
def = this->add("belt_slice_rotation", coEnum);
|
||||
def->label = L("Slicing rotation axis");
|
||||
def->category = L("Printable space");
|
||||
def->tooltip = L("Rotate the mesh by this axis before slicing. Use this for an "
|
||||
"isometric (no shear distortion) belt slicing transform. "
|
||||
"Mutually exclusive with the per-axis shear/scale controls "
|
||||
"in the UI; the pipeline composes them if both are set in JSON.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<BeltRotationAxis>::get_enum_values();
|
||||
def->enum_values = {"none", "x", "y", "z"};
|
||||
def->enum_labels = {L("None"), L("X"), L("Y"), L("Z")};
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<BeltRotationAxis>(BeltRotationAxis::None));
|
||||
|
||||
def = this->add("belt_slice_rotation_angle", coFloat);
|
||||
def->label = L("Slicing rotation angle");
|
||||
def->category = L("Printable space");
|
||||
def->tooltip = L("Magnitude of the slicing rotation, in degrees. Positive values "
|
||||
"rotate counter-clockwise looking down the positive axis.");
|
||||
def->sidetext = L("°");
|
||||
def->min = -180.;
|
||||
def->max = 180.;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.));
|
||||
|
||||
def = this->add("belt_slice_rotation_global", coBool);
|
||||
def->label = L("Global");
|
||||
def->category = L("Printable space");
|
||||
def->tooltip = L("Treat the slicing rotation as part of the global forward transform "
|
||||
"that BeltBackTransform inverts before the machine-frame remap. "
|
||||
"Required for rotation-mode belt printers; mirrors belt_shear_z_global. "
|
||||
"Defaults to on because virtually all rotation-mode printers need it.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
auto add_belt_transform_order = [this](const char *key, const char *label, const char *tooltip) {
|
||||
auto def = this->add(key, coEnum);
|
||||
def->label = L(label);
|
||||
@@ -6597,7 +6643,8 @@ void PrintConfigDef::init_fff_params()
|
||||
// First-layer plane: which surface defines "first layer" for fan / speed /
|
||||
// accel decisions. On belt printers the slicing-frame layer 0 is a tilted
|
||||
// slab that no longer corresponds to the physical first printed layer.
|
||||
// Auto picks BeltShear when belt_shear_z != None, otherwise XY (legacy).
|
||||
// Auto picks BeltAffine when any belt-side affine transform is active
|
||||
// (Z shear or slicing rotation), otherwise XY (legacy).
|
||||
def = this->add("first_layer_plane", coEnum);
|
||||
def->label = L("First layer plane");
|
||||
def->category = L("Printable space");
|
||||
@@ -6605,14 +6652,15 @@ void PrintConfigDef::init_fff_params()
|
||||
"first-layer settings (no fan, slow speed, initial-layer accel/jerk, "
|
||||
"deferred temperature drop). On belt printers a single slicing layer "
|
||||
"contains paths at many machine-Z values, so layer-index based detection "
|
||||
"fails. Auto resolves to Belt shear plane when belt_shear_z is non-None, "
|
||||
"otherwise XY (legacy). Pick XY explicitly to opt out and force the "
|
||||
"legacy slicing-layer-0 detection.");
|
||||
"fails. Auto resolves to Belt affine plane when any belt-side affine "
|
||||
"transform (Z shear or slicing rotation) is active, otherwise XY (legacy). "
|
||||
"Pick XY explicitly to opt out and force the legacy slicing-layer-0 "
|
||||
"detection.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<FirstLayerPlaneMode>::get_enum_values();
|
||||
def->enum_values = {"auto", "xy", "yz", "xz", "belt_shear"};
|
||||
def->enum_labels = {L("Auto"), L("XY (machine bed)"), L("YZ"), L("XZ"), L("Belt shear plane")};
|
||||
def->enum_values = {"auto", "xy", "yz", "xz", "belt_affine"};
|
||||
def->enum_labels = {L("Auto"), L("XY (machine bed)"), L("YZ"), L("XZ"), L("Belt affine plane")};
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionEnum<FirstLayerPlaneMode>(FirstLayerPlaneMode::BeltShear));
|
||||
def->set_default_value(new ConfigOptionEnum<FirstLayerPlaneMode>(FirstLayerPlaneMode::BeltAffine));
|
||||
|
||||
def = this->add("first_layer_plane_offset", coFloat);
|
||||
def->label = L("Belt plane offset");
|
||||
|
||||
@@ -194,6 +194,17 @@ enum class BeltAxis
|
||||
Z = 2,
|
||||
};
|
||||
|
||||
// Axis around which the mesh is rotated before slicing, when
|
||||
// `belt_slice_rotation` is set. None disables the rotation stage.
|
||||
// Distinct from BeltAxis because BeltAxis carries no "None" semantics.
|
||||
enum class BeltRotationAxis
|
||||
{
|
||||
None = 0,
|
||||
X = 1,
|
||||
Y = 2,
|
||||
Z = 3,
|
||||
};
|
||||
|
||||
// Order in which the belt shear and scale matrices are composed.
|
||||
// ScaleThenShear: applied to a point p, the result is shear(scale(p)).
|
||||
// ShearThenScale: applied to a point p, the result is scale(shear(p)).
|
||||
@@ -231,8 +242,9 @@ enum class BeltSupportZOffsetMode
|
||||
//
|
||||
// Auto resolves to:
|
||||
// - XY (inactive, legacy behavior) for non-belt printers and for belt
|
||||
// printers with no Z-axis shear.
|
||||
// - BeltShear for belt printers with belt_shear_z != None.
|
||||
// printers with no active belt-side transform.
|
||||
// - BeltAffine for belt printers with any active belt-side affine
|
||||
// transform (Z shear, slicing rotation, or both).
|
||||
//
|
||||
// XY is also used as an explicit "opt out" mode that forces legacy
|
||||
// per-layer first-layer detection even on belt printers.
|
||||
@@ -242,7 +254,7 @@ enum class FirstLayerPlaneMode
|
||||
XY,
|
||||
YZ,
|
||||
XZ,
|
||||
BeltShear,
|
||||
BeltAffine, // formerly BeltShear; renamed to reflect rotation support
|
||||
};
|
||||
|
||||
enum SupportMaterialPattern {
|
||||
@@ -607,6 +619,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SlicingMode)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltShearMode)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltScaleMode)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltAxis)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltRotationAxis)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltTransformOrder)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(RemapAxis)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportFloorMode)
|
||||
@@ -1579,6 +1592,12 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionFloat, belt_scale_y_angle))
|
||||
((ConfigOptionEnum<BeltScaleMode>, belt_scale_z))
|
||||
((ConfigOptionFloat, belt_scale_z_angle))
|
||||
// Global mesh rotation as an alternative to per-axis shear/scale (isometric
|
||||
// slicing transform). Composes with shear in the pipeline math; UI gates
|
||||
// them as mutually exclusive.
|
||||
((ConfigOptionEnum<BeltRotationAxis>, belt_slice_rotation))
|
||||
((ConfigOptionFloat, belt_slice_rotation_angle))
|
||||
((ConfigOptionBool, belt_slice_rotation_global))
|
||||
((ConfigOptionEnum<RemapAxis>, preslice_remap_x))
|
||||
((ConfigOptionEnum<RemapAxis>, preslice_remap_y))
|
||||
((ConfigOptionEnum<RemapAxis>, preslice_remap_z))
|
||||
|
||||
@@ -174,12 +174,15 @@ static std::vector<VolumeSlices> slice_volumes_inner(
|
||||
belt_strategy->apply_to_trafo(params_base.trafo, model_volumes, has_remap, out_belt_min_z);
|
||||
}
|
||||
// If only remap (no belt), still need z-shift detection.
|
||||
// Compose per-volume mv->get_matrix() so assembly volumes contribute their
|
||||
// actual object-space positions — see the matching note in BeltSliceStrategy.
|
||||
if (has_remap && !print_config.belt_printer.value) {
|
||||
double min_z = std::numeric_limits<double>::max();
|
||||
for (const ModelVolume *mv : model_volumes) {
|
||||
if (!mv->is_model_part()) continue;
|
||||
Transform3d vol_trafo = params_base.trafo * mv->get_matrix();
|
||||
for (const stl_vertex &v : mv->mesh().its.vertices) {
|
||||
Vec3d pt = params_base.trafo * v.cast<double>();
|
||||
Vec3d pt = vol_trafo * v.cast<double>();
|
||||
min_z = std::min(min_z, pt.z());
|
||||
}
|
||||
}
|
||||
@@ -322,17 +325,35 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
||||
}
|
||||
} else {
|
||||
zs_complex.reserve(zs.size());
|
||||
// region.bbox is computed in pre-belt-shear slicer space (see PrintApply.cpp::trafo_for_bbox).
|
||||
// When belt transforms are active, layer Z values are in post-shear/scale/remap space,
|
||||
// region.bbox is computed in pre-belt-transform slicer space (see PrintApply.cpp::trafo_for_bbox).
|
||||
// When belt transforms are active, layer Z values are in post-rotation/shear/scale/remap space,
|
||||
// so the Z components of region.bbox aren't comparable to z. Skipping the Z filter here
|
||||
// pushes those layers into the parallel_for path below, which handles multi-volume
|
||||
// clipping per layer without relying on the bbox Z range.
|
||||
const bool bbox_z_in_layer_frame = !(print_config.belt_printer.value &&
|
||||
(BeltTransformPipeline::has_shear(print_config)
|
||||
|| BeltTransformPipeline::has_scale(print_config)
|
||||
|| BeltTransformPipeline::has_rotation(print_config)
|
||||
|| BeltTransformPipeline::has_preslice_remap(print_config)));
|
||||
// Belt-transform addendum: with bbox-Z untrusted, the simple path's
|
||||
// "first model_part wins" logic drops subsequent volumes' slices unless
|
||||
// they XY-overlap with the first. Assemblies whose volumes are stacked
|
||||
// or side-by-side in pre-transform Z (different bbox.z ranges) thus lose
|
||||
// the volumes that originally sat outside the first volume's Z range —
|
||||
// showing up as truncation at the top or bottom of the assembly. Force
|
||||
// every layer in a multi-volume range through the parallel_for path,
|
||||
// which correctly merges all volumes per layer.
|
||||
int num_model_parts = 0;
|
||||
for (const PrintObjectRegions::VolumeRegion &vr : layer_range.volume_regions)
|
||||
if (vr.model_volume->is_model_part())
|
||||
++num_model_parts;
|
||||
const bool force_complex_for_belt = !bbox_z_in_layer_frame && num_model_parts > 1;
|
||||
for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.second; ++ z_idx) {
|
||||
float z = zs[z_idx];
|
||||
if (force_complex_for_belt) {
|
||||
zs_complex.push_back({ z_idx, z });
|
||||
continue;
|
||||
}
|
||||
int idx_first_printable_region = -1;
|
||||
bool complex = false;
|
||||
for (int idx_region = 0; idx_region < int(layer_range.volume_regions.size()); ++ idx_region) {
|
||||
@@ -949,7 +970,81 @@ void PrintObject::slice()
|
||||
BOOST_LOG_TRIVIAL(warning) << "Belt global: object " << this->model_object()->name
|
||||
<< " instances=" << this->instances().size()
|
||||
<< " shift=(" << unscale<double>(inst_shift.x()) << ", " << unscale<double>(inst_shift.y()) << ")";
|
||||
double global_z_offset = 0.;
|
||||
|
||||
// Per-object Z-shift compensation, applied regardless of global mode.
|
||||
//
|
||||
// BeltSliceStrategy::apply_to_trafo lifts the mesh by max(0, -m_belt_min_z)
|
||||
// so the slicer can slice with slicer_z >= 0. BeltBackTransform inverts
|
||||
// build_forward_transform() which DOES NOT include this per-object
|
||||
// Z-shift (it's not known until vertex scan time). Result: G-code
|
||||
// coords emerge offset by the un-undone Z-shift — for shear that's a
|
||||
// pure machine_z lift; for rotation it leaks into both machine_y and
|
||||
// machine_z because the inverse rotation couples slicer_z back into
|
||||
// both axes. Compensating layer.print_z by shear_min_z here makes the
|
||||
// back-transform produce correct machine-frame coordinates whether or
|
||||
// not any global mode is active.
|
||||
//
|
||||
// (The original global-mode-only application of shear_min_z was sized
|
||||
// for cube-vs-inverted-cone-tip differentiation; the same compensation
|
||||
// is what fixes assemblies and rotation-mode parts where z_shift > 0.)
|
||||
double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg)
|
||||
? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.;
|
||||
double shear_min_z = m_belt_min_z - belt_surface_z;
|
||||
double global_z_offset = shear_min_z;
|
||||
|
||||
// Centering correction: trafo_centered pretranslates by
|
||||
// -m_center_offset.{x,y}. Under the belt forward transform, the
|
||||
// Y component of that pretranslate couples into slicer-Z (shear:
|
||||
// tan*c.y, rotation: sin*c.y). BeltBackTransform inverts the
|
||||
// rotation/shear but doesn't undo centering, so this Z component
|
||||
// leaks into machine output as a position offset whenever
|
||||
// m_center_offset != 0. When a user moves a volume within an
|
||||
// assembly such that the combined bbox center shifts, this shows
|
||||
// up as a small Z translation in the print. Compensate by adding
|
||||
// the Z component of the centering through the forward transform.
|
||||
{
|
||||
Transform3d T_fwd = BeltTransformPipeline::build_forward_transform(pcfg);
|
||||
Vec3d c_off(unscale<double>(m_center_offset.x()),
|
||||
unscale<double>(m_center_offset.y()),
|
||||
0.);
|
||||
double centering_z_corr = (T_fwd.linear() * c_off).z();
|
||||
global_z_offset += centering_z_corr;
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] centering correction"
|
||||
<< " obj=" << this->model_object()->name
|
||||
<< " m_center_offset_mm=(" << c_off.x() << "," << c_off.y() << ")"
|
||||
<< " centering_z_corr=" << centering_z_corr
|
||||
<< " (added to global_z_offset)";
|
||||
}
|
||||
|
||||
// [BELT-DEBUG] Per-object summary so Case A vs Case B can be compared
|
||||
// side-by-side. Lays out every value that feeds into the final layer
|
||||
// print_z adjustment.
|
||||
{
|
||||
BoundingBoxf3 raw_bb = this->model_object()->raw_bounding_box();
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] slice() per-object summary"
|
||||
<< " obj=" << this->model_object()->name
|
||||
<< " n_volumes=" << this->model_object()->volumes.size()
|
||||
<< " raw_bbox.min=(" << raw_bb.min.x() << "," << raw_bb.min.y() << "," << raw_bb.min.z() << ")"
|
||||
<< " raw_bbox.max=(" << raw_bb.max.x() << "," << raw_bb.max.y() << "," << raw_bb.max.z() << ")"
|
||||
<< " raw_bbox.center=(" << raw_bb.center().x() << "," << raw_bb.center().y() << ")"
|
||||
<< " m_center_offset=(" << unscale<double>(m_center_offset.x()) << "," << unscale<double>(m_center_offset.y()) << ")"
|
||||
<< " inst_shift=(" << unscale<double>(inst_shift.x()) << "," << unscale<double>(inst_shift.y()) << ")"
|
||||
<< " m_belt_min_z=" << m_belt_min_z
|
||||
<< " belt_surface_z=" << belt_surface_z
|
||||
<< " shear_min_z=" << shear_min_z;
|
||||
// Per-volume bbox + get_matrix translation so order/composition is visible.
|
||||
int vi = 0;
|
||||
for (const ModelVolume *mv : this->model_object()->volumes) {
|
||||
if (!mv->is_model_part()) { ++vi; continue; }
|
||||
BoundingBoxf3 vol_bb = mv->mesh().transformed_bounding_box(mv->get_matrix());
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] vol[" << vi
|
||||
<< "] id=" << mv->id().id << " name='" << mv->name << "'"
|
||||
<< " get_matrix.translation=(" << mv->get_matrix().translation().x() << "," << mv->get_matrix().translation().y() << "," << mv->get_matrix().translation().z() << ")"
|
||||
<< " object_bbox.min=(" << vol_bb.min.x() << "," << vol_bb.min.y() << "," << vol_bb.min.z() << ")"
|
||||
<< " object_bbox.max=(" << vol_bb.max.x() << "," << vol_bb.max.y() << "," << vol_bb.max.z() << ")";
|
||||
++vi;
|
||||
}
|
||||
}
|
||||
|
||||
if (pcfg.belt_preslice_global.value) {
|
||||
// Global pre-slice mode: compute full correction c = (T.linear() - I) * d
|
||||
@@ -957,18 +1052,7 @@ void PrintObject::slice()
|
||||
Transform3d T = BeltTransformPipeline::build_forward_transform(pcfg);
|
||||
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
|
||||
Vec3d c = T.linear() * d - d;
|
||||
|
||||
// Per-object shape contribution: BeltSliceStrategy::apply_to_trafo
|
||||
// lifts the mesh by max(0, -m_belt_min_z) to keep slicer-frame Z
|
||||
// above 0. Two objects at the same bed position but different
|
||||
// m_belt_min_z (e.g. cube vs inverted-cone tip) otherwise end up
|
||||
// at the same print_z, which causes the inverted-cone tip to
|
||||
// start on the same layer as the cube's lowest sheared corner.
|
||||
double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg)
|
||||
? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.;
|
||||
double shear_min_z = m_belt_min_z - belt_surface_z;
|
||||
|
||||
global_z_offset = c.z() + shear_min_z;
|
||||
global_z_offset += c.z();
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_global_xy_correction tid=" << std::this_thread::get_id()
|
||||
<< " obj=" << this << " old=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y()
|
||||
<< ") new=(" << c.x() << "," << c.y() << ")";
|
||||
@@ -999,10 +1083,7 @@ void PrintObject::slice()
|
||||
Transform3d T = BeltTransformPipeline::build_forward_transform(pcfg);
|
||||
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
|
||||
Vec3d c = T.linear() * d - d;
|
||||
double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg)
|
||||
? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.;
|
||||
double shear_min_z = m_belt_min_z - belt_surface_z;
|
||||
global_z_offset += c.z() + shear_min_z;
|
||||
global_z_offset += c.z();
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_global_xy_correction tid=" << std::this_thread::get_id()
|
||||
<< " obj=" << this << " old=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y()
|
||||
<< ") new=(" << c.x() << "," << c.y() << ")";
|
||||
@@ -1012,6 +1093,19 @@ void PrintObject::slice()
|
||||
<< " shear_min_z=" << shear_min_z << " (m_belt_min_z=" << m_belt_min_z << ")";
|
||||
}
|
||||
|
||||
// Slicing rotation in global mode: bed-position-dependent Z offset.
|
||||
// For R(α, X): c.z = sin(α)*d.y so objects at different bed-Y
|
||||
// values print at different machine Z values along the inclined belt.
|
||||
if (pcfg.belt_slice_rotation_global.value
|
||||
&& pcfg.belt_slice_rotation.value != BeltRotationAxis::None
|
||||
&& std::abs(pcfg.belt_slice_rotation_angle.value) > EPSILON) {
|
||||
Transform3d T = BeltTransformPipeline::build_forward_transform(pcfg);
|
||||
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
|
||||
Vec3d c = T.linear() * d - d;
|
||||
global_z_offset += c.z();
|
||||
m_belt_global_xy_correction = Vec2d(c.x(), c.y());
|
||||
}
|
||||
|
||||
// Pre-slice remap global mode: when on, the remap accounts for the
|
||||
// instance bed position. The Z component of the correction
|
||||
// (R - I) * d shifts layer print_z so e.g. a Y↔Z swap with an
|
||||
@@ -1030,6 +1124,15 @@ void PrintObject::slice()
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELTRACE] write m_belt_global_z_offset tid=" << std::this_thread::get_id()
|
||||
<< " obj=" << this << " old=" << m_belt_global_z_offset << " new=" << global_z_offset;
|
||||
m_belt_global_z_offset = global_z_offset;
|
||||
// [BELT-DEBUG] Final breakdown of all contributions to layer.print_z
|
||||
// and where the first / last layer end up post-adjustment.
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] global_z_offset breakdown"
|
||||
<< " obj=" << this->model_object()->name
|
||||
<< " shear_min_z=" << shear_min_z
|
||||
<< " total_global_z_offset=" << global_z_offset
|
||||
<< " xy_correction=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y() << ")"
|
||||
<< " belt_floor_z_shift_before=" << (m_slicing_params.belt_floor_z_shift)
|
||||
<< " n_layers=" << m_layers.size();
|
||||
if (std::abs(global_z_offset) > EPSILON) {
|
||||
for (Layer *layer : m_layers)
|
||||
layer->print_z += global_z_offset;
|
||||
@@ -1038,6 +1141,12 @@ void PrintObject::slice()
|
||||
// layer print_z, so belt_floor_z_shift must match.
|
||||
m_slicing_params.belt_floor_z_shift += global_z_offset;
|
||||
}
|
||||
if (!m_layers.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] post-adjustment"
|
||||
<< " first_layer.print_z=" << m_layers.front()->print_z
|
||||
<< " last_layer.print_z=" << m_layers.back()->print_z
|
||||
<< " belt_floor_z_shift_after=" << m_slicing_params.belt_floor_z_shift;
|
||||
}
|
||||
if (!m_layers.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Belt global: first_layer_z=" << m_layers.front()->print_z
|
||||
<< " last_layer_z=" << m_layers.back()->print_z
|
||||
|
||||
@@ -4462,7 +4462,23 @@ void TabPrinter::build_fff()
|
||||
belt_og->append_single_option_line("belt_printer");
|
||||
belt_og->append_single_option_line("belt_printer_angle");
|
||||
belt_og->append_single_option_line("belt_printer_infinite_y");
|
||||
// Per-axis shear: group mode + angle + source on one row per axis
|
||||
// Mesh rotate (default belt-side transform): isometric, no distortion.
|
||||
// Shown above the per-axis shear/scale rows because most users should
|
||||
// pick rotation; shear/scale is the expert escape hatch.
|
||||
{
|
||||
Line line = { L("Mesh rotate"),
|
||||
L("Global mesh rotation applied before slicing. Isometric "
|
||||
"(no distortion); the back-transform inverts it before the "
|
||||
"machine-frame remap. Default belt transform. Mutually "
|
||||
"exclusive with per-axis shear/scale in the UI.") };
|
||||
line.append_option(belt_og->get_option("belt_slice_rotation"));
|
||||
line.append_option(belt_og->get_option("belt_slice_rotation_angle"));
|
||||
line.append_option(belt_og->get_option("belt_slice_rotation_global"));
|
||||
belt_og->append_line(line);
|
||||
}
|
||||
// Per-axis shear/scale: expert escape hatch for non-rigid belt transforms
|
||||
// (BlackBelt-style 1/sin scaling, asymmetric belt geometries, etc.).
|
||||
// Group mode + angle + source on one row per axis.
|
||||
{
|
||||
Line line = { L("Mesh shear X"), L("Shear applied to the X axis before slicing") };
|
||||
line.append_option(belt_og->get_option("belt_shear_x"));
|
||||
@@ -5594,17 +5610,19 @@ void TabPrinter::toggle_options()
|
||||
bool expert_or_above = (m_mode >= comExpert);
|
||||
toggle_line("belt_printer_angle", is_belt);
|
||||
toggle_line("belt_printer_infinite_y", is_belt);
|
||||
// Mesh shear/scale: only shear Z and scale Y are shown in Advanced; the others
|
||||
// are Expert-only. Each Line packs all its options on one row, so toggle the
|
||||
// Line here in addition to the per-option mode gating.
|
||||
// Mesh rotate: advanced (visible by default in belt mode).
|
||||
toggle_line("belt_slice_rotation", is_belt);
|
||||
// Mesh shear/scale: expert-only (the rigid rotation above is the
|
||||
// primary belt transform; shear/scale is reserved for power users
|
||||
// matching BlackBelt-style 1/sin scale or asymmetric belt geometries).
|
||||
toggle_line("belt_shear_x", is_belt && expert_or_above);
|
||||
toggle_line("belt_shear_y", is_belt && expert_or_above);
|
||||
toggle_line("belt_shear_z", is_belt);
|
||||
toggle_line("belt_shear_z", is_belt && expert_or_above);
|
||||
toggle_line("belt_scale_x", is_belt && expert_or_above);
|
||||
toggle_line("belt_scale_y", is_belt);
|
||||
toggle_line("belt_scale_y", is_belt && expert_or_above);
|
||||
toggle_line("belt_scale_z", is_belt && expert_or_above);
|
||||
for (auto el : {"belt_mesh_transform_order",
|
||||
"belt_origin_snap_x", "belt_origin_snap_y", "belt_origin_snap_z"})
|
||||
toggle_line("belt_mesh_transform_order", is_belt && expert_or_above);
|
||||
for (auto el : {"belt_origin_snap_x", "belt_origin_snap_y", "belt_origin_snap_z"})
|
||||
toggle_line(el, is_belt);
|
||||
|
||||
// Remap, back-transform, and global mesh-transforms toggles are gated by belt
|
||||
@@ -5619,31 +5637,59 @@ void TabPrinter::toggle_options()
|
||||
// preslice_remap_global: superseded by belt_preslice_global
|
||||
toggle_option("preslice_remap_global", is_belt && !belt_global);
|
||||
|
||||
// Mutual exclusion (UI only): slicing rotation and per-axis shear/scale are
|
||||
// alternatives in the printer panel. The pipeline math composes them; this
|
||||
// just disables the inactive set so users pick one path or the other.
|
||||
auto rot_axis = m_config->option<ConfigOptionEnum<BeltRotationAxis>>("belt_slice_rotation")->value;
|
||||
bool rotation_active = is_belt
|
||||
&& rot_axis != BeltRotationAxis::None
|
||||
&& std::abs(m_config->opt_float("belt_slice_rotation_angle")) > 1e-9;
|
||||
bool shear_or_scale_active = is_belt && (
|
||||
m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_x")->value != BeltShearMode::None ||
|
||||
m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_y")->value != BeltShearMode::None ||
|
||||
m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_z")->value != BeltShearMode::None ||
|
||||
m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_x")->value != BeltScaleMode::None ||
|
||||
m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_y")->value != BeltScaleMode::None ||
|
||||
m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_z")->value != BeltScaleMode::None);
|
||||
bool allow_shear_scale = !rotation_active;
|
||||
bool allow_rotation = !shear_or_scale_active;
|
||||
|
||||
// Disable shear/scale mode dropdowns when rotation is active.
|
||||
for (auto el : {"belt_shear_x", "belt_shear_y", "belt_shear_z",
|
||||
"belt_scale_x", "belt_scale_y", "belt_scale_z",
|
||||
"belt_mesh_transform_order"})
|
||||
toggle_option(el, is_belt && allow_shear_scale);
|
||||
|
||||
// Disable rotation controls when any shear/scale is active.
|
||||
toggle_option("belt_slice_rotation", is_belt && allow_rotation);
|
||||
toggle_option("belt_slice_rotation_angle", is_belt && allow_rotation && rot_axis != BeltRotationAxis::None);
|
||||
toggle_option("belt_slice_rotation_global", is_belt && allow_rotation && rot_axis != BeltRotationAxis::None);
|
||||
|
||||
// Gray out angle/from sub-options when their parent shear/scale mode is None.
|
||||
// Per-axis globals are superseded when belt_preslice_global is on.
|
||||
auto sx = m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_x")->value;
|
||||
toggle_option("belt_shear_x_angle", is_belt && sx != BeltShearMode::None);
|
||||
toggle_option("belt_shear_x_from", is_belt && sx != BeltShearMode::None);
|
||||
toggle_option("belt_shear_x_global", is_belt && sx != BeltShearMode::None && !belt_global);
|
||||
toggle_option("belt_shear_x_angle", is_belt && sx != BeltShearMode::None && allow_shear_scale);
|
||||
toggle_option("belt_shear_x_from", is_belt && sx != BeltShearMode::None && allow_shear_scale);
|
||||
toggle_option("belt_shear_x_global", is_belt && sx != BeltShearMode::None && !belt_global && allow_shear_scale);
|
||||
|
||||
auto sy = m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_y")->value;
|
||||
toggle_option("belt_shear_y_angle", is_belt && sy != BeltShearMode::None);
|
||||
toggle_option("belt_shear_y_from", is_belt && sy != BeltShearMode::None);
|
||||
toggle_option("belt_shear_y_global", is_belt && sy != BeltShearMode::None && !belt_global);
|
||||
toggle_option("belt_shear_y_angle", is_belt && sy != BeltShearMode::None && allow_shear_scale);
|
||||
toggle_option("belt_shear_y_from", is_belt && sy != BeltShearMode::None && allow_shear_scale);
|
||||
toggle_option("belt_shear_y_global", is_belt && sy != BeltShearMode::None && !belt_global && allow_shear_scale);
|
||||
|
||||
auto sz = m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_z")->value;
|
||||
toggle_option("belt_shear_z_angle", is_belt && sz != BeltShearMode::None);
|
||||
toggle_option("belt_shear_z_from", is_belt && sz != BeltShearMode::None);
|
||||
toggle_option("belt_shear_z_global", is_belt && sz != BeltShearMode::None && !belt_global);
|
||||
toggle_option("belt_shear_z_angle", is_belt && sz != BeltShearMode::None && allow_shear_scale);
|
||||
toggle_option("belt_shear_z_from", is_belt && sz != BeltShearMode::None && allow_shear_scale);
|
||||
toggle_option("belt_shear_z_global", is_belt && sz != BeltShearMode::None && !belt_global && allow_shear_scale);
|
||||
|
||||
auto scx = m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_x")->value;
|
||||
toggle_option("belt_scale_x_angle", is_belt && scx != BeltScaleMode::None);
|
||||
toggle_option("belt_scale_x_angle", is_belt && scx != BeltScaleMode::None && allow_shear_scale);
|
||||
|
||||
auto scy = m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_y")->value;
|
||||
toggle_option("belt_scale_y_angle", is_belt && scy != BeltScaleMode::None);
|
||||
toggle_option("belt_scale_y_angle", is_belt && scy != BeltScaleMode::None && allow_shear_scale);
|
||||
|
||||
auto scz = m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_z")->value;
|
||||
toggle_option("belt_scale_z_angle", is_belt && scz != BeltScaleMode::None);
|
||||
toggle_option("belt_scale_z_angle", is_belt && scz != BeltScaleMode::None && allow_shear_scale);
|
||||
|
||||
// Machine-frame transforms: shown only in belt mode.
|
||||
// Mirror the Advanced/Expert split used for mesh shear/scale.
|
||||
|
||||
Reference in New Issue
Block a user