diff --git a/resources/calib/PressureAdvance/pressure_advance_test.stl b/resources/calib/PressureAdvance/pressure_advance_test.stl new file mode 100644 index 0000000..0df26de Binary files /dev/null and b/resources/calib/PressureAdvance/pressure_advance_test.stl differ diff --git a/resources/calib/PressureAdvance/tower_with_seam.stl b/resources/calib/PressureAdvance/tower_with_seam.stl new file mode 100644 index 0000000..325ffb1 Binary files /dev/null and b/resources/calib/PressureAdvance/tower_with_seam.stl differ diff --git a/resources/calib/filament_flow/flowrate-test-pass1.3mf b/resources/calib/filament_flow/flowrate-test-pass1.3mf new file mode 100644 index 0000000..8f1a1b5 Binary files /dev/null and b/resources/calib/filament_flow/flowrate-test-pass1.3mf differ diff --git a/resources/calib/filament_flow/flowrate-test-pass2.3mf b/resources/calib/filament_flow/flowrate-test-pass2.3mf new file mode 100644 index 0000000..4d1d0c3 Binary files /dev/null and b/resources/calib/filament_flow/flowrate-test-pass2.3mf differ diff --git a/resources/icons/check_half.svg b/resources/icons/check_half.svg new file mode 100644 index 0000000..bc99d2d --- /dev/null +++ b/resources/icons/check_half.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/check_half_disabled.svg b/resources/icons/check_half_disabled.svg new file mode 100644 index 0000000..1e6fb24 --- /dev/null +++ b/resources/icons/check_half_disabled.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/check_half_focused.svg b/resources/icons/check_half_focused.svg new file mode 100644 index 0000000..517bb7a --- /dev/null +++ b/resources/icons/check_half_focused.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/check_off.svg b/resources/icons/check_off.svg new file mode 100644 index 0000000..cf58fbc --- /dev/null +++ b/resources/icons/check_off.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/check_off_disabled.svg b/resources/icons/check_off_disabled.svg new file mode 100644 index 0000000..8653c1f --- /dev/null +++ b/resources/icons/check_off_disabled.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/check_off_focused.svg b/resources/icons/check_off_focused.svg new file mode 100644 index 0000000..474375d --- /dev/null +++ b/resources/icons/check_off_focused.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/check_on.svg b/resources/icons/check_on.svg new file mode 100644 index 0000000..029abf8 --- /dev/null +++ b/resources/icons/check_on.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/check_on_disabled.svg b/resources/icons/check_on_disabled.svg new file mode 100644 index 0000000..dfd917f --- /dev/null +++ b/resources/icons/check_on_disabled.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/check_on_focused.svg b/resources/icons/check_on_focused.svg new file mode 100644 index 0000000..3c1c56c --- /dev/null +++ b/resources/icons/check_on_focused.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/checked.svg b/resources/icons/checked.svg new file mode 100644 index 0000000..88747cb --- /dev/null +++ b/resources/icons/checked.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 0de0b4e..3f432ae 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -35,6 +35,8 @@ set(SLIC3R_SOURCES BuildVolume.cpp BuildVolume.hpp BoostAdapter.hpp + calib.cpp + calib.hpp clipper.cpp clipper.hpp ClipperUtils.cpp diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 5f80a02..9ed53c0 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -162,6 +162,25 @@ std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait return gcode.str(); } + + +//B34 +std::string GCodeWriter::set_pressure_advance(double pa) const +{ + std::ostringstream gcode; + if (pa < 0) + return gcode.str(); + else{ + if (FLAVOR_IS(gcfKlipper)) + gcode << "SET_PRESSURE_ADVANCE ADVANCE=" << std::setprecision(4) << pa << "; Override pressure advance value\n"; + else if(FLAVOR_IS(gcfRepRapFirmware)) + gcode << ("M572 D0 S") << std::setprecision(4) << pa << "; Override pressure advance value\n"; + else + gcode << "M900 K" < + +namespace Slic3r { + +// Calculate the optimal Pressure Advance speed +float CalibPressureAdvance::find_optimal_PA_speed(const DynamicPrintConfig &config, double line_width, double layer_height, + int filament_idx) { + const double general_suggested_min_speed = 100.0; + double filament_max_volumetric_speed = config.option("filament_max_volumetric_speed")->get_at(0); + Flow pattern_line = Flow(line_width, layer_height, config.option("nozzle_diameter")->get_at(0)); + auto pa_speed = std::min(std::max(general_suggested_min_speed,config.option("outer_wall_speed")->value), filament_max_volumetric_speed / pattern_line.mm3_per_mm()); + + return std::floor(pa_speed); +} + +std::string CalibPressureAdvance::move_to(Vec2d pt, GCodeWriter& writer, std::string comment) +{ + std::stringstream gcode; + + gcode << writer.retract(); + gcode << writer.travel_to_xy(pt, comment); + gcode << writer.unretract(); + + m_last_pos = Vec3d(pt.x(), pt.y(), 0); + + return gcode.str(); +} + +double CalibPressureAdvance::e_per_mm( + double line_width, + double layer_height, + float nozzle_diameter, + float filament_diameter, + float print_flow_ratio +) const +{ + const Flow line_flow = Flow(line_width, layer_height, nozzle_diameter); + const double filament_area = M_PI * std::pow(filament_diameter / 2, 2); + + return line_flow.mm3_per_mm() / filament_area * print_flow_ratio; +} + +std::string CalibPressureAdvance::convert_number_to_string(double num) const +{ + auto sNumber = std::to_string(num); + sNumber.erase(sNumber.find_last_not_of('0') + 1, std::string::npos); + sNumber.erase(sNumber.find_last_not_of('.') + 1, std::string::npos); + + return sNumber; +} + +std::string CalibPressureAdvance::draw_digit( + double startx, + double starty, + char c, + CalibPressureAdvance::DrawDigitMode mode, + double line_width, + double e_per_mm, + GCodeWriter& writer +) +{ + const double len = m_digit_segment_len; + const double gap = line_width / 2.0; + + const auto dE = e_per_mm * len; + const auto two_dE = dE * 2; + + Vec2d p0, p1, p2, p3, p4, p5; + Vec2d p0_5, p4_5; + Vec2d gap_p0_toward_p3, gap_p2_toward_p3; + Vec2d dot_direction; + + if (mode == CalibPressureAdvance::DrawDigitMode::Bottom_To_Top) { + // 1-------2-------5 + // | | | + // | | | + // 0-------3-------4 + p0 = Vec2d(startx, starty); + p0_5 = Vec2d(startx, starty + len / 2); + p1 = Vec2d(startx, starty + len); + p2 = Vec2d(startx + len, starty + len); + p3 = Vec2d(startx + len, starty); + p4 = Vec2d(startx + len * 2, starty); + p4_5 = Vec2d(startx + len * 2, starty + len / 2); + p5 = Vec2d(startx + len * 2, starty + len); + + gap_p0_toward_p3 = p0 + Vec2d(gap, 0); + gap_p2_toward_p3 = p2 + Vec2d(0, gap); + + dot_direction = Vec2d(-len / 2, 0); + } else { + // 0-------1 + // | | + // 3-------2 + // | | + // 4-------5 + p0 = Vec2d(startx, starty); + p0_5 = Vec2d(startx + len / 2, starty); + p1 = Vec2d(startx + len, starty); + p2 = Vec2d(startx + len, starty - len); + p3 = Vec2d(startx, starty - len); + p4 = Vec2d(startx, starty - len * 2); + p4_5 = Vec2d(startx + len / 2, starty - len * 2); + p5 = Vec2d(startx + len, starty - len * 2); + + gap_p0_toward_p3 = p0 - Vec2d(0, gap); + gap_p2_toward_p3 = p2 - Vec2d(gap, 0); + + dot_direction = Vec2d(0, len / 2); + } + + std::stringstream gcode; + + switch (c) { + case '0': + gcode << move_to(p0, writer, "Glyph: 0"); + gcode << writer.extrude_to_xy(p1, dE); + gcode << writer.extrude_to_xy(p5, two_dE); + gcode << writer.extrude_to_xy(p4, dE); + gcode << writer.extrude_to_xy(gap_p0_toward_p3, two_dE); + break; + case '1': + gcode << move_to(p0_5, writer, "Glyph: 1"); + gcode << writer.extrude_to_xy(p4_5, two_dE); + break; + case '2': + gcode << move_to(p0, writer, "Glyph: 2"); + gcode << writer.extrude_to_xy(p1, dE); + gcode << writer.extrude_to_xy(p2, dE); + gcode << writer.extrude_to_xy(p3, dE); + gcode << writer.extrude_to_xy(p4, dE); + gcode << writer.extrude_to_xy(p5, dE); + break; + case '3': + gcode << move_to(p0, writer, "Glyph: 3"); + gcode << writer.extrude_to_xy(p1, dE); + gcode << writer.extrude_to_xy(p5, two_dE); + gcode << writer.extrude_to_xy(p4, dE); + gcode << move_to(gap_p2_toward_p3, writer); + gcode << writer.extrude_to_xy(p3, dE); + break; + case '4': + gcode << move_to(p0, writer, "Glyph: 4"); + gcode << writer.extrude_to_xy(p3, dE); + gcode << writer.extrude_to_xy(p2, dE); + gcode << move_to(p1, writer); + gcode << writer.extrude_to_xy(p5, two_dE); + break; + case '5': + gcode << move_to(p1, writer, "Glyph: 5"); + gcode << writer.extrude_to_xy(p0, dE); + gcode << writer.extrude_to_xy(p3, dE); + gcode << writer.extrude_to_xy(p2, dE); + gcode << writer.extrude_to_xy(p5, dE); + gcode << writer.extrude_to_xy(p4, dE); + break; + case '6': + gcode << move_to(p1, writer, "Glyph: 6"); + gcode << writer.extrude_to_xy(p0, dE); + gcode << writer.extrude_to_xy(p4, two_dE); + gcode << writer.extrude_to_xy(p5, dE); + gcode << writer.extrude_to_xy(p2, dE); + gcode << writer.extrude_to_xy(p3, dE); + break; + case '7': + gcode << move_to(p0, writer, "Glyph: 7"); + gcode << writer.extrude_to_xy(p1, dE); + gcode << writer.extrude_to_xy(p5, two_dE); + break; + case '8': + gcode << move_to(p2, writer, "Glyph: 8"); + gcode << writer.extrude_to_xy(p3, dE); + gcode << writer.extrude_to_xy(p4, dE); + gcode << writer.extrude_to_xy(p5, dE); + gcode << writer.extrude_to_xy(p1, two_dE); + gcode << writer.extrude_to_xy(p0, dE); + gcode << writer.extrude_to_xy(p3, dE); + break; + case '9': + gcode << move_to(p5, writer, "Glyph: 9"); + gcode << writer.extrude_to_xy(p1, two_dE); + gcode << writer.extrude_to_xy(p0, dE); + gcode << writer.extrude_to_xy(p3, dE); + gcode << writer.extrude_to_xy(p2, dE); + break; + case '.': + gcode << move_to(p4_5, writer, "Glyph: ."); + gcode << writer.extrude_to_xy(p4_5 + dot_direction, dE); + break; + default: + break; + } + + return gcode.str(); +} + +std::string CalibPressureAdvance::draw_number( + double startx, + double starty, + double value, + CalibPressureAdvance::DrawDigitMode mode, + double line_width, + double e_per_mm, + double speed, + GCodeWriter& writer +) +{ + auto sNumber = convert_number_to_string(value); + std::stringstream gcode; + gcode << writer.set_speed(speed); + + for (std::string::size_type i = 0; i < sNumber.length(); ++i) { + if (i > m_max_number_len) { + break; + } + switch (mode) { + case DrawDigitMode::Bottom_To_Top: + gcode << draw_digit( + startx, + starty + i * number_spacing(), + sNumber[i], + mode, + line_width, + e_per_mm, + writer + ); + break; + default: + gcode << draw_digit( + startx + i * number_spacing(), + starty, + sNumber[i], + mode, + line_width, + e_per_mm, + writer + ); + } + } + + return gcode.str(); +} + +std::string CalibPressureAdvanceLine::generate_test(double start_pa /*= 0*/, double step_pa /*= 0.002*/, int count /*= 10*/) +{ + BoundingBoxf bed_ext = get_extents(mp_gcodegen->config().bed_shape.values); + if (is_delta()) { + CalibPressureAdvanceLine::delta_scale_bed_ext(bed_ext); + } + + auto bed_sizes = mp_gcodegen->config().bed_shape.values; + const auto &w = bed_ext.size().x(); + const auto &h = bed_ext.size().y(); + count = std::min(count, int((h - 10) / m_space_y)); + + m_length_long = 40 + std::min(w - 120.0, 0.0); + + auto startx = (w - m_length_short * 2 - m_length_long - 20) / 2; + auto starty = (h - count * m_space_y) / 2; + if (is_delta()) { + CalibPressureAdvanceLine::delta_modify_start(startx, starty, count); + } + + return print_pa_lines(startx, starty, start_pa, step_pa, count); +} + +bool CalibPressureAdvanceLine::is_delta() const +{ + return mp_gcodegen->config().bed_shape.values.size() > 4; +} + +std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double start_y, double start_pa, double step_pa, int num) +{ + auto& writer = mp_gcodegen->writer(); + const auto& config = mp_gcodegen->config(); + + const auto filament_diameter = config.filament_diameter.get_at(0); + const auto print_flow_ratio = 1; + + const double e_per_mm = CalibPressureAdvance::e_per_mm( + m_line_width, + m_height_layer, + m_nozzle_diameter, + filament_diameter, + print_flow_ratio + ); + const double thin_e_per_mm = CalibPressureAdvance::e_per_mm( + m_thin_line_width, + m_height_layer, + m_nozzle_diameter, + filament_diameter, + print_flow_ratio + ); + const double number_e_per_mm = CalibPressureAdvance::e_per_mm( + m_number_line_width, + m_height_layer, + m_nozzle_diameter, + filament_diameter, + print_flow_ratio + ); + + const double fast = CalibPressureAdvance::speed_adjust(m_fast_speed); + const double slow = CalibPressureAdvance::speed_adjust(m_slow_speed); + std::stringstream gcode; + gcode << mp_gcodegen->writer().travel_to_z(m_height_layer); + double y_pos = start_y; + + // prime line + auto prime_x = start_x - 2; + gcode << move_to(Vec2d(prime_x, y_pos + (num - 4) * m_space_y), writer); + gcode << writer.set_speed(slow); + gcode << writer.extrude_to_xy(Vec2d(prime_x, y_pos + 3 * m_space_y), e_per_mm * m_space_y * num * 1.1); + + for (int i = 0; i < num; ++i) { + gcode << writer.set_pressure_advance(start_pa + i * step_pa); + gcode << move_to(Vec2d(start_x, y_pos + i * m_space_y), writer); + gcode << writer.set_speed(slow); + gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short, y_pos + i * m_space_y), e_per_mm * m_length_short); + gcode << writer.set_speed(fast); + gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long, y_pos + i * m_space_y), e_per_mm * m_length_long); + gcode << writer.set_speed(slow); + gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long + m_length_short, y_pos + i * m_space_y), e_per_mm * m_length_short); + } + gcode << writer.set_pressure_advance(0.0); + + if (m_draw_numbers) { + // draw indicator lines + gcode << writer.set_speed(fast); + gcode << move_to(Vec2d(start_x + m_length_short, y_pos + (num - 1) * m_space_y + 2), writer); + gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short, y_pos + (num - 1) * m_space_y + 7), thin_e_per_mm * 7); + gcode << move_to(Vec2d(start_x + m_length_short + m_length_long, y_pos + (num - 1) * m_space_y + 7), writer); + gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long, y_pos + (num - 1) * m_space_y + 2), thin_e_per_mm * 7); + + for (int i = 0; i < num; i += 2) { + gcode << draw_number( + start_x + m_length_short + m_length_long + m_length_short + 3, + y_pos + i * m_space_y + m_space_y / 2, + start_pa + i * step_pa, + m_draw_digit_mode, + m_number_line_width, + number_e_per_mm, + 3600, + writer + ); + } + } + return gcode.str(); +} + +void CalibPressureAdvanceLine::delta_modify_start(double& startx, double& starty, int count) +{ + startx = -startx; + starty = -(count * m_space_y) / 2; +} + +CalibPressureAdvancePattern::CalibPressureAdvancePattern( + const Calib_Params& params, + const DynamicPrintConfig& config, + bool is_bbl_machine, + Model& model, + const Vec3d& origin +) : + m_params(params) +{ + this->m_draw_digit_mode = DrawDigitMode::Bottom_To_Top; + + refresh_setup(config, is_bbl_machine, model, origin); +}; + +void CalibPressureAdvancePattern::generate_custom_gcodes( + const DynamicPrintConfig& config, + bool is_bbl_machine, + Model& model, + const Vec3d& origin +) +{ + std::stringstream gcode; + gcode << "; start pressure advance pattern for layer\n"; + + refresh_setup(config, is_bbl_machine, model, origin); + + gcode << move_to(Vec2d(m_starting_point.x(), m_starting_point.y()), m_writer, "Move to start XY position"); + gcode << m_writer.travel_to_z(height_first_layer(), "Move to start Z position"); + gcode << m_writer.set_pressure_advance(m_params.start); + + const DrawBoxOptArgs default_box_opt_args(*this); + + // create anchoring frame + gcode << draw_box( + m_starting_point.x(), + m_starting_point.y(), + print_size_x(), + frame_size_y(), + default_box_opt_args + ); + + // create tab for numbers + DrawBoxOptArgs draw_box_opt_args = default_box_opt_args; + draw_box_opt_args.is_filled = true; + draw_box_opt_args.num_perimeters = wall_count(); + gcode << draw_box( + m_starting_point.x(), + m_starting_point.y() + frame_size_y() + line_spacing_first_layer(), + glyph_tab_max_x() - m_starting_point.x(), + max_numbering_height() + line_spacing_first_layer() + m_glyph_padding_vertical * 2, + draw_box_opt_args + ); + + std::vector gcode_items; + const DrawLineOptArgs default_line_opt_args(*this); + const int num_patterns = get_num_patterns(); // "cache" for use in loops + + // draw pressure advance pattern + for (int i = 0; i < m_num_layers; ++i) { + if (i > 0) { + gcode << "; end pressure advance pattern for layer\n"; + CustomGCode::Item item; + item.print_z = height_first_layer() + (i - 1) * height_layer(); + item.type = CustomGCode::Type::Custom; + item.extra = gcode.str(); + gcode_items.push_back(item); + + gcode = std::stringstream(); // reset for next layer contents + gcode << "; start pressure advance pattern for layer\n"; + + const double layer_height = height_first_layer() + (i * height_layer()); + gcode << m_writer.travel_to_z(layer_height, "Move to layer height"); + } + + // line numbering + if (i == 1) { + gcode << m_writer.set_pressure_advance(m_params.start); + + double number_e_per_mm = e_per_mm( + line_width(), + height_layer(), + m_config.option("nozzle_diameter")->get_at(0), + m_config.option("filament_diameter")->get_at(0), + m_config.option("filament_flow_ratio")->get_at(0) + ); + + // glyph on every other line + for (int j = 0; j < num_patterns; j += 2) { + gcode << draw_number( + glyph_start_x(j), + m_starting_point.y() + frame_size_y() + m_glyph_padding_vertical + line_width(), + m_params.start + (j * m_params.step), + m_draw_digit_mode, + line_width(), + number_e_per_mm, + speed_first_layer(), + m_writer + ); + } + } + + DrawLineOptArgs draw_line_opt_args = default_line_opt_args; + + double to_x = m_starting_point.x() + pattern_shift(); + double to_y = m_starting_point.y(); + double side_length = m_wall_side_length; + + // shrink first layer to fit inside frame + if (i == 0) { + double shrink = + ( + line_spacing_first_layer() * (wall_count() - 1) + + (line_width_first_layer() * (1 - m_encroachment)) + ) / std::sin(to_radians(m_corner_angle) / 2) + ; + side_length = m_wall_side_length - shrink; + to_x += shrink * std::sin(to_radians(90) - to_radians(m_corner_angle) / 2); + to_y += + line_spacing_first_layer() * (wall_count() - 1) + + (line_width_first_layer() * (1 - m_encroachment)) + ; + } + + double initial_x = to_x; + double initial_y = to_y; + + gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to pattern start"); + + for (int j = 0; j < num_patterns; ++j) { + // increment pressure advance + gcode << m_writer.set_pressure_advance(m_params.start + (j * m_params.step)); + + for (int k = 0; k < wall_count(); ++k) { + to_x += std::cos(to_radians(m_corner_angle) / 2) * side_length; + to_y += std::sin(to_radians(m_corner_angle) / 2) * side_length; + + draw_line_opt_args = default_line_opt_args; + draw_line_opt_args.height = i == 0 ? height_first_layer() : height_layer(); + draw_line_opt_args.line_width = line_width(); // don't use line_width_first_layer so results are consistent across all layers + draw_line_opt_args.speed = i == 0 ? speed_adjust(speed_first_layer()) : speed_adjust(speed_perimeter()); + draw_line_opt_args.comment = "Print pattern wall"; + gcode << draw_line(Vec2d(to_x, to_y), draw_line_opt_args); + + to_x -= std::cos(to_radians(m_corner_angle) / 2) * side_length; + to_y += std::sin(to_radians(m_corner_angle) / 2) * side_length; + + gcode << draw_line(Vec2d(to_x, to_y), draw_line_opt_args); + + to_y = initial_y; + if (k != wall_count() - 1) { + // perimeters not done yet. move to next perimeter + to_x += line_spacing_angle(); + gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to start next pattern wall"); + } else if (j != num_patterns - 1) { + // patterns not done yet. move to next pattern + to_x += m_pattern_spacing + line_width(); + gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to next pattern"); + } else if (i != m_num_layers - 1) { + // layers not done yet. move back to start + to_x = initial_x; + gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move back to start position"); + } else { + // everything done + } + } + } + } + + gcode << m_writer.set_pressure_advance(m_params.start); + gcode << "; end pressure advance pattern for layer\n"; + + CustomGCode::Item item; + item.print_z = max_layer_z(); + item.type = CustomGCode::Type::Custom; + item.extra = gcode.str(); + gcode_items.push_back(item); + + CustomGCode::Info info; + info.mode = CustomGCode::Mode::SingleExtruder; + info.gcodes = gcode_items; + + //model.plates_custom_gcodes[model.curr_plate_index] = info; +} + +void CalibPressureAdvancePattern::refresh_setup( + const DynamicPrintConfig& config, + bool is_bbl_machine, + const Model& model, + const Vec3d& origin +) +{ + m_config = config; + m_config.apply(model.objects.front()->config.get(), true); + m_config.apply(model.objects.front()->volumes.front()->config.get(), true); + + m_is_delta = (m_config.option("bed_shape")->values.size() > 4); + + _refresh_starting_point(model); + _refresh_writer(is_bbl_machine, model, origin); +} + +void CalibPressureAdvancePattern::_refresh_starting_point(const Model& model) +{ + ModelObject* obj = model.objects.front(); + //BoundingBoxf3 bbox = + // obj->instance_bounding_box( + // *obj->instances.front(), + // false + // ) + //; + + m_starting_point = Vec3d(0, 0, 0); + m_starting_point.y() += m_handle_spacing; + + if (m_is_delta) { + m_starting_point.x() *= -1; + m_starting_point.y() -= (frame_size_y() / 2); + } +} + +void CalibPressureAdvancePattern::_refresh_writer( + bool is_bbl_machine, + const Model& model, + const Vec3d& origin +) +{ + PrintConfig print_config; + print_config.apply(m_config, true); + + m_writer.apply_print_config(print_config); + //m_writer.set_xy_offset(origin(0), origin(1)); + + const unsigned int extruder_id = model.objects.front()->volumes.front()->extruder_id(); + m_writer.set_extruders({ extruder_id }); + m_writer.set_extruder(extruder_id); +} + +std::string CalibPressureAdvancePattern::draw_line( + Vec2d to_pt, + DrawLineOptArgs opt_args +) +{ + const double e_per_mm = CalibPressureAdvance::e_per_mm( + opt_args.line_width, + opt_args.height, + m_config.option("nozzle_diameter")->get_at(0), + m_config.option("filament_diameter")->get_at(0), + m_config.option("filament_flow_ratio")->get_at(0) + ); + + const double length = get_distance(Vec2d(m_last_pos.x(), m_last_pos.y()), to_pt); + auto dE = e_per_mm * length; + + std::stringstream gcode; + + gcode << m_writer.set_speed(opt_args.speed); + gcode << m_writer.extrude_to_xy(to_pt, dE, opt_args.comment); + + m_last_pos = Vec3d(to_pt.x(), to_pt.y(), 0); + + return gcode.str(); +} + +std::string CalibPressureAdvancePattern::draw_box( + double min_x, + double min_y, + double size_x, + double size_y, + DrawBoxOptArgs opt_args +) +{ + std::stringstream gcode; + + double x = min_x; + double y = min_y; + const double max_x = min_x + size_x; + const double max_y = min_y + size_y; + + const double spacing = opt_args.line_width - opt_args.height * (1 - M_PI / 4); + + // if number of perims exceeds size of box, reduce it to max + const int max_perimeters = + std::min( + // this is the equivalent of number of perims for concentric fill + std::floor(size_x * std::sin(to_radians(45))) / (spacing / std::sin(to_radians(45))), + std::floor(size_y * std::sin(to_radians(45))) / (spacing / std::sin(to_radians(45))) + ) + ; + + opt_args.num_perimeters = std::min(opt_args.num_perimeters, max_perimeters); + + gcode << move_to(Vec2d(min_x, min_y), m_writer, "Move to box start"); + + DrawLineOptArgs line_opt_args(*this); + line_opt_args.height = opt_args.height; + line_opt_args.line_width = opt_args.line_width; + line_opt_args.speed = opt_args.speed; + + for (int i = 0; i < opt_args.num_perimeters; ++i) { + if (i != 0) { // after first perimeter, step inwards to start next perimeter + x += spacing; + y += spacing; + gcode << move_to(Vec2d(x, y), m_writer, "Step inwards to print next perimeter"); + } + + y += size_y - i * spacing * 2; + line_opt_args.comment = "Draw perimeter (up)"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + + x += size_x - i * spacing * 2; + line_opt_args.comment = "Draw perimeter (right)"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + + y -= size_y - i * spacing * 2; + line_opt_args.comment = "Draw perimeter (down)"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + + x -= size_x - i * spacing * 2; + line_opt_args.comment = "Draw perimeter (left)"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + } + + if (!opt_args.is_filled) { + return gcode.str(); + } + + // create box infill + const double spacing_45 = spacing / std::sin(to_radians(45)); + + const double bound_modifier = + (spacing * (opt_args.num_perimeters - 1)) + + (opt_args.line_width * (1 - m_encroachment)) + ; + const double x_min_bound = min_x + bound_modifier; + const double x_max_bound = max_x - bound_modifier; + const double y_min_bound = min_y + bound_modifier; + const double y_max_bound = max_y - bound_modifier; + const int x_count = std::floor((x_max_bound - x_min_bound) / spacing_45); + const int y_count = std::floor((y_max_bound - y_min_bound) / spacing_45); + + double x_remainder = std::fmod((x_max_bound - x_min_bound), spacing_45); + double y_remainder = std::fmod((y_max_bound - y_min_bound), spacing_45); + + x = x_min_bound; + y = y_min_bound; + + gcode << move_to(Vec2d(x, y), m_writer, "Move to fill start"); + + for (int i = 0; i < x_count + y_count + (x_remainder + y_remainder >= spacing_45 ? 1 : 0); ++i) { // this isn't the most robust way, but less expensive than finding line intersections + if (i < std::min(x_count, y_count)) { + if (i % 2 == 0) { + x += spacing_45; + y = y_min_bound; + gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step right"); + + y += x - x_min_bound; + x = x_min_bound; + line_opt_args.comment = "Fill: Print up/left"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + } else { + y += spacing_45; + x = x_min_bound; + gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step up"); + + x += y - y_min_bound; + y = y_min_bound; + line_opt_args.comment = "Fill: Print down/right"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + } + } else if (i < std::max(x_count, y_count)) { + if (x_count > y_count) { + // box is wider than tall + if (i % 2 == 0) { + x += spacing_45; + y = y_min_bound; + gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step right"); + + x -= y_max_bound - y_min_bound; + y = y_max_bound; + line_opt_args.comment = "Fill: Print up/left"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + } else { + if (i == y_count) { + x += spacing_45 - y_remainder; + y_remainder = 0; + } else { + x += spacing_45; + } + y = y_max_bound; + gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step right"); + + x += y_max_bound - y_min_bound; + y = y_min_bound; + line_opt_args.comment = "Fill: Print down/right"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + } + } else { + // box is taller than wide + if (i % 2 == 0) { + x = x_max_bound; + if (i == x_count) { + y += spacing_45 - x_remainder; + x_remainder = 0; + } else { + y += spacing_45; + } + gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step up"); + + x = x_min_bound; + y += x_max_bound - x_min_bound; + line_opt_args.comment = "Fill: Print up/left"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + } else { + x = x_min_bound; + y += spacing_45; + gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step up"); + + x = x_max_bound; + y -= x_max_bound - x_min_bound; + line_opt_args.comment = "Fill: Print down/right"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + } + } + } else { + if (i % 2 == 0) { + x = x_max_bound; + if (i == x_count) { + y += spacing_45 - x_remainder; + } else { + y += spacing_45; + } + gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step up"); + + x -= y_max_bound - y; + y = y_max_bound; + line_opt_args.comment = "Fill: Print up/left"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + } else { + if (i == y_count) { + x += spacing_45 - y_remainder; + } else { + x += spacing_45; + } + y = y_max_bound; + gcode << move_to(Vec2d(x, y), m_writer, "Fill: Step right"); + + y -= x_max_bound - x; + x = x_max_bound; + line_opt_args.comment = "Fill: Print down/right"; + gcode << draw_line(Vec2d(x, y), line_opt_args); + } + } + } + + return gcode.str(); +} + +double CalibPressureAdvancePattern::get_distance(Vec2d from, Vec2d to) const +{ + return std::hypot((to.x() - from.x()), (to.y() - from.y())); +} + +double CalibPressureAdvancePattern::object_size_x() const +{ + return get_num_patterns() * ((wall_count() - 1) * line_spacing_angle()) + + (get_num_patterns() - 1) * (m_pattern_spacing + line_width()) + + std::cos(to_radians(m_corner_angle) / 2) * m_wall_side_length + + line_spacing_first_layer() * wall_count() + ; +} + +double CalibPressureAdvancePattern::object_size_y() const +{ + return 2 * (std::sin(to_radians(m_corner_angle) / 2) * m_wall_side_length) + + max_numbering_height() + + m_glyph_padding_vertical * 2 + + line_width_first_layer(); +} + +double CalibPressureAdvancePattern::glyph_start_x(int pattern_i) const +{ + // note that pattern_i is zero-based! + // align glyph's start with first perimeter of specified pattern + double x = + // starting offset + m_starting_point.x() + + pattern_shift() + + + // width of pattern extrusions + pattern_i * (wall_count() - 1) * line_spacing_angle() + // center to center distance of extrusions + pattern_i * line_width() + // endcaps. center to end on either side = 1 line width + + // space between each pattern + pattern_i * m_pattern_spacing + ; + + // align to middle of pattern walls + x += wall_count() * line_spacing_angle() / 2; + + // shift so glyph is centered on pattern + // m_digit_segment_len = half of X length of glyph + x -= (glyph_length_x() / 2); + + return x; +} + +double CalibPressureAdvancePattern::glyph_length_x() const +{ + // half of line_width sticks out on each side + return line_width() + (2 * m_digit_segment_len); +} + +double CalibPressureAdvancePattern::glyph_tab_max_x() const +{ + // only every other glyph is shown, starting with 1 + int num = get_num_patterns(); + int max_num = + (num % 2 == 0) + ? num - 1 + : num + ; + + // padding at end should be same as padding at start + double padding = glyph_start_x(0) - m_starting_point.x(); + + return + glyph_start_x(max_num - 1) + // glyph_start_x is zero-based + (glyph_length_x() - line_width() / 2) + + padding + ; +} + +double CalibPressureAdvancePattern::max_numbering_height() const +{ + std::string::size_type most_characters = 0; + const int num_patterns = get_num_patterns(); + + // note: only every other number is printed + for (std::string::size_type i = 0; i < num_patterns; i += 2) { + std::string sNumber = convert_number_to_string(m_params.start + (i * m_params.step)); + + if (sNumber.length() > most_characters) { + most_characters = sNumber.length(); + } + } + + most_characters = std::min(most_characters, m_max_number_len); + + return (most_characters * m_digit_segment_len) + ((most_characters - 1) * m_digit_gap_len); +} + +double CalibPressureAdvancePattern::pattern_shift() const +{ + return + (wall_count() - 1) * line_spacing_first_layer() + + line_width_first_layer() + + m_glyph_padding_horizontal + ; +} +} // namespace Slic3r diff --git a/src/libslic3r/calib.hpp b/src/libslic3r/calib.hpp new file mode 100644 index 0000000..59d671d --- /dev/null +++ b/src/libslic3r/calib.hpp @@ -0,0 +1,280 @@ +#pragma once +#define calib_pressure_advance_dd + +#include "GCode.hpp" +#include "GCodeWriter.hpp" +#include "PrintConfig.hpp" + +namespace Slic3r { + +enum class CalibMode : int { + Calib_None = 0, + Calib_PA_Line, + Calib_PA_Pattern, + Calib_PA_Tower, + Calib_Temp_Tower, + Calib_Vol_speed_Tower, + Calib_VFA_Tower, + Calib_Retraction_tower +}; + +struct Calib_Params { + Calib_Params() : mode(CalibMode::Calib_None) { }; + double start, end, step; + bool print_numbers; + CalibMode mode; +}; + +class CalibPressureAdvance { + public: + static float find_optimal_PA_speed(const DynamicPrintConfig &config, double line_width, double layer_height, + int filament_idx = 0); + + protected: + CalibPressureAdvance() =default; + ~CalibPressureAdvance() =default; + + enum class DrawDigitMode { + Left_To_Right, + Bottom_To_Top + }; + + void delta_scale_bed_ext(BoundingBoxf& bed_ext) const { bed_ext.scale(1.0f / 1.41421f); } + + std::string move_to(Vec2d pt, GCodeWriter& writer, std::string comment = std::string()); + double e_per_mm( + double line_width, + double layer_height, + float nozzle_diameter, + float filament_diameter, + float print_flow_ratio + ) const; + double speed_adjust(int speed) const { return speed * 60; }; + + std::string convert_number_to_string(double num) const; + double number_spacing() const { return m_digit_segment_len + m_digit_gap_len; }; + std::string draw_digit( + double startx, + double starty, + char c, + CalibPressureAdvance::DrawDigitMode mode, + double line_width, + double e_per_mm, + GCodeWriter& writer + ); + std::string draw_number( + double startx, + double starty, + double value, + CalibPressureAdvance::DrawDigitMode mode, + double line_width, + double e_per_mm, + double speed, + GCodeWriter& writer + ); + + Vec3d m_last_pos; + + DrawDigitMode m_draw_digit_mode {DrawDigitMode::Left_To_Right}; + const double m_digit_segment_len {2}; + const double m_digit_gap_len {1}; + const std::string::size_type m_max_number_len {5}; +}; + +class CalibPressureAdvanceLine : public CalibPressureAdvance { +public: + CalibPressureAdvanceLine(GCode* gcodegen) : + mp_gcodegen(gcodegen), + m_nozzle_diameter(gcodegen->config().nozzle_diameter.get_at(0)) + { }; + ~CalibPressureAdvanceLine() { }; + + std::string generate_test(double start_pa = 0, double step_pa = 0.002, int count = 50); + + void set_speed(double fast = 100.0, double slow = 20.0) { + m_slow_speed = slow; + m_fast_speed = fast; + } + + const double& line_width() { return m_line_width; }; + bool is_delta() const; + bool& draw_numbers() { return m_draw_numbers; } + +private: + std::string print_pa_lines(double start_x, double start_y, double start_pa, double step_pa, int num); + + void delta_modify_start(double& startx, double& starty, int count); + + GCode* mp_gcodegen; + + double m_nozzle_diameter; + double m_slow_speed, m_fast_speed; + + const double m_height_layer {0.2}; + const double m_line_width {0.6}; + const double m_thin_line_width {0.44}; + const double m_number_line_width {0.48}; + const double m_space_y {3.5}; + + double m_length_short {20.0}, m_length_long {40.0}; + bool m_draw_numbers {true}; +}; + +struct SuggestedConfigCalibPAPattern { + const std::vector> float_pairs { + {"initial_layer_print_height", 0.25}, + {"layer_height", 0.2}, + {"initial_layer_speed", 30} + }; + + const std::vector> nozzle_ratio_pairs { + {"line_width", 112.5}, + {"initial_layer_line_width", 140} + }; + + const std::vector> int_pairs { + {"skirt_loops", 0}, + {"wall_loops", 3} + }; + + const std::pair brim_pair {"brim_type", BrimType::btNoBrim}; +}; + +class CalibPressureAdvancePattern : public CalibPressureAdvance { +friend struct DrawLineOptArgs; +friend struct DrawBoxOptArgs; + +public: + CalibPressureAdvancePattern( + const Calib_Params& params, + const DynamicPrintConfig& config, + bool is_bbl_machine, + Model& model, + const Vec3d& origin + ); + + double handle_xy_size() const { return m_handle_xy_size; }; + double handle_spacing() const { return m_handle_spacing; }; + double print_size_x() const { return object_size_x() + pattern_shift(); }; + double print_size_y() const { return object_size_y(); }; + double max_layer_z() const { return height_first_layer() + ((m_num_layers - 1) * height_layer()); }; + + void generate_custom_gcodes( + const DynamicPrintConfig& config, + bool is_bbl_machine, + Model& model, + const Vec3d& origin + ); + +protected: + double speed_first_layer() const { return m_config.option("initial_layer_speed")->value; }; + double speed_perimeter() const { return m_config.option("outer_wall_speed")->value; }; + double line_width_first_layer() const { return m_config.get_abs_value("initial_layer_line_width"); }; + double line_width() const { return m_config.get_abs_value("line_width"); }; + int wall_count() const { return m_config.option("wall_loops")->value; }; + +private: + struct DrawLineOptArgs { + DrawLineOptArgs(const CalibPressureAdvancePattern& p) : + height {p.height_layer()}, + line_width {p.line_width()}, + speed {p.speed_adjust(p.speed_perimeter())} + { }; + + double height; + double line_width; + double speed; + std::string comment {"Print line"}; + }; + + struct DrawBoxOptArgs { + DrawBoxOptArgs(const CalibPressureAdvancePattern& p) : + num_perimeters {p.wall_count()}, + height {p.height_first_layer()}, + line_width {p.line_width_first_layer()}, + speed {p.speed_adjust(p.speed_first_layer())} + { }; + + bool is_filled {false}; + int num_perimeters; + double height; + double line_width; + double speed; + }; + + void refresh_setup( + const DynamicPrintConfig& config, + bool is_bbl_machine, + const Model& model, + const Vec3d& origin + ); + void _refresh_starting_point(const Model& model); + void _refresh_writer( + bool is_bbl_machine, + const Model& model, + const Vec3d& origin + ); + + double height_first_layer() const { return m_config.option("initial_layer_print_height")->value; }; + double height_layer() const { return m_config.option("layer_height")->value; }; + const int get_num_patterns() const + { + return std::ceil((m_params.end - m_params.start) / m_params.step + 1); + } + + std::string draw_line( + Vec2d to_pt, + DrawLineOptArgs opt_args + ); + std::string draw_box( + double min_x, + double min_y, + double size_x, + double size_y, + DrawBoxOptArgs opt_args + ); + + double to_radians(double degrees) const { return degrees * M_PI / 180; }; + double get_distance(Vec2d from, Vec2d to) const; + + /* + from slic3r documentation: spacing = extrusion_width - layer_height * (1 - PI/4) + "spacing" = center-to-center distance of adjacent extrusions, which partially overlap + https://manual.slic3r.org/advanced/flow-math + https://ellis3dp.com/Print-Tuning-Guide/articles/misconceptions.html#two-04mm-perimeters--08mm + */ + double line_spacing() const { return line_width() - height_layer() * (1 - M_PI / 4); }; + double line_spacing_first_layer() const { return line_width_first_layer() - height_first_layer() * (1 - M_PI / 4); }; + double line_spacing_angle() const { return line_spacing() / std::sin(to_radians(m_corner_angle) / 2); }; + + double object_size_x() const; + double object_size_y() const; + double frame_size_y() const { return std::sin(to_radians(double(m_corner_angle) / 2)) * m_wall_side_length * 2; }; + + double glyph_start_x(int pattern_i = 0) const; + double glyph_length_x() const; + double glyph_tab_max_x() const; + double max_numbering_height() const; + + double pattern_shift() const; + + const Calib_Params& m_params; + + DynamicPrintConfig m_config; + GCodeWriter m_writer; + bool m_is_delta; + Vec3d m_starting_point; + + const double m_handle_xy_size {5}; + const double m_handle_spacing {2}; + const int m_num_layers {4}; + + const double m_wall_side_length {30.0}; + const int m_corner_angle {90}; + const int m_pattern_spacing {2}; + const double m_encroachment {1. / 3.}; + + const double m_glyph_padding_horizontal {1}; + const double m_glyph_padding_vertical {1}; +}; +} // namespace Slic3r diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index aca55aa..e76efeb 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -26,8 +26,27 @@ set(SLIC3R_GUI_SOURCES GUI/Widgets/StateHandler.hpp GUI/Widgets/StaticBox.cpp GUI/Widgets/StaticBox.hpp + GUI/Widgets/Button.cpp + GUI/Widgets/Button.hpp + GUI/Widgets/CheckBoxInWT.cpp + GUI/Widgets/CheckBoxInWT.hpp + GUI/Widgets/ComboBox.cpp + GUI/Widgets/ComboBox.hpp + GUI/Widgets/TextInput.cpp + GUI/Widgets/TextInput.hpp + GUI/Widgets/TextCtrl.h + GUI/Widgets/RoundedRectangle.cpp + GUI/Widgets/RoundedRectangle.hpp + GUI/Widgets/RadioBox.cpp + GUI/Widgets/RadioBox.hpp + GUI/Widgets/DropDown.cpp + GUI/Widgets/DropDown.hpp + GUI/Widgets/PopupWindow.cpp + GUI/Widgets/PopupWindow.hpp GUI/ConfigSnapshotDialog.cpp GUI/ConfigSnapshotDialog.hpp + GUI/calib_dlg.cpp + GUI/calib_dlg.hpp GUI/3DScene.cpp GUI/3DScene.hpp GUI/format.hpp diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 74a8719..7c2adb0 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -831,7 +831,8 @@ void Preview::load_print_as_fff(bool keep_z_range) unsigned int number_extruders = wxGetApp().is_editor() ? (unsigned int)print->extruders().size() : m_canvas->get_gcode_extruders_count(); - std::vector gcodes = wxGetApp().is_editor() ? + //B34 + std::vector gcodes = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_canvas->get_custom_gcode_per_print_z(); const bool contains_color_gcodes = std::any_of(std::begin(gcodes), std::end(gcodes), diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6f71b1b..a8189d9 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -880,6 +880,7 @@ void MainFrame::create_preset_tabs() else #endif m_tabpanel->AddPage(m_guide_view, _L("Guide")); + } void MainFrame::add_created_tab(Tab* panel, const std::string& bmp_name /*= ""*/) @@ -1566,6 +1567,45 @@ void MainFrame::init_menubar_as_editor() // Help menu auto helpMenu = generate_help_menu(); + + //B34 + //auto calibrationMenu = generate_calibration_menu(); + auto calibrationMenu = new wxMenu(); + auto flowrate_menu = new wxMenu(); + append_menu_item(flowrate_menu, wxID_ANY, _L("Pass 1"), _L("Flow rate test - Pass 1"), [this](wxCommandEvent &) { + if (m_plater) + m_plater->calib_flowrate(1); + }, + "", nullptr, + [this]() { + return m_plater->is_view3D_shown(); + ; + }, + this); + append_menu_item(flowrate_menu, wxID_ANY, _L("Pass 2"), _L("Flow rate test - Pass 2"), [this](wxCommandEvent &) { + if (m_plater) + m_plater->calib_flowrate(2); + }, + "", nullptr, + [this]() { + return m_plater->is_view3D_shown(); + ; + }, + this); + calibrationMenu->AppendSubMenu(flowrate_menu, _L("Flow rate")); + append_menu_item( + calibrationMenu, wxID_ANY, _L("Pressure advance"), _L("Pressure advance"), + [this](wxCommandEvent &) { + if (!m_pa_calib_dlg) + m_pa_calib_dlg = new PA_Calibration_Dlg((wxWindow *) this, wxID_ANY, m_plater); + m_pa_calib_dlg->ShowModal(); + }, + "", nullptr, + [this]() { + return m_plater->is_view3D_shown(); + ; + }, + this); // menubar // assign menubar to frame after appending items, otherwise special items @@ -1579,6 +1619,8 @@ void MainFrame::init_menubar_as_editor() // Add additional menus from C++ wxGetApp().add_config_menu(m_menubar); m_menubar->Append(helpMenu, _L("&Help")); + //B34 + m_menubar->Append(calibrationMenu, _L("&Calibration")); #ifdef _MSW_DARK_MODE if (wxGetApp().tabs_as_menu()) { diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 64c04f3..ccda7c8 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -21,6 +21,8 @@ #include "PrinterWebView.hpp" // B28 #include "GuideWebView.hpp" +//B34 +#include "calib_dlg.hpp" class wxBookCtrlBase; class wxProgressDialog; @@ -210,7 +212,8 @@ public: Plater *m_plater{nullptr}; - + //B34 + PA_Calibration_Dlg *m_pa_calib_dlg{nullptr}; //B4 wxString tem_host; PrinterWebView * m_printer_view{nullptr}; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index eab71b7..969eec0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5363,6 +5363,81 @@ void Plater::add_model(bool imperial_units/* = false*/) if (! load_files(paths, true, false, imperial_units).empty()) wxGetApp().mainframe->update_title(); } +//B34 +void Plater::add_model_calibration(bool imperial_units /* = false*/, std::string fname /* = ""*/) +{ + std::vector paths; + + if (fname.empty()) { + wxArrayString input_files; + wxGetApp().import_model(this, input_files); + if (input_files.empty()) + return; + + for (const auto &file : input_files) + paths.emplace_back(into_path(file)); + } else { + paths.emplace_back(fname); + } + + wxString snapshot_label; + assert(!paths.empty()); + if (paths.size() == 1) { + snapshot_label = "Import Object"; + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + } else { + snapshot_label = "Import Objects"; + snapshot_label += ": "; + snapshot_label += paths.front().filename().string().c_str(); + for (size_t i = 1; i < paths.size(); ++i) { + snapshot_label += ", "; + snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); + } + } + + Plater::TakeSnapshot snapshot(this, snapshot_label); + if (!load_files(paths, true, false, imperial_units).empty()) + wxGetApp().mainframe->update_title(); +} + +//B34 +void Plater::calib_flowrate(int pass) +{ + if (pass != 1 && pass != 2) + return; + const auto calib_name = wxString::Format(L"Flowrate Test - Pass%d", pass); + new_project(); + + wxGetApp().mainframe->select_tab(size_t(0)); + + if (pass == 1) + add_model_calibration(false, + (boost::filesystem::path(Slic3r::resources_dir()) / "calib" / "filament_flow" / "flowrate-test-pass1.3mf").string()); + else + add_model_calibration(false, + (boost::filesystem::path(Slic3r::resources_dir()) / "calib" / "filament_flow" / "flowrate-test-pass2.3mf").string()); + +} +//B34 +void Plater::calib_pa(const Calib_Params ¶ms) +{ + const auto calib_pa_name = wxString::Format(L"Pressure Advance Test"); + new_project(); + wxGetApp().mainframe->select_tab(size_t(0)); + + switch (params.mode) { + case CalibMode::Calib_PA_Line: + add_model_calibration(false, Slic3r::resources_dir() + "/calib/PressureAdvance/pressure_advance_test.stl"); + break; + //case CalibMode::Calib_PA_Pattern: _calib_pa_pattern(params); break; + //case CalibMode::Calib_PA_Tower: _calib_pa_tower(params); break; + default: break; + } + + //p->background_process.fff_print()->set_calib_params(params); +} + void Plater::import_zip_archive() { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index fd37720..54f7ff9 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -16,6 +16,8 @@ #include "Jobs/Job.hpp" #include "Jobs/Worker.hpp" #include "Search.hpp" +//B34 + #include "libslic3r/Calib.hpp" class wxButton; class ScalableButton; @@ -164,10 +166,17 @@ public: const SLAPrint& sla_print() const; SLAPrint& sla_print(); + //B34 + void calib_pa(const Calib_Params ¶ms); + void calib_flowrate(int pass); + + void new_project(); void load_project(); void load_project(const wxString& filename); void add_model(bool imperial_units = false); + //B34 + void add_model_calibration(bool imperial_units = false, std::string fname = ""); void import_zip_archive(); void import_sl1_archive(); void extract_config_from_project(); diff --git a/src/slic3r/GUI/Widgets/Button.cpp b/src/slic3r/GUI/Widgets/Button.cpp new file mode 100644 index 0000000..585e8e2 --- /dev/null +++ b/src/slic3r/GUI/Widgets/Button.cpp @@ -0,0 +1,327 @@ +#include "Button.hpp" +#include "Label.hpp" + +#include + +BEGIN_EVENT_TABLE(Button, StaticBox) + +EVT_LEFT_DOWN(Button::mouseDown) +EVT_LEFT_UP(Button::mouseReleased) +EVT_MOUSE_CAPTURE_LOST(Button::mouseCaptureLost) +EVT_KEY_DOWN(Button::keyDownUp) +EVT_KEY_UP(Button::keyDownUp) + +// catch paint events +EVT_PAINT(Button::paintEvent) + +END_EVENT_TABLE() + +/* + * Called by the system of by wxWidgets when the panel needs + * to be redrawn. You can also trigger this call by + * calling Refresh()/Update(). + */ + +Button::Button() + : paddingSize(10, 8) +{ + background_color = StateColor( + std::make_pair(0xF0F0F1, (int) StateColor::Disabled), + std::make_pair(0x52c7b8, (int) StateColor::Hovered | StateColor::Checked), + std::make_pair(0x009688, (int) StateColor::Checked), + std::make_pair(*wxLIGHT_GREY, (int) StateColor::Hovered), + std::make_pair(*wxWHITE, (int) StateColor::Normal)); + text_color = StateColor( + std::make_pair(*wxLIGHT_GREY, (int) StateColor::Disabled), + std::make_pair(*wxBLACK, (int) StateColor::Normal)); +} + +Button::Button(wxWindow* parent, wxString text, wxString icon, long style, int iconSize, wxWindowID btn_id) + : Button() +{ + Create(parent, text, icon, style, iconSize, btn_id); +} + +bool Button::Create(wxWindow* parent, wxString text, wxString icon, long style, int iconSize, wxWindowID btn_id) +{ + StaticBox::Create(parent, btn_id, wxDefaultPosition, wxDefaultSize, style); + state_handler.attach({&text_color}); + state_handler.update_binds(); + //BBS set default font + SetFont(Label::Body_14); + wxWindow::SetLabel(text); + if (!icon.IsEmpty()) { + //BBS set button icon default size to 20 + this->active_icon = ScalableBitmap(this, icon.ToStdString(), iconSize > 0 ? iconSize : 20); + } + messureSize(); + return true; +} + +void Button::SetLabel(const wxString& label) +{ + wxWindow::SetLabel(label); + messureSize(); + Refresh(); +} + +bool Button::SetFont(const wxFont& font) +{ + wxWindow::SetFont(font); + messureSize(); + Refresh(); + return true; +} + +void Button::SetIcon(const wxString& icon) +{ + if (!icon.IsEmpty()) { + //BBS set button icon default size to 20 + this->active_icon = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt()); + } + else + { + this->active_icon = ScalableBitmap(); + } + Refresh(); +} + +void Button::SetInactiveIcon(const wxString &icon) +{ + if (!icon.IsEmpty()) { + // BBS set button icon default size to 20 + this->inactive_icon = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt()); + } else { + this->inactive_icon = ScalableBitmap(); + } + Refresh(); +} + +void Button::SetMinSize(const wxSize& size) +{ + minSize = size; + messureSize(); +} + +void Button::SetPaddingSize(const wxSize& size) +{ + paddingSize = size; + messureSize(); +} + +void Button::SetTextColor(StateColor const& color) +{ + text_color = color; + state_handler.update_binds(); + Refresh(); +} + +void Button::SetTextColorNormal(wxColor const &color) +{ + text_color.setColorForStates(color, 0); + Refresh(); +} + +bool Button::Enable(bool enable) +{ + bool result = wxWindow::Enable(enable); + if (result) { + wxCommandEvent e(EVT_ENABLE_CHANGED); + e.SetEventObject(this); + GetEventHandler()->ProcessEvent(e); + } + return result; +} + +void Button::SetCanFocus(bool canFocus) { this->canFocus = canFocus; } + +void Button::SetValue(bool state) +{ + if (GetValue() == state) return; + state_handler.set_state(state ? StateHandler::Checked : 0, StateHandler::Checked); +} + +bool Button::GetValue() const { return state_handler.states() & StateHandler::Checked; } + +void Button::Rescale() +{ + if (this->active_icon.get_bitmap().IsOk()) + this->active_icon.msw_rescale(); + + if (this->inactive_icon.get_bitmap().IsOk()) + this->inactive_icon.msw_rescale(); + + messureSize(); +} + +void Button::paintEvent(wxPaintEvent& evt) +{ + // depending on your system you may need to look at double-buffered dcs + wxPaintDC dc(this); + render(dc); +} + +/* + * Here we do the actual rendering. I put it in a separate + * method so that it can work no matter what type of DC + * (e.g. wxPaintDC or wxClientDC) is used. + */ +void Button::render(wxDC& dc) +{ + StaticBox::render(dc); + int states = state_handler.states(); + wxSize size = GetSize(); + dc.SetBrush(*wxTRANSPARENT_BRUSH); + // calc content size + wxSize szIcon; + wxSize szContent = textSize.GetSize(); + + ScalableBitmap icon; + if (m_selected || ((states & (int)StateColor::State::Hovered) != 0)) + icon = active_icon; + else + icon = inactive_icon; + int padding = 5; + if (icon.get_bitmap().IsOk()) { + if (szContent.y > 0) { + //BBS norrow size between text and icon + szContent.x += padding; + } + szIcon = icon.GetSize(); + szContent.x += szIcon.x; + if (szIcon.y > szContent.y) + szContent.y = szIcon.y; + if (szContent.x > size.x) { + int d = std::min(padding, szContent.x - size.x); + padding -= d; + szContent.x -= d; + } + } + // move to center + wxRect rcContent = { {0, 0}, size }; + wxSize offset = (size - szContent) / 2; + if (offset.x < 0) offset.x = 0; + rcContent.Deflate(offset.x, offset.y); + // start draw + wxPoint pt = rcContent.GetLeftTop(); + if (icon.get_bitmap().IsOk()) { + pt.y += (rcContent.height - szIcon.y) / 2; + dc.DrawBitmap(icon.get_bitmap(), pt); + //BBS norrow size between text and icon + pt.x += szIcon.x + padding; + pt.y = rcContent.y; + } + auto text = GetLabel(); + if (!text.IsEmpty()) { + if (pt.x + textSize.width > size.x) + text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, size.x - pt.x); + pt.y += (rcContent.height - textSize.height) / 2; + dc.SetTextForeground(text_color.colorForStates(states)); +#if 0 + dc.SetBrush(*wxLIGHT_GREY); + dc.SetPen(wxPen(*wxLIGHT_GREY)); + dc.DrawRectangle(pt, textSize.GetSize()); +#endif +#ifdef __WXOSX__ + pt.y -= textSize.x / 2; +#endif + dc.DrawText(text, pt); + } +} + +void Button::messureSize() +{ + wxClientDC dc(this); + dc.GetTextExtent(GetLabel(), &textSize.width, &textSize.height, &textSize.x, &textSize.y); + wxSize szContent = textSize.GetSize(); + if (this->active_icon.get_bitmap().IsOk()) { + if (szContent.y > 0) { + //BBS norrow size between text and icon + szContent.x += 5; + } + wxSize szIcon = this->active_icon.GetSize(); + szContent.x += szIcon.x; + if (szIcon.y > szContent.y) + szContent.y = szIcon.y; + } + wxSize size = szContent + paddingSize * 2; + if (minSize.GetHeight() > 0) + size.SetHeight(minSize.GetHeight()); + + if (minSize.GetWidth() > size.GetWidth()) + wxWindow::SetMinSize(minSize); + else + wxWindow::SetMinSize(size); +} + +void Button::mouseDown(wxMouseEvent& event) +{ + event.Skip(); + pressedDown = true; + if (canFocus) + SetFocus(); + if (!HasCapture()) + CaptureMouse(); +} + +void Button::mouseReleased(wxMouseEvent& event) +{ + event.Skip(); + if (pressedDown) { + pressedDown = false; + if (HasCapture()) + ReleaseMouse(); + if (wxRect({0, 0}, GetSize()).Contains(event.GetPosition())) + sendButtonEvent(); + } +} + +void Button::mouseCaptureLost(wxMouseCaptureLostEvent &event) +{ + wxMouseEvent evt; + mouseReleased(evt); +} + +void Button::keyDownUp(wxKeyEvent &event) +{ + if (event.GetKeyCode() == WXK_SPACE || event.GetKeyCode() == WXK_RETURN) { + wxMouseEvent evt(event.GetEventType() == wxEVT_KEY_UP ? wxEVT_LEFT_UP : wxEVT_LEFT_DOWN); + event.SetEventObject(this); + GetEventHandler()->ProcessEvent(evt); + return; + } + if (event.GetEventType() == wxEVT_KEY_DOWN && + (event.GetKeyCode() == WXK_TAB || event.GetKeyCode() == WXK_LEFT || event.GetKeyCode() == WXK_RIGHT + || event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_DOWN)) + HandleAsNavigationKey(event); + else + event.Skip(); +} + +void Button::sendButtonEvent() +{ + wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId()); + event.SetEventObject(this); + GetEventHandler()->ProcessEvent(event); +} + +#ifdef __WIN32__ + +WXLRESULT Button::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) +{ + if (nMsg == WM_GETDLGCODE) { return DLGC_WANTMESSAGE; } + if (nMsg == WM_KEYDOWN) { + wxKeyEvent event(CreateKeyEvent(wxEVT_KEY_DOWN, wParam, lParam)); + switch (wParam) { + case WXK_RETURN: { // WXK_RETURN key is handled by default button + GetEventHandler()->ProcessEvent(event); + return 0; + } + } + } + return wxWindow::MSWWindowProc(nMsg, wParam, lParam); +} + +#endif + +bool Button::AcceptsFocus() const { return canFocus; } diff --git a/src/slic3r/GUI/Widgets/Button.hpp b/src/slic3r/GUI/Widgets/Button.hpp new file mode 100644 index 0000000..2f5c8ea --- /dev/null +++ b/src/slic3r/GUI/Widgets/Button.hpp @@ -0,0 +1,84 @@ +#ifndef slic3r_GUI_Button_hpp_ +#define slic3r_GUI_Button_hpp_ + +#include "../wxExtensions.hpp" +#include "StaticBox.hpp" + +class Button : public StaticBox +{ + wxRect textSize; + wxSize minSize; // set by outer + wxSize paddingSize; + ScalableBitmap active_icon; + ScalableBitmap inactive_icon; + + StateColor text_color; + + bool pressedDown = false; + bool m_selected = true; + bool canFocus = true; + + static const int buttonWidth = 200; + static const int buttonHeight = 50; + +public: + Button(); + + Button(wxWindow* parent, wxString text, wxString icon = "", long style = 0, int iconSize = 0, wxWindowID btn_id = wxID_ANY); + + bool Create(wxWindow* parent, wxString text, wxString icon = "", long style = 0, int iconSize = 0, wxWindowID btn_id = wxID_ANY); + + void SetLabel(const wxString& label) override; + + bool SetFont(const wxFont& font) override; + + void SetIcon(const wxString& icon); + + void SetInactiveIcon(const wxString& icon); + + void SetMinSize(const wxSize& size) override; + + void SetPaddingSize(const wxSize& size); + + void SetTextColor(StateColor const &color); + + void SetTextColorNormal(wxColor const &color); + + void SetSelected(bool selected = true) { m_selected = selected; } + + bool Enable(bool enable = true) override; + + void SetCanFocus(bool canFocus) override; + + void SetValue(bool state); + + bool GetValue() const; + + void Rescale(); + +protected: +#ifdef __WIN32__ + WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override; +#endif + + bool AcceptsFocus() const override; + +private: + void paintEvent(wxPaintEvent& evt); + + void render(wxDC& dc); + + void messureSize(); + + // some useful events + void mouseDown(wxMouseEvent& event); + void mouseReleased(wxMouseEvent& event); + void mouseCaptureLost(wxMouseCaptureLostEvent &event); + void keyDownUp(wxKeyEvent &event); + + void sendButtonEvent(); + + DECLARE_EVENT_TABLE() +}; + +#endif // !slic3r_GUI_Button_hpp_ diff --git a/src/slic3r/GUI/Widgets/CheckBoxInWT.cpp b/src/slic3r/GUI/Widgets/CheckBoxInWT.cpp new file mode 100644 index 0000000..efbea05 --- /dev/null +++ b/src/slic3r/GUI/Widgets/CheckBoxInWT.cpp @@ -0,0 +1,125 @@ +#include "CheckBoxInWT.hpp" + +#include "../wxExtensions.hpp" + +CheckBoxInWT::CheckBoxInWT(wxWindow *parent, int id) + : wxBitmapToggleButton(parent, id, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE) + , m_on(this, "check_on", 18) + , m_half(this, "check_half", 18) + , m_off(this, "check_off", 18) + , m_on_disabled(this, "check_on_disabled", 18) + , m_half_disabled(this, "check_half_disabled", 18) + , m_off_disabled(this, "check_off_disabled", 18) + , m_on_focused(this, "check_on_focused", 18) + , m_half_focused(this, "check_half_focused", 18) + , m_off_focused(this, "check_off_focused", 18) +{ + //SetBackgroundStyle(wxBG_STYLE_TRANSPARENT); + if (parent) + SetBackgroundColour(parent->GetBackgroundColour()); + Bind(wxEVT_TOGGLEBUTTON, [this](auto& e) { m_half_checked = false; update(); e.Skip(); }); +#ifdef __WXOSX__ // State not fully implement on MacOS + Bind(wxEVT_SET_FOCUS, &CheckBoxInWT::updateBitmap, this); + Bind(wxEVT_KILL_FOCUS, &CheckBoxInWT::updateBitmap, this); + Bind(wxEVT_ENTER_WINDOW, &CheckBoxInWT::updateBitmap, this); + Bind(wxEVT_LEAVE_WINDOW, &CheckBoxInWT::updateBitmap, this); +#endif + SetSize(m_on.GetSize()); + SetMinSize(m_on.GetSize()); + update(); +} + +void CheckBoxInWT::SetValue(bool value) +{ + wxBitmapToggleButton::SetValue(value); + update(); +} + +void CheckBoxInWT::SetHalfChecked(bool value) +{ + m_half_checked = value; + update(); +} + +void CheckBoxInWT::Rescale() +{ + m_on.msw_rescale(); + m_half.msw_rescale(); + m_off.msw_rescale(); + m_on_disabled.msw_rescale(); + m_half_disabled.msw_rescale(); + m_off_disabled.msw_rescale(); + m_on_focused.msw_rescale(); + m_half_focused.msw_rescale(); + m_off_focused.msw_rescale(); + SetSize(m_on.GetSize()); + update(); +} + +void CheckBoxInWT::update() +{ + SetBitmapLabel((m_half_checked ? m_half : GetValue() ? m_on : m_off).bmp()); + SetBitmapDisabled((m_half_checked ? m_half_disabled : GetValue() ? m_on_disabled : m_off_disabled).bmp()); +#ifdef __WXMSW__ + SetBitmapFocus((m_half_checked ? m_half_focused : GetValue() ? m_on_focused : m_off_focused).bmp()); +#endif + SetBitmapCurrent((m_half_checked ? m_half_focused : GetValue() ? m_on_focused : m_off_focused).bmp()); +#ifdef __WXOSX__ + wxCommandEvent e(wxEVT_UPDATE_UI); + updateBitmap(e); +#endif +} + +#ifdef __WXMSW__ + +CheckBoxInWT::State CheckBoxInWT::GetNormalState() const { return State_Normal; } + +#endif + + +#ifdef __WXOSX__ + +bool CheckBoxInWT::Enable(bool enable) +{ + bool result = wxBitmapToggleButton::Enable(enable); + if (result) { + m_disable = !enable; + wxCommandEvent e(wxEVT_ACTIVATE); + updateBitmap(e); + } + return result; +} + +wxBitmap CheckBoxInWT::DoGetBitmap(State which) const +{ + if (m_disable) { + return wxBitmapToggleButton::DoGetBitmap(State_Disabled); + } + if (m_focus) { + return wxBitmapToggleButton::DoGetBitmap(State_Current); + } + return wxBitmapToggleButton::DoGetBitmap(which); +} + +void CheckBoxInWT::updateBitmap(wxEvent &evt) +{ + evt.Skip(); + if (evt.GetEventType() == wxEVT_ENTER_WINDOW) { + m_hover = true; + } else if (evt.GetEventType() == wxEVT_LEAVE_WINDOW) { + m_hover = false; + } else { + if (evt.GetEventType() == wxEVT_SET_FOCUS) { + m_focus = true; + } else if (evt.GetEventType() == wxEVT_KILL_FOCUS) { + m_focus = false; + } + wxMouseEvent e; + if (m_hover) + OnEnterWindow(e); + else + OnLeaveWindow(e); + } +} + +#endif diff --git a/src/slic3r/GUI/Widgets/CheckBoxInWT.hpp b/src/slic3r/GUI/Widgets/CheckBoxInWT.hpp new file mode 100644 index 0000000..5e88c2d --- /dev/null +++ b/src/slic3r/GUI/Widgets/CheckBoxInWT.hpp @@ -0,0 +1,55 @@ +#ifndef slic3r_GUI_CheckBoxInWT_hpp_ +#define slic3r_GUI_CheckBoxInWT_hpp_ + +#include "../wxExtensions.hpp" + +#include + +class CheckBoxInWT : public wxBitmapToggleButton +{ +public: + CheckBoxInWT(wxWindow *parent, int id = wxID_ANY); + +public: + void SetValue(bool value) override; + + void SetHalfChecked(bool value = true); + + void Rescale(); + +#ifdef __WXOSX__ + virtual bool Enable(bool enable = true) wxOVERRIDE; +#endif + +protected: +#ifdef __WXMSW__ + virtual State GetNormalState() const wxOVERRIDE; +#endif + +#ifdef __WXOSX__ + virtual wxBitmap DoGetBitmap(State which) const wxOVERRIDE; + + void updateBitmap(wxEvent & evt); + + bool m_disable = false; + bool m_hover = false; + bool m_focus = false; +#endif + +private: + void update(); + +private: + ScalableBitmap m_on; + ScalableBitmap m_half; + ScalableBitmap m_off; + ScalableBitmap m_on_disabled; + ScalableBitmap m_half_disabled; + ScalableBitmap m_off_disabled; + ScalableBitmap m_on_focused; + ScalableBitmap m_half_focused; + ScalableBitmap m_off_focused; + bool m_half_checked = false; +}; + +#endif // !slic3r_GUI_CheckBoxInWT_hpp_ diff --git a/src/slic3r/GUI/Widgets/ComboBox.cpp b/src/slic3r/GUI/Widgets/ComboBox.cpp new file mode 100644 index 0000000..5600477 --- /dev/null +++ b/src/slic3r/GUI/Widgets/ComboBox.cpp @@ -0,0 +1,309 @@ +#include "ComboBox.hpp" +#include "Label.hpp" + +#include + +BEGIN_EVENT_TABLE(ComboBox, TextInput) + +EVT_LEFT_DOWN(ComboBox::mouseDown) +EVT_LEFT_DCLICK(ComboBox::mouseDown) +//EVT_MOUSEWHEEL(ComboBox::mouseWheelMoved) +EVT_KEY_DOWN(ComboBox::keyDown) + +// catch paint events +END_EVENT_TABLE() + +/* + * Called by the system of by wxWidgets when the panel needs + * to be redrawn. You can also trigger this call by + * calling Refresh()/Update(). + */ + +ComboBox::ComboBox(wxWindow * parent, + wxWindowID id, + const wxString &value, + const wxPoint & pos, + const wxSize & size, + int n, + const wxString choices[], + long style) + : drop(texts, icons) +{ + if (style & wxCB_READONLY) + style |= wxRIGHT; + text_off = style & CB_NO_TEXT; + TextInput::Create(parent, "", value, (style & CB_NO_DROP_ICON) ? "" : "drop_down", pos, size, + style | wxTE_PROCESS_ENTER); + drop.Create(this, style & DD_STYLE_MASK); + + if (style & wxCB_READONLY) { + GetTextCtrl()->Hide(); + TextInput::SetFont(Label::Body_14); + TextInput::SetBorderColor(StateColor(std::make_pair(0xDBDBDB, (int) StateColor::Disabled), + std::make_pair(0x009688, (int) StateColor::Hovered), + std::make_pair(0xDBDBDB, (int) StateColor::Normal))); + TextInput::SetBackgroundColor(StateColor(std::make_pair(0xF0F0F1, (int) StateColor::Disabled), + std::make_pair(0xEDFAF2, (int) StateColor::Focused), + std::make_pair(*wxWHITE, (int) StateColor::Normal))); + TextInput::SetLabelColor(StateColor(std::make_pair(0x909090, (int) StateColor::Disabled), + std::make_pair(0x262E30, (int) StateColor::Normal))); + } else { + GetTextCtrl()->Bind(wxEVT_KEY_DOWN, &ComboBox::keyDown, this); + } + drop.Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) { + SetSelection(e.GetInt()); + e.SetEventObject(this); + e.SetId(GetId()); + GetEventHandler()->ProcessEvent(e); + }); + drop.Bind(EVT_DISMISS, [this](auto &) { + drop_down = false; + wxCommandEvent e(wxEVT_COMBOBOX_CLOSEUP); + GetEventHandler()->ProcessEvent(e); + }); + for (int i = 0; i < n; ++i) Append(choices[i]); +} + +int ComboBox::GetSelection() const { return drop.GetSelection(); } + +void ComboBox::SetSelection(int n) +{ + drop.SetSelection(n); + SetLabel(drop.GetValue()); + if (drop.selection >= 0) + SetIcon(icons[drop.selection]); +} + +void ComboBox::SelectAndNotify(int n) { + SetSelection(n); + sendComboBoxEvent(); +} + + +void ComboBox::Rescale() +{ + TextInput::Rescale(); + drop.Rescale(); +} + +wxString ComboBox::GetValue() const +{ + return drop.GetSelection() >= 0 ? drop.GetValue() : GetLabel(); +} + +void ComboBox::SetValue(const wxString &value) +{ + drop.SetValue(value); + SetLabel(value); + if (drop.selection >= 0) + SetIcon(icons[drop.selection]); +} + +void ComboBox::SetLabel(const wxString &value) +{ + if (GetTextCtrl()->IsShown() || text_off) + GetTextCtrl()->SetValue(value); + else + TextInput::SetLabel(value); +} + +wxString ComboBox::GetLabel() const +{ + if (GetTextCtrl()->IsShown() || text_off) + return GetTextCtrl()->GetValue(); + else + return TextInput::GetLabel(); +} + +void ComboBox::SetTextLabel(const wxString& label) +{ + TextInput::SetLabel(label); +} + +wxString ComboBox::GetTextLabel() const +{ + return TextInput::GetLabel(); +} + +bool ComboBox::SetFont(wxFont const& font) +{ + if (GetTextCtrl() && GetTextCtrl()->IsShown()) + return GetTextCtrl()->SetFont(font); + else + return TextInput::SetFont(font); +} + +int ComboBox::Append(const wxString &item, const wxBitmap &bitmap) +{ + return Append(item, bitmap, nullptr); +} + +int ComboBox::Append(const wxString &item, + const wxBitmap &bitmap, + void * clientData) +{ + texts.push_back(item); + icons.push_back(bitmap); + datas.push_back(clientData); + types.push_back(wxClientData_None); + drop.Invalidate(); + return texts.size() - 1; +} + +void ComboBox::DoClear() +{ + texts.clear(); + icons.clear(); + datas.clear(); + types.clear(); + drop.Invalidate(true); +} + +void ComboBox::DoDeleteOneItem(unsigned int pos) +{ + if (pos >= texts.size()) return; + texts.erase(texts.begin() + pos); + icons.erase(icons.begin() + pos); + datas.erase(datas.begin() + pos); + types.erase(types.begin() + pos); + drop.Invalidate(true); +} + +unsigned int ComboBox::GetCount() const { return texts.size(); } + +wxString ComboBox::GetString(unsigned int n) const +{ + return n < texts.size() ? texts[n] : wxString{}; +} + +void ComboBox::SetString(unsigned int n, wxString const &value) +{ + if (n >= texts.size()) return; + texts[n] = value; + drop.Invalidate(); + if (n == drop.GetSelection()) SetLabel(value); +} + +wxBitmap ComboBox::GetItemBitmap(unsigned int n) { return icons[n]; } + +void ComboBox::SetItemBitmap(unsigned int n, wxBitmap const &bitmap) +{ + if (n >= texts.size()) return; + icons[n] = bitmap; + drop.Invalidate(); +} + +int ComboBox::DoInsertItems(const wxArrayStringsAdapter &items, + unsigned int pos, + void ** clientData, + wxClientDataType type) +{ + if (pos > texts.size()) return -1; + for (int i = 0; i < items.GetCount(); ++i) { + texts.insert(texts.begin() + pos, items[i]); + icons.insert(icons.begin() + pos, wxNullBitmap); + datas.insert(datas.begin() + pos, clientData ? clientData[i] : NULL); + types.insert(types.begin() + pos, type); + ++pos; + } + drop.Invalidate(true); + return pos - 1; +} + +void *ComboBox::DoGetItemClientData(unsigned int n) const { return n < texts.size() ? datas[n] : NULL; } + +void ComboBox::DoSetItemClientData(unsigned int n, void *data) +{ + if (n < texts.size()) + datas[n] = data; +} + +void ComboBox::mouseDown(wxMouseEvent &event) +{ + SetFocus(); + if (drop_down) { + drop.Hide(); + } else if (drop.HasDismissLongTime()) { + drop.autoPosition(); + drop_down = true; + drop.Popup(&drop); + wxCommandEvent e(wxEVT_COMBOBOX_DROPDOWN); + GetEventHandler()->ProcessEvent(e); + } +} + +void ComboBox::mouseWheelMoved(wxMouseEvent &event) +{ + event.Skip(); + if (drop_down) return; + auto delta = event.GetWheelRotation() < 0 ? 1 : -1; + unsigned int n = GetSelection() + delta; + if (n < GetCount()) { + SetSelection((int) n); + sendComboBoxEvent(); + } +} + +void ComboBox::keyDown(wxKeyEvent& event) +{ + switch (event.GetKeyCode()) { + case WXK_RETURN: + case WXK_SPACE: + if (drop_down) { + drop.DismissAndNotify(); + } else if (drop.HasDismissLongTime()) { + drop.autoPosition(); + drop_down = true; + drop.Popup(); + wxCommandEvent e(wxEVT_COMBOBOX_DROPDOWN); + GetEventHandler()->ProcessEvent(e); + } + break; + case WXK_UP: + case WXK_DOWN: + case WXK_LEFT: + case WXK_RIGHT: + if ((event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_LEFT) && GetSelection() > 0) { + SetSelection(GetSelection() - 1); + } else if ((event.GetKeyCode() == WXK_DOWN || event.GetKeyCode() == WXK_RIGHT) && GetSelection() + 1 < texts.size()) { + SetSelection(GetSelection() + 1); + } else { + break; + } + sendComboBoxEvent(); + break; + case WXK_TAB: + HandleAsNavigationKey(event); + break; + default: + event.Skip(); + break; + } +} + +void ComboBox::OnEdit() +{ + auto value = GetTextCtrl()->GetValue(); + SetValue(value); +} + +#ifdef __WIN32__ + +WXLRESULT ComboBox::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) +{ + if (nMsg == WM_GETDLGCODE) { + return DLGC_WANTALLKEYS; + } + return TextInput::MSWWindowProc(nMsg, wParam, lParam); +} + +#endif + +void ComboBox::sendComboBoxEvent() +{ + wxCommandEvent event(wxEVT_COMBOBOX, GetId()); + event.SetEventObject(this); + event.SetInt(drop.GetSelection()); + event.SetString(drop.GetValue()); + GetEventHandler()->ProcessEvent(event); +} diff --git a/src/slic3r/GUI/Widgets/ComboBox.hpp b/src/slic3r/GUI/Widgets/ComboBox.hpp new file mode 100644 index 0000000..bac3523 --- /dev/null +++ b/src/slic3r/GUI/Widgets/ComboBox.hpp @@ -0,0 +1,95 @@ +#ifndef slic3r_GUI_ComboBox_hpp_ +#define slic3r_GUI_ComboBox_hpp_ + +#include "TextInput.hpp" +#include "DropDown.hpp" + +#define CB_NO_DROP_ICON DD_NO_CHECK_ICON +#define CB_NO_TEXT DD_NO_TEXT + +class ComboBox : public wxWindowWithItems +{ + std::vector texts; + std::vector icons; + std::vector datas; + std::vector types; + + DropDown drop; + bool drop_down = false; + bool text_off = false; + +public: + ComboBox(wxWindow * parent, + wxWindowID id, + const wxString &value = wxEmptyString, + const wxPoint & pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + int n = 0, + const wxString choices[] = NULL, + long style = 0); + + DropDown & GetDropDown() { return drop; } + + virtual bool SetFont(wxFont const & font) override; + +public: + int Append(const wxString &item, const wxBitmap &bitmap = wxNullBitmap); + + int Append(const wxString &item, const wxBitmap &bitmap, void *clientData); + + unsigned int GetCount() const override; + + int GetSelection() const override; + + void SetSelection(int n) override; + + void SelectAndNotify(int n); + + virtual void Rescale() override; + + wxString GetValue() const; + void SetValue(const wxString &value); + + void SetLabel(const wxString &label) override; + wxString GetLabel() const override; + + void SetTextLabel(const wxString &label); + wxString GetTextLabel() const; + + wxString GetString(unsigned int n) const override; + void SetString(unsigned int n, wxString const &value) override; + + wxBitmap GetItemBitmap(unsigned int n); + void SetItemBitmap(unsigned int n, wxBitmap const &bitmap); + +protected: + virtual int DoInsertItems(const wxArrayStringsAdapter &items, + unsigned int pos, + void ** clientData, + wxClientDataType type) override; + virtual void DoClear() override; + + void DoDeleteOneItem(unsigned int pos) override; + + void *DoGetItemClientData(unsigned int n) const override; + void DoSetItemClientData(unsigned int n, void *data) override; + + void OnEdit() override; + + void sendComboBoxEvent(); + +#ifdef __WIN32__ + WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override; +#endif + +private: + + // some useful events + void mouseDown(wxMouseEvent &event); + void mouseWheelMoved(wxMouseEvent &event); + void keyDown(wxKeyEvent &event); + + DECLARE_EVENT_TABLE() +}; + +#endif // !slic3r_GUI_ComboBox_hpp_ diff --git a/src/slic3r/GUI/Widgets/DropDown.cpp b/src/slic3r/GUI/Widgets/DropDown.cpp new file mode 100644 index 0000000..0fd1e20 --- /dev/null +++ b/src/slic3r/GUI/Widgets/DropDown.cpp @@ -0,0 +1,492 @@ +#include "DropDown.hpp" +#include "Label.hpp" + +#include + +#ifdef __WXGTK__ +#include +#endif + +wxDEFINE_EVENT(EVT_DISMISS, wxCommandEvent); + +BEGIN_EVENT_TABLE(DropDown, PopupWindow) + +EVT_LEFT_DOWN(DropDown::mouseDown) +EVT_LEFT_UP(DropDown::mouseReleased) +EVT_MOUSE_CAPTURE_LOST(DropDown::mouseCaptureLost) +EVT_MOTION(DropDown::mouseMove) +EVT_MOUSEWHEEL(DropDown::mouseWheelMoved) + +// catch paint events +EVT_PAINT(DropDown::paintEvent) + +END_EVENT_TABLE() + +/* + * Called by the system of by wxWidgets when the panel needs + * to be redrawn. You can also trigger this call by + * calling Refresh()/Update(). + */ + +DropDown::DropDown(std::vector &texts, + std::vector &icons) + : texts(texts) + , icons(icons) + , state_handler(this) + , border_color(0xDBDBDB) + , text_color(0x363636) + , selector_border_color(std::make_pair(0x009688, (int) StateColor::Hovered), + std::make_pair(*wxWHITE, (int) StateColor::Normal)) + , selector_background_color(std::make_pair(0xEDFAF2, (int) StateColor::Checked), + std::make_pair(*wxWHITE, (int) StateColor::Normal)) +{ +} + +DropDown::DropDown(wxWindow * parent, + std::vector &texts, + std::vector &icons, + long style) + : DropDown(texts, icons) +{ + Create(parent, style); +} + +void DropDown::Create(wxWindow * parent, + long style) +{ + PopupWindow::Create(parent, wxPU_CONTAINS_CONTROLS); + SetBackgroundStyle(wxBG_STYLE_PAINT); + SetBackgroundColour(*wxWHITE); + state_handler.attach({&border_color, &text_color, &selector_border_color, &selector_background_color}); + state_handler.update_binds(); + if ((style & DD_NO_CHECK_ICON) == 0) + check_bitmap = ScalableBitmap(this, "checked", 16); + text_off = style & DD_NO_TEXT; + + // BBS set default font + SetFont(Label::Body_14); +#ifdef __WXOSX__ + // PopupWindow releases mouse on idle, which may cause various problems, + // such as losting mouse move, and dismissing soon on first LEFT_DOWN event. + Bind(wxEVT_IDLE, [] (wxIdleEvent & evt) {}); +#endif +} + +void DropDown::Invalidate(bool clear) +{ + if (clear) { + selection = hover_item = -1; + offset = wxPoint(); + } + assert(selection < (int) texts.size()); + need_sync = true; +} + +void DropDown::SetSelection(int n) +{ + assert(n < (int) texts.size()); + if (n >= (int) texts.size()) + n = -1; + if (selection == n) return; + selection = n; + paintNow(); +} + +wxString DropDown::GetValue() const +{ + return selection >= 0 ? texts[selection] : wxString(); +} + +void DropDown::SetValue(const wxString &value) +{ + auto i = std::find(texts.begin(), texts.end(), value); + selection = i == texts.end() ? -1 : std::distance(texts.begin(), i); +} + +void DropDown::SetCornerRadius(double radius) +{ + this->radius = radius; + paintNow(); +} + +void DropDown::SetBorderColor(StateColor const &color) +{ + border_color = color; + state_handler.update_binds(); + paintNow(); +} + +void DropDown::SetSelectorBorderColor(StateColor const &color) +{ + selector_border_color = color; + state_handler.update_binds(); + paintNow(); +} + +void DropDown::SetTextColor(StateColor const &color) +{ + text_color = color; + state_handler.update_binds(); + paintNow(); +} + +void DropDown::SetSelectorBackgroundColor(StateColor const &color) +{ + selector_background_color = color; + state_handler.update_binds(); + paintNow(); +} + +void DropDown::SetUseContentWidth(bool use) +{ + if (use_content_width == use) + return; + use_content_width = use; + need_sync = true; + messureSize(); +} + +void DropDown::SetAlignIcon(bool align) { align_icon = align; } + +void DropDown::Rescale() +{ + need_sync = true; +} + +bool DropDown::HasDismissLongTime() +{ + auto now = boost::posix_time::microsec_clock::universal_time(); + return !IsShown() && + (now - dismissTime).total_milliseconds() >= 20; +} + +void DropDown::paintEvent(wxPaintEvent& evt) +{ + // depending on your system you may need to look at double-buffered dcs + wxBufferedPaintDC dc(this); + render(dc); +} + +/* + * Alternatively, you can use a clientDC to paint on the panel + * at any time. Using this generally does not free you from + * catching paint events, since it is possible that e.g. the window + * manager throws away your drawing when the window comes to the + * background, and expects you will redraw it when the window comes + * back (by sending a paint event). + */ +void DropDown::paintNow() +{ + // depending on your system you may need to look at double-buffered dcs + //wxClientDC dc(this); + //render(dc); + Refresh(); +} + +static wxSize GetBmpSize(wxBitmap & bmp) +{ +#ifdef __APPLE__ + return bmp.GetScaledSize(); +#else + return bmp.GetSize(); +#endif +} + +/* + * Here we do the actual rendering. I put it in a separate + * method so that it can work no matter what type of DC + * (e.g. wxPaintDC or wxClientDC) is used. + */ +void DropDown::render(wxDC &dc) +{ + if (texts.size() == 0) return; + int states = state_handler.states(); + dc.SetPen(wxPen(border_color.colorForStates(states))); + dc.SetBrush(wxBrush(StateColor::darkModeColorFor(GetBackgroundColour()))); + // if (GetWindowStyle() & wxBORDER_NONE) + // dc.SetPen(wxNullPen); + + // draw background + wxSize size = GetSize(); + if (radius == 0) + dc.DrawRectangle(0, 0, size.x, size.y); + else + dc.DrawRoundedRectangle(0, 0, size.x, size.y, radius); + + // draw hover rectangle + wxRect rcContent = {{0, offset.y}, rowSize}; + if (hover_item >= 0 && (states & StateColor::Hovered)) { + rcContent.y += rowSize.y * hover_item; + if (rcContent.GetBottom() > 0 && rcContent.y < size.y) { + if (selection == hover_item) + dc.SetBrush(wxBrush(selector_background_color.colorForStates(states | StateColor::Checked))); + dc.SetPen(wxPen(selector_border_color.colorForStates(states))); + rcContent.Deflate(4, 1); + dc.DrawRectangle(rcContent); + rcContent.Inflate(4, 1); + } + rcContent.y = offset.y; + } + // draw checked rectangle + if (selection >= 0 && (selection != hover_item || (states & StateColor::Hovered) == 0)) { + rcContent.y += rowSize.y * selection; + if (rcContent.GetBottom() > 0 && rcContent.y < size.y) { + dc.SetBrush(wxBrush(selector_background_color.colorForStates(states | StateColor::Checked))); + dc.SetPen(wxPen(selector_background_color.colorForStates(states))); + rcContent.Deflate(4, 1); + dc.DrawRectangle(rcContent); + rcContent.Inflate(4, 1); + } + rcContent.y = offset.y; + } + dc.SetBrush(*wxTRANSPARENT_BRUSH); + { + wxSize offset = (rowSize - textSize) / 2; + rcContent.Deflate(0, offset.y); + } + + // draw position bar + if (rowSize.y * texts.size() > size.y) { + int height = rowSize.y * texts.size(); + wxRect rect = {size.x - 6, -offset.y * size.y / height, 4, + size.y * size.y / height}; + dc.SetPen(wxPen(border_color.defaultColor())); + dc.SetBrush(wxBrush(*wxLIGHT_GREY)); + dc.DrawRoundedRectangle(rect, 2); + rcContent.width -= 6; + } + + // draw check icon + rcContent.x += 5; + rcContent.width -= 5; + if (check_bitmap.get_bitmap().IsOk()) { + auto szBmp = check_bitmap.GetSize(); + if (selection >= 0) { + wxPoint pt = rcContent.GetLeftTop(); + pt.y += (rcContent.height - szBmp.y) / 2; + pt.y += rowSize.y * selection; + if (pt.y + szBmp.y > 0 && pt.y < size.y) + dc.DrawBitmap(check_bitmap.get_bitmap(), pt); + } + rcContent.x += szBmp.x + 5; + rcContent.width -= szBmp.x + 5; + } + // draw texts & icons + dc.SetTextForeground(text_color.colorForStates(states)); + for (int i = 0; i < texts.size(); ++i) { + if (rcContent.GetBottom() < 0) { + rcContent.y += rowSize.y; + continue; + } + if (rcContent.y > size.y) break; + wxPoint pt = rcContent.GetLeftTop(); + auto & icon = icons[i]; + auto size2 = GetBmpSize(icon); + if (iconSize.x > 0) { + if (icon.IsOk()) { + pt.y += (rcContent.height - size2.y) / 2; + dc.DrawBitmap(icon, pt); + } + pt.x += iconSize.x + 5; + pt.y = rcContent.y; + } else if (icon.IsOk()) { + pt.y += (rcContent.height - size2.y) / 2; + dc.DrawBitmap(icon, pt); + pt.x += size2.x + 5; + pt.y = rcContent.y; + } + auto text = texts[i]; + if (!text_off && !text.IsEmpty()) { + wxSize tSize = dc.GetMultiLineTextExtent(text); + if (pt.x + tSize.x > rcContent.GetRight()) { + text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, + rcContent.GetRight() - pt.x); + } + pt.y += (rcContent.height - textSize.y) / 2; + dc.SetFont(GetFont()); + dc.DrawText(text, pt); + } + rcContent.y += rowSize.y; + } +} + +void DropDown::messureSize() +{ + if (!need_sync) return; + textSize = wxSize(); + iconSize = wxSize(); + wxClientDC dc(GetParent() ? GetParent() : this); + for (size_t i = 0; i < texts.size(); ++i) { + wxSize size1 = text_off ? wxSize() : dc.GetMultiLineTextExtent(texts[i]); + if (icons[i].IsOk()) { + wxSize size2 = GetBmpSize(icons[i]); + if (size2.x > iconSize.x) iconSize = size2; + if (!align_icon) { + size1.x += size2.x + (text_off ? 0 : 5); + } + } + if (size1.x > textSize.x) textSize = size1; + } + if (!align_icon) iconSize.x = 0; + wxSize szContent = textSize; + szContent.x += 10; + if (check_bitmap.get_bitmap().IsOk()) { + auto szBmp = check_bitmap.GetSize(); + szContent.x += szBmp.x + 5; + } + if (iconSize.x > 0) szContent.x += iconSize.x + (text_off ? 0 : 5); + if (iconSize.y > szContent.y) szContent.y = iconSize.y; + szContent.y += 10; + if (texts.size() > 15) szContent.x += 6; + if (GetParent()) { + auto x = GetParent()->GetSize().x; + if (!use_content_width || x > szContent.x) + szContent.x = x; + } + rowSize = szContent; + szContent.y *= std::min((size_t)15, texts.size()); + szContent.y += texts.size() > 15 ? rowSize.y / 2 : 0; + wxWindow::SetSize(szContent); +#ifdef __WXGTK__ + // Gtk has a wrapper window for popup widget + gtk_window_resize (GTK_WINDOW (m_widget), szContent.x, szContent.y); +#endif + need_sync = false; +} + +void DropDown::autoPosition() +{ + messureSize(); + wxPoint pos = GetParent()->ClientToScreen(wxPoint(0, -6)); + wxPoint old = GetPosition(); + wxSize size = GetSize(); + Position(pos, {0, GetParent()->GetSize().y + 12}); + if (old != GetPosition()) { + size = rowSize; + size.y *= std::min((size_t)15, texts.size()); + size.y += texts.size() > 15 ? rowSize.y / 2 : 0; + if (size != GetSize()) { + wxWindow::SetSize(size); + offset = wxPoint(); + Position(pos, {0, GetParent()->GetSize().y + 12}); + } + } + if (GetPosition().y > pos.y) { + // may exceed + auto drect = wxDisplay(GetParent()).GetGeometry(); + if (GetPosition().y + size.y + 10 > drect.GetBottom()) { + if (use_content_width && texts.size() <= 15) size.x += 6; + size.y = drect.GetBottom() - GetPosition().y - 10; + wxWindow::SetSize(size); + if (selection >= 0) { + if (offset.y + rowSize.y * (selection + 1) > size.y) + offset.y = size.y - rowSize.y * (selection + 1); + else if (offset.y + rowSize.y * selection < 0) + offset.y = -rowSize.y * selection; + } + } + } +} + +void DropDown::mouseDown(wxMouseEvent& event) +{ + // Receivce unexcepted LEFT_DOWN on Mac after OnDismiss + if (!IsShown()) + return; + // force calc hover item again + mouseMove(event); + pressedDown = true; + CaptureMouse(); + dragStart = event.GetPosition(); +} + +void DropDown::mouseReleased(wxMouseEvent& event) +{ + if (pressedDown) { + dragStart = wxPoint(); + pressedDown = false; + if (HasCapture()) + ReleaseMouse(); + if (hover_item >= 0) { // not moved + sendDropDownEvent(); + DismissAndNotify(); + } + } +} + +void DropDown::mouseCaptureLost(wxMouseCaptureLostEvent &event) +{ + wxMouseEvent evt; + mouseReleased(evt); +} + +void DropDown::mouseMove(wxMouseEvent &event) +{ + wxPoint pt = event.GetPosition(); + if (pressedDown) { + wxPoint pt2 = offset + pt - dragStart; + wxSize size = GetSize(); + dragStart = pt; + if (pt2.y > 0) + pt2.y = 0; + else if (pt2.y + rowSize.y * int(texts.size()) < size.y) + pt2.y = size.y - rowSize.y * int(texts.size()); + if (pt2.y != offset.y) { + offset = pt2; + hover_item = -1; // moved + } else { + return; + } + } + if (!pressedDown || hover_item >= 0) { + int hover = (pt.y - offset.y) / rowSize.y; + if (hover >= (int) texts.size()) hover = -1; + if (hover == hover_item) return; + hover_item = hover; + if (hover >= 0) + SetToolTip(texts[hover]); + } + paintNow(); +} + +void DropDown::mouseWheelMoved(wxMouseEvent &event) +{ + auto delta = event.GetWheelRotation(); + wxSize size = GetSize(); + wxPoint pt2 = offset + wxPoint{0, delta}; + if (pt2.y > 0) + pt2.y = 0; + else if (pt2.y + rowSize.y * int(texts.size()) < size.y) + pt2.y = size.y - rowSize.y * int(texts.size()); + if (pt2.y != offset.y) { + offset = pt2; + } else { + return; + } + int hover = (event.GetPosition().y - offset.y) / rowSize.y; + if (hover >= (int) texts.size()) hover = -1; + if (hover != hover_item) { + hover_item = hover; + if (hover >= 0) SetToolTip(texts[hover]); + } + paintNow(); +} + +// currently unused events +void DropDown::sendDropDownEvent() +{ + selection = hover_item; + wxCommandEvent event(wxEVT_COMBOBOX, GetId()); + event.SetEventObject(this); + event.SetInt(selection); + event.SetString(GetValue()); + GetEventHandler()->ProcessEvent(event); +} + +void DropDown::OnDismiss() +{ + dismissTime = boost::posix_time::microsec_clock::universal_time(); + hover_item = -1; + wxCommandEvent e(EVT_DISMISS); + GetEventHandler()->ProcessEvent(e); +} diff --git a/src/slic3r/GUI/Widgets/DropDown.hpp b/src/slic3r/GUI/Widgets/DropDown.hpp new file mode 100644 index 0000000..e2a6cb4 --- /dev/null +++ b/src/slic3r/GUI/Widgets/DropDown.hpp @@ -0,0 +1,112 @@ +#ifndef slic3r_GUI_DropDown_hpp_ +#define slic3r_GUI_DropDown_hpp_ + +#include +#include "../wxExtensions.hpp" +#include "StateHandler.hpp" +#include "PopupWindow.hpp" + +#define DD_NO_CHECK_ICON 0x0001 +#define DD_NO_TEXT 0x0002 +#define DD_STYLE_MASK 0x0003 + +wxDECLARE_EVENT(EVT_DISMISS, wxCommandEvent); + +class DropDown : public PopupWindow +{ + std::vector & texts; + std::vector & icons; + bool need_sync = false; + int selection = -1; + int hover_item = -1; + + double radius = 0; + bool use_content_width = false; + bool align_icon = false; + bool text_off = false; + + wxSize textSize; + wxSize iconSize; + wxSize rowSize; + + StateHandler state_handler; + StateColor text_color; + StateColor border_color; + StateColor selector_border_color; + StateColor selector_background_color; + ScalableBitmap check_bitmap; + + bool pressedDown = false; + boost::posix_time::ptime dismissTime; + wxPoint offset; // x not used + wxPoint dragStart; + +public: + DropDown(std::vector &texts, + std::vector &icons); + + DropDown(wxWindow * parent, + std::vector &texts, + std::vector &icons, + long style = 0); + + void Create(wxWindow * parent, + long style = 0); + +public: + void Invalidate(bool clear = false); + + int GetSelection() const { return selection; } + + void SetSelection(int n); + + wxString GetValue() const; + void SetValue(const wxString &value); + +public: + void SetCornerRadius(double radius); + + void SetBorderColor(StateColor const & color); + + void SetSelectorBorderColor(StateColor const & color); + + void SetTextColor(StateColor const &color); + + void SetSelectorBackgroundColor(StateColor const &color); + + void SetUseContentWidth(bool use); + + void SetAlignIcon(bool align); + +public: + void Rescale(); + + bool HasDismissLongTime(); + +protected: + void OnDismiss() override; + +private: + void paintEvent(wxPaintEvent& evt); + void paintNow(); + + void render(wxDC& dc); + + friend class ComboBox; + void messureSize(); + void autoPosition(); + + // some useful events + void mouseDown(wxMouseEvent& event); + void mouseReleased(wxMouseEvent &event); + void mouseCaptureLost(wxMouseCaptureLostEvent &event); + void mouseMove(wxMouseEvent &event); + void mouseWheelMoved(wxMouseEvent &event); + + void sendDropDownEvent(); + + + DECLARE_EVENT_TABLE() +}; + +#endif // !slic3r_GUI_DropDown_hpp_ diff --git a/src/slic3r/GUI/Widgets/PopupWindow.cpp b/src/slic3r/GUI/Widgets/PopupWindow.cpp new file mode 100644 index 0000000..135802c --- /dev/null +++ b/src/slic3r/GUI/Widgets/PopupWindow.cpp @@ -0,0 +1,37 @@ +#include "PopupWindow.hpp" + +static wxWindow *GetTopParent(wxWindow *pWindow) +{ + wxWindow *pWin = pWindow; + while (pWin->GetParent()) { + pWin = pWin->GetParent(); + if (auto top = dynamic_cast(pWin)) + return top; + } + return pWin; +} + +bool PopupWindow::Create(wxWindow *parent, int style) +{ + if (!wxPopupTransientWindow::Create(parent, style)) + return false; +#ifdef __WXGTK__ + GetTopParent(parent)->Bind(wxEVT_ACTIVATE, &PopupWindow::topWindowActiavate, this); +#endif + return true; +} + +PopupWindow::~PopupWindow() +{ +#ifdef __WXGTK__ + GetTopParent(this)->Unbind(wxEVT_ACTIVATE, &PopupWindow::topWindowActiavate, this); +#endif +} + +#ifdef __WXGTK__ +void PopupWindow::topWindowActiavate(wxActivateEvent &event) +{ + event.Skip(); + if (!event.GetActive() && IsShown()) DismissAndNotify(); +} +#endif diff --git a/src/slic3r/GUI/Widgets/PopupWindow.hpp b/src/slic3r/GUI/Widgets/PopupWindow.hpp new file mode 100644 index 0000000..88993de --- /dev/null +++ b/src/slic3r/GUI/Widgets/PopupWindow.hpp @@ -0,0 +1,24 @@ +#ifndef slic3r_GUI_PopupWindow_hpp_ +#define slic3r_GUI_PopupWindow_hpp_ + +#include + +class PopupWindow : public wxPopupTransientWindow +{ +public: + PopupWindow() {} + + ~PopupWindow(); + + PopupWindow(wxWindow *parent, int style = wxBORDER_NONE) + { Create(parent, style); } + + bool Create(wxWindow *parent, int flags = wxBORDER_NONE); + +private: +#ifdef __WXGTK__ + void topWindowActiavate(wxActivateEvent &event); +#endif +}; + +#endif // !slic3r_GUI_PopupWindow_hpp_ diff --git a/src/slic3r/GUI/Widgets/RadioBox.cpp b/src/slic3r/GUI/Widgets/RadioBox.cpp new file mode 100644 index 0000000..17712a8 --- /dev/null +++ b/src/slic3r/GUI/Widgets/RadioBox.cpp @@ -0,0 +1,42 @@ +#include "RadioBox.hpp" + +#include "../wxExtensions.hpp" + +namespace Slic3r { +namespace GUI { +RadioBox::RadioBox(wxWindow *parent) + : wxBitmapToggleButton(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE), m_on(this, "radio_on", 18), m_off(this, "radio_off", 18) +{ + // SetBackgroundStyle(wxBG_STYLE_TRANSPARENT); + if (parent) SetBackgroundColour(parent->GetBackgroundColour()); + // Bind(wxEVT_TOGGLEBUTTON, [this](auto& e) { update(); e.Skip(); }); + SetSize(m_on.GetSize()); + SetMinSize(m_on.GetSize()); + update(); +} + +void RadioBox::SetValue(bool value) +{ + wxBitmapToggleButton::SetValue(value); + update(); +} + +bool RadioBox::GetValue() +{ + return wxBitmapToggleButton::GetValue(); +} + + +void RadioBox::Rescale() +{ + m_on.msw_rescale(); + m_off.msw_rescale(); + SetSize(m_on.GetSize()); + update(); +} + +void RadioBox::update() { SetBitmap((GetValue() ? m_on : m_off).bmp()); } + +} +} + diff --git a/src/slic3r/GUI/Widgets/RadioBox.hpp b/src/slic3r/GUI/Widgets/RadioBox.hpp new file mode 100644 index 0000000..8f30994 --- /dev/null +++ b/src/slic3r/GUI/Widgets/RadioBox.hpp @@ -0,0 +1,39 @@ +#ifndef slic3r_GUI_RADIOBOX_hpp_ +#define slic3r_GUI_RADIOBOX_hpp_ + +#include "../wxExtensions.hpp" + +#include + +namespace Slic3r { +namespace GUI { + +class RadioBox : public wxBitmapToggleButton +{ +public: + RadioBox(wxWindow *parent); + +public: + void SetValue(bool value) override; + bool GetValue(); + void Rescale(); + bool Disable() { + return wxBitmapToggleButton::Disable(); + } + bool Enable() { + return wxBitmapToggleButton::Enable(); + } + +private: + void update(); + +private: + ScalableBitmap m_on; + ScalableBitmap m_off; +}; + +}} + + + +#endif // !slic3r_GUI_CheckBox_hpp_ diff --git a/src/slic3r/GUI/Widgets/RoundedRectangle.cpp b/src/slic3r/GUI/Widgets/RoundedRectangle.cpp new file mode 100644 index 0000000..a3c7c13 --- /dev/null +++ b/src/slic3r/GUI/Widgets/RoundedRectangle.cpp @@ -0,0 +1,35 @@ +#include "RoundedRectangle.hpp" +#include "../wxExtensions.hpp" +#include + +BEGIN_EVENT_TABLE(RoundedRectangle, wxPanel) +EVT_PAINT(RoundedRectangle::OnPaint) +END_EVENT_TABLE() + + RoundedRectangle::RoundedRectangle(wxWindow *parent, wxColour col, wxPoint pos, wxSize size, double radius, int type) + : wxWindow(parent, wxID_ANY, pos, size, wxBORDER_NONE) +{ + SetBackgroundColour(wxColour(255,255,255)); + m_type = type; + m_color = col; + m_radius = radius; +} + +void RoundedRectangle::OnPaint(wxPaintEvent &evt) +{ + //draw RoundedRectangle + if (m_type == 0) { + wxPaintDC dc(this); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(wxBrush(m_color)); + dc.DrawRoundedRectangle(0, 0, GetSize().GetWidth(), GetSize().GetHeight(), m_radius); + } + + //draw RoundedRectangle only board + if (m_type == 1) { + wxPaintDC dc(this); + dc.SetPen(m_color); + dc.SetBrush(wxBrush(*wxTRANSPARENT_BRUSH)); + dc.DrawRoundedRectangle(0, 0, GetSize().GetWidth(), GetSize().GetHeight(), m_radius); + } +} \ No newline at end of file diff --git a/src/slic3r/GUI/Widgets/RoundedRectangle.hpp b/src/slic3r/GUI/Widgets/RoundedRectangle.hpp new file mode 100644 index 0000000..9709010 --- /dev/null +++ b/src/slic3r/GUI/Widgets/RoundedRectangle.hpp @@ -0,0 +1,21 @@ +#ifndef slic3r_GUI_ROUNDEDRECTANGLE_hpp_ +#define slic3r_GUI_ROUNDEDRECTANGLE_hpp_ + +#include "../wxExtensions.hpp" + +class RoundedRectangle : public wxWindow +{ +public: + RoundedRectangle(wxWindow *parent, wxColour col, wxPoint pos, wxSize size, double radius, int type = 0); + ~RoundedRectangle(){}; + +private: + double m_radius; + int m_type; + wxColour m_color; + +public: + void OnPaint(wxPaintEvent &evt); + DECLARE_EVENT_TABLE() +}; +#endif // !slic3r_GUI_RoundedRectangle_hpp_ diff --git a/src/slic3r/GUI/Widgets/TextCtrl.h b/src/slic3r/GUI/Widgets/TextCtrl.h new file mode 100644 index 0000000..3f3ab2b --- /dev/null +++ b/src/slic3r/GUI/Widgets/TextCtrl.h @@ -0,0 +1,10 @@ +#ifdef __WXMSW__ +class TextCtrl : public wxTextCtrl +{ +public: + using wxTextCtrl::wxTextCtrl; + WXHBRUSH DoMSWControlColor(WXHDC pDC, wxColour colBg, WXHWND hWnd) { return wxTextCtrl::DoMSWControlColor(pDC, wxColour(), hWnd); } +}; +#else +typedef wxTextCtrl TextCtrl; +#endif diff --git a/src/slic3r/GUI/Widgets/TextInput.cpp b/src/slic3r/GUI/Widgets/TextInput.cpp new file mode 100644 index 0000000..08bfb4f --- /dev/null +++ b/src/slic3r/GUI/Widgets/TextInput.cpp @@ -0,0 +1,240 @@ +#include "TextInput.hpp" +#include "Label.hpp" +#include "TextCtrl.h" +#include "slic3r/GUI/Widgets/Label.hpp" + +#include + +BEGIN_EVENT_TABLE(TextInput, wxPanel) + +EVT_PAINT(TextInput::paintEvent) + +END_EVENT_TABLE() + +/* + * Called by the system of by wxWidgets when the panel needs + * to be redrawn. You can also trigger this call by + * calling Refresh()/Update(). + */ + +TextInput::TextInput() + : label_color(std::make_pair(0x909090, (int) StateColor::Disabled), + std::make_pair(0x6B6B6B, (int) StateColor::Normal)) + , text_color(std::make_pair(0x909090, (int) StateColor::Disabled), + std::make_pair(0x262E30, (int) StateColor::Normal)) +{ + radius = 0; + border_width = 1; + border_color = StateColor(std::make_pair(0xDBDBDB, (int) StateColor::Disabled), std::make_pair(0x009688, (int) StateColor::Hovered), + std::make_pair(0xDBDBDB, (int) StateColor::Normal)); + background_color = StateColor(std::make_pair(0xF0F0F1, (int) StateColor::Disabled), std::make_pair(*wxWHITE, (int) StateColor::Normal)); + SetFont(Label::Body_12); +} + +TextInput::TextInput(wxWindow * parent, + wxString text, + wxString label, + wxString icon, + const wxPoint &pos, + const wxSize & size, + long style) + : TextInput() +{ + Create(parent, text, label, icon, pos, size, style); +} + +void TextInput::Create(wxWindow * parent, + wxString text, + wxString label, + wxString icon, + const wxPoint &pos, + const wxSize & size, + long style) +{ + text_ctrl = nullptr; + StaticBox::Create(parent, wxID_ANY, pos, size, style); + wxWindow::SetLabel(label); + style &= ~wxRIGHT; + state_handler.attach({&label_color, & text_color}); + state_handler.update_binds(); + text_ctrl = new TextCtrl(this, wxID_ANY, text, {4, 4}, wxDefaultSize, style | wxBORDER_NONE | wxTE_PROCESS_ENTER); + text_ctrl->SetFont(Label::Body_14); + text_ctrl->SetInitialSize(text_ctrl->GetBestSize()); + text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states())); + text_ctrl->SetForegroundColour(text_color.colorForStates(state_handler.states())); + state_handler.attach_child(text_ctrl); + text_ctrl->Bind(wxEVT_KILL_FOCUS, [this](auto &e) { + OnEdit(); + e.SetId(GetId()); + ProcessEventLocally(e); + e.Skip(); + }); + text_ctrl->Bind(wxEVT_TEXT_ENTER, [this](auto &e) { + OnEdit(); + e.SetId(GetId()); + ProcessEventLocally(e); + }); + text_ctrl->Bind(wxEVT_RIGHT_DOWN, [this](auto &e) {}); // disable context menu + if (!icon.IsEmpty()) { + this->icon = ScalableBitmap(this, icon.ToStdString(), 16); + } + messureSize(); +} + +void TextInput::SetCornerRadius(double radius) +{ + this->radius = radius; + Refresh(); +} + +void TextInput::SetLabel(const wxString& label) +{ + wxWindow::SetLabel(label); + messureSize(); + Refresh(); +} + +void TextInput::SetIcon(const wxBitmap &icon) +{ + this->icon.get_bitmap() = icon; + Rescale(); +} + +void TextInput::SetLabelColor(StateColor const &color) +{ + label_color = color; + state_handler.update_binds(); +} + +void TextInput::SetTextColor(StateColor const& color) +{ + text_color= color; + state_handler.update_binds(); +} + +void TextInput::Rescale() +{ + if (!this->icon.name().empty()) + this->icon.msw_rescale(); + messureSize(); + Refresh(); +} + +bool TextInput::Enable(bool enable) +{ + bool result = text_ctrl->Enable(enable) && wxWindow::Enable(enable); + if (result) { + wxCommandEvent e(EVT_ENABLE_CHANGED); + e.SetEventObject(this); + GetEventHandler()->ProcessEvent(e); + text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states())); + text_ctrl->SetForegroundColour(text_color.colorForStates(state_handler.states())); + } + return result; +} + +void TextInput::SetMinSize(const wxSize& size) +{ + wxSize size2 = size; + if (size2.y < 0) { +#ifdef __WXMAC__ + if (GetPeer()) // peer is not ready in Create on mac +#endif + size2.y = GetSize().y; + } + wxWindow::SetMinSize(size2); +} + +void TextInput::DoSetSize(int x, int y, int width, int height, int sizeFlags) +{ + wxWindow::DoSetSize(x, y, width, height, sizeFlags); + if (sizeFlags & wxSIZE_USE_EXISTING) return; + wxSize size = GetSize(); + wxPoint textPos = {5, 0}; + if (this->icon.get_bitmap().IsOk()) { + wxSize szIcon = this->icon.GetSize(); + textPos.x += szIcon.x; + } + bool align_right = GetWindowStyle() & wxRIGHT; + if (align_right) + textPos.x += labelSize.x; + if (text_ctrl) { + wxSize textSize = text_ctrl->GetSize(); + textSize.x = size.x - textPos.x - labelSize.x - 10; + text_ctrl->SetSize(textSize); + text_ctrl->SetPosition({textPos.x, (size.y - textSize.y) / 2}); + } +} + +void TextInput::DoSetToolTipText(wxString const &tip) +{ + wxWindow::DoSetToolTipText(tip); + text_ctrl->SetToolTip(tip); +} + +void TextInput::paintEvent(wxPaintEvent &evt) +{ + // depending on your system you may need to look at double-buffered dcs + wxPaintDC dc(this); + render(dc); +} + +/* + * Here we do the actual rendering. I put it in a separate + * method so that it can work no matter what type of DC + * (e.g. wxPaintDC or wxClientDC) is used. + */ +void TextInput::render(wxDC& dc) +{ + StaticBox::render(dc); + int states = state_handler.states(); + wxSize size = GetSize(); + bool align_right = GetWindowStyle() & wxRIGHT; + // start draw + wxPoint pt = {5, 0}; + if (icon.get_bitmap().IsOk()) { + wxSize szIcon = icon.GetSize(); + pt.y = (size.y - szIcon.y) / 2; + dc.DrawBitmap(icon.get_bitmap(), pt); + pt.x += szIcon.x + 0; + } + auto text = wxWindow::GetLabel(); + if (!text.IsEmpty()) { + wxSize textSize = text_ctrl->GetSize(); + if (align_right) { + if (pt.x + labelSize.x > size.x) + text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, size.x - pt.x); + pt.y = (size.y - labelSize.y) / 2; + } else { + pt.x += textSize.x; + pt.y = (size.y + textSize.y) / 2 - labelSize.y; + } + dc.SetTextForeground(label_color.colorForStates(states)); + if(align_right) + dc.SetFont(GetFont()); + else + dc.SetFont(Label::Body_12); + dc.DrawText(text, pt); + } +} + +void TextInput::messureSize() +{ + wxSize size = GetSize(); + wxClientDC dc(this); + bool align_right = GetWindowStyle() & wxRIGHT; + if (align_right) + dc.SetFont(GetFont()); + else + dc.SetFont(Label::Body_12); + labelSize = dc.GetTextExtent(wxWindow::GetLabel()); + wxSize textSize = text_ctrl->GetSize(); + int h = textSize.y + 8; + if (size.y < h) { + size.y = h; + } + wxSize minSize = size; + minSize.x = GetMinWidth(); + SetMinSize(minSize); + SetSize(size); +} diff --git a/src/slic3r/GUI/Widgets/TextInput.hpp b/src/slic3r/GUI/Widgets/TextInput.hpp new file mode 100644 index 0000000..1013e7c --- /dev/null +++ b/src/slic3r/GUI/Widgets/TextInput.hpp @@ -0,0 +1,77 @@ +#ifndef slic3r_GUI_TextInput_hpp_ +#define slic3r_GUI_TextInput_hpp_ + +#include +#include "StaticBox.hpp" + +class TextInput : public wxNavigationEnabled +{ + + wxSize labelSize; + ScalableBitmap icon; + StateColor label_color; + StateColor text_color; + wxTextCtrl * text_ctrl; + + static const int TextInputWidth = 200; + static const int TextInputHeight = 50; + +public: + TextInput(); + + TextInput(wxWindow * parent, + wxString text, + wxString label = "", + wxString icon = "", + const wxPoint &pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0); + +public: + void Create(wxWindow * parent, + wxString text, + wxString label = "", + wxString icon = "", + const wxPoint &pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0); + + void SetCornerRadius(double radius); + + void SetLabel(const wxString& label); + + void SetIcon(const wxBitmap &icon); + + void SetLabelColor(StateColor const &color); + + void SetTextColor(StateColor const &color); + + virtual void Rescale(); + + virtual bool Enable(bool enable = true) override; + + virtual void SetMinSize(const wxSize& size) override; + + wxTextCtrl *GetTextCtrl() { return text_ctrl; } + + wxTextCtrl const *GetTextCtrl() const { return text_ctrl; } + +protected: + virtual void OnEdit() {} + + virtual void DoSetSize( + int x, int y, int width, int height, int sizeFlags = wxSIZE_AUTO); + + void DoSetToolTipText(wxString const &tip) override; + +private: + void paintEvent(wxPaintEvent& evt); + + void render(wxDC& dc); + + void messureSize(); + + DECLARE_EVENT_TABLE() +}; + +#endif // !slic3r_GUI_TextInput_hpp_ diff --git a/src/slic3r/GUI/calib_dlg.cpp b/src/slic3r/GUI/calib_dlg.cpp new file mode 100644 index 0000000..bf46f8b --- /dev/null +++ b/src/slic3r/GUI/calib_dlg.cpp @@ -0,0 +1,242 @@ +#include "calib_dlg.hpp" +#include "GUI_App.hpp" +#include "MsgDialog.hpp" +#include "I18N.hpp" +#include +#include "MainFrame.hpp" +#include +namespace Slic3r { +namespace GUI { + +wxBoxSizer *create_item_checkbox(wxString title, wxWindow *parent, bool *value, CheckBoxInWT *&checkbox) +{ + wxBoxSizer* m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL); + + m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 5); + + checkbox = new ::CheckBoxInWT(parent); + m_sizer_checkbox->Add(checkbox, 0, wxALIGN_CENTER, 0); + m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 8); + + auto checkbox_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxSize(-1, -1), 0); + checkbox_title->SetForegroundColour(wxColour(144, 144, 144)); + checkbox_title->SetFont(::Label::Body_13); + checkbox_title->Wrap(-1); + m_sizer_checkbox->Add(checkbox_title, 0, wxALIGN_CENTER | wxALL, 3); + + checkbox->SetValue(true); + + checkbox->Bind(wxEVT_TOGGLEBUTTON, [parent, checkbox, value](wxCommandEvent& e) { + (*value) = (*value) ? false : true; + e.Skip(); + }); + + return m_sizer_checkbox; +} + +PA_Calibration_Dlg::PA_Calibration_Dlg(wxWindow* parent, wxWindowID id, Plater* plater) + : DPIDialog(parent, id, _L("PA Calibration"), wxDefaultPosition, parent->FromDIP(wxSize(-1, 280)), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), m_plater(plater) +{ + wxBoxSizer* v_sizer = new wxBoxSizer(wxVERTICAL); + SetSizer(v_sizer); + wxBoxSizer* choice_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxString m_rbExtruderTypeChoices[] = { _L("DDE"), _L("Bowden") }; + int m_rbExtruderTypeNChoices = sizeof(m_rbExtruderTypeChoices) / sizeof(wxString); + m_rbExtruderType = new wxRadioBox(this, wxID_ANY, _L("Extruder type"), wxDefaultPosition, wxDefaultSize, m_rbExtruderTypeNChoices, m_rbExtruderTypeChoices, 2, wxRA_SPECIFY_COLS); + m_rbExtruderType->SetSelection(0); + choice_sizer->Add(m_rbExtruderType, 0, wxALL, 5); + choice_sizer->Add(FromDIP(5), 0, 0, wxEXPAND, 5); + wxString m_rbMethodChoices[] = { _L("PA Tower"), _L("PA Line"), _L("PA Pattern") }; + int m_rbMethodNChoices = sizeof(m_rbMethodChoices) / sizeof(wxString); + m_rbMethod = new wxRadioBox(this, wxID_ANY, _L("Method"), wxDefaultPosition, wxDefaultSize, m_rbMethodNChoices, m_rbMethodChoices, 2, wxRA_SPECIFY_COLS); + m_rbMethod->SetSelection(0); + choice_sizer->Add(m_rbMethod, 0, wxALL, 5); + + v_sizer->Add(choice_sizer); + + // Settings + // + wxString start_pa_str = _L("Start PA: "); + wxString end_pa_str = _L("End PA: "); + wxString PA_step_str = _L("PA step: "); + auto text_size = wxWindow::GetTextExtent(start_pa_str); + text_size.IncTo(wxWindow::GetTextExtent(end_pa_str)); + text_size.IncTo(wxWindow::GetTextExtent(PA_step_str)); + text_size.x = text_size.x * 1.5; + wxStaticBoxSizer* settings_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _L("Settings")); + + auto st_size = FromDIP(wxSize(text_size.x, -1)); + auto ti_size = FromDIP(wxSize(90, -1)); + // start PA + auto start_PA_sizer = new wxBoxSizer(wxHORIZONTAL); + auto start_pa_text = new wxStaticText(this, wxID_ANY, start_pa_str, wxDefaultPosition, st_size, wxALIGN_LEFT); + m_tiStartPA = new TextInput(this, "", "", "", wxDefaultPosition, ti_size, wxTE_CENTRE | wxTE_PROCESS_ENTER); + m_tiStartPA->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); + + start_PA_sizer->Add(start_pa_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); + start_PA_sizer->Add(m_tiStartPA, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); + settings_sizer->Add(start_PA_sizer); + + // end PA + auto end_PA_sizer = new wxBoxSizer(wxHORIZONTAL); + auto end_pa_text = new wxStaticText(this, wxID_ANY, end_pa_str, wxDefaultPosition, st_size, wxALIGN_LEFT); + m_tiEndPA = new TextInput(this, "", "", "", wxDefaultPosition, ti_size, wxTE_CENTRE | wxTE_PROCESS_ENTER); + m_tiStartPA->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); + end_PA_sizer->Add(end_pa_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); + end_PA_sizer->Add(m_tiEndPA, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); + settings_sizer->Add(end_PA_sizer); + + // PA step + auto PA_step_sizer = new wxBoxSizer(wxHORIZONTAL); + auto PA_step_text = new wxStaticText(this, wxID_ANY, PA_step_str, wxDefaultPosition, st_size, wxALIGN_LEFT); + m_tiPAStep = new TextInput(this, "", "", "", wxDefaultPosition, ti_size, wxTE_CENTRE | wxTE_PROCESS_ENTER); + m_tiStartPA->GetTextCtrl()->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); + PA_step_sizer->Add(PA_step_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); + PA_step_sizer->Add(m_tiPAStep, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); + settings_sizer->Add(PA_step_sizer); + + settings_sizer->Add(create_item_checkbox(_L("Print numbers"), this, &m_params.print_numbers, m_cbPrintNum)); + m_cbPrintNum->SetValue(false); + + v_sizer->Add(settings_sizer); + v_sizer->Add(0, FromDIP(10), 0, wxEXPAND, 5); + m_btnStart = new Button(this, _L("OK")); + StateColor btn_bg_green(std::pair(wxColour(0, 137, 123), StateColor::Pressed), + std::pair(wxColour(38, 166, 154), StateColor::Hovered), + std::pair(wxColour(0, 150, 136), StateColor::Normal)); + + m_btnStart->SetBackgroundColor(btn_bg_green); + m_btnStart->SetBorderColor(wxColour(0, 150, 136)); + m_btnStart->SetTextColor(wxColour("#FFFFFE")); + m_btnStart->SetSize(wxSize(FromDIP(48), FromDIP(24))); + m_btnStart->SetMinSize(wxSize(FromDIP(48), FromDIP(24))); + m_btnStart->SetCornerRadius(FromDIP(3)); + m_btnStart->Bind(wxEVT_BUTTON, &PA_Calibration_Dlg::on_start, this); + v_sizer->Add(m_btnStart, 0, wxALL | wxALIGN_RIGHT, FromDIP(5)); + + PA_Calibration_Dlg::reset_params(); + + // Connect Events + m_rbExtruderType->Connect(wxEVT_COMMAND_RADIOBOX_SELECTED, wxCommandEventHandler(PA_Calibration_Dlg::on_extruder_type_changed), NULL, this); + m_rbMethod->Connect(wxEVT_COMMAND_RADIOBOX_SELECTED, wxCommandEventHandler(PA_Calibration_Dlg::on_method_changed), NULL, this); + this->Connect(wxEVT_SHOW, wxShowEventHandler(PA_Calibration_Dlg::on_show)); + //wxGetApp().UpdateDlgDarkUI(this); + + Layout(); + Fit(); +} + +PA_Calibration_Dlg::~PA_Calibration_Dlg() { + // Disconnect Events + m_rbExtruderType->Disconnect(wxEVT_COMMAND_RADIOBOX_SELECTED, wxCommandEventHandler(PA_Calibration_Dlg::on_extruder_type_changed), NULL, this); + m_rbMethod->Disconnect(wxEVT_COMMAND_RADIOBOX_SELECTED, wxCommandEventHandler(PA_Calibration_Dlg::on_method_changed), NULL, this); + m_btnStart->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(PA_Calibration_Dlg::on_start), NULL, this); +} + +void PA_Calibration_Dlg::reset_params() { + bool isDDE = m_rbExtruderType->GetSelection() == 0 ? true : false; + int method = m_rbMethod->GetSelection(); + + m_tiStartPA->GetTextCtrl()->SetValue(wxString::FromDouble(0.0)); + + switch (method) { + case 1: + m_params.mode = CalibMode::Calib_PA_Line; + m_tiEndPA->GetTextCtrl()->SetValue(wxString::FromDouble(0.1)); + m_tiPAStep->GetTextCtrl()->SetValue(wxString::FromDouble(0.002)); + m_cbPrintNum->SetValue(true); + m_cbPrintNum->Enable(true); + break; + case 2: + m_params.mode = CalibMode::Calib_PA_Pattern; + m_tiEndPA->GetTextCtrl()->SetValue(wxString::FromDouble(0.08)); + m_tiPAStep->GetTextCtrl()->SetValue(wxString::FromDouble(0.005)); + m_cbPrintNum->SetValue(true); + m_cbPrintNum->Enable(false); + break; + default: + m_params.mode = CalibMode::Calib_PA_Tower; + m_tiEndPA->GetTextCtrl()->SetValue(wxString::FromDouble(0.1)); + m_tiPAStep->GetTextCtrl()->SetValue(wxString::FromDouble(0.002)); + m_cbPrintNum->SetValue(false); + m_cbPrintNum->Enable(false); + break; + } + + if (!isDDE) { + m_tiEndPA->GetTextCtrl()->SetValue(wxString::FromDouble(1.0)); + + if (m_params.mode == CalibMode::Calib_PA_Pattern) { + m_tiPAStep->GetTextCtrl()->SetValue(wxString::FromDouble(0.05)); + } else { + m_tiPAStep->GetTextCtrl()->SetValue(wxString::FromDouble(0.02)); + } + } +} + +void PA_Calibration_Dlg::on_start(wxCommandEvent& event) { + bool read_double = false; + read_double = m_tiStartPA->GetTextCtrl()->GetValue().ToDouble(&m_params.start); + read_double = read_double && m_tiEndPA->GetTextCtrl()->GetValue().ToDouble(&m_params.end); + read_double = read_double && m_tiPAStep->GetTextCtrl()->GetValue().ToDouble(&m_params.step); + if (!read_double || m_params.start < 0 || m_params.step < EPSILON || m_params.end < m_params.start + m_params.step) { + MessageDialog msg_dlg(nullptr, _L("Please input valid values:\nStart PA: >= 0.0\nEnd PA: > Start PA\nPA step: >= 0.001)"), wxEmptyString, wxICON_WARNING | wxOK); + msg_dlg.ShowModal(); + return; + } + + switch (m_rbMethod->GetSelection()) { + case 1: + m_params.mode = CalibMode::Calib_PA_Line; + break; + case 2: + m_params.mode = CalibMode::Calib_PA_Pattern; + break; + default: + m_params.mode = CalibMode::Calib_PA_Tower; + } + + m_params.print_numbers = m_cbPrintNum->GetValue(); + + m_plater->calib_pa(m_params); + EndModal(wxID_OK); + +} +void PA_Calibration_Dlg::on_extruder_type_changed(wxCommandEvent& event) { + PA_Calibration_Dlg::reset_params(); + event.Skip(); +} +void PA_Calibration_Dlg::on_method_changed(wxCommandEvent& event) { + PA_Calibration_Dlg::reset_params(); + event.Skip(); +} + +void PA_Calibration_Dlg::on_dpi_changed(const wxRect& suggested_rect) { + this->Refresh(); + Fit(); +} + +void PA_Calibration_Dlg::on_show(wxShowEvent& event) { + PA_Calibration_Dlg::reset_params(); +} + +// Temp Calib dlg +// +enum FILAMENT_TYPE : int +{ + tPLA = 0, + tABS_ASA, + tPETG, + tTPU, + tPA_CF, + tPET_CF, + tCustom +}; + + + + + + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/calib_dlg.hpp b/src/slic3r/GUI/calib_dlg.hpp new file mode 100644 index 0000000..4889c4d --- /dev/null +++ b/src/slic3r/GUI/calib_dlg.hpp @@ -0,0 +1,52 @@ +#ifndef slic3r_calib_dlg_hpp_ +#define slic3r_calib_dlg_hpp_ + +#include "wxExtensions.hpp" +#include "GUI_Utils.hpp" +#include "Widgets/Button.hpp" +#include "Widgets/Label.hpp" +#include "Widgets/RadioBox.hpp" +#include "Widgets/RoundedRectangle.hpp" +#include "Widgets/CheckBoxInWT.hpp" +#include "Widgets/ComboBox.hpp" +#include "Widgets/TextInput.hpp" +#include "GUI_App.hpp" +#include "wx/hyperlink.h" +#include +#include "libslic3r/calib.hpp" +#include "Plater.hpp" + +namespace Slic3r { namespace GUI { + +class PA_Calibration_Dlg : public DPIDialog +{ +public: + PA_Calibration_Dlg(wxWindow* parent, wxWindowID id, Plater* plater); + ~PA_Calibration_Dlg(); + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_show(wxShowEvent& event); +protected: + void reset_params(); + virtual void on_start(wxCommandEvent& event); + virtual void on_extruder_type_changed(wxCommandEvent& event); + virtual void on_method_changed(wxCommandEvent& event); + +protected: + bool m_bDDE; + Calib_Params m_params; + + + wxRadioBox* m_rbExtruderType; + wxRadioBox* m_rbMethod; + TextInput* m_tiStartPA; + TextInput* m_tiEndPA; + TextInput* m_tiPAStep; + CheckBoxInWT *m_cbPrintNum; + Button* m_btnStart; + + Plater* m_plater; +}; + +}} // namespace Slic3r::GUI + +#endif diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index d8eaa44..e48d323 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -795,6 +795,56 @@ void ScalableBitmap::sys_color_changed() m_bmp = *get_bmp_bundle(m_icon_name, m_px_cnt); } +//B34 +// win is used to get a correct em_unit value +// It's important for bitmaps of dialogs. +// if win == nullptr, em_unit value of MainFrame will be used +wxBitmap create_scaled_bitmap(const std::string &bmp_name_in, + wxWindow * win /* = nullptr*/, + const int px_cnt /* = 16*/, + const bool grayscale /* = false*/, + const std::string &new_color /* = std::string()*/, // color witch will used instead of orange + const bool menu_bitmap /* = false*/, + const bool resize /* = false*/) +{ + static Slic3r::GUI::BitmapCache cache; + + unsigned int width = 0; + unsigned int height = (unsigned int) (win->FromDIP(px_cnt) + 0.5f); + + std::string bmp_name = bmp_name_in; + boost::replace_last(bmp_name, ".png", ""); + + bool dark_mode = +#ifdef _WIN32 + menu_bitmap ? Slic3r::GUI::check_dark_mode() : +#endif + Slic3r::GUI::wxGetApp().dark_mode(); + + // Try loading an SVG first, then PNG if SVG is not found: + wxBitmap *bmp = cache.load_svg(bmp_name, width, height, grayscale, dark_mode, new_color); + if (bmp == nullptr) { + bmp = cache.load_png(bmp_name, width, height, grayscale); + } + + if (bmp == nullptr) { + // Neither SVG nor PNG has been found, raise error + throw Slic3r::RuntimeError("Could not load bitmap: " + bmp_name); + } + + return *bmp; +} + + + +//B34 +void ScalableBitmap::msw_rescale() +{ + + m_bmp = create_scaled_bitmap(m_icon_name, m_parent, m_px_cnt, m_grayscale, std::string(), false, m_resize); +} + + // ---------------------------------------------------------------------------- // QIDIButton // ---------------------------------------------------------------------------- diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 00f117e..aad5aea 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -158,6 +158,9 @@ public: return m_bmp.GetDefaultSize(); #endif } + //B34 + void msw_rescale(); + int GetWidth() const { return GetSize().GetWidth(); } int GetHeight() const { return GetSize().GetHeight(); } @@ -167,6 +170,9 @@ private: wxBitmap m_bitmap = wxBitmap(); std::string m_icon_name = ""; int m_px_cnt {16}; + //B34 + bool m_grayscale{false}; + bool m_resize{false}; };