diff --git a/resources/web/flush/WipingDialog.html b/resources/web/flush/WipingDialog.html index 1e0e9e1eea..85750866ab 100644 --- a/resources/web/flush/WipingDialog.html +++ b/resources/web/flush/WipingDialog.html @@ -6,6 +6,7 @@ * { user-select: none; -webkit-user-select: none; + box-sizing: border-box; } html, body { @@ -16,19 +17,24 @@ font-family: sans-serif; overscroll-behavior: none; } - + body { position: fixed; inset: 0; } .container { + display: flex; + flex-direction: column; + height: 100%; background: #fff; padding: 20px; padding-bottom: 10px; border: 1px solid #ccc; - max-width:fit-content(1000px); + max-width: fit-content(1000px); margin: 0 auto; + overflow: hidden; + box-sizing: border-box; } .tip-panel { @@ -36,9 +42,19 @@ padding: 10px; margin-bottom: 10px; font-size: 12px; + flex-shrink: 0; + } + + .scroll-wrapper { + flex: 1 1 0%; + min-height: 0; + display: flex; + flex-direction: column; } .scroll-container { + flex: 1 1 0%; + min-height: 80px; max-width: 100%; max-height: 500px; overflow: auto; @@ -47,6 +63,10 @@ margin: 0px; } + .description-section { + flex-shrink: 0; + } + table { border-collapse: collapse; width: 100%; @@ -152,7 +172,8 @@ display: flex; justify-content: center; gap: 0px; - margin: 10px; + margin-top: 10px; + flex-shrink: 0; } /* 暗色模式样式 */ @@ -245,7 +266,7 @@ in Orca Slicer > Preferences. -
+
-
- - - - - - - -
+
+
+ + + + + + + +
+
+ +
+
+ Flushing volume (mm³) for each filament pair. +
+
+ Suggestion: Flushing Volume in range [50, 999] +
+
+ + +
+
+ The multiplier should be in range [0.50, 3.00]. +
+
-
-
- Flushing volume (mm³) for each filament pair. -
-
- Suggestion: Flushing Volume in range [50, 999] -
-
- - -
-
- The multiplier should be in range [0.50, 3.00]. -
-
Save diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index fb39dc542c..14a20680ea 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -5734,6 +5734,9 @@ std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref, // if polyline was shorter than the clipping distance we'd get a null polyline, so // we discard it in that case const double seam_gap = scale_(m_config.seam_gap.get_abs_value(nozzle_diameter)); + const bool seam_gap_applied = enable_seam_slope || m_enable_loop_clipping; + const double seam_gap_distance_mm = seam_gap_applied ? unscale_(seam_gap) : 0.0; + double seam_scarf_distance_mm = 0.0; const double clip_length = m_enable_loop_clipping && !enable_seam_slope ? seam_gap : 0; // get paths @@ -5882,6 +5885,7 @@ std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref, const double slope_min_length = slope_entire_loop ? loop_length : std::min(m_config.seam_slope_min_length.value, loop_length); const int slope_steps = m_config.seam_slope_steps; const double slope_max_segment_length = scale_(slope_min_length / slope_steps); + seam_scarf_distance_mm = slope_min_length; // Calculate the sloped loop ExtrusionLoopSloped new_loop(paths, seam_gap, slope_min_length, slope_max_segment_length, start_slope_ratio, loop.loop_role()); @@ -5906,6 +5910,11 @@ std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref, } } + if (description == "perimeter") { + m_processor.result().print_statistics.total_seam_gap_distance += static_cast(seam_gap_distance_mm); + m_processor.result().print_statistics.total_seam_scarf_distance += static_cast(seam_scarf_distance_mm); + } + // BBS if (m_wipe.enable && FILAMENT_CONFIG(wipe)) { m_wipe.path = Polyline(); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 598ae5bcd2..0ab32a6780 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -3821,9 +3821,10 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes return; EMoveType type = move_type(delta_pos); + const float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); + m_travel_dist = delta_xyz; + if (type == EMoveType::Extrude) { - const float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); - m_travel_dist = delta_xyz; float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; float area_toolpath_cross_section = volume_extruded_filament / delta_xyz; @@ -5445,6 +5446,11 @@ void GCodeProcessor::process_filament_change(int id) int next_extruder_id = m_filament_maps[id]; int next_filament_id = id; float extra_time = 0; + unsigned int filament_changes_delta = 0; + unsigned int extruder_changes_delta = 0; + float filament_load_time_delta = 0.0f; + float filament_unload_time_delta = 0.0f; + float tool_change_time_delta = 0.0f; if (prev_filament_id == next_filament_id) return; @@ -5457,12 +5463,14 @@ void GCodeProcessor::process_filament_change(int id) assert(prev_extruder_id != -1); process_filaments(CustomGCode::ToolChange); m_filament_id[next_extruder_id] = next_filament_id; - m_result.lock(); - m_result.print_statistics.total_filament_changes += 1; - m_result.unlock(); - extra_time += get_filament_unload_time(static_cast(prev_filament_id)); + filament_changes_delta += 1; + const float filament_unload_time = get_filament_unload_time(static_cast(prev_filament_id)); + extra_time += filament_unload_time; + filament_unload_time_delta += filament_unload_time; m_time_processor.extruder_unloaded = false; - extra_time += get_filament_load_time(static_cast(next_filament_id)); + const float filament_load_time = get_filament_load_time(static_cast(next_filament_id)); + extra_time += filament_load_time; + filament_load_time_delta += filament_load_time; } else { if (prev_extruder_id == -1) { @@ -5470,7 +5478,9 @@ void GCodeProcessor::process_filament_change(int id) m_extruder_id = next_extruder_id; m_filament_id[next_extruder_id] = next_filament_id; m_time_processor.extruder_unloaded = false; - extra_time += get_filament_load_time(static_cast(next_filament_id)); + const float filament_load_time = get_filament_load_time(static_cast(next_filament_id)); + extra_time += filament_load_time; + filament_load_time_delta += filament_load_time; } else { //first process cache generated by last extruder @@ -5481,24 +5491,39 @@ void GCodeProcessor::process_filament_change(int id) //no filament in current extruder m_filament_id[next_extruder_id] = next_filament_id; m_time_processor.extruder_unloaded = false; - extra_time += get_filament_load_time(static_cast(next_filament_id)); + const float filament_load_time = get_filament_load_time(static_cast(next_filament_id)); + extra_time += filament_load_time; + filament_load_time_delta += filament_load_time; } else if (m_last_filament_id[next_extruder_id] != next_filament_id) { //need to change filament m_filament_id[next_extruder_id] = next_filament_id; - m_result.lock(); - m_result.print_statistics.total_filament_changes += 1; - m_result.unlock(); - extra_time += get_filament_unload_time(static_cast(prev_filament_id)); + filament_changes_delta += 1; + const float filament_unload_time = get_filament_unload_time(static_cast(prev_filament_id)); + extra_time += filament_unload_time; + filament_unload_time_delta += filament_unload_time; m_time_processor.extruder_unloaded = false; - extra_time += get_filament_load_time(static_cast(next_filament_id)); + const float filament_load_time = get_filament_load_time(static_cast(next_filament_id)); + extra_time += filament_load_time; + filament_load_time_delta += filament_load_time; } - m_result.lock(); - m_result.print_statistics.total_extruder_changes++; - m_result.unlock(); - extra_time += get_extruder_change_time(next_extruder_id); + extruder_changes_delta += 1; + const float tool_change_time = get_extruder_change_time(next_extruder_id); + extra_time += tool_change_time; + tool_change_time_delta += tool_change_time; } } + + if (filament_changes_delta > 0 || extruder_changes_delta > 0 || filament_load_time_delta > 0.0f || filament_unload_time_delta > 0.0f || tool_change_time_delta > 0.0f) { + m_result.lock(); + m_result.print_statistics.total_filament_changes += filament_changes_delta; + m_result.print_statistics.total_extruder_changes += extruder_changes_delta; + m_result.print_statistics.total_filament_load_time += filament_load_time_delta; + m_result.print_statistics.total_filament_unload_time += filament_unload_time_delta; + m_result.print_statistics.total_tool_change_time += tool_change_time_delta; + m_result.unlock(); + } + m_cp_color.current = m_extruder_colors[next_filament_id]; simulate_st_synchronize(extra_time); // store tool change move @@ -5543,6 +5568,11 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type, m_line_id + 1 : ((type == EMoveType::Seam) ? m_last_line_id : m_line_id); + if (type == EMoveType::Travel) { + m_result.print_statistics.total_travel_moves++; + m_result.print_statistics.total_travel_distance += m_travel_dist; + } + m_result.moves.push_back({ m_last_line_id, type, diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 867c8561b1..546df6fbb2 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -78,6 +78,13 @@ class Print; std::array(ETimeMode::Count)> modes; unsigned int total_filament_changes; unsigned int total_extruder_changes; + float total_filament_load_time; + float total_filament_unload_time; + float total_tool_change_time; + float total_travel_distance; + unsigned int total_travel_moves; + float total_seam_gap_distance; + float total_seam_scarf_distance; PrintEstimatedStatistics() { reset(); } @@ -95,6 +102,13 @@ class Print; used_filaments_per_role.clear(); total_filament_changes = 0; total_extruder_changes = 0; + total_filament_load_time = 0.0f; + total_filament_unload_time = 0.0f; + total_tool_change_time = 0.0f; + total_travel_distance = 0.0f; + total_travel_moves = 0; + total_seam_gap_distance = 0.0f; + total_seam_scarf_distance = 0.0f; } }; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 72386563a3..33294735b7 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4317,7 +4317,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->max = 90; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(0)); + def->set_default_value(new ConfigOptionFloat(35)); def = this->add("zaa_dont_alternate_fill_direction", coBool); def->label = L("Don't alternate fill direction"); @@ -6811,7 +6811,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.emplace_back(L("Cone")); def->enum_labels.emplace_back(L("Rib")); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(wtwRectangle)); + def->set_default_value(new ConfigOptionEnum(wtwRib)); def = this->add("wipe_tower_extra_rib_length", coFloat); def->label = L("Extra rib length"); diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 1e59b44e30..ee37acb91e 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -21,6 +21,32 @@ namespace Slic3r { bool PrintObject::clip_multipart_objects = true; bool PrintObject::infill_only_where_needed = false; +static coordf_t compute_slice_z(PrintObject* print_object, size_t i_layer, coordf_t lo, coordf_t hi) +{ + bool zaa_active = false; + coordf_t z_offset = 0.0; + + size_t num_regions = print_object->num_printing_regions(); + for (size_t rid = 0; rid < num_regions; ++rid) { + const auto& rcfg = print_object->printing_region(rid).config(); + if (rcfg.zaa_enabled) { + if (!zaa_active || rcfg.zaa_min_z < z_offset) + z_offset = rcfg.zaa_min_z; + zaa_active = true; + } + } + + if (!zaa_active || i_layer == 0) { + return 0.5 * (lo + hi); + } + + coordf_t slice_z = lo + z_offset; + if ((slice_z < lo && !is_approx(slice_z, lo)) || (slice_z > hi && !is_approx(slice_z, hi))) { + throw RuntimeError("Bad min Z value"); + } + return slice_z; +} + LayerPtrs new_layers( PrintObject *print_object, // Object layers (pairs of bottom/top Z coordinate), without the raft. @@ -34,24 +60,8 @@ LayerPtrs new_layers( for (size_t i_layer = 0; i_layer < object_layers.size(); i_layer += 2) { coordf_t lo = object_layers[i_layer]; coordf_t hi = object_layers[i_layer + 1]; - coordf_t slice_z = 0.5 * (lo + hi); - bool zaa_active = false; - coordf_t z_offset = 0.0; - size_t num_regions = print_object->num_printing_regions(); - for (size_t rid = 0; rid < num_regions; ++rid) { - const auto &rcfg = print_object->printing_region(rid).config(); - if (rcfg.zaa_enabled) { - if (!zaa_active || rcfg.zaa_min_z < z_offset) - z_offset = rcfg.zaa_min_z; - zaa_active = true; - } - } - if (zaa_active) { - slice_z = lo + z_offset; - if ((slice_z < lo && !is_approx(slice_z, lo)) || (slice_z > hi && !is_approx(slice_z, hi))) { - throw RuntimeError("Bad min Z value"); - } - } + coordf_t slice_z = compute_slice_z(print_object, i_layer, lo, hi); + Layer *layer = new Layer(id ++, print_object, hi - lo, hi + zmin, slice_z); out.emplace_back(layer); if (prev != nullptr) { diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 01635e85b8..5dac81b7fe 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -43,6 +43,7 @@ #include #include +#include #include @@ -94,7 +95,7 @@ static std::string get_view_type_string(libvgcode::EViewType view_type) return _u8L("Filament"); else if (view_type == libvgcode::EViewType::LayerTimeLinear) return _u8L("Layer Time"); -else if (view_type == libvgcode::EViewType::LayerTimeLogarithmic) + else if (view_type == libvgcode::EViewType::LayerTimeLogarithmic) return _u8L("Layer Time (log)"); // ORCA: Add Pressure Advance visualization support else if (view_type == libvgcode::EViewType::PressureAdvance) @@ -124,6 +125,29 @@ static int find_close_layer_idx(const std::vector &zs, double &z, double return -1; } +static std::string format_compact_weight(double value_in_grams, bool imperial_units) +{ + char buffer[64]; + if (imperial_units) { + ::sprintf(buffer, "%.2f oz", value_in_grams / GizmoObjectManipulation::oz_to_g); + return buffer; + } + + const double abs_value = value_in_grams < 0.0 ? -value_in_grams : value_in_grams; + const char* unit = "g"; + double scaled_value = abs_value; + if (scaled_value >= 1000000.0) { + scaled_value /= 1000000.0; + unit = "t"; + } else if (scaled_value >= 1000.0) { + scaled_value /= 1000.0; + unit = "kg"; + } + + ::sprintf(buffer, "%s%.2f%s", value_in_grams < 0.0 ? "-" : "", scaled_value, unit); + return buffer; +} + #if ENABLE_ACTUAL_SPEED_DEBUG int GCodeViewer::SequentialView::ActualSpeedImguiWidget::plot(const char* label, const std::array& frame_size) { @@ -1287,6 +1311,25 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const //BBS: move the id to the end of reset m_last_result_id = gcode_result.id; m_gcode_result = &gcode_result; + m_move_type_counts.fill(0); + for (auto& move_type_times : m_move_type_times) + move_type_times.fill(0.0f); + m_move_type_distances.fill(0.0f); + for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) { + if (move.internal_only) + continue; + + const size_t move_type = static_cast(move.type); + if (move_type < m_move_type_counts.size()) { + ++m_move_type_counts[move_type]; + for (size_t mode = 0; mode < move.time.size(); ++mode) + m_move_type_times[move_type][mode] += move.time[mode]; + if (move.type == EMoveType::Retract || move.type == EMoveType::Unretract) + m_move_type_distances[move_type] += std::fabs(move.delta_extruder); + else + m_move_type_distances[move_type] += move.travel_dist; + } + } m_only_gcode_in_preview = only_gcode; m_sequential_view.gcode_window.load_gcode(gcode_result.filename, gcode_result.lines_ends); @@ -1449,6 +1492,29 @@ void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data) { m_loaded_as_preview = true; + m_move_type_counts.fill(0); + for (auto& move_type_times : m_move_type_times) + move_type_times.fill(0.0f); + m_move_type_distances.fill(0.0f); + const size_t normal_time_mode_idx = static_cast(PrintEstimatedStatistics::ETimeMode::Normal); + for (size_t i = 0; i < data.vertices.size(); ++i) { + const libvgcode::PathVertex& vertex = data.vertices[i]; + const size_t move_type = static_cast(vertex.type); + if (move_type < m_move_type_counts.size()) { + ++m_move_type_counts[move_type]; + for (size_t mode = 0; mode < vertex.times.size(); ++mode) + m_move_type_times[move_type][mode] += vertex.times[mode]; + if (vertex.type == libvgcode::EMoveType::Retract || vertex.type == libvgcode::EMoveType::Unretract) { + m_move_type_distances[move_type] += std::fabs(vertex.feedrate) * vertex.times[normal_time_mode_idx]; + } else if (i > 0) { + const float dx = vertex.position[0] - data.vertices[i - 1].position[0]; + const float dy = vertex.position[1] - data.vertices[i - 1].position[1]; + const float dz = vertex.position[2] - data.vertices[i - 1].position[2]; + m_move_type_distances[move_type] += std::sqrt(dx * dx + dy * dy + dz * dz); + } + } + } + m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::Skirt, { 127, 255, 127 }); m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::ExternalPerimeter, { 255, 255, 0 }); m_viewer.set_extrusion_role_color(libvgcode::EGCodeExtrusionRole::SupportMaterial, { 127, 255, 127 }); @@ -1496,6 +1562,10 @@ void GCodeViewer::reset() m_extruders_count = 0; m_filament_diameters = std::vector(); m_filament_densities = std::vector(); + m_move_type_counts.fill(0); + for (auto& move_type_times : m_move_type_times) + move_type_times.fill(0.0f); + m_move_type_distances.fill(0.0f); m_print_statistics.reset(); m_custom_gcode_per_print_z = std::vector(); m_left_extruder_filament.clear(); @@ -2676,39 +2746,42 @@ void GCodeViewer::render_all_plates_stats(const std::vectorfirst + 1), offsets[_u8L("Filament")]}); char buf[64]; - double unit_conver = imperial_units ? GizmoObjectManipulation::oz_to_g : 1.0; - float column_sum_m = 0.0f; float column_sum_g = 0.0f; if (displayed_columns & ColumnData::Model) { + const std::string weight_text = format_compact_weight(model_used_filaments_g_all_plates[i], imperial_units); if ((displayed_columns & ~ColumnData::Model) > 0) - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m_all_plates[i], model_used_filaments_g_all_plates[i] / unit_conver); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", model_used_filaments_m_all_plates[i], weight_text.c_str()); else - ::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", model_used_filaments_m_all_plates[i], model_used_filaments_g_all_plates[i] / unit_conver); + ::sprintf(buf, imperial_units ? "%.2f in %s" : "%.2f m %s", model_used_filaments_m_all_plates[i], weight_text.c_str()); columns_offsets.push_back({ buf, offsets[_u8L("Model")] }); column_sum_m += model_used_filaments_m_all_plates[i]; column_sum_g += model_used_filaments_g_all_plates[i]; } if (displayed_columns & ColumnData::Support) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", support_used_filaments_m_all_plates[i], support_used_filaments_g_all_plates[i] / unit_conver); + const std::string weight_text = format_compact_weight(support_used_filaments_g_all_plates[i], imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", support_used_filaments_m_all_plates[i], weight_text.c_str()); columns_offsets.push_back({ buf, offsets[_u8L("Support")] }); column_sum_m += support_used_filaments_m_all_plates[i]; column_sum_g += support_used_filaments_g_all_plates[i]; } if (displayed_columns & ColumnData::Flushed) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m_all_plates[i], flushed_filaments_g_all_plates[i] / unit_conver); + const std::string weight_text = format_compact_weight(flushed_filaments_g_all_plates[i], imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", flushed_filaments_m_all_plates[i], weight_text.c_str()); columns_offsets.push_back({ buf, offsets[_u8L("Flushed")] }); column_sum_m += flushed_filaments_m_all_plates[i]; column_sum_g += flushed_filaments_g_all_plates[i]; } if (displayed_columns & ColumnData::WipeTower) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", wipe_tower_used_filaments_m_all_plates[i], wipe_tower_used_filaments_g_all_plates[i] / unit_conver); + const std::string weight_text = format_compact_weight(wipe_tower_used_filaments_g_all_plates[i], imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", wipe_tower_used_filaments_m_all_plates[i], weight_text.c_str()); columns_offsets.push_back({ buf, offsets[_u8L("Tower")] }); column_sum_m += wipe_tower_used_filaments_m_all_plates[i]; column_sum_g += wipe_tower_used_filaments_g_all_plates[i]; } if ((displayed_columns & ~ColumnData::Model) > 0) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", column_sum_m, column_sum_g / unit_conver); + const std::string weight_text = format_compact_weight(column_sum_g, imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", column_sum_m, weight_text.c_str()); columns_offsets.push_back({ buf, offsets[_u8L("Total")] }); } @@ -3072,7 +3145,9 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_viewer.get_time_mode())]; const libvgcode::EViewType curr_view_type = m_viewer.get_view_type(); const int curr_view_type_i = static_cast(curr_view_type); - bool show_estimated_time = time_mode.time > 0.0f && (curr_view_type == libvgcode::EViewType::FeatureType || + const size_t current_time_mode = static_cast(m_viewer.get_time_mode()); + const float total_estimated_time = time_mode.time > 0.0f ? time_mode.time : m_viewer.get_estimated_time(); + bool show_estimated_time = total_estimated_time > 0.0f && (curr_view_type == libvgcode::EViewType::FeatureType || curr_view_type == libvgcode::EViewType::LayerTimeLinear || curr_view_type == libvgcode::EViewType::LayerTimeLogarithmic || (curr_view_type == libvgcode::EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); @@ -3086,6 +3161,39 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ImVec2 pos_rect = ImGui::GetCursorScreenPos(); float window_padding = 4.0f * m_scale; + auto format_compact_count = [](unsigned long long value) { + static constexpr const char* suffixes[] = { "", "K", "M", "B", "T", "P", "E" }; + constexpr size_t suffix_count = sizeof(suffixes) / sizeof(suffixes[0]); + + if (value < 1000) + return std::to_string(value); + + size_t suffix_index = 0; + unsigned long long divisor = 1; + while (suffix_index + 1 < suffix_count && value / divisor >= 1000) { + divisor *= 1000; + ++suffix_index; + } + + const unsigned long long whole = value / divisor; + const unsigned long long tenths = (value % divisor) * 10 / divisor; + + std::string ret = std::to_string(whole); + if (tenths != 0) + ret += "." + std::to_string(tenths); + ret += suffixes[suffix_index]; + return ret; + }; + + auto format_percent = [](float percent) { + if (percent == 0.0f) + return std::string("0"); + + char buffer[64]; + percent > 0.001f ? ::sprintf(buffer, "%.1f", percent * 100.0f) : ::sprintf(buffer, "<0.1"); + return std::string(buffer); + }; + // ORCA dont use background on top bar to give modern look //draw_list->AddRectFilled(ImVec2(pos_rect.x,pos_rect.y - ImGui::GetStyle().WindowPadding.y), //ImVec2(pos_rect.x + ImGui::GetWindowWidth() + ImGui::GetFrameHeight(),pos_rect.y + ImGui::GetFrameHeight() + window_padding * 2.5), @@ -3122,10 +3230,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv case EItemType::Line: { draw_list->AddLine({ pos.x + 1, pos.y + icon_size + 2 }, { pos.x + icon_size - 1, pos.y + 4 }, ImGuiWrapper::to_ImU32(color), 3.0f); break; + } case EItemType::None: break; } - } ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0 * m_scale, 6.0 * m_scale)); @@ -3297,14 +3405,26 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); }; - auto role_time_and_percent = [this, time_mode](libvgcode::EGCodeExtrusionRole role) { + auto role_time_and_percent = [this, total_estimated_time](libvgcode::EGCodeExtrusionRole role) { const float time = m_viewer.get_extrusion_role_estimated_time(role); - return std::make_pair(time, time / time_mode.time); + return std::make_pair(time, total_estimated_time > 0.0f ? time / total_estimated_time : 0.0f); }; - auto travel_time_and_percent = [this, time_mode]() { + auto travel_time_and_percent = [this, total_estimated_time]() { const float time = m_viewer.get_travels_estimated_time(); - return std::make_pair(time, time / time_mode.time); + return std::make_pair(time, total_estimated_time > 0.0f ? time / total_estimated_time : 0.0f); + }; + + auto format_distance = [imperial_units](float distance_mm) { + char buffer[64]; + if (imperial_units) { + ::sprintf(buffer, "%.2fin", distance_mm / GizmoObjectManipulation::in_to_mm); + } else if (std::fabs(distance_mm) < 1000.0f) { + ::sprintf(buffer, "%.0fmm", distance_mm); + } else { + ::sprintf(buffer, "%.2fm", distance_mm / 1000.0f); + } + return std::string(buffer); }; auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) { @@ -3409,6 +3529,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv std::vector used_filaments_length; std::vector used_filaments_weight; std::string travel_percent; + std::string travel_distance; + std::string travel_moves; std::vector model_used_filaments_m; std::vector model_used_filaments_g; double total_model_used_filament_m = 0, total_model_used_filament_g = 0; @@ -3528,8 +3650,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv auto [model_used_filament_m, model_used_filament_g] = used_filament_per_role(convert(role)); ::sprintf(buffer, imperial_units ? "%.2fin" : "%.2fm", model_used_filament_m); // ORCA dont use spacing between value and unit used_filaments_length.push_back(buffer); - ::sprintf(buffer, imperial_units ? "%.2foz" : "%.2fg", model_used_filament_g); // ORCA dont use spacing between value and unit - used_filaments_weight.push_back(buffer); + used_filaments_weight.push_back(format_compact_weight(model_used_filament_g, imperial_units)); } } @@ -3542,10 +3663,19 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv else percent > 0.001 ? ::sprintf(buffer, "%.1f", percent * 100) : ::sprintf(buffer, "<0.1"); travel_percent = buffer; + percents.push_back(travel_percent); + + // Set travel distance and moves for the Travel row Usage columns + travel_distance = format_distance(m_print_statistics.total_travel_distance); + used_filaments_length.push_back(travel_distance); + + travel_moves = format_compact_count(m_print_statistics.total_travel_moves); + used_filaments_weight.push_back(travel_moves); } // ORCA use % symbol for percentage and use "Usage" for "Used filaments" offsets = calculate_offsets({ {_u8L("Line Type"), labels}, {_u8L("Time"), times}, {"%", percents}, {"", used_filaments_length}, {"", used_filaments_weight}, {_u8L("Display"), {""}}}, icon_size); + percents.pop_back(); append_headers({{_u8L("Line Type"), offsets[0]}, {_u8L("Time"), offsets[1]}, {"%", offsets[2]}, {_u8L("Usage"), offsets[3]}, {_u8L("Display"), offsets[5]}}); break; } @@ -3609,7 +3739,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv { std::vector total_filaments; char buffer[64]; - ::sprintf(buffer, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", ps.total_used_filament / /*1000*/koef, ps.total_weight / unit_conver); + const std::string total_weight_text = format_compact_weight(ps.total_weight, imperial_units); + ::sprintf(buffer, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", ps.total_used_filament / /*1000*/koef, total_weight_text.c_str()); total_filaments.push_back(buffer); @@ -3644,9 +3775,54 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv default: { break; } } - auto append_option_item = [this, append_item](libvgcode::EOptionType type, std::vector offsets) { - auto append_option_item_with_type = [this, offsets, append_item](libvgcode::EOptionType type, const ColorRGBA& color, const std::string& label, bool visible) { - append_item(EItemType::Rect, color, {{ label , offsets[0] }}, true, offsets.back()/*ORCA checkbox_pos*/, visible, [this, type, visible]() { + auto append_option_item = [this, append_item, current_time_mode, total_estimated_time, &format_compact_count, &format_percent, &format_distance](libvgcode::EOptionType type, std::vector offsets) { + const bool full_layout = offsets.size() > 4; + auto option_stats = [this, current_time_mode, total_estimated_time, &format_compact_count, &format_percent, &format_distance, full_layout](libvgcode::EOptionType option_type) -> std::array { + libvgcode::EMoveType move_type; + bool has_move_type = true; + switch (option_type) { + case libvgcode::EOptionType::Wipes: { move_type = libvgcode::EMoveType::Wipe; break; } + case libvgcode::EOptionType::Retractions: { move_type = libvgcode::EMoveType::Retract; break; } + case libvgcode::EOptionType::Unretractions: { move_type = libvgcode::EMoveType::Unretract; break; } + case libvgcode::EOptionType::Seams: { move_type = libvgcode::EMoveType::Seam; break; } + case libvgcode::EOptionType::ToolChanges: { move_type = libvgcode::EMoveType::ToolChange; break; } + default: { has_move_type = false; break; } + } + + if (!has_move_type) + return { "", "", "", "" }; + + const size_t move_type_idx = static_cast(move_type); + float time = m_move_type_times[move_type_idx][current_time_mode]; + if (option_type == libvgcode::EOptionType::ToolChanges) { + // Toolchange delays are injected via synchronize() and are not attributed to ToolChange move vertices. + time = m_print_statistics.total_filament_load_time + m_print_statistics.total_filament_unload_time + m_print_statistics.total_tool_change_time; + } + const std::string time_text = full_layout && time > 0.0f ? short_time(get_time_dhms(time)) : ""; + const std::string percent_text = full_layout && total_estimated_time > 0.0f ? format_percent(time / total_estimated_time) : ""; + const float seam_distance = m_print_statistics.total_seam_gap_distance + m_print_statistics.total_seam_scarf_distance; + const float distance = (option_type == libvgcode::EOptionType::Seams && seam_distance > 0.0f) ? seam_distance : m_move_type_distances[move_type_idx]; + const std::string distance_text = full_layout && (option_type == libvgcode::EOptionType::Wipes || option_type == libvgcode::EOptionType::Retractions || option_type == libvgcode::EOptionType::Unretractions || option_type == libvgcode::EOptionType::Seams) + ? format_distance(distance) + : ""; + const std::string count_text = full_layout ? format_compact_count(m_move_type_counts[move_type_idx]) : ""; + + return { time_text, percent_text, distance_text, count_text }; + }; + + auto append_option_item_with_type = [this, offsets, append_item, full_layout](libvgcode::EOptionType type, const ColorRGBA& color, const std::string& label, bool visible, + const std::string& time_text, const std::string& percent_text, const std::string& distance_text, const std::string& count_text) { + std::vector> columns_offsets; + columns_offsets.push_back({ label , offsets[0] }); + if (full_layout && !time_text.empty()) + columns_offsets.push_back({ time_text, offsets[1] }); + if (full_layout && !percent_text.empty()) + columns_offsets.push_back({ percent_text, offsets[2] }); + if (full_layout && !distance_text.empty()) + columns_offsets.push_back({ distance_text, offsets[3] }); + if (full_layout && !count_text.empty()) + columns_offsets.push_back({ count_text, distance_text.empty() ? offsets[3] : offsets[4] }); + append_item(EItemType::Rect, color, columns_offsets, true, offsets.back()/*ORCA checkbox_pos*/, visible, [this, type, visible]() { m_viewer.toggle_option_visibility(type); update_moves_slider(); }); @@ -3654,18 +3830,33 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv const bool visible = m_viewer.is_option_visible(type); if (type == libvgcode::EOptionType::Travels) { //BBS: only display travel time in FeatureType view - append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Travels)), _u8L("Travel"), visible); + append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Travels)), _u8L("Travel"), visible, "", "", "", ""); + } + else if (type == libvgcode::EOptionType::Seams) { + const auto option_values = option_stats(type); + append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Seams)), _u8L("Seams"), visible, + option_values[0], option_values[1], option_values[2], option_values[3]); + } + else if (type == libvgcode::EOptionType::Retractions) { + const auto option_values = option_stats(type); + append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Retractions)), _u8L("Retract"), visible, + option_values[0], option_values[1], option_values[2], option_values[3]); + } + else if (type == libvgcode::EOptionType::Unretractions) { + const auto option_values = option_stats(type); + append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Unretractions)), _u8L("Unretract"), visible, + option_values[0], option_values[1], option_values[2], option_values[3]); + } + else if (type == libvgcode::EOptionType::ToolChanges) { + const auto option_values = option_stats(type); + append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::ToolChanges)), _u8L("Filament Changes"), visible, + option_values[0], option_values[1], option_values[2], option_values[3]); + } + else if (type == libvgcode::EOptionType::Wipes) { + const auto option_values = option_stats(type); + append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Wipes)), _u8L("Wipe"), visible, + option_values[0], option_values[1], option_values[2], option_values[3]); } - else if (type == libvgcode::EOptionType::Seams) - append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Seams)), _u8L("Seams"), visible); - else if (type == libvgcode::EOptionType::Retractions) - append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Retractions)), _u8L("Retract"), visible); - else if (type == libvgcode::EOptionType::Unretractions) - append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Unretractions)), _u8L("Unretract"), visible); - else if (type == libvgcode::EOptionType::ToolChanges) - append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::ToolChanges)), _u8L("Filament Changes"), visible); - else if (type == libvgcode::EOptionType::Wipes) - append_option_item_with_type(type, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Wipes)), _u8L("Wipe"), visible); }; const libvgcode::EViewType new_view_type = curr_view_type; @@ -3705,6 +3896,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv columns_offsets.push_back({ _u8L("Travel"), offsets[0] }); columns_offsets.push_back({ travel_time, offsets[1] }); columns_offsets.push_back({ travel_percent, offsets[2] }); + columns_offsets.push_back({ travel_distance, offsets[3] }); // Usage column + columns_offsets.push_back({ travel_moves, offsets[4] }); // Usage column append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_option_color(libvgcode::EOptionType::Travels)), columns_offsets, true, offsets.back()/*ORCA checkbox_pos*/, visible, [this, item, visible]() { m_viewer.toggle_option_visibility(item); update_moves_slider(); @@ -3796,7 +3989,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv size_t i = 0; const std::vector& used_extruders_ids = m_viewer.get_used_extruders_ids(); for (uint8_t extruder_id : used_extruders_ids) { - ::sprintf(buf, imperial_units ? "%.2f in %.2f g" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i]); + const std::string weight_text = format_compact_weight(model_used_filaments_g[i], imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in %s" : "%.2f m %s", model_used_filaments_m[i], weight_text.c_str()); append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_tool_colors()[extruder_id]), { { _u8L("Extruder") + " " + std::to_string(extruder_id + 1), offsets[0]}, {buf, offsets[1]} }); // append_item(EItemType::Rect, libvgcode::convert(m_viewer.get_tool_colors()[extruder_id]), _u8L("Extruder") + " " + std::to_string(extruder_id + 1), // true, "", 0.0f, 0.0f, offsets, used_filaments_m[extruder_id], used_filaments_g[extruder_id]); @@ -3809,7 +4003,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv char buf[64]; imgui.text(_u8L("Total") + ":"); ImGui::SameLine(); - ::sprintf(buf, imperial_units ? "%.2f in / %.2f oz" : "%.2f m / %.2f g", ps.total_used_filament / koef, ps.total_weight / unit_conver); + const std::string total_weight_text = format_compact_weight(ps.total_weight, imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in / %s" : "%.2f m / %s", ps.total_used_filament / koef, total_weight_text.c_str()); imgui.text(buf); ImGui::Dummy({window_padding, window_padding}); @@ -3855,34 +4050,39 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv float column_sum_m = 0.0f; float column_sum_g = 0.0f; if (displayed_columns & ColumnData::Model) { + const std::string weight_text = format_compact_weight(model_used_filaments_g[i], imperial_units); if ((displayed_columns & ~ColumnData::Model) > 0) - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", model_used_filaments_m[i], weight_text.c_str()); else - ::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", model_used_filaments_m[i], model_used_filaments_g[i] / unit_conver); + ::sprintf(buf, imperial_units ? "%.2f in %s" : "%.2f m %s", model_used_filaments_m[i], weight_text.c_str()); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] }); column_sum_m += model_used_filaments_m[i]; column_sum_g += model_used_filaments_g[i]; } if (displayed_columns & ColumnData::Support) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", support_used_filaments_m[i], support_used_filaments_g[i] / unit_conver); + const std::string weight_text = format_compact_weight(support_used_filaments_g[i], imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", support_used_filaments_m[i], weight_text.c_str()); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] }); column_sum_m += support_used_filaments_m[i]; column_sum_g += support_used_filaments_g[i]; } if (displayed_columns & ColumnData::Flushed) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m[i], flushed_filaments_g[i] / unit_conver); + const std::string weight_text = format_compact_weight(flushed_filaments_g[i], imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", flushed_filaments_m[i], weight_text.c_str()); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")]}); column_sum_m += flushed_filaments_m[i]; column_sum_g += flushed_filaments_g[i]; } if (displayed_columns & ColumnData::WipeTower) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", wipe_tower_used_filaments_m[i], wipe_tower_used_filaments_g[i] / unit_conver); + const std::string weight_text = format_compact_weight(wipe_tower_used_filaments_g[i], imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", wipe_tower_used_filaments_m[i], weight_text.c_str()); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] }); column_sum_m += wipe_tower_used_filaments_m[i]; column_sum_g += wipe_tower_used_filaments_g[i]; } if ((displayed_columns & ~ColumnData::Model) > 0) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", column_sum_m, column_sum_g / unit_conver); + const std::string weight_text = format_compact_weight(column_sum_g, imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", column_sum_m, weight_text.c_str()); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] }); } @@ -3908,27 +4108,32 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv std::vector> columns_offsets; columns_offsets.push_back({ _u8L("Total"), color_print_offsets[_u8L("Filament")]}); if (displayed_columns & ColumnData::Model) { + const std::string weight_text = format_compact_weight(total_model_used_filament_g, imperial_units); if ((displayed_columns & ~ColumnData::Model) > 0) - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", total_model_used_filament_m, weight_text.c_str()); else - ::sprintf(buf, imperial_units ? "%.2f in %.2f oz" : "%.2f m %.2f g", total_model_used_filament_m, total_model_used_filament_g / unit_conver); + ::sprintf(buf, imperial_units ? "%.2f in %s" : "%.2f m %s", total_model_used_filament_m, weight_text.c_str()); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Model")] }); } if (displayed_columns & ColumnData::Support) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_support_used_filament_m, total_support_used_filament_g / unit_conver); + const std::string weight_text = format_compact_weight(total_support_used_filament_g, imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", total_support_used_filament_m, weight_text.c_str()); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Support")] }); } if (displayed_columns & ColumnData::Flushed) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_flushed_filament_m, total_flushed_filament_g / unit_conver); + const std::string weight_text = format_compact_weight(total_flushed_filament_g, imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", total_flushed_filament_m, weight_text.c_str()); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Flushed")] }); } if (displayed_columns & ColumnData::WipeTower) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_wipe_tower_used_filament_m, total_wipe_tower_used_filament_g / unit_conver); + const std::string weight_text = format_compact_weight(total_wipe_tower_used_filament_g, imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", total_wipe_tower_used_filament_m, weight_text.c_str()); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Tower")] }); } if ((displayed_columns & ~ColumnData::Model) > 0) { - ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", total_model_used_filament_m + total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m, - (total_model_used_filament_g + total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g) / unit_conver); + const std::string weight_text = format_compact_weight(total_model_used_filament_g + total_support_used_filament_g + total_flushed_filament_g + total_wipe_tower_used_filament_g, imperial_units); + ::sprintf(buf, imperial_units ? "%.2f in\n%s" : "%.2f m\n%s", total_model_used_filament_m + total_support_used_filament_m + total_flushed_filament_m + total_wipe_tower_used_filament_m, + weight_text.c_str()); columns_offsets.push_back({ buf, color_print_offsets[_u8L("Total")] }); } append_item(EItemType::None, libvgcode::convert(tool_colors[0]), columns_offsets); @@ -3939,16 +4144,14 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ImGui::SameLine(); imgui.text(_u8L("Filament change times") + ":"); ImGui::SameLine(); - ::sprintf(buf, "%d", m_print_statistics.total_filament_changes); - imgui.text(buf); + imgui.text(format_compact_count(m_print_statistics.total_filament_changes)); //display tool change times ImGui::Dummy({window_padding, window_padding}); ImGui::SameLine(); imgui.text(_u8L("Tool changes") + ":"); ImGui::SameLine(); - ::sprintf(buf, "%d", m_print_statistics.total_extruder_changes); - imgui.text(buf); + imgui.text(format_compact_count(m_print_statistics.total_extruder_changes)); //BBS display cost ImGui::Dummy({ window_padding, window_padding }); @@ -4075,8 +4278,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv imgui.text(buffer); ImGui::SameLine(offsets[3]); - ::sprintf(buffer, "%.2f g", used_filament.second); - imgui.text(buffer); + imgui.text(format_compact_weight(used_filament.second, imperial_units)); } }; @@ -4401,8 +4603,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", ps.total_used_filament / koef); imgui.text(buf); ImGui::SameLine(); - ::sprintf(buf, imperial_units ? " %.2f oz" : " %.2f g", ps.total_weight / unit_conver); - imgui.text(buf); + imgui.text(" " + format_compact_weight(ps.total_weight, imperial_units)); ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); imgui.text(model_filament_str + ":"); @@ -4412,8 +4613,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", ps.total_used_filament / koef - exlude_m); imgui.text(buf); ImGui::SameLine(); - ::sprintf(buf, imperial_units ? " %.2f oz" : " %.2f g", (ps.total_weight - exlude_g) / unit_conver); - imgui.text(buf); + imgui.text(" " + format_compact_weight(ps.total_weight - exlude_g, imperial_units)); //BBS: display cost of filaments ImGui::Dummy({ window_padding, window_padding }); ImGui::SameLine(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 727e2b3b98..600c59d829 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -14,6 +14,7 @@ // needed for tech VGCODE_ENABLE_COG_AND_TOOL_MARKERS #include +#include #include #include #include @@ -184,6 +185,9 @@ private: unsigned int m_last_result_id{ 0 }; //BBS: save m_gcode_result as well const GCodeProcessorResult* m_gcode_result; + std::array(EMoveType::Count)> m_move_type_counts{}; + std::array(PrintEstimatedStatistics::ETimeMode::Count)>, static_cast(EMoveType::Count)> m_move_type_times{}; + std::array(EMoveType::Count)> m_move_type_distances{}; //BBS: add only gcode mode bool m_only_gcode_in_preview {false}; diff --git a/src/slic3r/GUI/ParamsPanel.cpp b/src/slic3r/GUI/ParamsPanel.cpp index 2c18edfaeb..c694f8647f 100644 --- a/src/slic3r/GUI/ParamsPanel.cpp +++ b/src/slic3r/GUI/ParamsPanel.cpp @@ -279,9 +279,12 @@ ParamsPanel::ParamsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, c }); m_mode_icon->SetToolTip(_L("Cycle settings visibility")); m_mode_view = new ModeSwitchButton(m_top_panel); - m_mode_view->SetSelection(mode_to_selection(wxGetApp().get_saved_mode())); - if (wxGetApp().get_mode() == comDevelop) + if (wxGetApp().get_mode() == comDevelop) { + m_mode_view->SetSelection(mode_to_selection(comExpert)); m_mode_view->Enable(false); + } else { + m_mode_view->SetSelection(mode_to_selection(wxGetApp().get_saved_mode())); + } // BBS: new layout //m_search_btn = new ScalableButton(m_top_panel, wxID_ANY, "search", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true); @@ -648,12 +651,13 @@ void ParamsPanel::update_mode() if (mode_view == nullptr) return; - mode_view->SetSelection(mode_to_selection(Slic3r::GUI::wxGetApp().get_saved_mode())); if (app_mode == comDevelop) { + mode_view->SetSelection(mode_to_selection(comExpert)); mode_view->Enable(false); return; } + mode_view->SetSelection(mode_to_selection(Slic3r::GUI::wxGetApp().get_saved_mode())); if (!mode_view->IsEnabled()) mode_view->Enable(); }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 5f591131ee..b21d1a8829 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -433,9 +433,12 @@ void Tab::create_preset_tab() }); m_top_sizer->Add(m_mode_icon, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(SidebarProps::WideSpacing())); m_mode_view = new ModeSwitchButton(m_top_panel); - m_mode_view->SetSelection(mode_to_selection(wxGetApp().get_saved_mode())); - if (wxGetApp().get_mode() == comDevelop) + if (wxGetApp().get_mode() == comDevelop) { + m_mode_view->SetSelection(mode_to_selection(comExpert)); m_mode_view->Enable(false); + } else { + m_mode_view->SetSelection(mode_to_selection(wxGetApp().get_saved_mode())); + } m_top_sizer->AddSpacer(FromDIP(SidebarProps::ElementSpacing())); m_top_sizer->Add( m_mode_view, 0, wxALIGN_CENTER_VERTICAL); } diff --git a/src/slic3r/GUI/WebUserLoginDialog.cpp b/src/slic3r/GUI/WebUserLoginDialog.cpp index b6d5f055fa..2a9b4cb984 100644 --- a/src/slic3r/GUI/WebUserLoginDialog.cpp +++ b/src/slic3r/GUI/WebUserLoginDialog.cpp @@ -5,6 +5,7 @@ #include "libslic3r/AppConfig.hpp" #include "slic3r/GUI/wxExtensions.hpp" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "libslic3r_version.h" #include @@ -371,7 +372,97 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt) { m_AutotestToken = j["data"]["token"]; } - if (strCmd == "user_login") { + if (strCmd == "user_ticket_login") { + auto* agent = wxGetApp().getAgent(); + if (!agent || !m_cloud_agent || !j.contains("data") || !j["data"].is_object() || !j["data"].contains("ticket")) { + wxMessageBox(_L("Login failed. Please try again."), _L("Login"), wxICON_WARNING); + return; + } + + const auto provider = m_cloud_agent->get_id(); + std::string ticket = j["data"]["ticket"].get(); + + unsigned int token_http_code = 0; + std::string token_body; + int token_result = agent->get_my_token(ticket, &token_http_code, &token_body, provider); + if (token_result != 0) { + BOOST_LOG_TRIVIAL(warning) << "embedded_login: get_my_token failed, http_code=" << token_http_code; + wxMessageBox(_L("Login failed. Please try again."), _L("Login"), wxICON_WARNING); + return; + } + + std::string access_token; + std::string refresh_token; + std::string expires_in_str; + std::string refresh_expires_in_str; + try { + json token_j = json::parse(token_body); + if (token_j.contains("accessToken")) + access_token = token_j["accessToken"].get(); + if (token_j.contains("refreshToken")) + refresh_token = token_j["refreshToken"].get(); + if (token_j.contains("expiresIn")) + expires_in_str = std::to_string(token_j["expiresIn"].get()); + if (token_j.contains("refreshExpiresIn")) + refresh_expires_in_str = std::to_string(token_j["refreshExpiresIn"].get()); + } catch (...) { + wxMessageBox(_L("Login failed. Please try again."), _L("Login"), wxICON_WARNING); + return; + } + + if (access_token.empty()) { + wxMessageBox(_L("Login failed. Please try again."), _L("Login"), wxICON_WARNING); + return; + } + + unsigned int profile_http_code = 0; + std::string profile_body; + int profile_result = agent->get_my_profile(access_token, &profile_http_code, &profile_body, provider); + if (profile_result != 0) { + BOOST_LOG_TRIVIAL(warning) << "embedded_login: get_my_profile failed, http_code=" << profile_http_code; + wxMessageBox(_L("Login failed. Please try again."), _L("Login"), wxICON_WARNING); + return; + } + + std::string user_id; + std::string user_name; + std::string user_account; + std::string user_avatar; + try { + json user_j = json::parse(profile_body); + if (user_j.contains("uidStr")) + user_id = user_j["uidStr"].get(); + if (user_j.contains("name")) + user_name = user_j["name"].get(); + if (user_j.contains("avatar")) + user_avatar = user_j["avatar"].get(); + if (user_j.contains("account")) + user_account = user_j["account"].get(); + } catch (...) { + BOOST_LOG_TRIVIAL(warning) << "embedded_login: profile JSON parse failed"; + } + + json login_j; + login_j["command"] = "user_login"; + login_j["data"]["autotest_token"] = m_AutotestToken; + login_j["data"]["refresh_token"] = refresh_token; + login_j["data"]["token"] = access_token; + login_j["data"]["expires_in"] = expires_in_str; + login_j["data"]["refresh_expires_in"] = refresh_expires_in_str; + login_j["data"]["user"]["uid"] = user_id; + login_j["data"]["user"]["name"] = user_name; + login_j["data"]["user"]["account"] = user_account; + login_j["data"]["user"]["avatar"] = user_avatar; + std::string message_json = login_j.dump(); + + // End modal dialog first to unblock event loop before processing callbacks + EndModal(wxID_OK); + + // Handle message after modal dialog ends to avoid deadlock + // Use wxTheApp->CallAfter to ensure it runs after modal loop exits + wxTheApp->CallAfter([message_json, provider]() { wxGetApp().handle_script_message(message_json, provider); }); + } + else if (strCmd == "user_login") { j["data"]["autotest_token"] = m_AutotestToken; std::string message_json = j.dump(); diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index f7d679051a..abf8baf086 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -372,27 +372,31 @@ WipingDialog::WipingDialog(wxWindow* parent, const int max_flush_volume) : wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); this->SetSizer(main_sizer); this->SetBackgroundColour(*wxWHITE); - auto filament_count= wxGetApp().preset_bundle->project_config.option("filament_colour")->values.size(); - wxSize extra_size = { FromDIP(100),FromDIP(235) }; - if (filament_count <= 2) - extra_size.y += FromDIP(16) * 3 + FromDIP(32); - else if (filament_count == 3) - extra_size.y += FromDIP(16) * 3; - else if (4 <= filament_count && filament_count <= 8) - extra_size.y += FromDIP(16) * 2; - else - extra_size.y += FromDIP(16); + auto filament_count = wxGetApp().preset_bundle->project_config.option("filament_colour")->values.size(); - wxSize max_scroll_size = { FromDIP(1000),FromDIP(500) }; - wxSize estimate_size = { (int)(filament_count + 1) * FromDIP(60),(int)(filament_count + 1) * FromDIP(30)+FromDIP(2)}; - wxSize scroll_size ={ std::min(max_scroll_size.x,estimate_size.x),std::min(max_scroll_size.y,estimate_size.y) }; - wxSize applied_size = scroll_size + extra_size; + // Estimate table scroll area size based on filament count + // Each table cell is ~60x25 DIP, plus headers and borders + wxSize max_scroll_size = { FromDIP(1000), FromDIP(500) }; + wxSize table_size = { (int)(filament_count + 1) * FromDIP(60), (int)(filament_count + 1) * FromDIP(25) + FromDIP(2) }; + wxSize scroll_size = { std::min(max_scroll_size.x, table_size.x), std::min(max_scroll_size.y, table_size.y) }; + // Fixed overhead: padding (~30), tip panel (~70), controls row (~50), + // description/multiplier section (~130), button row (~45) = ~325 DIP + wxSize fixed_overhead = { FromDIP(100), FromDIP(325) }; + wxSize applied_size = scroll_size + fixed_overhead; + + // Clamp to screen size (leave some margin for window decorations) wxSize scaled_screen_size = wxGetDisplaySize(); double scale_factor = wxDisplay().GetScaleFactor(); - scaled_screen_size = { (int)(scaled_screen_size.x / scale_factor),(int)(scaled_screen_size.y / scale_factor) }; + scaled_screen_size = { (int)(scaled_screen_size.x / scale_factor), (int)(scaled_screen_size.y / scale_factor) }; + wxSize screen_margin = { FromDIP(40), FromDIP(60) }; + scaled_screen_size -= screen_margin; - applied_size = { std::min(applied_size.x,scaled_screen_size.x),std::min(applied_size.y,scaled_screen_size.y) }; + applied_size = { std::min(applied_size.x, scaled_screen_size.x), std::min(applied_size.y, scaled_screen_size.y) }; + + // Ensure a reasonable minimum size so the dialog is usable even when clamped + applied_size.x = std::max(applied_size.x, FromDIP(350)); + applied_size.y = std::max(applied_size.y, FromDIP(450)); m_webview = wxWebView::New(this, wxID_ANY, wxEmptyString, wxDefaultPosition,