Part 2.5: Add global shear transform, support clipping, and belt UI improvements

- Implement per-object global shear transform in PrintObject with
  layer Z-offset calculation, config invalidation, and fix for
  shared-object layer optimization breaking copied objects
- Clip support layers to the transformed belt floor plane and begin
  work on tree support adaptation for sheared coordinate space
- Improve belt UI: gray out inactive sub-options, add B keyboard
  shortcut for G-code viewer design-view toggle, fix mesh clipping
  through build plate after shear/scale transform

y' = y + z·cot(α),
  while x' = x and z' = z

getting closer to customizable variant

getting closer

X/Y/Z shear initial

clean up UI

add 1/sin(a) transform, idea taken from blackbelt cura plugin

Things work now (turns out I've been using the wrong set of  transforms)
This commit is contained in:
harrierpigeon
2026-04-09 23:07:07 -05:00
parent 501aff7e53
commit 98f4d34dcb
13 changed files with 452 additions and 91 deletions
+8 -21
View File
@@ -2414,28 +2414,16 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_writer.set_is_bbl_machine(is_bbl_printers); m_writer.set_is_bbl_machine(is_bbl_printers);
// Belt printer: initialize coordinate transformation on the writer. // Belt printer: initialize coordinate transformation and axis remap on the writer.
if (print.config().belt_printer.value) { if (print.config().belt_printer.value) {
m_writer.set_belt_angle(print.config().belt_printer_angle.value); m_writer.set_belt_angle(print.config().belt_printer_angle.value);
// Compute the Z-shift that was applied during slicing (same logic as PrintObjectSlice.cpp). m_writer.set_axis_remap(
// This is needed by to_machine_coords() to undo the slicing transform. int(print.config().belt_gcode_remap_x.value),
// For multiple objects, use the minimum min_z_rotated across all objects. int(print.config().belt_gcode_remap_y.value),
double angle_rad = Geometry::deg2rad(print.config().belt_printer_angle.value); int(print.config().belt_gcode_remap_z.value));
Transform3d belt_rotation = Transform3d::Identity(); // Build volume extents for Rev remap mode.
belt_rotation.rotate(Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX())); BoundingBoxf bbox_bed(print.config().printable_area.values);
double global_min_z = std::numeric_limits<double>::max(); m_writer.set_build_volume_max(Vec3d(bbox_bed.max.x(), bbox_bed.max.y(), print.config().printable_height.value));
for (const PrintObject *obj : print.objects()) {
Transform3d obj_trafo = belt_rotation * obj->trafo_centered();
for (const ModelVolume *mv : obj->model_object()->volumes) {
if (! mv->is_model_part() && ! mv->is_modifier())
continue;
BoundingBoxf3 bb = mv->mesh().bounding_box();
bb = bb.transformed(obj_trafo * mv->get_matrix());
global_min_z = std::min(global_min_z, bb.min.z());
}
}
if (global_min_z != std::numeric_limits<double>::max() && std::abs(global_min_z) > EPSILON)
m_writer.set_belt_z_shift(global_min_z); // typically negative
} }
// How many times will be change_layer() called? // How many times will be change_layer() called?
@@ -2530,7 +2518,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// Belt printer: embed angle in header for G-code processor detection. // Belt printer: embed angle in header for G-code processor detection.
if (print.config().belt_printer.value) { if (print.config().belt_printer.value) {
file.write_format("; belt_printer_angle = %.1f\n", print.config().belt_printer_angle.value); file.write_format("; belt_printer_angle = %.1f\n", print.config().belt_printer_angle.value);
file.write_format("; belt_z_shift = %.4f\n", m_writer.belt_z_shift());
} }
if (is_bbl_printers) if (is_bbl_printers)
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
-8
View File
@@ -3051,14 +3051,6 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
} catch (...) {} } catch (...) {}
return; return;
} }
// Belt printer Z-shift for raw G-code toggle in preview.
if (boost::starts_with(comment, " belt_z_shift = ")) {
try {
m_result.belt_z_shift = std::stof(std::string(comment.substr(16)));
} catch (...) {}
return;
}
// wipe start tag // wipe start tag
if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) { if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) {
m_wiping = true; m_wiping = true;
+1 -2
View File
@@ -227,9 +227,8 @@ class Print;
bool support_traditional_timelapse{true}; bool support_traditional_timelapse{true};
float printable_height; float printable_height;
float z_offset; float z_offset;
// Belt printer: angle and Z-shift for coordinate transformation in preview. // Belt printer: angle for coordinate transformation in preview.
float belt_printer_angle{ 0.f }; float belt_printer_angle{ 0.f };
float belt_z_shift{ 0.f };
SettingsIds settings_ids; SettingsIds settings_ids;
size_t filaments_count; size_t filaments_count;
bool backtrace_enabled; bool backtrace_enabled;
+20 -14
View File
@@ -23,26 +23,32 @@ bool GCodeWriter::full_gcode_comment = true;
void GCodeWriter::set_belt_angle(double angle_deg) void GCodeWriter::set_belt_angle(double angle_deg)
{ {
m_belt_angle_rad = Geometry::deg2rad(angle_deg); m_belt_angle_rad = Geometry::deg2rad(angle_deg);
m_belt_cos = std::cos(m_belt_angle_rad); }
m_belt_sin = std::sin(m_belt_angle_rad);
void GCodeWriter::set_axis_remap(int rx, int ry, int rz)
{
m_remap_x = rx;
m_remap_y = ry;
m_remap_z = rz;
}
void GCodeWriter::set_build_volume_max(const Vec3d &max)
{
m_build_vol_max = max;
} }
Vec3d GCodeWriter::to_machine_coords(const Vec3d &pos) const Vec3d GCodeWriter::to_machine_coords(const Vec3d &pos) const
{ {
if (!is_belt_printer()) if (!is_belt_printer())
return pos; return pos;
// BeltRemapAxis: 0-2 = +X/+Y/+Z, 3-5 = -X/-Y/-Z, 6-8 = Rev X/Y/Z
// Undo the slicing transform to recover machine-frame coordinates. auto remap = [this, &pos](int r) -> double {
// Slicing applied: T(0,0,-min_z_rot) * R(-alpha, X) * original_pos int axis = r % 3;
// Inverse: R(+alpha, X) * T(0,0,+min_z_rot) * slicing_pos if (r < 3) return pos[axis];
// if (r < 6) return -pos[axis];
// First undo the Z-shift, then apply R(+alpha, X). return m_build_vol_max[axis] - pos[axis];
double sz = pos.z() + m_belt_z_shift; // undo T(0,0,-min_z_rot) };
return Vec3d( return { remap(m_remap_x), remap(m_remap_y), remap(m_remap_z) };
pos.x(),
pos.y() * m_belt_cos - sz * m_belt_sin, // R(+alpha, X)
pos.y() * m_belt_sin + sz * m_belt_cos
);
} }
bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor) bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor)
+10 -7
View File
@@ -127,10 +127,12 @@ public:
// Belt printer: set the belt angle and precompute sin/cos for coordinate transformation. // Belt printer: set the belt angle and precompute sin/cos for coordinate transformation.
void set_belt_angle(double angle_deg); void set_belt_angle(double angle_deg);
void set_belt_z_shift(double z_shift) { m_belt_z_shift = z_shift; }
double belt_z_shift() const { return m_belt_z_shift; }
bool is_belt_printer() const { return m_belt_angle_rad != 0.; } bool is_belt_printer() const { return m_belt_angle_rad != 0.; }
// Transform a point from the slicing frame to machine/world coordinates (inverse rotation). // Set axis remap for G-code output (BeltRemapAxis enum values).
void set_axis_remap(int rx, int ry, int rz);
// Set build volume extents for Rev remap mode (max X, Y, Z).
void set_build_volume_max(const Vec3d &max);
// Transform a point from the slicing frame to machine coordinates.
Vec3d to_machine_coords(const Vec3d &pos) const; Vec3d to_machine_coords(const Vec3d &pos) const;
// Returns whether this flavor supports separate print and travel acceleration. // Returns whether this flavor supports separate print and travel acceleration.
@@ -186,11 +188,12 @@ public:
//SoftFever //SoftFever
bool m_is_bbl_printers = false; bool m_is_bbl_printers = false;
// Belt printer coordinate transformation (inverse of slicing rotation) // Belt printer state
double m_belt_angle_rad = 0.; double m_belt_angle_rad = 0.;
double m_belt_cos = 1.0; int m_remap_x = 0;
double m_belt_sin = 0.0; int m_remap_y = 1;
double m_belt_z_shift = 0.; // Z-shift applied during slicing (min_z_rotated, typically negative) int m_remap_z = 2;
Vec3d m_build_vol_max = Vec3d::Zero();
double m_current_speed; double m_current_speed;
bool m_is_first_layer = true; bool m_is_first_layer = true;
+5 -1
View File
@@ -1010,7 +1010,11 @@ static std::vector<std::string> s_Preset_machine_limits_options {
static std::vector<std::string> s_Preset_printer_options { static std::vector<std::string> s_Preset_printer_options {
"printer_technology", "printer_technology",
"printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "build_plate_tilt_x", "build_plate_tilt_y", "belt_printer", "belt_printer_angle", "belt_printer_infinite_y", "gcode_flavor", "printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "build_plate_tilt_x", "build_plate_tilt_y", "belt_printer", "belt_printer_angle", "belt_printer_infinite_y", "belt_shear_x", "belt_shear_x_angle", "belt_shear_x_from",
"belt_shear_y", "belt_shear_y_angle", "belt_shear_y_from",
"belt_shear_z", "belt_shear_z_angle", "belt_shear_z_from",
"belt_scale_x", "belt_scale_x_angle", "belt_scale_y", "belt_scale_y_angle", "belt_scale_z", "belt_scale_z_angle",
"belt_gcode_remap_x", "belt_gcode_remap_y", "belt_gcode_remap_z", "gcode_flavor",
"fan_kickstart", "fan_speedup_time", "fan_speedup_overhangs", "fan_kickstart", "fan_speedup_time", "fan_speedup_overhangs",
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode", "single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type", "printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
+135
View File
@@ -288,6 +288,44 @@ static t_config_enum_values s_keys_map_SlicingMode {
}; };
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode) CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode)
static t_config_enum_values s_keys_map_BeltShearMode {
{ "none", int(BeltShearMode::None) },
{ "pos_cot", int(BeltShearMode::PosCot) },
{ "neg_cot", int(BeltShearMode::NegCot) },
{ "pos_tan", int(BeltShearMode::PosTan) },
{ "neg_tan", int(BeltShearMode::NegTan) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltShearMode)
static t_config_enum_values s_keys_map_BeltAxis {
{ "x", int(BeltAxis::X) },
{ "y", int(BeltAxis::Y) },
{ "z", int(BeltAxis::Z) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltAxis)
static t_config_enum_values s_keys_map_BeltScaleMode {
{ "none", int(BeltScaleMode::None) },
{ "inv_sin", int(BeltScaleMode::InvSin) },
{ "inv_cos", int(BeltScaleMode::InvCos) },
{ "sin", int(BeltScaleMode::Sin) },
{ "cos", int(BeltScaleMode::Cos) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltScaleMode)
static t_config_enum_values s_keys_map_BeltRemapAxis {
{ "pos_x", int(BeltRemapAxis::PosX) },
{ "pos_y", int(BeltRemapAxis::PosY) },
{ "pos_z", int(BeltRemapAxis::PosZ) },
{ "neg_x", int(BeltRemapAxis::NegX) },
{ "neg_y", int(BeltRemapAxis::NegY) },
{ "neg_z", int(BeltRemapAxis::NegZ) },
{ "rev_x", int(BeltRemapAxis::RevX) },
{ "rev_y", int(BeltRemapAxis::RevY) },
{ "rev_z", int(BeltRemapAxis::RevZ) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltRemapAxis)
static t_config_enum_values s_keys_map_SupportMaterialPattern { static t_config_enum_values s_keys_map_SupportMaterialPattern {
{ "rectilinear", smpRectilinear }, { "rectilinear", smpRectilinear },
{ "rectilinear-grid", smpRectilinearGrid }, { "rectilinear-grid", smpRectilinearGrid },
@@ -5978,6 +6016,103 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true)); def->set_default_value(new ConfigOptionBool(true));
// Per-axis shear controls for belt printer
auto add_belt_shear_mode = [this](const char *key, const char *label, BeltShearMode default_mode) {
auto def = this->add(key, coEnum);
def->label = L(label);
def->category = L("Printable space");
def->tooltip = L("Shear function applied to this axis in belt printer mode.");
def->enum_keys_map = &ConfigOptionEnum<BeltShearMode>::get_enum_values();
def->enum_values = {"none", "pos_cot", "neg_cot", "pos_tan", "neg_tan"};
def->enum_labels = {L("None"), L("+cot(α)"), L("-cot(α)"), L("+tan(α)"), L("-tan(α)")};
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<BeltShearMode>(default_mode));
};
auto add_belt_shear_angle = [this](const char *key, const char *label) {
auto def = this->add(key, coFloat);
def->label = L(label);
def->category = L("Printable space");
def->tooltip = L("Angle (degrees) for the shear function on this axis.");
def->sidetext = L("°");
def->min = 0.1;
def->max = 89.9;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(45));
};
auto add_belt_axis_enum = [this](const char *key, const char *label, const char *tooltip, BeltAxis default_axis) {
auto def = this->add(key, coEnum);
def->label = L(label);
def->category = L("Printable space");
def->tooltip = L(tooltip);
def->enum_keys_map = &ConfigOptionEnum<BeltAxis>::get_enum_values();
def->enum_values = {"x", "y", "z"};
def->enum_labels = {L("X"), L("Y"), L("Z")};
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<BeltAxis>(default_axis));
};
add_belt_shear_mode("belt_shear_x", "Function", BeltShearMode::None);
add_belt_shear_angle("belt_shear_x_angle", "Angle");
add_belt_axis_enum("belt_shear_x_from", "From", "Source axis for X shear.", BeltAxis::Z);
add_belt_shear_mode("belt_shear_y", "Function", BeltShearMode::PosCot);
add_belt_shear_angle("belt_shear_y_angle", "Angle");
add_belt_axis_enum("belt_shear_y_from", "From", "Source axis for Y shear.", BeltAxis::Z);
add_belt_shear_mode("belt_shear_z", "Function", BeltShearMode::None);
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);
// Per-axis scale controls for belt printer
auto add_belt_scale_mode = [this](const char *key, const char *label, BeltScaleMode default_mode) {
auto def = this->add(key, coEnum);
def->label = L(label);
def->category = L("Printable space");
def->tooltip = L("Scale factor applied to this axis in belt printer mode.");
def->enum_keys_map = &ConfigOptionEnum<BeltScaleMode>::get_enum_values();
def->enum_values = {"none", "inv_sin", "inv_cos", "sin", "cos"};
def->enum_labels = {L("None"), L("1/sin(α)"), L("1/cos(α)"), L("sin(α)"), L("cos(α)")};
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<BeltScaleMode>(default_mode));
};
auto add_belt_scale_angle = [this](const char *key, const char *label) {
auto def = this->add(key, coFloat);
def->label = L(label);
def->category = L("Printable space");
def->tooltip = L("Angle (degrees) for the scale function on this axis.");
def->sidetext = L("°");
def->min = 0.1;
def->max = 89.9;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(45));
};
add_belt_scale_mode("belt_scale_x", "Function", BeltScaleMode::None);
add_belt_scale_angle("belt_scale_x_angle", "Angle");
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_z", "Function", BeltScaleMode::None);
add_belt_scale_angle("belt_scale_z_angle", "Angle");
// G-code axis remap with sign
auto add_belt_remap = [this](const char *key, const char *label, const char *tooltip, BeltRemapAxis default_axis) {
auto def = this->add(key, coEnum);
def->label = L(label);
def->category = L("Printable space");
def->tooltip = L(tooltip);
def->enum_keys_map = &ConfigOptionEnum<BeltRemapAxis>::get_enum_values();
def->enum_values = {"pos_x", "pos_y", "pos_z", "neg_x", "neg_y", "neg_z", "rev_x", "rev_y", "rev_z"};
def->enum_labels = {L("+X"), L("+Y"), L("+Z"), L("-X"), L("-Y"), L("-Z"), L("Rev X"), L("Rev Y"), L("Rev Z")};
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<BeltRemapAxis>(default_axis));
};
add_belt_remap("belt_gcode_remap_x", "X", "Which slicing axis maps to machine X in G-code output.", BeltRemapAxis::PosX);
add_belt_remap("belt_gcode_remap_y", "Y", "Which slicing axis maps to machine Y in G-code output.", BeltRemapAxis::PosY);
add_belt_remap("belt_gcode_remap_z", "Z", "Which slicing axis maps to machine Z in G-code output.", BeltRemapAxis::PosZ);
def = this->add("tree_support_branch_angle", coFloat); def = this->add("tree_support_branch_angle", coFloat);
def->label = L("Tree support branch angle"); def->label = L("Tree support branch angle");
def->category = L("Support"); def->category = L("Support");
+54
View File
@@ -156,6 +156,38 @@ enum class SlicingMode
CloseHoles, CloseHoles,
}; };
enum class BeltShearMode
{
None, // No shear on this axis
PosCot, // += cot(α)
NegCot, // -= cot(α)
PosTan, // += tan(α)
NegTan, // -= tan(α)
};
enum class BeltScaleMode
{
None, // No scaling (factor = 1)
InvSin, // 1/sin(α)
InvCos, // 1/cos(α)
Sin, // sin(α)
Cos, // cos(α)
};
enum class BeltAxis
{
X = 0,
Y = 1,
Z = 2,
};
enum class BeltRemapAxis
{
PosX = 0, PosY = 1, PosZ = 2,
NegX = 3, NegY = 4, NegZ = 5,
RevX = 6, RevY = 7, RevZ = 8, // Reversed: max - pos
};
enum SupportMaterialPattern { enum SupportMaterialPattern {
smpDefault, smpDefault,
smpRectilinear, smpRectilinearGrid, smpHoneycomb, smpRectilinear, smpRectilinearGrid, smpHoneycomb,
@@ -499,6 +531,10 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NoiseType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(InfillPattern) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(InfillPattern)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(IroningType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(IroningType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SlicingMode) 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(BeltRemapAxis)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialPattern) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialPattern)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialStyle) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialStyle)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern)
@@ -1416,6 +1452,24 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionBool, belt_printer)) ((ConfigOptionBool, belt_printer))
((ConfigOptionFloat, belt_printer_angle)) ((ConfigOptionFloat, belt_printer_angle))
((ConfigOptionBool, belt_printer_infinite_y)) ((ConfigOptionBool, belt_printer_infinite_y))
((ConfigOptionEnum<BeltShearMode>, belt_shear_x))
((ConfigOptionFloat, belt_shear_x_angle))
((ConfigOptionEnum<BeltAxis>, belt_shear_x_from))
((ConfigOptionEnum<BeltShearMode>, belt_shear_y))
((ConfigOptionFloat, belt_shear_y_angle))
((ConfigOptionEnum<BeltAxis>, belt_shear_y_from))
((ConfigOptionEnum<BeltShearMode>, belt_shear_z))
((ConfigOptionFloat, belt_shear_z_angle))
((ConfigOptionEnum<BeltAxis>, belt_shear_z_from))
((ConfigOptionEnum<BeltScaleMode>, belt_scale_x))
((ConfigOptionFloat, belt_scale_x_angle))
((ConfigOptionEnum<BeltScaleMode>, belt_scale_y))
((ConfigOptionFloat, belt_scale_y_angle))
((ConfigOptionEnum<BeltScaleMode>, belt_scale_z))
((ConfigOptionFloat, belt_scale_z_angle))
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_x))
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_y))
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_z))
//BBS //BBS
((ConfigOptionInts, additional_cooling_fan_speed)) ((ConfigOptionInts, additional_cooling_fan_speed))
((ConfigOptionBool, reduce_crossing_wall)) ((ConfigOptionBool, reduce_crossing_wall))
+92 -9
View File
@@ -3392,14 +3392,55 @@ void PrintObject::update_slicing_parameters()
// Orca: updated function call for XYZ shrinkage compensation // Orca: updated function call for XYZ shrinkage compensation
if (!m_slicing_params.valid) { if (!m_slicing_params.valid) {
coordf_t object_height = this->model_object()->max_z(); coordf_t object_height = this->model_object()->max_z();
if (this->print()->config().belt_printer.value) { // Belt shear/scale may change the effective Z height.
// After mesh rotation R(-alpha, X), effective height in the slicing const auto &pcfg = this->print()->config();
// direction is y_extent*sin(a) + z_extent*cos(a). if (pcfg.belt_printer.value) {
double angle_rad = Geometry::deg2rad(this->print()->config().belt_printer_angle.value); bool has_z_shear = pcfg.belt_shear_z.value != BeltShearMode::None;
BoundingBoxf3 bb = this->model_object()->raw_bounding_box(); bool has_z_scale = pcfg.belt_scale_z.value != BeltScaleMode::None;
object_height = bb.size().y() * std::sin(angle_rad) + bb.size().z() * std::cos(angle_rad); if (has_z_shear || has_z_scale) {
auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double {
double angle_rad = Geometry::deg2rad(angle_deg);
double sin_a = std::sin(angle_rad), cos_a = std::cos(angle_rad);
switch (mode) {
case BeltShearMode::PosCot: return (sin_a > EPSILON) ? cos_a / sin_a : 0.;
case BeltShearMode::NegCot: return (sin_a > EPSILON) ? -cos_a / sin_a : 0.;
case BeltShearMode::PosTan: return (cos_a > EPSILON) ? sin_a / cos_a : 0.;
case BeltShearMode::NegTan: return (cos_a > EPSILON) ? -sin_a / cos_a : 0.;
default: return 0.;
} }
m_slicing_params = SlicingParameters::create_from_config(this->print()->config(), m_config, object_height, };
auto compute_scale_factor = [](BeltScaleMode mode, double angle_deg) -> double {
if (mode == BeltScaleMode::None) return 1.;
double angle_rad = Geometry::deg2rad(angle_deg);
double sin_a = std::sin(angle_rad), cos_a = std::cos(angle_rad);
switch (mode) {
case BeltScaleMode::InvSin: return (sin_a > EPSILON) ? 1. / sin_a : 1.;
case BeltScaleMode::InvCos: return (cos_a > EPSILON) ? 1. / cos_a : 1.;
case BeltScaleMode::Sin: return sin_a;
case BeltScaleMode::Cos: return cos_a;
default: return 1.;
}
};
double shear_factor = has_z_shear ? compute_shear_factor(pcfg.belt_shear_z.value, pcfg.belt_shear_z_angle.value) : 0.;
double scale_z = compute_scale_factor(pcfg.belt_scale_z.value, pcfg.belt_scale_z_angle.value);
if (has_z_shear && std::abs(shear_factor) > EPSILON) {
int from = int(pcfg.belt_shear_z_from.value);
BoundingBoxf3 bb = this->model_object()->raw_bounding_box();
double min_rz = std::numeric_limits<double>::max();
double max_rz = std::numeric_limits<double>::lowest();
for (double vz : {bb.min.z(), bb.max.z()})
for (double vs : {bb.min(from), bb.max(from)}) {
double new_z = scale_z * (vz + shear_factor * vs);
min_rz = std::min(min_rz, new_z);
max_rz = std::max(max_rz, new_z);
}
object_height = max_rz - min_rz;
} else {
object_height *= scale_z;
}
}
}
m_slicing_params = SlicingParameters::create_from_config(pcfg, m_config, object_height,
this->object_extruders(), this->print()->shrinkage_compensation()); this->object_extruders(), this->print()->shrinkage_compensation());
} }
} }
@@ -3441,9 +3482,51 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
if (object_max_z <= 0.f) { if (object_max_z <= 0.f) {
BoundingBoxf3 bb = model_object.raw_bounding_box(); BoundingBoxf3 bb = model_object.raw_bounding_box();
object_max_z = (float)bb.size().z(); object_max_z = (float)bb.size().z();
// Belt shear/scale may change the effective Z height.
if (print_config.belt_printer.value) { if (print_config.belt_printer.value) {
double angle_rad = Geometry::deg2rad(print_config.belt_printer_angle.value); bool has_z_shear = print_config.belt_shear_z.value != BeltShearMode::None;
object_max_z = (float)(bb.size().y() * std::sin(angle_rad) + bb.size().z() * std::cos(angle_rad)); bool has_z_scale = print_config.belt_scale_z.value != BeltScaleMode::None;
if (has_z_shear || has_z_scale) {
auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double {
double angle_rad = Geometry::deg2rad(angle_deg);
double sin_a = std::sin(angle_rad), cos_a = std::cos(angle_rad);
switch (mode) {
case BeltShearMode::PosCot: return (sin_a > EPSILON) ? cos_a / sin_a : 0.;
case BeltShearMode::NegCot: return (sin_a > EPSILON) ? -cos_a / sin_a : 0.;
case BeltShearMode::PosTan: return (cos_a > EPSILON) ? sin_a / cos_a : 0.;
case BeltShearMode::NegTan: return (cos_a > EPSILON) ? -sin_a / cos_a : 0.;
default: return 0.;
}
};
auto compute_scale_factor = [](BeltScaleMode mode, double angle_deg) -> double {
if (mode == BeltScaleMode::None) return 1.;
double angle_rad = Geometry::deg2rad(angle_deg);
double sin_a = std::sin(angle_rad), cos_a = std::cos(angle_rad);
switch (mode) {
case BeltScaleMode::InvSin: return (sin_a > EPSILON) ? 1. / sin_a : 1.;
case BeltScaleMode::InvCos: return (cos_a > EPSILON) ? 1. / cos_a : 1.;
case BeltScaleMode::Sin: return sin_a;
case BeltScaleMode::Cos: return cos_a;
default: return 1.;
}
};
double shear_factor = has_z_shear ? compute_shear_factor(print_config.belt_shear_z.value, print_config.belt_shear_z_angle.value) : 0.;
double scale_z = compute_scale_factor(print_config.belt_scale_z.value, print_config.belt_scale_z_angle.value);
if (has_z_shear && std::abs(shear_factor) > EPSILON) {
int from = int(print_config.belt_shear_z_from.value);
double min_rz = std::numeric_limits<double>::max();
double max_rz = std::numeric_limits<double>::lowest();
for (double vz : {bb.min.z(), bb.max.z()})
for (double vs : {bb.min(from), bb.max(from)}) {
double new_z = scale_z * (vz + shear_factor * vs);
min_rz = std::min(min_rz, new_z);
max_rz = std::max(max_rz, new_z);
}
object_max_z = (float)(max_rz - min_rz);
} else {
object_max_z *= (float)scale_z;
}
}
} }
} }
return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders, object_shrinkage_compensation); return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders, object_shrinkage_compensation);
+62 -16
View File
@@ -141,23 +141,69 @@ static std::vector<VolumeSlices> slice_volumes_inner(
params_base.extra_offset = 0; params_base.extra_offset = 0;
params_base.trafo = object_trafo; params_base.trafo = object_trafo;
if (print_config.belt_printer.value) { if (print_config.belt_printer.value) {
double angle_rad = Geometry::deg2rad(print_config.belt_printer_angle.value); // Build per-axis shear matrix from 3 independent axis configs.
// Rotate mesh by -alpha about X so horizontal slice planes = gantry-parallel planes. auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double {
// The gantry (XY) is tilted by belt_printer_angle; this rotation aligns it with horizontal. double angle_rad = Geometry::deg2rad(angle_deg);
Transform3d belt_rotation = Transform3d::Identity(); double sin_a = std::sin(angle_rad);
belt_rotation.rotate(Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX())); double cos_a = std::cos(angle_rad);
params_base.trafo = belt_rotation * params_base.trafo; switch (mode) {
// Compute Z-shift from model_volumes: find min-Z of all rotated meshes case BeltShearMode::PosCot: return (sin_a > EPSILON) ? cos_a / sin_a : 0.;
// so the rotated geometry starts at Z=0 (the belt surface in slicing frame). case BeltShearMode::NegCot: return (sin_a > EPSILON) ? -cos_a / sin_a : 0.;
double min_z_rotated = std::numeric_limits<double>::max(); case BeltShearMode::PosTan: return (cos_a > EPSILON) ? sin_a / cos_a : 0.;
for (const ModelVolume *mv : model_volumes) { case BeltShearMode::NegTan: return (cos_a > EPSILON) ? -sin_a / cos_a : 0.;
if (!model_volume_needs_slicing(*mv)) continue; default: return 0.;
BoundingBoxf3 bb = mv->mesh().bounding_box();
bb = bb.transformed(params_base.trafo * mv->get_matrix());
min_z_rotated = std::min(min_z_rotated, bb.min.z());
} }
if (min_z_rotated != std::numeric_limits<double>::max() && std::abs(min_z_rotated) > EPSILON) };
params_base.trafo = Eigen::Translation3d(0, 0, -min_z_rotated) * params_base.trafo;
struct AxisShear { BeltShearMode mode; double angle; int from; };
AxisShear axes[3] = {
{ print_config.belt_shear_x.value, print_config.belt_shear_x_angle.value, int(print_config.belt_shear_x_from.value) },
{ print_config.belt_shear_y.value, print_config.belt_shear_y_angle.value, int(print_config.belt_shear_y_from.value) },
{ print_config.belt_shear_z.value, print_config.belt_shear_z_angle.value, int(print_config.belt_shear_z_from.value) },
};
Transform3d belt_shear = Transform3d::Identity();
bool has_shear = false;
for (int row = 0; row < 3; ++row) {
if (axes[row].mode != BeltShearMode::None) {
double factor = compute_shear_factor(axes[row].mode, axes[row].angle);
if (std::abs(factor) > EPSILON) {
belt_shear.matrix()(row, axes[row].from) += factor;
has_shear = true;
}
}
}
// Build per-axis scale matrix.
auto compute_scale_factor = [](BeltScaleMode mode, double angle_deg) -> double {
if (mode == BeltScaleMode::None) return 1.;
double angle_rad = Geometry::deg2rad(angle_deg);
double sin_a = std::sin(angle_rad);
double cos_a = std::cos(angle_rad);
switch (mode) {
case BeltScaleMode::InvSin: return (sin_a > EPSILON) ? 1. / sin_a : 1.;
case BeltScaleMode::InvCos: return (cos_a > EPSILON) ? 1. / cos_a : 1.;
case BeltScaleMode::Sin: return sin_a;
case BeltScaleMode::Cos: return cos_a;
default: return 1.;
}
};
Transform3d belt_scale = Transform3d::Identity();
bool has_scale = false;
double sx = compute_scale_factor(print_config.belt_scale_x.value, print_config.belt_scale_x_angle.value);
double sy = compute_scale_factor(print_config.belt_scale_y.value, print_config.belt_scale_y_angle.value);
double sz = compute_scale_factor(print_config.belt_scale_z.value, print_config.belt_scale_z_angle.value);
if (std::abs(sx - 1.) > EPSILON || std::abs(sy - 1.) > EPSILON || std::abs(sz - 1.) > EPSILON) {
belt_scale.matrix()(0, 0) = sx;
belt_scale.matrix()(1, 1) = sy;
belt_scale.matrix()(2, 2) = sz;
has_scale = true;
}
// Apply: scale * shear * trafo (shear first, then scale).
if (has_shear || has_scale)
params_base.trafo = belt_scale * belt_shear * params_base.trafo;
} }
//BBS: 0.0025mm is safe enough to simplify the data to speed slicing up for high-resolution model. //BBS: 0.0025mm is safe enough to simplify the data to speed slicing up for high-resolution model.
//Also has on influence on arc fitting which has default resolution 0.0125mm. //Also has on influence on arc fitting which has default resolution 0.0125mm.
+12 -10
View File
@@ -1271,7 +1271,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
m_max_print_height = gcode_result.printable_height; m_max_print_height = gcode_result.printable_height;
m_z_offset = gcode_result.z_offset; m_z_offset = gcode_result.z_offset;
m_belt_z_shift = gcode_result.belt_z_shift;
// load_toolpaths(gcode_result, build_volume, exclude_bounding_box); // load_toolpaths(gcode_result, build_volume, exclude_bounding_box);
@@ -2210,15 +2210,17 @@ void GCodeViewer::render_toolpaths()
{ {
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
Matrix4f view = camera.get_view_matrix().matrix().cast<float>(); Matrix4f view = camera.get_view_matrix().matrix().cast<float>();
// Belt "raw" view: apply slicing rotation to view matrix so toolpaths appear // Belt "designed" view: apply inverse shear to view matrix so toolpaths appear
// in the slicing frame (rotated part with horizontal layers). // upright (as originally designed) instead of sheared on the belt.
if (m_belt_show_raw && m_belt_view_enabled && m_belt_angle_deg > 0.f) { if (m_belt_show_designed && m_belt_view_enabled && m_belt_angle_deg > 0.f) {
double angle_rad = Geometry::deg2rad(static_cast<double>(m_belt_angle_deg)); double angle_rad = Geometry::deg2rad(static_cast<double>(m_belt_angle_deg));
// Apply R(-alpha, X) * T(0,0,-z_shift) to bring machine coords back to slicing frame. double sin_a = std::sin(angle_rad);
Transform3d slicing_trafo = Transform3d::Identity(); if (sin_a > 1e-6) {
slicing_trafo.translate(Vec3d(0., 0., -static_cast<double>(m_belt_z_shift))); double cot_alpha = std::cos(angle_rad) / sin_a;
slicing_trafo = Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX()) * slicing_trafo; Transform3d inverse_shear = Transform3d::Identity();
view = (camera.get_view_matrix() * slicing_trafo).matrix().cast<float>(); inverse_shear.matrix()(1, 2) = -cot_alpha; // Y -= Z * cot(α)
view = (camera.get_view_matrix() * inverse_shear).matrix().cast<float>();
}
} }
const libvgcode::Mat4x4 converted_view_matrix = libvgcode::convert(view); const libvgcode::Mat4x4 converted_view_matrix = libvgcode::convert(view);
const libvgcode::Mat4x4 converted_projetion_matrix = libvgcode::convert(static_cast<Matrix4f>(camera.get_projection_matrix().matrix().cast<float>())); const libvgcode::Mat4x4 converted_projetion_matrix = libvgcode::convert(static_cast<Matrix4f>(camera.get_projection_matrix().matrix().cast<float>()));
@@ -4421,7 +4423,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
ImGui::Spacing(); ImGui::Spacing();
ImGui::Dummy({ window_padding, 0 }); ImGui::Dummy({ window_padding, 0 });
ImGui::SameLine(); ImGui::SameLine();
ImGui::Checkbox("Show raw G-code (slicing frame)", &m_belt_show_raw); ImGui::Checkbox("Show designed view (upright)", &m_belt_show_designed);
} }
legend_height = ImGui::GetCurrentWindow()->Size.y; legend_height = ImGui::GetCurrentWindow()->Size.y;
+1 -2
View File
@@ -239,8 +239,7 @@ mutable bool m_no_render_path { false };
bool m_belt_view_enabled = false; bool m_belt_view_enabled = false;
float m_belt_angle_deg = 0.f; float m_belt_angle_deg = 0.f;
float m_belt_z_shift = 0.f; bool m_belt_show_designed = false; // Toggle: show designed (upright) view via inverse shear
bool m_belt_show_raw = false; // Toggle: show raw slicing-frame G-code (rotated part)
libvgcode::Viewer m_viewer; libvgcode::Viewer m_viewer;
bool m_loaded_as_preview{ false }; bool m_loaded_as_preview{ false };
+51
View File
@@ -4370,6 +4370,53 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("belt_printer"); optgroup->append_single_option_line("belt_printer");
optgroup->append_single_option_line("belt_printer_angle"); optgroup->append_single_option_line("belt_printer_angle");
optgroup->append_single_option_line("belt_printer_infinite_y"); optgroup->append_single_option_line("belt_printer_infinite_y");
// Per-axis shear: group mode + angle + source on one row per axis
{
Line line = { L("Shear X"), L("Shear applied to the X axis before slicing") };
line.append_option(optgroup->get_option("belt_shear_x"));
line.append_option(optgroup->get_option("belt_shear_x_angle"));
line.append_option(optgroup->get_option("belt_shear_x_from"));
optgroup->append_line(line);
}
{
Line line = { L("Shear Y"), L("Shear applied to the Y axis before slicing") };
line.append_option(optgroup->get_option("belt_shear_y"));
line.append_option(optgroup->get_option("belt_shear_y_angle"));
line.append_option(optgroup->get_option("belt_shear_y_from"));
optgroup->append_line(line);
}
{
Line line = { L("Shear Z"), L("Shear applied to the Z axis before slicing") };
line.append_option(optgroup->get_option("belt_shear_z"));
line.append_option(optgroup->get_option("belt_shear_z_angle"));
line.append_option(optgroup->get_option("belt_shear_z_from"));
optgroup->append_line(line);
}
{
Line line = { L("Scale X"), L("Scale applied to the X axis before slicing") };
line.append_option(optgroup->get_option("belt_scale_x"));
line.append_option(optgroup->get_option("belt_scale_x_angle"));
optgroup->append_line(line);
}
{
Line line = { L("Scale Y"), L("Scale applied to the Y axis before slicing") };
line.append_option(optgroup->get_option("belt_scale_y"));
line.append_option(optgroup->get_option("belt_scale_y_angle"));
optgroup->append_line(line);
}
{
Line line = { L("Scale Z"), L("Scale applied to the Z axis before slicing") };
line.append_option(optgroup->get_option("belt_scale_z"));
line.append_option(optgroup->get_option("belt_scale_z_angle"));
optgroup->append_line(line);
}
{
Line line = { L("G-code axis remap"), L("Remap slicing-frame axes to machine axes in G-code output") };
line.append_option(optgroup->get_option("belt_gcode_remap_x"));
line.append_option(optgroup->get_option("belt_gcode_remap_y"));
line.append_option(optgroup->get_option("belt_gcode_remap_z"));
optgroup->append_line(line);
}
optgroup->append_single_option_line("support_multi_bed_types","printer_basic_information_printable_space#support-multi-bed-types"); optgroup->append_single_option_line("support_multi_bed_types","printer_basic_information_printable_space#support-multi-bed-types");
optgroup->append_single_option_line("best_object_pos", "printer_basic_information_printable_space#best-object-position"); optgroup->append_single_option_line("best_object_pos", "printer_basic_information_printable_space#best-object-position");
// todo: for multi_extruder test // todo: for multi_extruder test
@@ -5233,6 +5280,10 @@ void TabPrinter::toggle_options()
bool is_belt = m_config->opt_bool("belt_printer"); bool is_belt = m_config->opt_bool("belt_printer");
toggle_line("belt_printer_angle", is_belt); toggle_line("belt_printer_angle", is_belt);
toggle_line("belt_printer_infinite_y", is_belt); toggle_line("belt_printer_infinite_y", is_belt);
for (auto el : {"belt_shear_x", "belt_shear_y", "belt_shear_z",
"belt_scale_x", "belt_scale_y", "belt_scale_z",
"belt_gcode_remap_x"})
toggle_line(el, is_belt);
} }