diff --git a/result.json b/result.json new file mode 100644 index 0000000000..e309ad45a1 --- /dev/null +++ b/result.json @@ -0,0 +1,7 @@ +{ + "error_string": "Success.", + "export_time": 0, + "plate_index": 0, + "prepare_time": 0, + "return_code": 0 +} diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 35d971a465..0534734c65 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -881,6 +881,10 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_ std::vector> &objPrintVec, std::vector& printExtruders) { + // Belt printer: brim is not compatible with belt printing. + if (print.config().belt_printer.value) + return; + std::map brim_width_map; std::map brimAreaMap; std::map supportBrimAreaMap; diff --git a/src/libslic3r/BuildVolume.cpp b/src/libslic3r/BuildVolume.cpp index 3802fbc014..132794ccfe 100644 --- a/src/libslic3r/BuildVolume.cpp +++ b/src/libslic3r/BuildVolume.cpp @@ -176,6 +176,26 @@ BuildVolume::BuildVolume(const std::vector &printable_area, const double BOOST_LOG_TRIVIAL(debug) << "BuildVolume printable_area clasified as: " << this->type_name(); } +void BuildVolume::set_belt_printer(bool enabled, double angle_deg, bool infinite_y) +{ + m_is_belt_printer = enabled; + m_belt_angle = angle_deg; + m_belt_infinite_y = infinite_y; + + if (enabled) { + if (infinite_y) { + // Extend the Y bound to a very large value for infinite belt. + m_bboxf.max.y() = 100000.; + } + // Adjust max print height to diagonal reach: printable_height / sin(belt_angle). + if (angle_deg > 0. && angle_deg < 90.) { + double sin_a = std::sin(angle_deg * M_PI / 180.0); + if (sin_a > 0.) + m_bboxf.max.z() = m_max_print_height / sin_a; + } + } +} + #if 0 // Tests intersections of projected triangles, not just their vertices against a bounding box. // This test also correctly evaluates collision of a non-convex object with the bounding box. @@ -384,6 +404,11 @@ BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& i build_volume.max.z() = std::numeric_limits::max(); if (ignore_bottom) build_volume.min.z() = -std::numeric_limits::max(); + // Belt printer: extend Y bounds for infinite Y. + if (m_is_belt_printer && m_belt_infinite_y) { + build_volume.min.y() = -std::numeric_limits::max(); + build_volume.max.y() = std::numeric_limits::max(); + } BoundingBox3Base build_volumef(build_volume.min.cast(), build_volume.max.cast()); // The following test correctly interprets intersection of a non-convex object with a rectangular build volume. //return rectangle_test(its, trafo, to_2d(build_volume.min), to_2d(build_volume.max), build_volume.max.z()); diff --git a/src/libslic3r/BuildVolume.hpp b/src/libslic3r/BuildVolume.hpp index 6f53fdd704..0494efe9ba 100644 --- a/src/libslic3r/BuildVolume.hpp +++ b/src/libslic3r/BuildVolume.hpp @@ -57,6 +57,10 @@ public: // Initialize from PrintConfig::printable_area and PrintConfig::printable_height BuildVolume(const std::vector &printable_area, const double printable_height, const std::vector> &extruder_areas, const std::vector& extruder_printable_heights); + // Belt printer configuration. + void set_belt_printer(bool enabled, double angle_deg, bool infinite_y); + bool is_belt_printer() const { return m_is_belt_printer; } + // Source data, unscaled coordinates. const std::vector& printable_area() const { return m_bed_shape; } double printable_height() const { return m_max_print_height; } @@ -139,6 +143,10 @@ private: // Source definition of the print volume height (PrintConfig::printable_height) double m_max_print_height { 0.f }; std::vector m_extruder_printable_height; + // Belt printer state. + bool m_is_belt_printer { false }; + double m_belt_angle { 0. }; + bool m_belt_infinite_y { false }; // Derived values. BuildVolume_Type m_type { BuildVolume_Type::Invalid }; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index fe10cbcd41..bff35d321c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2413,6 +2413,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_writer.set_is_bbl_machine(is_bbl_printers); + // Belt printer: initialize coordinate transformation on the writer. + if (print.config().belt_printer.value) + m_writer.set_belt_angle(print.config().belt_printer_angle.value); + // How many times will be change_layer() called? // change_layer() in turn increments the progress bar status. m_layer_count = 0; @@ -2502,6 +2506,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write_format("; HEADER_BLOCK_START\n"); // Write information on the generator. file.write_format("; generated by %s on %s\n", Slic3r::header_slic3r_generated().c_str(), Slic3r::Utils::local_timestamp().c_str()); + // 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); if (is_bbl_printers) file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); //BBS: total layer number @@ -6704,7 +6711,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } // BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode or we are doing sloped extrusion // Attention: G2 and G3 is not supported in spiral_mode mode - if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode || sloped != nullptr) { + if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode || sloped != nullptr || m_config.belt_printer) { double path_length = 0.; double total_length = sloped == nullptr ? 0. : path.polyline.length() * SCALING_FACTOR; for (const Line& line : path.polyline.lines()) { diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 906c2070e4..0dc00e1522 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2597,6 +2597,8 @@ void GCodeProcessor::finalize(bool post_process) } } + // Belt printer: preview coordinate transform placeholder (to be implemented in next cycle). + calculate_time(m_result); // process the time blocks @@ -3044,6 +3046,14 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers return; } + // Belt printer angle detection from G-code header comment. + if (boost::starts_with(comment, " belt_printer_angle = ")) { + try { + m_result.belt_printer_angle = std::stof(std::string(comment.substr(22))); + } 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 1d72b1c614..e432a165a8 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -227,6 +227,8 @@ class Print; bool support_traditional_timelapse{true}; float printable_height; float z_offset; + // Belt printer: angle for coordinate transformation in preview. + float belt_printer_angle{ 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 1aff72daeb..1b056a946f 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -1,5 +1,6 @@ #include "GCodeWriter.hpp" #include "CustomGCode.hpp" +#include "Geometry.hpp" #include "PrintConfig.hpp" #include #include @@ -19,6 +20,19 @@ namespace Slic3r { 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); +} + +Vec3d GCodeWriter::to_machine_coords(const Vec3d &pos) const +{ + // Belt printer: coordinate transform placeholder (to be implemented in next cycle). + return pos; +} + bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor) { return (flavor == gcfRepetier || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware); @@ -547,7 +561,13 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset }; GCodeG1Formatter w; - w.emit_xy(point_on_plate); + if (is_belt_printer()) { + // Belt printer: transform to machine coordinates (XY travel also needs Z due to YZ rotation) + Vec3d machine = to_machine_coords(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z())); + w.emit_xyz(machine); + } else { + w.emit_xy(point_on_plate); + } auto speed = m_is_first_layer ? this->config.get_abs_value("initial_layer_travel_speed") : this->config.travel_speed.value; w.emit_f(speed * 60.0); @@ -561,6 +581,11 @@ it will not perform subsequent lifts, even if Z was raised manually (i.e. with travel_to_z()) and thus _lifted was reduced. */ std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase) { + // Belt printer: force NormalLift since SpiralLift and SlopeLift compute slope angles + // that don't account for the YZ coordinate rotation. + if (is_belt_printer()) + lift_type = LiftType::NormalLift; + // check whether the above/below conditions are met double target_lift = 0; { @@ -587,8 +612,10 @@ std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase) } // BBS: immediately execute an undelayed lift move with a spiral lift pattern -// designed specifically for subsequent gcode injection (e.g. timelapse) +// designed specifically for subsequent gcode injection (e.g. timelapse) std::string GCodeWriter::eager_lift(const LiftType type) { + // Belt printer: force NormalLift (SpiralLift/SlopeLift don't account for YZ rotation). + const LiftType effective_type = is_belt_printer() ? LiftType::NormalLift : type; std::string lift_move; double target_lift = 0; { @@ -603,7 +630,7 @@ std::string GCodeWriter::eager_lift(const LiftType type) { // BBS: spiral lift only safe with known position // TODO: check the arc will move within bed area - if (type == LiftType::SpiralLift && this->is_current_position_clear()) { + if (effective_type == LiftType::SpiralLift && this->is_current_position_clear()) { double radius = target_lift / (2 * PI * atan(filament()->travel_slope())); // static spiral alignment when no move in x,y plane. // spiral centra is a radius distance to the right (y=0) @@ -675,6 +702,8 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co // / to make the z list early to avoid to hit some warping place when travel is long. Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->filament()->travel_slope()); Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source; + if (is_belt_printer()) + slope_top_point = to_machine_coords(slope_top_point); GCodeG1Formatter w0; w0.emit_xyz(slope_top_point); w0.emit_f(travel_speed * 60.0); @@ -689,18 +718,27 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co std::string xy_z_move; { + Vec3d emit_target = is_belt_printer() ? to_machine_coords(target) : target; GCodeG1Formatter w0; if (this->is_current_position_clear()) { - w0.emit_xyz(target); + w0.emit_xyz(emit_target); w0.emit_f(travel_speed * 60.0); w0.emit_comment(GCodeWriter::full_gcode_comment, comment); xy_z_move = w0.string(); } else { - w0.emit_xy(Vec2d(target.x(), target.y())); - w0.emit_f(travel_speed * 60.0); - w0.emit_comment(GCodeWriter::full_gcode_comment, comment); - xy_z_move = w0.string() + _travel_to_z(target.z(), comment); + if (is_belt_printer()) { + // Belt mode: can't split XY and Z moves independently, emit full XYZ + w0.emit_xyz(emit_target); + w0.emit_f(travel_speed * 60.0); + w0.emit_comment(GCodeWriter::full_gcode_comment, comment); + xy_z_move = w0.string(); + } else { + w0.emit_xy(Vec2d(target.x(), target.y())); + w0.emit_f(travel_speed * 60.0); + w0.emit_comment(GCodeWriter::full_gcode_comment, comment); + xy_z_move = w0.string() + _travel_to_z(target.z(), comment); + } } } m_pos = dest_point; @@ -726,15 +764,25 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co //BBS: take plate offset into consider Vec3d point_on_plate = { dest_point(0) - m_x_offset, dest_point(1) - m_y_offset, dest_point(2) }; + if (is_belt_printer()) + point_on_plate = to_machine_coords(point_on_plate); std::string out_string; GCodeG1Formatter w; if (!this->is_current_position_clear()) { - //force to move xy first then z after filament change - w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y())); - w.emit_f(this->config.travel_speed.value * 60.0); - w.emit_comment(GCodeWriter::full_gcode_comment, comment); - out_string = w.string() + _travel_to_z(point_on_plate.z(), comment); + if (is_belt_printer()) { + // Belt mode: emit full XYZ since Y and Z are coupled + w.emit_xyz(point_on_plate); + w.emit_f(this->config.travel_speed.value * 60.0); + w.emit_comment(GCodeWriter::full_gcode_comment, comment); + out_string = w.string(); + } else { + //force to move xy first then z after filament change + w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y())); + w.emit_f(this->config.travel_speed.value * 60.0); + w.emit_comment(GCodeWriter::full_gcode_comment, comment); + out_string = w.string() + _travel_to_z(point_on_plate.z(), comment); + } } else { GCodeG1Formatter w; w.emit_xyz(point_on_plate); @@ -778,7 +826,13 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) } GCodeG1Formatter w; - w.emit_z(z); + if (is_belt_printer()) { + // Belt printer: a Z-only move in slicing frame needs to emit both Y and Z in machine coords. + Vec3d machine = to_machine_coords(Vec3d(m_pos.x() - m_x_offset, m_pos.y() - m_y_offset, z)); + w.emit_xyz(machine); + } else { + w.emit_z(z); + } w.emit_f(speed * 60.0); //BBS w.emit_comment(GCodeWriter::full_gcode_comment, comment); @@ -883,7 +937,12 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset }; GCodeG1Formatter w; - w.emit_xy(point_on_plate); + if (is_belt_printer()) { + Vec3d machine = to_machine_coords(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z())); + w.emit_xyz(machine); + } else { + w.emit_xy(point_on_plate); + } if (!force_no_extrusion) w.emit_e(filament()->E()); //BBS @@ -924,6 +983,8 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) }; GCodeG1Formatter w; + if (is_belt_printer()) + point_on_plate = to_machine_coords(point_on_plate); w.emit_xyz(point_on_plate); if (!force_no_extrusion) w.emit_e(filament()->E()); diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index dbc64694e2..10159d941f 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -125,6 +125,12 @@ public: void set_is_first_layer(bool bval) { m_is_first_layer = bval; } GCodeFlavor get_gcode_flavor() const { return config.gcode_flavor; } + // Belt printer: set the belt angle and precompute sin/cos for coordinate transformation. + void set_belt_angle(double angle_deg); + bool is_belt_printer() const { return m_belt_angle_rad != 0.; } + // Transform a point from the slicing frame to machine/world coordinates (inverse shear). + Vec3d to_machine_coords(const Vec3d &pos) const; + // Returns whether this flavor supports separate print and travel acceleration. static bool supports_separate_travel_acceleration(GCodeFlavor flavor); private: @@ -177,6 +183,11 @@ public: //SoftFever bool m_is_bbl_printers = false; + + // Belt printer coordinate transformation (YZ shear) + double m_belt_angle_rad = 0.; + double m_belt_cos = 1.0; + double m_belt_sin = 0.0; double m_current_speed; bool m_is_first_layer = true; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ef2c83b13c..98532bccc7 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1010,7 +1010,7 @@ 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", "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", "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/Print.cpp b/src/libslic3r/Print.cpp index 6463338f7d..f245cc32b6 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -565,6 +565,9 @@ std::vector Print::print_object_ids() const bool Print::has_infinite_skirt() const { + // Belt printer: no skirt support. + if (m_config.belt_printer.value) + return false; // Orca: unclear why (m_config.ooze_prevention && this->extruders().size() > 1) logic is here, removed. // return (m_config.draft_shield == dsEnabled && m_config.skirt_loops > 0) || (m_config.ooze_prevention && this->extruders().size() > 1); @@ -573,6 +576,9 @@ bool Print::has_infinite_skirt() const bool Print::has_skirt() const { + // Belt printer: no skirt support. + if (m_config.belt_printer.value) + return false; return (m_config.skirt_height > 0); } @@ -1194,6 +1200,16 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* if (extruders.empty()) return { L("No extrusions under current settings.") }; + // Belt printer validation: incompatible features. + if (m_config.belt_printer.value) { + for (const PrintObject *object : m_objects) { + if (object->config().raft_layers > 0) + return { L("Raft is not compatible with belt printer mode.") }; + } + if (m_config.draft_shield != dsDisabled) + return { L("Draft shield is not compatible with belt printer mode.") }; + } + if (nozzles < 2 && extruders.size() > 1) { auto ret = check_multi_filament_valid(*this); if (!ret.string.empty()) @@ -2501,6 +2517,10 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor void Print::_make_skirt() { + // Belt printer: skirt is not compatible. + if (m_config.belt_printer.value) + return; + // First off we need to decide how tall the skirt must be. // The skirt_height option from config is expressed in layers, but our // object might have different layer heights, so we need to find the print_z diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 918fc1b97f..7eea294d4e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2,6 +2,7 @@ #include "PrintConfigConstants.hpp" #include "ClipperUtils.hpp" #include "Config.hpp" +#include "Geometry.hpp" #include "MaterialType.hpp" #include "I18N.hpp" #include "format.hpp" @@ -5923,10 +5924,11 @@ void PrintConfigDef::init_fff_params() def->category = L("Support"); def->tooltip = L("Tilt angle of the build plate along the X axis. " "A positive value tilts the plate so the +X side is higher, shifting gravity toward -X and increasing overhangs on the +X side. " - "A negative value tilts the -X side higher. Set to 0 for no X-axis tilt."); + "A negative value tilts the -X side higher. Set to 0 for no X-axis tilt. " + "In belt printer mode, this is automatically synced to the belt angle."); def->sidetext = u8"\u00B0"; - def->min = -45; - def->max = 45; + def->min = -90; + def->max = 90; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); @@ -5937,11 +5939,41 @@ void PrintConfigDef::init_fff_params() "A positive value tilts the plate so the +Y side is higher, shifting gravity toward -Y and increasing overhangs on the +Y side. " "A negative value tilts the -Y side higher. Set to 0 for no Y-axis tilt."); def->sidetext = u8"\u00B0"; - def->min = -45; - def->max = 45; + def->min = -90; + def->max = 90; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("belt_printer", coBool); + def->label = L("Belt printer"); + def->category = L("Printable space"); + def->tooltip = L("Enable belt printer mode. Belt printers use a conveyor belt as the build surface, " + "tilted at an angle (typically 45 degrees). The slicer will rotate the slicing plane " + "and transform G-code coordinates for the tilted build surface."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("belt_printer_angle", coFloat); + def->label = L("Belt angle"); + def->category = L("Printable space"); + def->tooltip = L("The tilt angle of the belt surface in degrees. " + "Most belt printers use a 45-degree angle. " + "This controls the rotation applied to the slicing plane and G-code coordinates."); + def->sidetext = u8"\u00B0"; + def->min = 0; + def->max = 90; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(45.)); + + def = this->add("belt_printer_infinite_y", coBool); + def->label = L("Infinite Y axis"); + def->category = L("Printable space"); + def->tooltip = L("Enable infinite Y axis for belt printers. " + "When enabled, the Y axis build volume limit is effectively removed, " + "allowing objects of any length to be printed along the belt direction."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + def = this->add("tree_support_branch_angle", coFloat); def->label = L("Tree support branch angle"); def->category = L("Support"); @@ -10823,10 +10855,16 @@ Polygons get_bed_excluded_area(const PrintConfig& cfg) { const Pointfs exclude_area_points = cfg.bed_exclude_area.values; + // Belt printer: project exclusion zone points from belt surface to machine-frame XY. + // On the belt surface, Z=0, so machine_Y = belt_Y * cos(angle). + const bool is_belt = cfg.belt_printer.value; + const double belt_cos = is_belt ? std::cos(Geometry::deg2rad(cfg.belt_printer_angle.value)) : 1.0; + Polygon exclude_poly; for (int i = 0; i < exclude_area_points.size(); i++) { auto pt = exclude_area_points[i]; - exclude_poly.points.emplace_back(scale_(pt.x()), scale_(pt.y())); + double y = is_belt ? pt.y() * belt_cos : pt.y(); + exclude_poly.points.emplace_back(scale_(pt.x()), scale_(y)); } exclude_poly.make_counter_clockwise(); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c862956544..f36b5d3e93 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1413,6 +1413,10 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( // Build plate tilt for off-axis gravity support generation (printer-level setting). ((ConfigOptionFloat, build_plate_tilt_x)) ((ConfigOptionFloat, build_plate_tilt_y)) + // Belt printer settings (printer-level). + ((ConfigOptionBool, belt_printer)) + ((ConfigOptionFloat, belt_printer_angle)) + ((ConfigOptionBool, belt_printer_infinite_y)) //BBS ((ConfigOptionInts, additional_cooling_fan_speed)) ((ConfigOptionBool, reduce_crossing_wall)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index fc68fce76b..979953fb94 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -3391,7 +3391,9 @@ void PrintObject::update_slicing_parameters() { // Orca: updated function call for XYZ shrinkage compensation if (!m_slicing_params.valid) { - m_slicing_params = SlicingParameters::create_from_config(this->print()->config(), m_config, this->model_object()->max_z(), + coordf_t object_height = this->model_object()->max_z(); + // Belt printer: height adjustment placeholder (to be implemented in next cycle). + m_slicing_params = SlicingParameters::create_from_config(this->print()->config(), m_config, object_height, this->object_extruders(), this->print()->shrinkage_compensation()); } } @@ -3432,6 +3434,7 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full if (object_max_z <= 0.f) object_max_z = (float)model_object.raw_bounding_box().size().z(); + // Belt printer: height adjustment placeholder (to be implemented in next cycle). 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 2d67ac0939..bba4593764 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -8,6 +8,7 @@ #include "Layer.hpp" #include "MultiMaterialSegmentation.hpp" #include "Print.hpp" +#include "Geometry.hpp" //BBS #include "ShortestPath.hpp" #include "libslic3r/Feature/Interlocking/InterlockingGenerator.hpp" @@ -139,6 +140,7 @@ static std::vector slice_volumes_inner( params_base.closing_radius = print_object_config.slice_closing_radius.value; params_base.extra_offset = 0; params_base.trafo = object_trafo; + // Belt printer: mesh transform placeholder (to be implemented in next cycle). //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. params_base.resolution = print_config.resolution <= 0.001 ? 0.0f : 0.0025; diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 3f5cd306ad..101279d744 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -387,14 +387,18 @@ void Bed3D::render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, m_model.set_color(m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR); + // Belt printer: bed view transform placeholder (to be implemented in next cycle). + Transform3d belt_view_matrix = view_matrix; + switch (m_type) { - case Type::System: { render_system(canvas, view_matrix, projection_matrix, bottom); break; } + case Type::System: { render_system(canvas, belt_view_matrix, projection_matrix, bottom); break; } default: - case Type::Custom: { render_custom(canvas, view_matrix, projection_matrix, bottom); break; } + case Type::Custom: { render_custom(canvas, belt_view_matrix, projection_matrix, bottom); break; } } render_gravity_arrow(view_matrix, projection_matrix); + render_slicing_plane(view_matrix, projection_matrix); glsafe(::glDisable(GL_DEPTH_TEST)); } @@ -738,6 +742,10 @@ void Bed3D::render_gravity_arrow(const Transform3d& view_matrix, const Transform const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config; double tilt_x_deg = cfg.opt_float("build_plate_tilt_x"); double tilt_y_deg = cfg.opt_float("build_plate_tilt_y"); + // Belt printer: auto-derive gravity direction from belt angle if belt mode is active. + if (m_is_belt_printer && m_belt_angle > 0.f) { + tilt_x_deg = m_belt_angle; + } if (tilt_x_deg == 0. && tilt_y_deg == 0.) { m_gravity_arrow.reset(); return; @@ -790,6 +798,61 @@ void Bed3D::render_gravity_arrow(const Transform3d& view_matrix, const Transform shader->stop_using(); } +void Bed3D::render_slicing_plane(const Transform3d& view_matrix, const Transform3d& projection_matrix) +{ + if (!m_is_belt_printer || m_belt_angle <= 0.f) + return; + + // Build a quad in the XZ plane (world frame) representing the belt slicing plane. + // The plane is tilted at belt_angle from horizontal, with normal (0, -sin(a), cos(a)). + // We render it as a semi-transparent quad centered on the build plate. + if (!m_slicing_plane.is_initialized()) { + const float half_size = 120.f; // mm, large enough to be visible + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(2); // 2 triangles + + // Quad corners in local frame (XY plane, will be rotated to match slicing plane) + Vec3f n = Vec3f::UnitZ(); + init_data.add_vertex(Vec3f(-half_size, -half_size, 0.f), n); + init_data.add_vertex(Vec3f( half_size, -half_size, 0.f), n); + init_data.add_vertex(Vec3f( half_size, half_size, 0.f), n); + init_data.add_vertex(Vec3f(-half_size, half_size, 0.f), n); + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(0, 2, 3); + + m_slicing_plane.init_from(std::move(init_data)); + } + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + shader->start_using(); + + // Show a tilted plane representing the slicing direction. + // The slicing plane is rotated by belt_angle about X from horizontal. + // Raise it slightly so it's visible above the bed surface. + double angle_rad = Geometry::deg2rad(static_cast(m_belt_angle)); + Transform3d model_matrix = Transform3d::Identity(); + model_matrix.translate(Vec3d(0., 0., 30.)); + model_matrix.rotate(Eigen::AngleAxisd(angle_rad, Vec3d::UnitX())); + + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + + m_slicing_plane.set_color({ 0.2f, 0.6f, 1.0f, 0.3f }); // semi-transparent blue + m_slicing_plane.render(); + + glsafe(::glDisable(GL_BLEND)); + shader->stop_using(); +} + void Bed3D::render_default(bool bottom, const Transform3d& view_matrix, const Transform3d& projection_matrix) { // m_texture.reset(); diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index b16ead575b..005dbff534 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -111,6 +111,7 @@ private: GLModel m_model; Vec3d m_model_offset{ Vec3d::Zero() }; GLModel m_gravity_arrow; + GLModel m_slicing_plane; // Debug: shows the intended slicing plane direction Axes m_axes; float m_scale_factor{ 1.0f }; @@ -120,6 +121,9 @@ private: std::vector> m_extruder_shapes; std::vector m_extruder_heights; bool m_is_dark = false; + // Belt printer state for rendering. + bool m_is_belt_printer = false; + float m_belt_angle = 0.f; public: Bed3D() = default; @@ -139,6 +143,12 @@ public: // Build volume geometry for various collision detection tasks. const BuildVolume& build_volume() const { return m_build_volume; } + BuildVolume& build_volume() { return m_build_volume; } + + // Belt printer bed settings. + void set_belt_printer(bool enabled, float angle_deg) { m_is_belt_printer = enabled; m_belt_angle = angle_deg; } + bool is_belt_printer() const { return m_is_belt_printer; } + float belt_angle() const { return m_belt_angle; } // Was the model provided, or was it generated procedurally? Type get_type() const { return m_type; } @@ -179,6 +189,7 @@ private: void render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom); void render_default(bool bottom, const Transform3d& view_matrix, const Transform3d& projection_matrix); void render_gravity_arrow(const Transform3d& view_matrix, const Transform3d& projection_matrix); + void render_slicing_plane(const Transform3d& view_matrix, const Transform3d& projection_matrix); // BBS: remove the bed picking logic // void register_raycasters_for_picking(const GLModel::Geometry& geometry, const Transform3d& trafo); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index f3f98a6fad..0b5e6d3906 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -575,9 +575,17 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co auto gcflavor = preset_bundle->printers.get_edited_preset().config.option>("gcode_flavor")->value; + // Belt printer: detect early since it affects multiple toggle decisions below. + bool is_belt_printer = false; + { + const auto *belt_opt = preset_bundle->printers.get_edited_preset().config.option("belt_printer"); + if (belt_opt) + is_belt_printer = belt_opt->value; + } + bool have_volumetric_extrusion_rate_slope = config->option("max_volumetric_extrusion_rate_slope")->value > 0; float have_volumetric_extrusion_rate_slope_segment_length = config->option("max_volumetric_extrusion_rate_slope_segment_length")->value; - toggle_field("enable_arc_fitting", !have_volumetric_extrusion_rate_slope); + toggle_field("enable_arc_fitting", !have_volumetric_extrusion_rate_slope && !is_belt_printer); toggle_line("max_volumetric_extrusion_rate_slope_segment_length", have_volumetric_extrusion_rate_slope); toggle_line("extrusion_rate_smoothing_external_perimeter_only", have_volumetric_extrusion_rate_slope); if(have_volumetric_extrusion_rate_slope) config->set_key_value("enable_arc_fitting", new ConfigOptionBool(false)); @@ -697,13 +705,20 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co } } - bool have_skirt = config->opt_int("skirt_loops") > 0; + // Belt printer: disable skirt, brim, raft, and draft shield controls. + bool have_skirt = config->opt_int("skirt_loops") > 0 && !is_belt_printer; toggle_field("skirt_height", have_skirt && config->opt_enum("draft_shield") != dsEnabled); toggle_line("single_loop_draft_shield", have_skirt); // ORCA: Display one wall if skirt enabled for (auto el : {"skirt_type", "min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_speed", "draft_shield"}) toggle_field(el, have_skirt); + if (is_belt_printer) { + toggle_field("skirt_loops", false); + toggle_field("skirt_height", false); + } - bool have_brim = (config->opt_enum("brim_type") != btNoBrim); + bool have_brim = (config->opt_enum("brim_type") != btNoBrim) && !is_belt_printer; + if (is_belt_printer) + toggle_field("brim_type", false); toggle_field("brim_object_gap", have_brim); toggle_field("brim_use_efc_outline", have_brim); bool have_brim_width = (config->opt_enum("brim_type") != btNoBrim) && config->opt_enum("brim_type") != btAutoBrim && @@ -724,7 +739,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co // Hide Elephant foot compensation layers if elefant_foot_compensation is not enabled toggle_line("elefant_foot_compensation_layers", config->opt_float("elefant_foot_compensation") > 0); - bool have_raft = config->opt_int("raft_layers") > 0; + bool have_raft = config->opt_int("raft_layers") > 0 && !is_belt_printer; + if (is_belt_printer) + toggle_field("raft_layers", false); bool have_support_material = config->opt_bool("enable_support") || have_raft; SupportType support_type = config->opt_enum("support_type"); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 4814548a10..7df0b06ff5 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2208,7 +2208,9 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p void GCodeViewer::render_toolpaths() { const Camera& camera = wxGetApp().plater()->get_camera(); - const libvgcode::Mat4x4 converted_view_matrix = libvgcode::convert(static_cast(camera.get_view_matrix().matrix().cast())); + Matrix4f view = camera.get_view_matrix().matrix().cast(); + // Belt view: view matrix transform placeholder (to be implemented in next cycle). + 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())); #if VGCODE_ENABLE_COG_AND_TOOL_MARKERS m_viewer.set_cog_marker_scale_factor(m_cog_marker_fixed_screen_size ? 10.0f * m_cog_marker_size * camera.get_inv_zoom() : m_cog_marker_size); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index dc928b822a..a299b93058 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -237,6 +237,9 @@ private: mutable bool m_no_render_path { false }; bool m_is_dark = false; + bool m_belt_view_enabled = false; + float m_belt_angle_deg = 0.f; + libvgcode::Viewer m_viewer; bool m_loaded_as_preview{ false }; @@ -336,6 +339,9 @@ public: void export_toolpaths_to_obj(const char* filename) const; + void set_belt_printer(bool enabled, float angle_deg) { m_belt_view_enabled = enabled; m_belt_angle_deg = angle_deg; } + bool is_belt_view() const { return m_belt_view_enabled && m_belt_angle_deg > 0.f; } + size_t get_extruders_count() { return m_extruders_count; } void push_combo_style(); void pop_combo_style(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ef46801722..373ee2a3dc 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -10980,6 +10980,24 @@ void Plater::priv::set_bed_shape(const Pointfs &shape, Vec2d shape_position = partplate_list.get_current_shape_position(); bool new_shape = bed.set_shape(shape, printable_height, extruder_areas, extruder_heights, custom_model, force_as_custom, shape_position); + // Belt printer: configure build volume and bed rendering for belt mode. + { + const auto *belt_opt = config->option("belt_printer"); + bool is_belt = belt_opt && belt_opt->value; + if (is_belt) { + double belt_angle = config->opt_float("belt_printer_angle"); + bool infinite_y = config->opt_bool("belt_printer_infinite_y"); + bed.build_volume().set_belt_printer(true, belt_angle, infinite_y); + bed.set_belt_printer(true, static_cast(belt_angle)); + if (preview) + preview->get_canvas3d()->get_gcode_viewer().set_belt_printer(true, static_cast(belt_angle)); + } else { + bed.set_belt_printer(false, 0.f); + if (preview) + preview->get_canvas3d()->get_gcode_viewer().set_belt_printer(false, 0.f); + } + } + float prev_height_lid, prev_height_rod; partplate_list.get_height_limits(prev_height_lid, prev_height_rod); double height_to_lid = config->opt_float("extruder_clearance_height_to_lid"); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6ac8ca9a0e..fafe46c952 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4366,6 +4366,9 @@ void TabPrinter::build_fff() optgroup->append_single_option_line("printable_height", "printer_basic_information_printable_space#printable-height"); optgroup->append_single_option_line("build_plate_tilt_x"); optgroup->append_single_option_line("build_plate_tilt_y"); + optgroup->append_single_option_line("belt_printer"); + optgroup->append_single_option_line("belt_printer_angle"); + optgroup->append_single_option_line("belt_printer_infinite_y"); 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 @@ -5224,6 +5227,11 @@ void TabPrinter::toggle_options() auto gcf = m_config->option>("gcode_flavor")->value; toggle_line("enable_power_loss_recovery", is_BBL_printer || gcf == gcfMarlinFirmware); + + // Belt printer: show belt-specific settings only when belt_printer is enabled. + bool is_belt = m_config->opt_bool("belt_printer"); + toggle_line("belt_printer_angle", is_belt); + toggle_line("belt_printer_infinite_y", is_belt); } @@ -5395,6 +5403,23 @@ void TabPrinter::update_fff() m_use_silent_mode = m_config->opt_bool("silent_mode"); } + // Belt printer: auto-sync build_plate_tilt_x to belt_printer_angle when belt mode is active. + // When belt mode is off, reset build_plate_tilt_x to 0 if it was set by belt mode. + if (m_config->opt_bool("belt_printer")) { + double belt_angle = m_config->opt_float("belt_printer_angle"); + if (m_config->opt_float("build_plate_tilt_x") != belt_angle) { + m_config->set_key_value("build_plate_tilt_x", new ConfigOptionFloat(belt_angle)); + } + } else { + // Only reset if build_plate_tilt_x matches a typical belt angle (was set by auto-sync). + // Avoid clobbering a manually-set tilt value for non-belt tilted printers. + double current_tilt = m_config->opt_float("build_plate_tilt_x"); + double belt_angle = m_config->opt_float("belt_printer_angle"); + if (current_tilt != 0. && std::abs(current_tilt - belt_angle) < 0.01) { + m_config->set_key_value("build_plate_tilt_x", new ConfigOptionFloat(0.)); + } + } + toggle_options(); }