diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index dd9e477149..1aeb5497a3 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2411,31 +2411,19 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_is_role_based_fan_on.fill(false); m_fan_mover.release(); - + 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) { 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). - // This is needed by to_machine_coords() to undo the slicing transform. - // For multiple objects, use the minimum min_z_rotated across all objects. - double angle_rad = Geometry::deg2rad(print.config().belt_printer_angle.value); - Transform3d belt_rotation = Transform3d::Identity(); - belt_rotation.rotate(Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX())); - double global_min_z = std::numeric_limits::max(); - 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::max() && std::abs(global_min_z) > EPSILON) - m_writer.set_belt_z_shift(global_min_z); // typically negative + m_writer.set_axis_remap( + int(print.config().belt_gcode_remap_x.value), + int(print.config().belt_gcode_remap_y.value), + int(print.config().belt_gcode_remap_z.value)); + // Build volume extents for Rev remap mode. + BoundingBoxf bbox_bed(print.config().printable_area.values); + m_writer.set_build_volume_max(Vec3d(bbox_bed.max.x(), bbox_bed.max.y(), print.config().printable_height.value)); } // 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. if (print.config().belt_printer.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) file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index eddfc56440..5dd2c0fc84 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -3051,14 +3051,6 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers } catch (...) {} 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 if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) { m_wiping = true; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index af68a5a6b7..e432a165a8 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -227,9 +227,8 @@ class Print; bool support_traditional_timelapse{true}; float printable_height; 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_z_shift{ 0.f }; SettingsIds settings_ids; size_t filaments_count; bool backtrace_enabled; diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index bb606b222e..da655a6548 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -23,26 +23,32 @@ bool GCodeWriter::full_gcode_comment = true; void GCodeWriter::set_belt_angle(double 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 { if (!is_belt_printer()) return pos; - - // Undo the slicing transform to recover machine-frame coordinates. - // Slicing applied: T(0,0,-min_z_rot) * R(-alpha, X) * original_pos - // Inverse: R(+alpha, X) * T(0,0,+min_z_rot) * slicing_pos - // - // First undo the Z-shift, then apply R(+alpha, X). - double sz = pos.z() + m_belt_z_shift; // undo T(0,0,-min_z_rot) - return Vec3d( - pos.x(), - pos.y() * m_belt_cos - sz * m_belt_sin, // R(+alpha, X) - pos.y() * m_belt_sin + sz * m_belt_cos - ); + // BeltRemapAxis: 0-2 = +X/+Y/+Z, 3-5 = -X/-Y/-Z, 6-8 = Rev X/Y/Z + auto remap = [this, &pos](int r) -> double { + int axis = r % 3; + if (r < 3) return pos[axis]; + if (r < 6) return -pos[axis]; + return m_build_vol_max[axis] - pos[axis]; + }; + return { remap(m_remap_x), remap(m_remap_y), remap(m_remap_z) }; } bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor) diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index f9b5ed79ac..6074eb247c 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -127,10 +127,12 @@ public: // Belt printer: set the belt angle and precompute sin/cos for coordinate transformation. 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.; } - // 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; // Returns whether this flavor supports separate print and travel acceleration. @@ -186,11 +188,12 @@ public: //SoftFever 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_cos = 1.0; - double m_belt_sin = 0.0; - double m_belt_z_shift = 0.; // Z-shift applied during slicing (min_z_rotated, typically negative) + int m_remap_x = 0; + int m_remap_y = 1; + int m_remap_z = 2; + Vec3d m_build_vol_max = Vec3d::Zero(); double m_current_speed; bool m_is_first_layer = true; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 6d8c113480..f2a0cd9d24 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1010,7 +1010,11 @@ static std::vector s_Preset_machine_limits_options { static std::vector s_Preset_printer_options { "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", "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", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 976a35c126..7290af09fb 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -288,6 +288,44 @@ static t_config_enum_values s_keys_map_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 { { "rectilinear", smpRectilinear }, { "rectilinear-grid", smpRectilinearGrid }, @@ -5978,6 +6016,103 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; 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::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(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::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(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::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(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::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(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->label = L("Tree support branch angle"); def->category = L("Support"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 080956c1dd..68e9425a82 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -156,6 +156,38 @@ enum class SlicingMode 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 { smpDefault, 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(IroningType) 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(SupportMaterialStyle) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern) @@ -1416,6 +1452,24 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionBool, belt_printer)) ((ConfigOptionFloat, belt_printer_angle)) ((ConfigOptionBool, belt_printer_infinite_y)) + ((ConfigOptionEnum, belt_shear_x)) + ((ConfigOptionFloat, belt_shear_x_angle)) + ((ConfigOptionEnum, belt_shear_x_from)) + ((ConfigOptionEnum, belt_shear_y)) + ((ConfigOptionFloat, belt_shear_y_angle)) + ((ConfigOptionEnum, belt_shear_y_from)) + ((ConfigOptionEnum, belt_shear_z)) + ((ConfigOptionFloat, belt_shear_z_angle)) + ((ConfigOptionEnum, belt_shear_z_from)) + ((ConfigOptionEnum, belt_scale_x)) + ((ConfigOptionFloat, belt_scale_x_angle)) + ((ConfigOptionEnum, belt_scale_y)) + ((ConfigOptionFloat, belt_scale_y_angle)) + ((ConfigOptionEnum, belt_scale_z)) + ((ConfigOptionFloat, belt_scale_z_angle)) + ((ConfigOptionEnum, belt_gcode_remap_x)) + ((ConfigOptionEnum, belt_gcode_remap_y)) + ((ConfigOptionEnum, belt_gcode_remap_z)) //BBS ((ConfigOptionInts, additional_cooling_fan_speed)) ((ConfigOptionBool, reduce_crossing_wall)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 08ff847951..abd309fbff 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -3392,14 +3392,55 @@ void PrintObject::update_slicing_parameters() // Orca: updated function call for XYZ shrinkage compensation if (!m_slicing_params.valid) { coordf_t object_height = this->model_object()->max_z(); - if (this->print()->config().belt_printer.value) { - // After mesh rotation R(-alpha, X), effective height in the slicing - // direction is y_extent*sin(a) + z_extent*cos(a). - double angle_rad = Geometry::deg2rad(this->print()->config().belt_printer_angle.value); - BoundingBoxf3 bb = this->model_object()->raw_bounding_box(); - object_height = bb.size().y() * std::sin(angle_rad) + bb.size().z() * std::cos(angle_rad); + // Belt shear/scale may change the effective Z height. + const auto &pcfg = this->print()->config(); + if (pcfg.belt_printer.value) { + bool has_z_shear = pcfg.belt_shear_z.value != BeltShearMode::None; + bool has_z_scale = pcfg.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(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::max(); + double max_rz = std::numeric_limits::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(this->print()->config(), m_config, object_height, + m_slicing_params = SlicingParameters::create_from_config(pcfg, m_config, object_height, this->object_extruders(), this->print()->shrinkage_compensation()); } } @@ -3441,9 +3482,51 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full if (object_max_z <= 0.f) { BoundingBoxf3 bb = model_object.raw_bounding_box(); object_max_z = (float)bb.size().z(); + // Belt shear/scale may change the effective Z height. if (print_config.belt_printer.value) { - double angle_rad = Geometry::deg2rad(print_config.belt_printer_angle.value); - object_max_z = (float)(bb.size().y() * std::sin(angle_rad) + bb.size().z() * std::cos(angle_rad)); + bool has_z_shear = print_config.belt_shear_z.value != BeltShearMode::None; + 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::max(); + double max_rz = std::numeric_limits::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); diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 2748151aa0..294c245eaa 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -141,23 +141,69 @@ static std::vector slice_volumes_inner( params_base.extra_offset = 0; params_base.trafo = object_trafo; if (print_config.belt_printer.value) { - double angle_rad = Geometry::deg2rad(print_config.belt_printer_angle.value); - // Rotate mesh by -alpha about X so horizontal slice planes = gantry-parallel planes. - // The gantry (XY) is tilted by belt_printer_angle; this rotation aligns it with horizontal. - Transform3d belt_rotation = Transform3d::Identity(); - belt_rotation.rotate(Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX())); - params_base.trafo = belt_rotation * params_base.trafo; - // Compute Z-shift from model_volumes: find min-Z of all rotated meshes - // so the rotated geometry starts at Z=0 (the belt surface in slicing frame). - double min_z_rotated = std::numeric_limits::max(); - for (const ModelVolume *mv : model_volumes) { - if (!model_volume_needs_slicing(*mv)) continue; - 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()); + // Build per-axis shear matrix from 3 independent axis configs. + auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double { + double angle_rad = Geometry::deg2rad(angle_deg); + double sin_a = std::sin(angle_rad); + double 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.; + } + }; + + 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; + } + } } - if (min_z_rotated != std::numeric_limits::max() && std::abs(min_z_rotated) > EPSILON) - params_base.trafo = Eigen::Translation3d(0, 0, -min_z_rotated) * params_base.trafo; + + // 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. //Also has on influence on arc fitting which has default resolution 0.0125mm. diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index dd66c918a7..25b4ba4e9c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1271,7 +1271,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const m_max_print_height = gcode_result.printable_height; 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); @@ -2210,15 +2210,17 @@ void GCodeViewer::render_toolpaths() { const Camera& camera = wxGetApp().plater()->get_camera(); Matrix4f view = camera.get_view_matrix().matrix().cast(); - // Belt "raw" view: apply slicing rotation to view matrix so toolpaths appear - // in the slicing frame (rotated part with horizontal layers). - if (m_belt_show_raw && m_belt_view_enabled && m_belt_angle_deg > 0.f) { + // Belt "designed" view: apply inverse shear to view matrix so toolpaths appear + // upright (as originally designed) instead of sheared on the belt. + if (m_belt_show_designed && m_belt_view_enabled && m_belt_angle_deg > 0.f) { double angle_rad = Geometry::deg2rad(static_cast(m_belt_angle_deg)); - // Apply R(-alpha, X) * T(0,0,-z_shift) to bring machine coords back to slicing frame. - Transform3d slicing_trafo = Transform3d::Identity(); - slicing_trafo.translate(Vec3d(0., 0., -static_cast(m_belt_z_shift))); - slicing_trafo = Eigen::AngleAxisd(-angle_rad, Vec3d::UnitX()) * slicing_trafo; - view = (camera.get_view_matrix() * slicing_trafo).matrix().cast(); + double sin_a = std::sin(angle_rad); + if (sin_a > 1e-6) { + double cot_alpha = std::cos(angle_rad) / sin_a; + Transform3d inverse_shear = Transform3d::Identity(); + inverse_shear.matrix()(1, 2) = -cot_alpha; // Y -= Z * cot(α) + view = (camera.get_view_matrix() * inverse_shear).matrix().cast(); + } } const libvgcode::Mat4x4 converted_view_matrix = libvgcode::convert(view); const libvgcode::Mat4x4 converted_projetion_matrix = libvgcode::convert(static_cast(camera.get_projection_matrix().matrix().cast())); @@ -4421,7 +4423,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ImGui::Spacing(); ImGui::Dummy({ window_padding, 0 }); 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; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 4bd3746991..184c56e915 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -239,8 +239,7 @@ mutable bool m_no_render_path { false }; bool m_belt_view_enabled = false; float m_belt_angle_deg = 0.f; - float m_belt_z_shift = 0.f; - bool m_belt_show_raw = false; // Toggle: show raw slicing-frame G-code (rotated part) + bool m_belt_show_designed = false; // Toggle: show designed (upright) view via inverse shear libvgcode::Viewer m_viewer; bool m_loaded_as_preview{ false }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 3dce081708..9d1ee6b2ad 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4370,6 +4370,53 @@ void TabPrinter::build_fff() optgroup->append_single_option_line("belt_printer"); optgroup->append_single_option_line("belt_printer_angle"); 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("best_object_pos", "printer_basic_information_printable_space#best-object-position"); // todo: for multi_extruder test @@ -5233,6 +5280,10 @@ void TabPrinter::toggle_options() bool is_belt = m_config->opt_bool("belt_printer"); toggle_line("belt_printer_angle", 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); }