diff --git a/CMakeLists.txt b/CMakeLists.txt index 972627d0c2..78dd2586da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,33 +95,6 @@ else () add_compile_definitions("$<$:WXINSPECTOR_DISABLE>") endif () -find_package(Git) -if(DEFINED ENV{git_commit_hash} AND NOT "$ENV{git_commit_hash}" STREQUAL "") - message(STATUS "Specified git commit hash: $ENV{git_commit_hash}") - if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") - # Convert the given hash to short hash - execute_process( - COMMAND ${GIT_EXECUTABLE} rev-parse --short "$ENV{git_commit_hash}" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_COMMIT_HASH - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - else() - # No .git directory (e.g., Flatpak sandbox) — truncate directly - string(SUBSTRING "$ENV{git_commit_hash}" 0 7 GIT_COMMIT_HASH) - endif() - add_definitions("-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"") -elseif(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") - # Check current Git commit hash - execute_process( - COMMAND ${GIT_EXECUTABLE} log -1 --format=%h - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_COMMIT_HASH - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - add_definitions("-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"") -endif() - if(DEFINED ENV{SLIC3R_STATIC}) set(SLIC3R_STATIC_INITIAL $ENV{SLIC3R_STATIC}) else() diff --git a/build_win.bat b/build_win.bat index 15399d5e3d..c447d8a71f 100644 --- a/build_win.bat +++ b/build_win.bat @@ -875,7 +875,7 @@ REM get_str_len -> length in %ret% echo ORCA_DEPS_CMAKE_ARGS Extra arguments for the deps configure echo ORCA_SLICER_CMAKE_ARGS Extra arguments for the slicer configure echo ORCA_UPDATER_SIG_KEY Update signing key baked into the slicer - echo git_commit_hash Revision to stamp, so a commit does not rebuild everything + echo git_commit_hash Revision to stamp into the build, as CI does echo NINJA_STATUS Ninja progress format, if you want your own echo debugscript Set to ON to trace this script echo. diff --git a/deps/python3/python3.cmake b/deps/python3/python3.cmake index 78eb6e72b2..2eae315d0b 100644 --- a/deps/python3/python3.cmake +++ b/deps/python3/python3.cmake @@ -59,12 +59,9 @@ if(WIN32) set(_python_pcbuild_output_dir win32) endif() + # pybind11 undefines _DEBUG around Python.h so a debug build links the + # release python3xx.lib; Py_DEBUG could not load release plugin modules. set(_python_pcbuild_config Release) - set(_python_layout_debug OFF) - if(DEFINED DEP_DEBUG AND DEP_DEBUG) - set(_python_pcbuild_config Debug) - set(_python_layout_debug ON) - endif() # CPython's PCbuild needs a 64-bit-hosted toolchain: find_msbuild.bat picks the # 32-bit Bin\MSBuild.exe, whose x86 cl.exe/link.exe run out of address space @@ -107,7 +104,6 @@ if(WIN32) -DPYTHON_BUILD_DIR=/PCbuild/${_python_pcbuild_output_dir} -DPYTHON_DEST_DIR=${DESTDIR}/libpython -DPYTHON_LAYOUT_ARCH=${_python_layout_arch} - -DPYTHON_DEBUG=${_python_layout_debug} -P ${CMAKE_CURRENT_LIST_DIR}/stage_windows.cmake ) elseif(APPLE) diff --git a/deps/python3/stage_windows.cmake b/deps/python3/stage_windows.cmake index b127f06b68..758e0be9f3 100644 --- a/deps/python3/stage_windows.cmake +++ b/deps/python3/stage_windows.cmake @@ -9,9 +9,6 @@ foreach(_var PYTHON_SOURCE_DIR PYTHON_BUILD_DIR PYTHON_DEST_DIR PYTHON_LAYOUT_AR endforeach() set(_python_exe "${PYTHON_BUILD_DIR}/python.exe") -if(PYTHON_DEBUG) - set(_python_exe "${PYTHON_BUILD_DIR}/python_d.exe") -endif() if(NOT EXISTS "${_python_exe}") message(FATAL_ERROR "Built Python executable not found: ${_python_exe}") @@ -49,22 +46,11 @@ endif() set(_required_files "${PYTHON_DEST_DIR}/Lib/encodings/__init__.py" "${PYTHON_DEST_DIR}/include/Python.h" + "${PYTHON_DEST_DIR}/python.exe" + "${PYTHON_DEST_DIR}/python${_python_abi}.dll" + "${PYTHON_DEST_DIR}/libs/python${_python_abi}.lib" ) -if(PYTHON_DEBUG) - list(APPEND _required_files - "${PYTHON_DEST_DIR}/python_d.exe" - "${PYTHON_DEST_DIR}/python${_python_abi}_d.dll" - "${PYTHON_DEST_DIR}/libs/python${_python_abi}_d.lib" - ) -else() - list(APPEND _required_files - "${PYTHON_DEST_DIR}/python.exe" - "${PYTHON_DEST_DIR}/python${_python_abi}.dll" - "${PYTHON_DEST_DIR}/libs/python${_python_abi}.lib" - ) -endif() - foreach(_required_file IN LISTS _required_files) if(NOT EXISTS "${_required_file}") message(FATAL_ERROR "Staged Python file missing: ${_required_file}") diff --git a/resources/profiles/Snapmaker.json b/resources/profiles/Snapmaker.json index 9a6fab7942..65b1d12a3a 100644 --- a/resources/profiles/Snapmaker.json +++ b/resources/profiles/Snapmaker.json @@ -1,6 +1,6 @@ { "name": "Snapmaker", - "version": "02.04.00.09", + "version": "02.04.00.10", "force_update": "0", "description": "Snapmaker configurations", "machine_model_list": [ diff --git a/resources/profiles/Snapmaker/machine/fdm_U1.json b/resources/profiles/Snapmaker/machine/fdm_U1.json index 717215b507..0b3d89a303 100644 --- a/resources/profiles/Snapmaker/machine/fdm_U1.json +++ b/resources/profiles/Snapmaker/machine/fdm_U1.json @@ -184,7 +184,7 @@ "nozzle_type": "undefine", "auxiliary_fan": "0", "support_multi_bed_types": "1", - "default_bed_type": "4", + "default_bed_type": "Textured PEI Plate", "printable_area": [ "0.5x1", "270.5x1", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0036af0a8c..f0de57f12c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,7 +33,8 @@ if (SLIC3R_GUI) set (wxWidgets_CONFIG_OPTIONS "--toolkit=gtk${SLIC3R_GTK}") find_package(wxWidgets 3.3 REQUIRED COMPONENTS base core adv html gl aui net media webview) else () - find_package(wxWidgets 3.3 CONFIG REQUIRED COMPONENTS html adv gl core base webview aui net media) + # propgrid is required by wxInspector. + find_package(wxWidgets 3.3 CONFIG REQUIRED COMPONENTS html adv gl core base webview aui net media propgrid) endif () if(UNIX) @@ -90,6 +91,16 @@ if (SLIC3R_GUI) # list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc) find_package(wxInspector REQUIRED) + + # wxInspector's exported interface names the release wxWidgets import + # libraries, which a Debug build cannot link. wx is linked above instead. + get_target_property(_wxinspector_interface wxInspector::wxInspector INTERFACE_LINK_LIBRARIES) + if (_wxinspector_interface) + list(FILTER _wxinspector_interface EXCLUDE REGEX "wx(base|msw)3[0-9]u[_.]") + set_target_properties(wxInspector::wxInspector PROPERTIES + INTERFACE_LINK_LIBRARIES "${_wxinspector_interface}") + endif () + list(APPEND wxWidgets_LIBRARIES "wxInspector::wxInspector") message(STATUS "wx libs: ${wxWidgets_LIBRARIES}") diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 33669abcf2..dcd5ec0082 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -1793,8 +1793,9 @@ int CLI::run(int argc, char **argv) old_printable_area = config.option("printable_area", true)->values; old_exclude_area = config.option("bed_exclude_area", true)->values; if (old_printable_area.size() >= 4) { - old_printable_width = (int)(old_printable_area[2].x() - old_printable_area[0].x()); - old_printable_depth = (int)(old_printable_area[2].y() - old_printable_area[0].y()); + BoundingBoxf old_printable_bbox(old_printable_area); + old_printable_width = static_cast(old_printable_bbox.size().x()); + old_printable_depth = static_cast(old_printable_bbox.size().y()); } old_printable_height = (int)(config.opt_float("printable_height")); @@ -2343,8 +2344,9 @@ int CLI::run(int argc, char **argv) Pointfs orig_printable_area; orig_printable_area = config.option("printable_area", true)->values; if (orig_printable_area.size() >= 4) { - orig_printable_width = (int)(orig_printable_area[2].x() - orig_printable_area[0].x()); - orig_printable_depth = (int)(orig_printable_area[2].y() - orig_printable_area[0].y()); + BoundingBoxf orig_printable_bbox(orig_printable_area); + orig_printable_width = static_cast(orig_printable_bbox.size().x()); + orig_printable_depth = static_cast(orig_printable_bbox.size().y()); } orig_printable_height = (int)(config.opt_float("printable_height")); BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(":%1%, check printable size: old_printable_width=%2%, orig_printable_width=%3%, old_printable_depth=%4%, orig_printable_depth=%5%, old_printable_height=%6%, orig_printable_height=%7%") @@ -3138,7 +3140,25 @@ int CLI::run(int argc, char **argv) std::vector old_variant_counts(filament_count, 1), new_variant_counts; ConfigOptionInts* filament_self_index_opt = m_print_config.option("filament_self_index"); - if (!filament_self_index_opt) { + bool need_regenerate_self_index = !filament_self_index_opt; + if (filament_self_index_opt) { + // a filament_self_index carried over from a stale project can disagree with the + // current filament_count. old_start_indice/old_variant_counts below are sized to + // filament_count and walked with 1-based group indices, so an index above + // filament_count overruns old_start_indice[++k], and a non-positive first index + // writes old_variant_counts[-1] - both heap corruption. + int max_self_index = 0, min_self_index = 1; + for (int v : filament_self_index_opt->values) { + max_self_index = std::max(max_self_index, v); + min_self_index = std::min(min_self_index, v); + } + if (max_self_index > filament_count || min_self_index < 1) { + BOOST_LOG_TRIVIAL(warning) << boost::format("filament_self_index range [%1%, %2%] is invalid for filament_count %3%, regenerating") + % min_self_index % max_self_index % filament_count; + need_regenerate_self_index = true; + } + } + if (need_regenerate_self_index) { filament_self_index_opt = m_print_config.option("filament_self_index", true); std::vector& filament_self_indice = filament_self_index_opt->values; filament_self_indice.resize(filament_count); @@ -3732,6 +3752,8 @@ int CLI::run(int argc, char **argv) double height_to_lid = m_print_config.opt_float("extruder_clearance_height_to_lid"); double height_to_rod = m_print_config.opt_float("extruder_clearance_height_to_rod"); double clearance_radius = m_print_config.opt_float("extruder_clearance_radius"); + double nozzle_height = m_print_config.opt_float("nozzle_height"); + Vec2d align_center = m_print_config.option("best_object_pos")->value; int shared_printable_width = 0, shared_printable_depth = 0, shared_printable_height = 0, shared_center_x = 0, shared_center_y = 0; //double plate_stride; std::string bed_texture; @@ -3742,8 +3764,11 @@ int CLI::run(int argc, char **argv) if (m_print_config.opt("extruder_printable_height")) { current_extruder_print_heights = m_print_config.opt("extruder_printable_height")->values; } - current_printable_width = current_printable_area[2].x() - current_printable_area[0].x(); - current_printable_depth = current_printable_area[2].y() - current_printable_area[0].y(); + { + BoundingBoxf current_printable_bbox(current_printable_area); + current_printable_width = static_cast(current_printable_bbox.size().x()); + current_printable_depth = static_cast(current_printable_bbox.size().y()); + } current_printable_height = print_height; if (old_printable_width == 0) old_printable_width = current_printable_width; @@ -3944,6 +3969,11 @@ int CLI::run(int argc, char **argv) ConfigOptionFloats *wipe_x_option = dynamic_cast(print_config.option("wipe_tower_x")); ConfigOptionFloats *wipe_y_option = dynamic_cast(print_config.option("wipe_tower_y")); + // get_at() silently clamps an out-of-range index to entry 0 - make the reuse visible + if (static_cast(plate_index) >= wipe_x_option->values.size() || static_cast(plate_index) >= wipe_y_option->values.size()) { + BOOST_LOG_TRIVIAL(warning) << boost::format("plate %1%: wipe_tower_x/y only has %2%/%3% entries, reusing entry 0's position") + %(plate_index+1) %wipe_x_option->values.size() %wipe_y_option->values.size(); + } plate_obj_size_info.wipe_x = wipe_x_option->get_at(plate_index); plate_obj_size_info.wipe_y = wipe_y_option->get_at(plate_index); @@ -4139,8 +4169,9 @@ int CLI::run(int argc, char **argv) temp_extruder_print_heights = config.option("extruder_printable_height", true)->values; if (temp_printable_area.size() >= 4) { - printer_plate.printable_width = (int)(temp_printable_area[2].x() - temp_printable_area[0].x()); - printer_plate.printable_depth = (int)(temp_printable_area[2].y() - temp_printable_area[0].y()); + BoundingBoxf temp_printable_bbox(temp_printable_area); + printer_plate.printable_width = static_cast(temp_printable_bbox.size().x()); + printer_plate.printable_depth = static_cast(temp_printable_bbox.size().y()); printer_plate.printable_height = (int)(config.opt_float("printable_height")); } if (temp_exclude_area.size() >= 4) { @@ -4788,6 +4819,8 @@ int CLI::run(int argc, char **argv) arrange_cfg.clearance_height_to_rod = height_to_rod; arrange_cfg.clearance_height_to_lid = height_to_lid; arrange_cfg.clearance_radius = clearance_radius; + arrange_cfg.nozzle_height = nozzle_height; + arrange_cfg.align_center = align_center; arrange_cfg.printable_height = print_height; arrange_cfg.min_obj_distance = 0; if (arrange_cfg.is_seq_print) { @@ -5166,7 +5199,8 @@ int CLI::run(int argc, char **argv) Vec3d wipe_tower_size = cur_plate->estimate_wipe_tower_size(m_print_config, w, v, new_extruder_count, filaments_cnt, false, enable_wrapping); Vec3d plate_origin = cur_plate->get_origin(); - int plate_width, plate_depth, plate_height; + int plate_width, plate_depth; + double plate_height; partplate_list.get_plate_size(plate_width, plate_depth, plate_height); float depth = wipe_tower_size(1); float margin = 15.f, wp_brim_width = 0.f; @@ -5238,6 +5272,8 @@ int CLI::run(int argc, char **argv) arrange_cfg.clearance_height_to_rod = height_to_rod; arrange_cfg.clearance_height_to_lid = height_to_lid; arrange_cfg.clearance_radius = clearance_radius; + arrange_cfg.nozzle_height = nozzle_height; + arrange_cfg.align_center = align_center; arrange_cfg.printable_height = print_height; arrange_cfg.min_obj_distance = 0; if (arrange_cfg.is_seq_print) { @@ -6497,7 +6533,6 @@ int CLI::run(int argc, char **argv) bool need_create_thumbnail_group = false, need_create_no_light_group = false, need_create_top_group = false; // get type and color for platedata - auto* filament_types = dynamic_cast(m_print_config.option("filament_type")); const ConfigOptionStrings* filament_color = dynamic_cast(m_print_config.option("filament_colour")); auto* filament_id = dynamic_cast(m_print_config.option("filament_ids")); const ConfigOptionFloats* nozzle_diameter_option = dynamic_cast(m_print_config.option("nozzle_diameter")); @@ -6516,10 +6551,11 @@ int CLI::run(int argc, char **argv) plate_data->nozzle_diameters = nozzle_diameter_str; for (auto it = plate_data->slice_filaments_info.begin(); it != plate_data->slice_filaments_info.end(); it++) { + // get_at() on an empty vector option is UB - these can be unpopulated on a from-scratch slice std::string display_filament_type; it->type = m_print_config.get_filament_type(display_filament_type, it->id); - it->color = filament_color ? filament_color->get_at(it->id) : "#FFFFFF"; - it->filament_id = filament_id?filament_id->get_at(it->id):""; + it->color = (filament_color && !filament_color->values.empty()) ? filament_color->get_at(it->id) : "#FFFFFF"; + it->filament_id = (filament_id && !filament_id->values.empty()) ? filament_id->get_at(it->id) : ""; } if (!plate_data->plate_thumbnail.is_valid()) { @@ -7311,6 +7347,10 @@ bool CLI::setup(int argc, char **argv) m_config.option(optdef.first, true); set_data_dir(m_config.opt_string("datadir")); + if (!data_dir().empty() && !boost::filesystem::exists(data_dir())) { + boost::nowide::cerr << "Could not create data directory: " << data_dir() << std::endl; + return false; + } //FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet. if (!validity.empty()) { @@ -7384,7 +7424,7 @@ void CLI::print_help(bool include_print_options, PrinterTechnology printer_techn << std::endl << "Print setting priorities:" << std::endl << "\t1) setting values from the command line (highest priority)"<< std::endl - << "\t2) setting values loaded with --load_settings and --load_filaments" << std::endl + << "\t2) setting values loaded with --load-settings and --load-filaments" << std::endl << "\t3) setting values loaded from 3mf(lowest priority)" << std::endl; /*if (include_print_options) { @@ -7423,6 +7463,10 @@ bool CLI::export_models(IO::ExportFormat format, std::string path_dir) for (ModelObject* model_object : model.objects) { const std::string path = this->output_filepath(*model_object, index++, format, path_dir); + if (path.empty()) { + boost::nowide::cerr << "Could not create output directory for STL export" << std::endl; + return false; + } success = Slic3r::store_stl(path.c_str(), model_object, true); if (success) BOOST_LOG_TRIVIAL(info) << "Model successfully exported to " << path << std::endl; @@ -7548,8 +7592,19 @@ std::string CLI::output_filepath(const ModelObject &object, unsigned int index, output_path = subdir + "/"+file_name; boost::filesystem::path subdir_path(subdir); - if (!boost::filesystem::exists(subdir_path)) - boost::filesystem::create_directory(subdir_path); + if (!boost::filesystem::exists(subdir_path)) { + try { + boost::filesystem::create_directories(subdir_path); + } catch (const boost::filesystem::filesystem_error &ex) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to create output directory " << subdir_path.string() << ": " << ex.what(); + } + if (!boost::filesystem::exists(subdir_path)) { + // Directory creation failed and won't succeed on a retry (same path, same cause) - + // signal failure now instead of letting every object in the model repeat the same + // doomed attempt and fail with a less specific "export failed" error later. + return std::string(); + } + } return output_path; } diff --git a/src/dev-utils/BaseException.cpp b/src/dev-utils/BaseException.cpp index efb7a98245..048eadf151 100644 --- a/src/dev-utils/BaseException.cpp +++ b/src/dev-utils/BaseException.cpp @@ -9,6 +9,7 @@ #include #include +#include "git_commit_hash.h" #include "libslic3r_version.h" static std::string g_log_folder; @@ -39,7 +40,7 @@ CBaseException::CBaseException(HANDLE hProcess, WORD wPID, LPCTSTR lpSymbolPath, output_file->open(log_filename, std::ios::out | std::ios::app); // Output app build info in crash log so we could look for the correct PDB files - OutputString(_T("%s\n\n"), _T(SLIC3R_APP_NAME " " SoftFever_VERSION " Build " GIT_COMMIT_HASH)); + OutputString(_T("%s\n\n"), _T(SLIC3R_APP_NAME " " SoftFever_VERSION " Build " GIT_COMMIT_HASH GIT_COMMIT_SUFFIX)); } } diff --git a/src/libslic3r/BuildVolume.cpp b/src/libslic3r/BuildVolume.cpp index 1ac200d9c8..15e6668bae 100644 --- a/src/libslic3r/BuildVolume.cpp +++ b/src/libslic3r/BuildVolume.cpp @@ -13,7 +13,6 @@ BuildVolume::BuildVolume(const std::vector &printable_area, const double : m_bed_shape(printable_area), m_max_print_height(printable_height), m_extruder_shapes(extruder_areas), m_extruder_printable_height(extruder_printable_heights) { assert(printable_height >= 0); - //assert(extruder_printable_heights.size() == extruder_areas.size()); m_polygon = Polygon::new_scale(printable_area); assert(m_polygon.is_counter_clockwise()); @@ -86,6 +85,9 @@ BuildVolume::BuildVolume(const std::vector &printable_area, const double m_shared_volume.data[2] = m_bboxf.max.x(); m_shared_volume.data[3] = m_bboxf.max.y(); m_shared_volume.zs[1] = m_bboxf.max.z(); + if (extruder_printable_heights.size() < m_extruder_shapes.size()) + BOOST_LOG_TRIVIAL(warning) << boost::format("extruder_printable_height has only %1% entries but extruder_printable_area has %2%, falling back to the bed printable_height for the missing ones") + % extruder_printable_heights.size() % m_extruder_shapes.size(); for (unsigned int index = 0; index < m_extruder_shapes.size(); index++) { std::vector& extruder_shape = m_extruder_shapes[index]; @@ -100,7 +102,9 @@ BuildVolume::BuildVolume(const std::vector &printable_area, const double return; } - if ((extruder_shape == printable_area)&&(extruder_printable_heights[index] == printable_height)) { + const double extruder_height = index < extruder_printable_heights.size() ? extruder_printable_heights[index] : printable_height; + + if ((extruder_shape == printable_area)&&(extruder_height == printable_height)) { extruder_volume.same_with_bed = true; extruder_volume.type = m_type; extruder_volume.bbox = m_bbox; @@ -113,7 +117,7 @@ BuildVolume::BuildVolume(const std::vector &printable_area, const double double poly_area = poly.area(); extruder_volume.bbox = get_extents(poly); BoundingBoxf temp_bboxf = get_extents(extruder_shape); - extruder_volume.bboxf = BoundingBoxf3{ to_3d(temp_bboxf.min, 0.), to_3d(temp_bboxf.max, extruder_printable_heights[index]) }; + extruder_volume.bboxf = BoundingBoxf3{ to_3d(temp_bboxf.min, 0.), to_3d(temp_bboxf.max, extruder_height) }; if (extruder_shape.size() >= 4 && std::abs((poly_area - double(extruder_volume.bbox.size().x()) * double(extruder_volume.bbox.size().y()))) < sqr(SCALED_EPSILON)) { diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 242e4bb146..394cfb5b74 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -1049,7 +1049,8 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex std::vector& different_settings = this->option("different_settings_to_system", true)->values; size_t size = different_settings.size(); if (size == 0) { - size = this->option("filament_settings_id")->values.size() + 2; + const auto *filament_ids = this->option("filament_settings_id"); + size = (filament_ids ? filament_ids->values.size() : 0) + 2; different_settings.resize(size); } @@ -1715,6 +1716,36 @@ const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key) co return (it == options.end()) ? nullptr : it->second.get(); } +// ConfigOptionBool(s)::deserialize only understands "1" and "0", but scripts commonly spell CLI +// flags as --opt=true or --opt=no. Map the usual spellings onto what deserialize() accepts, per +// comma-separated item so vector options keep working, and pass anything else through unchanged +// so a genuine typo is still reported as invalid. +static std::string normalize_cli_bool_value(const std::string &value) +{ + static const char* true_values[] = { "1", "true", "yes", "on", "enabled" }; + static const char* false_values[] = { "0", "false", "no", "off", "disabled" }; + + auto matches = [](const std::string &item, const char* const* candidates, size_t count) { + return std::any_of(candidates, candidates + count, [&item](const char* candidate) { return boost::iequals(item, candidate); }); + }; + + std::string normalized; + std::istringstream is(value); + std::string item; + while (std::getline(is, item, ',')) { + boost::trim(item); + if (! normalized.empty()) + normalized += ","; + if (matches(item, true_values, std::size(true_values))) + normalized += "1"; + else if (matches(item, false_values, std::size(false_values))) + normalized += "0"; + else + normalized += item; + } + return normalized; +} + bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys) { // cache the CLI option => opt_key mapping @@ -1812,17 +1843,32 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option // to the end of the value. if (opt_base->type() == coBools && value.empty()) static_cast(opt_base)->values.push_back(!no); - else + else { // Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way // they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape // will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be // unescaped by the calling shell. - opt_vector->deserialize(value, true); + const std::string vector_value = opt_base->type() == coBools ? normalize_cli_bool_value(value) : value; + bool deserialized = false; + try { + deserialized = opt_vector->deserialize(vector_value, true); + } catch (const std::exception &ex) { + // e.g. "nil" deserialized into a non-nullable vector option throws instead of + // returning false - treat that the same as any other invalid value here. + deserialized = false; + } + if (! deserialized) { + boost::nowide::cerr << "Invalid value for option --" << token.c_str() << std::endl; + return false; + } + } } else if (opt_base->type() == coBool) { if (value.empty()) static_cast(opt_base)->value = !no; - else - opt_base->deserialize(value); + else if (! opt_base->deserialize(normalize_cli_bool_value(value))) { + boost::nowide::cerr << "Invalid value for option --" << token.c_str() << std::endl; + return false; + } } else if (opt_base->type() == coString) { // Do not unescape single string values, the unescaping is left to the calling shell. static_cast(opt_base)->value = value; diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp index 2f378f5778..0374c5d4aa 100644 --- a/src/libslic3r/Measure.hpp +++ b/src/libslic3r/Measure.hpp @@ -33,15 +33,6 @@ public: SurfaceFeature(const Vec3d& pt) : m_type{SurfaceFeatureType::Point}, m_pt1{pt} {} - SurfaceFeature(const SurfaceFeature& sf){ - this->clone(sf); - volume = sf.volume; - plane_indices = sf.plane_indices; - world_tran = sf.world_tran; - world_plane_features = sf.world_plane_features; - origin_surface_feature = sf.origin_surface_feature; - } - void clone(const SurfaceFeature &sf) { m_type = sf.get_type(); diff --git a/src/libslic3r/Orient.cpp b/src/libslic3r/Orient.cpp index ae1954087d..7d54048176 100644 --- a/src/libslic3r/Orient.cpp +++ b/src/libslic3r/Orient.cpp @@ -39,7 +39,6 @@ namespace orientation { float height_to_bottom_hull_ratio = 0; // affects stability, the lower the better float unprintability = 0; Eigen::VectorXf areas_cooling; - CostItems(CostItems const & other) = default; CostItems() = default; static std::string field_names() { return " overhang, bottom, bothull, contour, A_laf, A_prj, unprintability"; diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 9037118f0c..a6b38889a5 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -2127,7 +2127,7 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim bridgeable_filtered = union_ex(offset_ex(remaining, perimeter_spacing), bridgeable_filtered); bridgeable_filtered = offset_ex(bridgeable_filtered, -perimeter_spacing); bridgeable_filtered = diff_ex(bridgeable_filtered, remaining, ApplySafetyOffset::Yes); - bridgeable_filtered = opening_ex(bridgeable_filtered, perimeter_spacing); // filter noise from the diff_ex + bridgeable_filtered = opening_ex(bridgeable_filtered, ext_perimeter_width / 2); // filter noise from the diff_ex bridgeable_filtered = offset_ex(bridgeable_filtered, perimeter_spacing); // restore the size to the original bridgeable area // Safety measure: Keep the bridge mask from intruding deeper into the // supported anchor region than the explicit anchor overlap. diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 039f361eaa..21e5355fc5 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -195,7 +195,6 @@ public: Point(int64_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {} Point(int32_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} Point(double x, double y) : Vec2crd(coord_t(std::round(x)), coord_t(std::round(y))) {} - Point(const Point &rhs) { *this = rhs; } explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {} // This constructor allows you to construct Point from Eigen expressions // This constructor has to be implicit (non-explicit) to allow implicit conversion from Eigen expressions. @@ -278,7 +277,6 @@ public: Point3(int32_t x, int32_t y, int32_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {} Point3(int64_t x, int64_t y, int64_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {} Point3(double x, double y, double z = 0.0) : Vec3crd(coord_t(std::round(x)), coord_t(std::round(y)), coord_t(std::round(z))) {} - Point3(const Point3 &rhs) { *this = rhs; } explicit Point3(const Vec2crd& vec2crd, coord_t z = 0) : Vec3crd(vec2crd.x(), vec2crd.y(), z) {} explicit Point3(const Vec3crd &vec3crd) : Vec3crd(vec3crd) {} // This constructor allows you to construct Point from Eigen expressions diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 304c8957d5..235ea66ad2 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -983,15 +983,19 @@ BedType Preset::get_default_bed_type(PresetBundle* preset_bundle) if (config.has("default_bed_type") && !config.opt_string("default_bed_type").empty()) { try { std::string str_bed_type = config.opt_string("default_bed_type"); - - // Try parsing as integer first (legacy format) + BedType bed_type; + if (ConfigOptionEnum::from_string(str_bed_type, bed_type) && + bed_type > btDefault && bed_type < btCount) { + return bed_type; + } + + // Try parsing as integer (legacy format) int bed_type_value = atoi(str_bed_type.c_str()); - if (bed_type_value > 0) { + if (bed_type_value > 0 && bed_type_value < BedType::btCount) { return BedType(bed_type_value); } - else { - BOOST_LOG_TRIVIAL(error) << "default_bed_type: invalid bed type: " << str_bed_type; - } + + BOOST_LOG_TRIVIAL(error) << "default_bed_type: invalid bed type: " << str_bed_type; return BedType::btPEI; } catch(...) { diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index c9b3197a6f..22089be0b6 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -840,13 +840,12 @@ public: protected: PresetCollection() = default; - // Copy constructor and copy operators are not to be used from outside PresetBundle, - // as the Profile::vendor points to an instance of VendorProfile stored at parent PresetBundle! - PresetCollection(const PresetCollection &other) = default; - //BBS: add operator= logic insteadof default + // Deleted by the std::recursive_mutex member. PresetBundle copies by assignment. + PresetCollection(const PresetCollection &other) = delete; + //BBS: hand-written because m_mutex cannot be copy-assigned. PresetCollection& operator=(const PresetCollection &other); - // After copying a collection with the default operators above, call this function - // to adjust Profile::vendor pointers. + // Copying leaves every Preset::vendor pointing into the source bundle's vendor map. + // This re-points them at the matching entries in vendors. void update_vendor_ptrs_after_copy(const VendorMap &vendors); // Select a preset, if it exists. If it does not exist, select an invalid (-1) index. @@ -984,7 +983,8 @@ public: bool only_default_printers() const; private: PrinterPresetCollection() = default; - PrinterPresetCollection(const PrinterPresetCollection &other) = default; + // Deleted along with the base copy constructor. + PrinterPresetCollection(const PrinterPresetCollection &other) = delete; PrinterPresetCollection& operator=(const PrinterPresetCollection &other) = default; friend class PresetBundle; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 8e5d565c55..2dfc967e3f 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2920,6 +2920,16 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p // If executed due to a Config Wizard update, preferred_printer contains the first newly installed printer, otherwise nullptr. const Preset *preferred_printer = printers.find_system_preset_by_model_and_variant(preferred_selection.printer_model_id, preferred_selection.printer_variant); printers.select_preset_by_name(preferred_printer ? preferred_printer->name : initial_printer_profile_name, true); + Preset &selected_printer = printers.get_edited_preset(); + if (selected_printer.printer_technology() == ptFFF) { + BedType bed_type = selected_printer.get_default_bed_type(this); + const std::string saved_bed_type = config.get_printer_setting(selected_printer.name, "curr_bed_type"); + const int saved_bed_type_value = atoi(saved_bed_type.c_str()); + if (saved_bed_type_value > btDefault && saved_bed_type_value < btCount) + bed_type = static_cast(saved_bed_type_value); + project_config.set_key_value("curr_bed_type", new ConfigOptionEnum(bed_type)); + config.set("curr_bed_type", std::to_string(static_cast(bed_type))); + } CNumericLocalesSetter locales_setter; // Orca: load from orca_presets diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 29362fcf39..100089fd24 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -9865,7 +9865,15 @@ std::string DynamicPrintConfig::get_filament_type(std::string &displayed_filamen auto* filament_type = dynamic_cast(this->option("filament_type")); auto* filament_is_support = dynamic_cast(this->option("filament_is_support")); - if (!filament_type) + // get_at() on an empty vector option is undefined behavior (.front() of an empty vector), + // and e.g. filament_id is never populated on a CLI from-scratch slice - treat an empty + // option the same as a missing one. + if (filament_id && filament_id->values.empty()) + filament_id = nullptr; + if (filament_is_support && filament_is_support->values.empty()) + filament_is_support = nullptr; + + if (!filament_type || filament_type->values.empty()) return ""; if (!filament_is_support) { @@ -11990,13 +11998,11 @@ CLIActionsConfigDef::CLIActionsConfigDef() def = this->add("load_defaultfila", coBool); def->label = L("Load default filaments"); def->tooltip = L("Load first filament as default for those not loaded."); - def->cli_params = "option"; def->set_default_value(new ConfigOptionBool(false)); def = this->add("min_save", coBool); def->label = L("Minimum save"); def->tooltip = L("Export 3MF with minimum size."); - def->cli_params = "option"; def->set_default_value(new ConfigOptionBool(false)); def = this->add("mtcpp", coInt); @@ -12022,7 +12028,6 @@ CLIActionsConfigDef::CLIActionsConfigDef() def = this->add("normative_check", coBool); def->label = L("Normative check"); def->tooltip = L("Check the normative items."); - def->cli_params = "option"; def->set_default_value(new ConfigOptionBool(true)); /*def = this->add("help_fff", coBool); @@ -12289,7 +12294,7 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->cli_params = "level"; def->set_default_value(new ConfigOptionInt(1)); - def = this->add("logfile", coInt); + def = this->add("logfile", coString); def->label = L("Log file"); def->tooltip = L("Redirects debug logging to file.\n"); def->cli_params = "file"; @@ -12337,7 +12342,6 @@ CLIMiscConfigDef::CLIMiscConfigDef() def = this->add("skip_modified_gcodes", coBool); def->label = L("Skip modified G-code in 3MF"); def->tooltip = L("Skip the modified G-code in 3MF from printer or filament presets."); - def->cli_params = "option"; def->set_default_value(new ConfigOptionBool(false)); def = this->add("makerlab_name", coString); @@ -12367,14 +12371,12 @@ CLIMiscConfigDef::CLIMiscConfigDef() def = this->add("allow_newer_file", coBool); def->label = L("Allow 3MF with newer version to be sliced"); def->tooltip = L("Allow 3MF with newer version to be sliced."); - def->cli_params = "option"; def->set_default_value(new ConfigOptionBool(false)); def = this->add("allow_mix_temp", coBool); // internal use only, don't need translation def->label = "Allow filaments with high/low temperature to be printed together"; def->tooltip = "Allow filaments with high/low temperature to be printed together."; - def->cli_params = "option"; def->set_default_value(new ConfigOptionBool(false)); } diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index b57513fe72..f6beaadb24 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -44,9 +44,6 @@ struct DrainHole : pos(p), normal(n), radius(r), height(h), failed(fl) {} - DrainHole(const DrainHole& rhs) : - DrainHole(rhs.pos, rhs.normal, rhs.radius, rhs.height, rhs.failed) {} - bool operator==(const DrainHole &sp) const; bool operator!=(const DrainHole &sp) const { return !(sp == (*this)); } diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 0c7a4b832e..e734608803 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -65,11 +65,13 @@ std::pair generate_interfa const bool smooth_supports = support_params.support_style != smsGrid; SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first; SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second; - // The user-facing interface layer counts include the contact layer. Internally, - // contact layers are generated separately, so only the remaining layers are - // projected into intermediate interface/base-interface layers here. - const size_t num_top_interface_layers = support_params.has_top_contacts ? support_params.num_top_interface_layers - 1 : 0; - const size_t num_bottom_interface_layers = support_params.has_bottom_contacts ? support_params.num_bottom_interface_layers - 1 : 0; + // Contacts printed separately consume one requested interface layer. Organic + // bottom contacts are projection seeds and are not printed separately. + const bool organic_tree = support_params.support_style == smsTreeOrganic; + const size_t num_top_interface_layers = support_params.has_top_contacts ? + support_params.num_top_interface_layers - 1 : 0; + const size_t num_bottom_interface_layers = support_params.has_bottom_contacts ? + support_params.num_bottom_interface_layers - (organic_tree ? 0 : 1) : 0; const size_t num_top_base_interface_layers = std::min(support_params.num_top_base_interface_layers, num_top_interface_layers); const size_t num_bottom_base_interface_layers = std::min(support_params.num_bottom_base_interface_layers, num_bottom_interface_layers); const size_t num_top_interface_layers_only = num_top_interface_layers - num_top_base_interface_layers; @@ -1234,10 +1236,6 @@ static void modulate_extrusion_by_overlapping_layers( (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); } private: - ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) { - return *this; - } - const std::vector &m_path_fragments; }; const coord_t search_radius = 7; @@ -1656,28 +1654,32 @@ void generate_support_toolpaths( if (top_contact_layer.could_merge(interface_layer) && ! raft_layer) top_contact_layer.merge(std::move(interface_layer)); } - if (!bottom_interfaces && support_params.can_merge_support_regions) { - if (base_layer.could_merge(bottom_contact_layer)) - base_layer.merge(std::move(bottom_contact_layer)); - else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) - base_layer = std::move(bottom_contact_layer); - } else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) { - if (top_interfaces && bottom_interfaces) { - top_contact_layer.merge(std::move(bottom_contact_layer)); - } else if (bottom_interfaces) { - top_contact_layer.set_polygons_to_extrude( - diff(top_contact_layer.polygons_to_extrude(), bottom_contact_layer.polygons_to_extrude())); - } else { - bottom_contact_layer.set_polygons_to_extrude( - diff(bottom_contact_layer.polygons_to_extrude(), top_contact_layer.polygons_to_extrude())); - } - } else if (bottom_contact_layer.could_merge(interface_layer) && ! organic_tree) { - const bool interface_layer_is_bottom = interface_layer.layer->layer_type == SupporLayerType::BottomInterface; - if (bottom_interfaces && interface_layer_is_bottom) { - bottom_contact_layer.merge(std::move(interface_layer)); - } else { - bottom_contact_layer.set_polygons_to_extrude( - diff(bottom_contact_layer.polygons_to_extrude(), interface_layer.polygons_to_extrude())); + // Orca: Organic bottom contacts are projection seeds, not same-layer toolpaths. + // Do not merge them into another same-layer support region. + if (!organic_tree) { + if (!bottom_interfaces && support_params.can_merge_support_regions) { + if (base_layer.could_merge(bottom_contact_layer)) + base_layer.merge(std::move(bottom_contact_layer)); + else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) + base_layer = std::move(bottom_contact_layer); + } else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) { + if (top_interfaces && bottom_interfaces) { + top_contact_layer.merge(std::move(bottom_contact_layer)); + } else if (bottom_interfaces) { + top_contact_layer.set_polygons_to_extrude( + diff(top_contact_layer.polygons_to_extrude(), bottom_contact_layer.polygons_to_extrude())); + } else { + bottom_contact_layer.set_polygons_to_extrude( + diff(bottom_contact_layer.polygons_to_extrude(), top_contact_layer.polygons_to_extrude())); + } + } else if (bottom_contact_layer.could_merge(interface_layer)) { + const bool interface_layer_is_bottom = interface_layer.layer->layer_type == SupporLayerType::BottomInterface; + if (bottom_interfaces && interface_layer_is_bottom) { + bottom_contact_layer.merge(std::move(interface_layer)); + } else { + bottom_contact_layer.set_polygons_to_extrude( + diff(bottom_contact_layer.polygons_to_extrude(), interface_layer.polygons_to_extrude())); + } } } diff --git a/src/libslic3r/Support/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp index 7a769c77d1..70c33aab0d 100644 --- a/src/libslic3r/Support/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -32,7 +32,7 @@ namespace Slic3r::TreeSupport3D using namespace std::literals; // or warning -// had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL() +// had to use a define because the macro processing inside macro BOOST_LOG_TRIVIAL() #define error_level_not_in_cache debug //FIXME Machine border is currently ignored. diff --git a/src/libslic3r/Support/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp index e0446ad5f1..61e030ef86 100644 --- a/src/libslic3r/Support/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -204,8 +204,9 @@ public: clear_nodes(); } - TreeSupportData(TreeSupportData&&) = default; - TreeSupportData& operator=(TreeSupportData&&) = default; + // Deleted by the tbb::spin_mutex member. + TreeSupportData(TreeSupportData&&) = delete; + TreeSupportData& operator=(TreeSupportData&&) = delete; TreeSupportData(const TreeSupportData&) = delete; TreeSupportData& operator=(const TreeSupportData&) = delete; diff --git a/src/libslic3r/calib.hpp b/src/libslic3r/calib.hpp index ed0f76ee86..abca5e79dc 100644 --- a/src/libslic3r/calib.hpp +++ b/src/libslic3r/calib.hpp @@ -91,29 +91,15 @@ class CaliPresetInfo { public: int tray_id; - int extruder_id; - NozzleVolumeType nozzle_volume_type; - BedType bed_type; + int extruder_id = 0; + NozzleVolumeType nozzle_volume_type{nvtStandard}; + BedType bed_type{btDefault}; float nozzle_diameter; int nozzle_pos_id{-1}; std::string nozzle_sn; std::string filament_id; std::string setting_id; std::string name; - - CaliPresetInfo &operator=(const CaliPresetInfo &other) - { - this->tray_id = other.tray_id; - this->extruder_id = other.extruder_id; - this->nozzle_volume_type = other.nozzle_volume_type; - this->nozzle_diameter = other.nozzle_diameter; - this->nozzle_pos_id = other.nozzle_pos_id; - this->nozzle_sn = other.nozzle_sn; - this->filament_id = other.filament_id; - this->setting_id = other.setting_id; - this->name = other.name; - return *this; - } }; struct PrinterCaliInfo diff --git a/src/libslic3r/libslic3r_version.h.in b/src/libslic3r/libslic3r_version.h.in index 750e092d28..df83e813c9 100644 --- a/src/libslic3r/libslic3r_version.h.in +++ b/src/libslic3r/libslic3r_version.h.in @@ -5,9 +5,6 @@ #define SLIC3R_APP_KEY "@SLIC3R_APP_KEY@" #define SLIC3R_VERSION "@SLIC3R_VERSION@" #define SoftFever_VERSION "@SoftFever_VERSION@" -#ifndef GIT_COMMIT_HASH - #define GIT_COMMIT_HASH "0000000" // 0000000 means uninitialized -#endif #define SLIC3R_BUILD_ID "@SLIC3R_BUILD_ID@" //#define SLIC3R_RC_VERSION "@SLIC3R_VERSION@" #define BBL_INTERNAL_TESTING @BBL_INTERNAL_TESTING@ diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 58ec8318a6..5f4baac951 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -310,7 +310,11 @@ void set_data_dir(const std::string &dir) { g_data_dir = dir; if (!g_data_dir.empty() && !boost::filesystem::exists(g_data_dir)) { - boost::filesystem::create_directory(g_data_dir); + try { + boost::filesystem::create_directories(g_data_dir); + } catch (const boost::filesystem::filesystem_error &ex) { + BOOST_LOG_TRIVIAL(error) << "set_data_dir: failed to create data directory " << g_data_dir << ": " << ex.what(); + } } } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 140b1cec39..1e39e30b0d 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -63,6 +63,8 @@ set(SLIC3R_GUI_SOURCES GUI/BitmapComboBox.hpp GUI/BonjourDialog.cpp GUI/BonjourDialog.hpp + GUI/BuildCommit.cpp + GUI/BuildCommit.hpp GUI/CrealityDiscoveryDialog.cpp GUI/CrealityDiscoveryDialog.hpp GUI/calib_dlg.cpp @@ -845,6 +847,18 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES}) encoding_check(libslic3r_gui) +# Only BuildCommit.cpp includes the generated header, plus BaseException.cpp on +# Windows. Both build into libslic3r_gui, so the header only has to exist before +# that target builds. +set(_git_commit_hash_header "${CMAKE_CURRENT_BINARY_DIR}/git_commit_hash.h") +add_custom_target(git_commit_hash_header + BYPRODUCTS "${_git_commit_hash_header}" + COMMAND ${CMAKE_COMMAND} + "-DSOURCE_DIR=${CMAKE_SOURCE_DIR}" + "-DOUT_FILE=${_git_commit_hash_header}" + -P "${CMAKE_CURRENT_LIST_DIR}/GitCommitHash.cmake" + COMMENT "Resolving the git commit hash") +add_dependencies(libslic3r_gui git_commit_hash_header) if(APPLE AND CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") set(_opengl_link_lib "") diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index a4f0f06d65..9e1cefbc84 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -3,6 +3,7 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Color.hpp" +#include "BuildCommit.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" @@ -245,7 +246,7 @@ AboutDialog::AboutDialog() vesizer->Add(0, 0, 1, wxEXPAND, FromDIP(5)); auto version_string = std::string(SoftFever_VERSION); // _L("Orca Slicer ") + " " + std::string(SoftFever_VERSION); wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize); - wxStaticText* credits_string = new wxStaticText(this, wxID_ANY, wxString::Format("Build %s", std::string(GIT_COMMIT_HASH)), wxDefaultPosition, wxDefaultSize); + wxStaticText* credits_string = new wxStaticText(this, wxID_ANY, wxString::Format("Build %s", build_commit_label), wxDefaultPosition, wxDefaultSize); credits_string->SetFont(_build_string_font); wxFont version_font = GetFont(); version_font = version_font.Scaled(1.85f); // SetPointSize(20) not works on macOS because it uses a 72 PPI reference diff --git a/src/slic3r/GUI/BuildCommit.cpp b/src/slic3r/GUI/BuildCommit.cpp new file mode 100644 index 0000000000..adfe66af68 --- /dev/null +++ b/src/slic3r/GUI/BuildCommit.cpp @@ -0,0 +1,9 @@ +#include "BuildCommit.hpp" +#include "git_commit_hash.h" + +namespace Slic3r { namespace GUI { + +const char *const build_commit_hash = GIT_COMMIT_HASH; +const char *const build_commit_label = GIT_COMMIT_HASH GIT_COMMIT_SUFFIX; + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/BuildCommit.hpp b/src/slic3r/GUI/BuildCommit.hpp new file mode 100644 index 0000000000..0450e710c9 --- /dev/null +++ b/src/slic3r/GUI/BuildCommit.hpp @@ -0,0 +1,15 @@ +#pragma once + +// Read these rather than including git_commit_hash.h, which changes with every +// commit and rebuilds everything that includes it. + +namespace Slic3r { namespace GUI { + +// The commit alone, safe to use in a commit URL. +extern const char *const build_commit_hash; + +// The same, with "-dirty" when the build had uncommitted changes. Use this +// wherever the build is shown to a person. +extern const char *const build_commit_label; + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index d661b012a3..5b80a866ca 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -9,6 +9,7 @@ #include "slic3r/GUI/TaskManager.hpp" #include "format.hpp" #include "libslic3r_version.h" +#include "BuildCommit.hpp" #include "Downloader.hpp" #include #include @@ -2580,7 +2581,7 @@ void GUI_App::init_app_config() set_log_path_and_level(log_filename, 3); #endif - BOOST_LOG_TRIVIAL(info) << boost::format("gui mode, Current OrcaSlicer Version %1% build %2%") % SoftFever_VERSION % GIT_COMMIT_HASH; + BOOST_LOG_TRIVIAL(info) << boost::format("gui mode, Current OrcaSlicer Version %1% build %2%") % SoftFever_VERSION % build_commit_label; //BBS: remove GCodeViewer as seperate APP logic if (!app_config) diff --git a/src/slic3r/GUI/MediaPlayCtrl.cpp b/src/slic3r/GUI/MediaPlayCtrl.cpp index 71fbcb054b..0d75c3770d 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.cpp +++ b/src/slic3r/GUI/MediaPlayCtrl.cpp @@ -494,13 +494,13 @@ void MediaPlayCtrl::ToggleStream() DownloadProgressDialog2(MediaPlayCtrl *ctrl) : DownloadProgressDialog(_L("Downloading Virtual Camera Tools")), ctrl(ctrl) {} struct UpgradeNetworkJob2 : UpgradeNetworkJob { - UpgradeNetworkJob2(std::shared_ptr pri) : UpgradeNetworkJob() { + UpgradeNetworkJob2() { name = "cameratools"; package_name = "camera_tools.zip"; } }; - std::shared_ptr make_job(std::shared_ptr pri) - { return std::make_shared(pri); } + std::unique_ptr make_job() override + { return std::make_unique(); } void on_finish() override { ctrl->CallAfter([ctrl = this->ctrl] { ctrl->ToggleStream(); }); diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 59a857816e..849aa4e31a 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -157,7 +157,7 @@ PartPlate::PartPlate() init(); } -PartPlate::PartPlate(PartPlateList *partplate_list, Vec3d origin, int width, int depth, int height, Plater* platerObj, Model* modelObj, bool printable, PrinterTechnology tech) +PartPlate::PartPlate(PartPlateList *partplate_list, Vec3d origin, int width, int depth, double height, Plater* platerObj, Model* modelObj, bool printable, PrinterTechnology tech) :m_partplate_list(partplate_list), m_plater(platerObj), m_model(modelObj), printer_technology(tech), m_origin(origin), m_width(width), m_depth(depth), m_height(height), m_printable(printable) { init(); @@ -1757,26 +1757,25 @@ std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D else obj_support = glb_support; - if (!obj_support) - continue; + if (obj_support) { + int obj_support_intf_extr = 0; + const ConfigOption* support_intf_extr_opt = object->config.option("support_interface_filament"); + if (support_intf_extr_opt != nullptr) + obj_support_intf_extr = support_intf_extr_opt->getInt(); + if (obj_support_intf_extr != 0) + plate_extruders.push_back(obj_support_intf_extr); + else if (glb_support_intf_extr != 0) + plate_extruders.push_back(glb_support_intf_extr); - int obj_support_intf_extr = 0; - const ConfigOption* support_intf_extr_opt = object->config.option("support_interface_filament"); - if (support_intf_extr_opt != nullptr) - obj_support_intf_extr = support_intf_extr_opt->getInt(); - if (obj_support_intf_extr != 0) - plate_extruders.push_back(obj_support_intf_extr); - else if (glb_support_intf_extr != 0) - plate_extruders.push_back(glb_support_intf_extr); - - int obj_support_extr = 0; - const ConfigOption* support_extr_opt = object->config.option("support_filament"); - if (support_extr_opt != nullptr) - obj_support_extr = support_extr_opt->getInt(); - if (obj_support_extr != 0) - plate_extruders.push_back(obj_support_extr); - else if (glb_support_extr != 0) - plate_extruders.push_back(glb_support_extr); + int obj_support_extr = 0; + const ConfigOption* support_extr_opt = object->config.option("support_filament"); + if (support_extr_opt != nullptr) + obj_support_extr = support_extr_opt->getInt(); + if (obj_support_extr != 0) + plate_extruders.push_back(obj_support_extr); + else if (glb_support_extr != 0) + plate_extruders.push_back(glb_support_extr); + } int obj_outer_wall_extr = 0; if (const ConfigOption* wall_opt = object->config.option("outer_wall_filament_id"); wall_opt != nullptr) @@ -2473,7 +2472,7 @@ void PartPlate::clear(bool clear_sliced_result) /* size and position related functions*/ //set position and size -void PartPlate::set_pos_and_size(Vec3d& origin, int width, int depth, int height, bool with_instance_move, bool do_clear) +void PartPlate::set_pos_and_size(Vec3d& origin, int width, int depth, double height, bool with_instance_move, bool do_clear) { bool size_changed = false; //size changed means the machine changed bool pos_changed = false; @@ -2772,10 +2771,10 @@ bool PartPlate::check_outside(int obj_id, int instance_id, BoundingBoxf3* boundi if (instance_box.min.z() < SINKING_Z_THRESHOLD) { // Orca: For sinking object, we use a more expensive algorithm so part below build plate won't be considered - // m_plater is null in CLI mode. - if (m_plater && plate_box.intersects(instance_box)) { + // m_height mirrors the printer's printable height and is set in CLI mode too, unlike m_plater. + if (plate_box.intersects(instance_box)) { // TODO: FIXME: this does not take exclusion area into account - const BuildVolume build_volume(get_shape(), m_plater->build_volume().printable_height(), m_extruder_areas, m_extruder_heights); + const BuildVolume build_volume(get_shape(), m_height, m_extruder_areas, m_extruder_heights); const auto state = instance->calc_print_volume_state(build_volume); outside = state == ModelInstancePVS_Partly_Outside; } @@ -4099,7 +4098,7 @@ void PartPlate::on_filament_deleted(int filament_count, int filament_id) /* PartPlate List related functions*/ -PartPlateList::PartPlateList(int width, int depth, int height, Plater* platerObj, Model* modelObj, PrinterTechnology tech) +PartPlateList::PartPlateList(int width, int depth, double height, Plater* platerObj, Model* modelObj, PrinterTechnology tech) :m_plate_width(width), m_plate_depth(depth), m_plate_height(height), m_plater(platerObj), m_model(modelObj), printer_technology(tech), unprintable_plate(this, Vec3d(0.0 + width * (1. + LOGICAL_PART_PLATE_GAP), 0.0, 0.0), width, depth, height, platerObj, modelObj, false, tech) { @@ -4544,7 +4543,7 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini } //this may be happened after machine changed -void PartPlateList::reset_size(int width, int depth, int height, bool reload_objects, bool update_shapes) +void PartPlateList::reset_size(int width, int depth, double height, bool reload_objects, bool update_shapes) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":before size: plate_width %1%, plate_depth %2%, plate_height %3%") % m_plate_width % m_plate_depth % m_plate_height; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":after size: plate_width %1%, plate_depth %2%, plate_height %3%") % width % depth % height; @@ -6699,7 +6698,7 @@ int PartPlateList::load_gcode_files() //BoundingBoxf3 print_volume = m_plate_list[i]->get_bounding_box(false); //print_volume.max(2) = this->m_plate_height; //print_volume.min(2) = -1e10; - m_model->update_print_volume_state({m_plate_list[i]->get_shape(), (double)this->m_plate_height, m_plate_list[i]->get_extruder_areas(), m_plate_list[i]->get_extruder_heights() }); + m_model->update_print_volume_state({m_plate_list[i]->get_shape(), this->m_plate_height, m_plate_list[i]->get_extruder_areas(), m_plate_list[i]->get_extruder_heights() }); if (!m_plate_list[i]->load_gcode_from_file(m_plate_list[i]->m_gcode_path_from_3mf)) ret ++; diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index 5760320b49..8ad2f4a7d1 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -96,7 +96,7 @@ private: Vec3d m_origin; int m_width; int m_depth; - int m_height; + double m_height; float m_height_to_lid; float m_height_to_rod; bool m_printable; @@ -227,7 +227,7 @@ public: static void load_render_colors(); PartPlate(); - PartPlate(PartPlateList *partplate_list, Vec3d origin, int width, int depth, int height, Plater* platerObj, Model* modelObj, bool printable=true, PrinterTechnology tech = ptFFF); + PartPlate(PartPlateList *partplate_list, Vec3d origin, int width, int depth, double height, Plater* platerObj, Model* modelObj, bool printable=true, PrinterTechnology tech = ptFFF); ~PartPlate(); bool operator<(PartPlate&) const; @@ -328,7 +328,7 @@ public: Vec3d get_center_origin(); /* size and position related functions*/ //set position and size - void set_pos_and_size(Vec3d& origin, int width, int depth, int height, bool with_instance_move, bool do_clear = true); + void set_pos_and_size(Vec3d& origin, int width, int depth, double height, bool with_instance_move, bool do_clear = true); // BBS Vec2d get_size() const { return Vec2d(m_width, m_depth); } @@ -590,7 +590,7 @@ class PartPlateList : public ObjectBase int m_plate_width; int m_plate_depth; - int m_plate_height; + double m_plate_height; float m_height_to_lid; float m_height_to_rod; @@ -675,16 +675,6 @@ public: offset = Vec2d(0, 0); } - TexturePart(const TexturePart& part) { - this->x = part.x; - this->y = part.y; - this->w = part.w; - this->h = part.h; - this->offset = part.offset; - this->buffer = part.buffer; - this->filename = part.filename; - this->texture = part.texture; - } void update_pos(float xx, float yy, float ww, float hh) { x = xx; y = yy; @@ -708,12 +698,12 @@ public: static bool is_load_cali_texture; static bool is_load_extruder_only_area_textures; - PartPlateList(int width, int depth, int height, Plater* platerObj, Model* modelObj, PrinterTechnology tech = ptFFF); + PartPlateList(int width, int depth, double height, Plater* platerObj, Model* modelObj, PrinterTechnology tech = ptFFF); PartPlateList(Plater* platerObj, Model* modelObj, PrinterTechnology tech = ptFFF); ~PartPlateList(); //this may be happened after machine changed - void reset_size(int width, int depth, int height, bool reload_objects = true, bool update_shapes = false); + void reset_size(int width, int depth, double height, bool reload_objects = true, bool update_shapes = false); //clear all the instances in the plate, but keep the plates void clear(bool delete_plates = false, bool release_print_list = false, bool except_locked = false, int plate_index = -1); //clear all the instances in the plate, and delete the plates, only keep the first default plate @@ -727,7 +717,7 @@ public: //get the plate stride double plate_stride_x(); double plate_stride_y(); - void get_plate_size(int& width, int& depth, int& height) { + void get_plate_size(int& width, int& depth, double& height) { width = m_plate_width; depth = m_plate_depth; height = m_plate_height; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0347803dbc..90206a438c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3988,16 +3988,16 @@ void Sidebar::update_mixed_filament_list() p->m_panel_mixed_warning->Show(false); // Show/dismiss 3D canvas notification for broken mixed filaments - if (has_mixed && !broken_set.empty()) { - auto* notify = wxGetApp().plater()->get_notification_manager(); - if (notify) + auto* notify = plater->get_notification_manager(); + GLCanvas3D* view3d_canvas = plater->get_view3D_canvas3D(); + if(view3d_canvas && view3d_canvas->is_initialized() && notify){ + if (has_mixed && !broken_set.empty()) { notify->push_notification(NotificationType::BBLMixedFilamentBroken, NotificationManager::NotificationLevel::ErrorNotificationLevel, _u8L("Mixed filament has invalid or mismatched components. Please re-edit affected entries.")); - } else { - auto* notify = wxGetApp().plater()->get_notification_manager(); - if (notify) + } else { notify->close_notification_of_type(NotificationType::BBLMixedFilamentBroken); + } } if (has_mixed) { @@ -8278,7 +8278,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ bool dlg_cont = true; bool is_user_cancel = false; bool translate_old = false; - int current_width = 0, current_depth = 0, current_height = 0, project_filament_count = 1; + int current_width = 0, current_depth = 0, project_filament_count = 1; + double current_height = 0; if (input_files.empty()) return std::vector(); @@ -16267,6 +16268,7 @@ void Plater::calib_retraction(const Calib_Params& params) obj->config.set_key_value("wall_sequence", new ConfigOptionEnum(WallSequence::InnerOuter)); obj->config.set_key_value("overhang_reverse", new ConfigOptionBool(false)); obj->config.set_key_value("precise_z_height", new ConfigOptionBool(false)); + obj->config.set_key_value("seam_slope_type", new ConfigOptionEnum(SeamScarfType::None)); changed_objects({ 0 }); diff --git a/src/slic3r/GUI/SavePresetDialog.cpp b/src/slic3r/GUI/SavePresetDialog.cpp index b3f34a8cb9..65b75c3d92 100644 --- a/src/slic3r/GUI/SavePresetDialog.cpp +++ b/src/slic3r/GUI/SavePresetDialog.cpp @@ -149,7 +149,10 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string &suffix, wxBox // Set initial state (unchecked by default) detach_checkbox->SetValue(m_detach); // Bind the checkbox event to update the detach state for this item - detach_checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, detach_checkbox](wxCommandEvent&) { m_detach = detach_checkbox->GetValue(); }); + detach_checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, detach_checkbox](wxCommandEvent& event) { + m_detach = detach_checkbox->GetValue(); + event.Skip(); // Let CheckBox update its bitmap for the new state. + }); detach_label->SetForegroundColour(wxColour("#363636")); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 644c5258a1..e650c45126 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -5130,7 +5130,7 @@ void TabPrinter::build_fff() optgroup->append_single_option_line("adaptive_bed_mesh_margin", "printer_basic_information_adaptive_bed_mesh#mesh-margin"); optgroup = page->new_optgroup(L("Accessory"), "param_accessory"); - optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type"); + optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type", 0); optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc"); optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan"); optgroup->append_single_option_line("fan_direction"); diff --git a/src/slic3r/GUI/TroubleshootDialog.cpp b/src/slic3r/GUI/TroubleshootDialog.cpp index a8c36eb975..5acb75c6ea 100644 --- a/src/slic3r/GUI/TroubleshootDialog.cpp +++ b/src/slic3r/GUI/TroubleshootDialog.cpp @@ -1,6 +1,7 @@ #include "TroubleshootDialog.hpp" #include "I18N.hpp" +#include "BuildCommit.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" @@ -137,9 +138,9 @@ TroubleshootDialog::TroubleshootDialog() version->SetFont(version_font); version->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636"))); - auto build = new Button(this, wxString(GIT_COMMIT_HASH)); + auto build = new Button(this, wxString(build_commit_label)); build->SetStyle(ButtonStyle::Regular, ButtonType::Window); - auto hash_url = "https://github.com/OrcaSlicer/OrcaSlicer/commit/" + wxString(GIT_COMMIT_HASH); + auto hash_url = "https://github.com/OrcaSlicer/OrcaSlicer/commit/" + wxString(build_commit_hash); build->SetToolTip(hash_url); build->Bind(wxEVT_BUTTON, [hash_url](wxCommandEvent &e) { wxLaunchDefaultBrowser(hash_url); @@ -371,7 +372,7 @@ wxString TroubleshootDialog::GetSysInfoAll() { wxString info; info += "Version : " + wxString(SoftFever_VERSION) + "\n" - + "Build : " + wxString(GIT_COMMIT_HASH) + "\n" + + "Build : " + wxString(build_commit_label) + "\n" + "Package : " + GetPackageType() + "\n" + "Platform : " + GetOSinfo() + "\n" + "Processor : " + GetCPUinfo() + "\n" diff --git a/src/slic3r/GitCommitHash.cmake b/src/slic3r/GitCommitHash.cmake new file mode 100644 index 0000000000..9302885e6a --- /dev/null +++ b/src/slic3r/GitCommitHash.cmake @@ -0,0 +1,51 @@ +# Writes GIT_COMMIT_HASH and GIT_COMMIT_SUFFIX into a generated header. +# GIT_COMMIT_SUFFIX is "-dirty" for a build with uncommitted changes, and empty +# otherwise. +# +# A custom target runs this at the start of every build, which picks up a new +# commit without a reconfigure. The header is rewritten only when the value +# changes. +# +# Inputs: SOURCE_DIR, OUT_FILE. + +find_package(Git QUIET) + +set(HASH "") +set(SUFFIX "") + +if (DEFINED ENV{git_commit_hash} AND NOT "$ENV{git_commit_hash}" STREQUAL "") + if (GIT_FOUND AND EXISTS "${SOURCE_DIR}/.git") + execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short "$ENV{git_commit_hash}" + WORKING_DIRECTORY ${SOURCE_DIR} OUTPUT_VARIABLE HASH OUTPUT_STRIP_TRAILING_WHITESPACE) + else () + # No .git directory (e.g. Flatpak sandbox) - truncate directly + string(SUBSTRING "$ENV{git_commit_hash}" 0 7 HASH) + endif () +elseif (GIT_FOUND AND EXISTS "${SOURCE_DIR}/.git") + execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%h + WORKING_DIRECTORY ${SOURCE_DIR} OUTPUT_VARIABLE HASH OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT_EXECUTABLE} diff --quiet HEAD + WORKING_DIRECTORY ${SOURCE_DIR} RESULT_VARIABLE DIRTY ERROR_QUIET) + if (DIRTY EQUAL 1) + set(SUFFIX "-dirty") + endif () +endif () + +if (NOT HASH) + set(HASH "0000000") # uninitialized +endif () + +message(STATUS "Build commit: ${HASH}${SUFFIX}") + +string(CONCAT CONTENT + "#pragma once\n" + "#define GIT_COMMIT_HASH \"${HASH}\"\n" + "#define GIT_COMMIT_SUFFIX \"${SUFFIX}\"\n") + +set(OLD "") +if (EXISTS "${OUT_FILE}") + file(READ "${OUT_FILE}" OLD) +endif () +if (NOT OLD STREQUAL CONTENT) + file(WRITE "${OUT_FILE}" "${CONTENT}") +endif () diff --git a/src/slic3r/plugin/PluginAuditManager.hpp b/src/slic3r/plugin/PluginAuditManager.hpp index d6751a57fd..153f9d6107 100644 --- a/src/slic3r/plugin/PluginAuditManager.hpp +++ b/src/slic3r/plugin/PluginAuditManager.hpp @@ -1,7 +1,8 @@ #ifndef slic3r_PluginAuditManager_hpp_ #define slic3r_PluginAuditManager_hpp_ -#include +// Via pybind11 so this file requests the same python3xx.lib as everything else. +#include #include #include #include diff --git a/src/slic3r/plugin/PythonInterpreter.hpp b/src/slic3r/plugin/PythonInterpreter.hpp index ff69ce6846..e15aa91598 100644 --- a/src/slic3r/plugin/PythonInterpreter.hpp +++ b/src/slic3r/plugin/PythonInterpreter.hpp @@ -1,7 +1,8 @@ #ifndef slic3r_PythonInterpreter_hpp_ #define slic3r_PythonInterpreter_hpp_ -#include +// Via pybind11 so this file requests the same python3xx.lib as everything else. +#include #include #include #include diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index bf3981f519..5d3e301ea7 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(${_TEST_NAME}_tests test_arachne_walls.cpp test_arrange.cpp test_bambu_networking.cpp + test_buildvolume.cpp test_calib.cpp test_clipper_offset.cpp test_clipper_utils.cpp diff --git a/tests/libslic3r/test_buildvolume.cpp b/tests/libslic3r/test_buildvolume.cpp new file mode 100644 index 0000000000..b2e8040d68 --- /dev/null +++ b/tests/libslic3r/test_buildvolume.cpp @@ -0,0 +1,40 @@ +#include + +#include "libslic3r/BuildVolume.hpp" + +using namespace Slic3r; + +static std::vector rect_area(double w, double d) +{ + return { { 0., 0. }, { w, 0. }, { w, d }, { 0., d } }; +} + +// extruder_printable_height and extruder_printable_area are independent config options, so a +// profile can leave the heights short. BuildVolume must not index past the end of the heights. +TEST_CASE("BuildVolume falls back to the bed height when extruder_printable_height is short", "[BuildVolume]") +{ + const std::vector bed = rect_area(200., 200.); + const std::vector> areas = { rect_area(200., 200.), rect_area(100., 200.) }; + const std::vector heights = { 180. }; + + const BuildVolume build_volume(bed, 250., areas, heights); + + REQUIRE(build_volume.get_extruder_area_count() == 2); + // The extruder with a height of its own keeps it, and differs from the bed, so it gets its own volume. + CHECK_THAT(build_volume.get_extruder_area_volume(0).bboxf.max.z(), Catch::Matchers::WithinAbs(180., 1e-6)); + // The extruder without one falls back to the bed's printable_height instead of reading out of range. + CHECK_THAT(build_volume.get_extruder_area_volume(1).bboxf.max.z(), Catch::Matchers::WithinAbs(250., 1e-6)); +} + +TEST_CASE("BuildVolume keeps per-extruder heights when both vectors match", "[BuildVolume]") +{ + const std::vector bed = rect_area(200., 200.); + const std::vector> areas = { rect_area(120., 200.), rect_area(100., 200.) }; + const std::vector heights = { 180., 200.5 }; + + const BuildVolume build_volume(bed, 250., areas, heights); + + REQUIRE(build_volume.get_extruder_area_count() == 2); + CHECK_THAT(build_volume.get_extruder_area_volume(0).bboxf.max.z(), Catch::Matchers::WithinAbs(180., 1e-6)); + CHECK_THAT(build_volume.get_extruder_area_volume(1).bboxf.max.z(), Catch::Matchers::WithinAbs(200.5, 1e-6)); +} diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index 12b161322d..bd147b5881 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -828,3 +828,266 @@ SCENARIO("ConfigOptionVector::set_to_index throws on incompatible type", "[Confi } } } + +TEST_CASE("read_cli applies valid values and collects non-option arguments", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--nozzle-temperature", "210,190", "--reduce-crossing-wall=1", "model.3mf"}; + REQUIRE(config.read_cli(5, argv, &extra, &keys)); + REQUIRE(config.opt("nozzle_temperature")->values == std::vector{210, 190}); + REQUIRE(config.opt("reduce_crossing_wall")->value); + REQUIRE(extra == t_config_option_keys{"model.3mf"}); + REQUIRE(keys == t_config_option_keys{"nozzle_temperature", "reduce_crossing_wall"}); +} + +TEST_CASE("read_cli rejects nil for a non-nullable vector option", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--nozzle-temperature", "nil"}; + REQUIRE_FALSE(config.read_cli(3, argv, &extra, &keys)); +} + +TEST_CASE("read_cli rejects an invalid boolean value", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--reduce-crossing-wall=maybe"}; + REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys)); +} + +TEST_CASE("read_cli accepts the common spellings of a boolean value", "[Config]") { + const auto [text, expected] = GENERATE(table({ + {"--reduce-crossing-wall=1", true}, + {"--reduce-crossing-wall=true", true}, + {"--reduce-crossing-wall=Yes", true}, + {"--reduce-crossing-wall=on", true}, + {"--reduce-crossing-wall=enabled", true}, + {"--reduce-crossing-wall=TRUE", true}, + {"--reduce-crossing-wall=oN", true}, + {"--reduce-crossing-wall=0", false}, + {"--reduce-crossing-wall=false", false}, + {"--reduce-crossing-wall=No", false}, + {"--reduce-crossing-wall=off", false}, + {"--reduce-crossing-wall=disabled", false}, + {"--reduce-crossing-wall=FALSE", false}, + {"--reduce-crossing-wall=DiSaBlEd", false}, + })); + + DYNAMIC_SECTION(text) { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", text}; + REQUIRE(config.read_cli(2, argv, &extra, &keys)); + REQUIRE(config.opt("reduce_crossing_wall")->value == expected); + } +} + +TEST_CASE("read_cli accepts the common boolean spellings inside a bools vector", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--filament-soluble=true,no,1"}; + REQUIRE(config.read_cli(2, argv, &extra, &keys)); + REQUIRE(config.opt("filament_soluble")->values == std::vector{1, 0, 1}); +} + +TEST_CASE("read_cli trims whitespace around boolean spellings", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--reduce-crossing-wall= true ", "--filament-soluble= true , no ,1"}; + REQUIRE(config.read_cli(3, argv, &extra, &keys)); + REQUIRE(config.opt("reduce_crossing_wall")->value); + REQUIRE(config.opt("filament_soluble")->values == std::vector{1, 0, 1}); +} + +TEST_CASE("read_cli normalizes boolean spellings when a bools vector is repeated", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--filament-soluble=true", "--filament-soluble=off"}; + REQUIRE(config.read_cli(3, argv, &extra, &keys)); + REQUIRE(config.opt("filament_soluble")->values == std::vector{1, 0}); +} + +TEST_CASE("read_cli keeps nil alongside boolean spellings in a nullable bools vector", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--enable-overhang-speed=nil,yes,off"}; + REQUIRE(config.read_cli(2, argv, &extra, &keys)); + auto* opt = config.opt("enable_overhang_speed"); + REQUIRE(opt != nullptr); + REQUIRE(opt->values.size() == 3); + REQUIRE(opt->is_nil(0)); + REQUIRE(opt->values[1] == 1); + REQUIRE(opt->values[2] == 0); +} + +TEST_CASE("read_cli rejects an empty item inside a bools vector", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--filament-soluble=true,,1"}; + REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys)); +} + +TEST_CASE("read_cli rejects an unknown spelling next to a valid one in a bools vector", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--filament-soluble=true,affirmative"}; + REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys)); +} + +// The normalization lives in read_cli's boolean branches, so options of other types keep the +// value verbatim - a path named "on" or a colour named "true" must not turn into "1". +TEST_CASE("read_cli leaves boolean spellings alone for non-boolean options", "[Config]") { + SECTION("string option") { + Slic3r::DynamicPrintAndCLIConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--logfile=true"}; + REQUIRE(config.read_cli(2, argv, &extra, &keys)); + REQUIRE(config.opt("logfile")->value == "true"); + } + SECTION("strings vector option") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--filament-colour=on;off"}; + REQUIRE(config.read_cli(2, argv, &extra, &keys)); + REQUIRE(config.opt("filament_colour")->values == std::vector{"on", "off"}); + } +} + +TEST_CASE("read_cli treats a bare boolean flag as true without consuming the next argument", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--reduce-crossing-wall", "model.3mf"}; + REQUIRE(config.read_cli(3, argv, &extra, &keys)); + REQUIRE(config.opt("reduce_crossing_wall")->value); + REQUIRE(extra == t_config_option_keys{"model.3mf"}); +} + +TEST_CASE("read_cli rejects an invalid scalar numeric value", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--top-shell-layers", "several"}; + REQUIRE_FALSE(config.read_cli(3, argv, &extra, &keys)); +} + +TEST_CASE("read_cli appends values when a vector option is repeated", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--nozzle-temperature", "210", "--nozzle-temperature", "190,200"}; + REQUIRE(config.read_cli(5, argv, &extra, &keys)); + REQUIRE(config.opt("nozzle_temperature")->values == std::vector{210, 190, 200}); + // the key is recorded once, on first use + REQUIRE(keys == t_config_option_keys{"nozzle_temperature"}); +} + +TEST_CASE("read_cli parses a bools vector given in the --flag=values form", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--filament-soluble=1,0,1"}; + REQUIRE(config.read_cli(2, argv, &extra, &keys)); + REQUIRE(config.opt("filament_soluble")->values == std::vector{1, 0, 1}); +} + +TEST_CASE("read_cli rejects an invalid value inside a bools vector", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--filament-soluble=1,maybe"}; + REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys)); +} + +TEST_CASE("read_cli appends true for a bare bools vector flag", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--filament-soluble"}; + REQUIRE(config.read_cli(2, argv, &extra, &keys)); + REQUIRE(config.opt("filament_soluble")->values == std::vector{1}); +} + +TEST_CASE("read_cli splits a strings vector on semicolons and unescapes quoted items", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--filament-colour", "#FF0000;\"a\\nb\";#00FF00"}; + REQUIRE(config.read_cli(3, argv, &extra, &keys)); + auto& values = config.opt("filament_colour")->values; + REQUIRE(values == std::vector{"#FF0000", "a\nb", "#00FF00"}); +} + +TEST_CASE("read_cli rejects a strings vector with an unterminated quote", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--filament-colour", "\"oops"}; + REQUIRE_FALSE(config.read_cli(3, argv, &extra, &keys)); +} + +TEST_CASE("read_cli parses a points vector in the NxM coordinate form", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--printable-area", "0x0,200x0,200x200,0x200"}; + REQUIRE(config.read_cli(3, argv, &extra, &keys)); + auto& points = config.opt("printable_area")->values; + REQUIRE(points.size() == 4); + REQUIRE_THAT(points[1].x(), Catch::Matchers::WithinAbs(200.0, 1e-9)); + REQUIRE_THAT(points[1].y(), Catch::Matchers::WithinAbs(0.0, 1e-9)); + REQUIRE_THAT(points[3].x(), Catch::Matchers::WithinAbs(0.0, 1e-9)); + REQUIRE_THAT(points[3].y(), Catch::Matchers::WithinAbs(200.0, 1e-9)); +} + +// logfile is a CLI-only option, so it needs the config type whose def pulls in cli_misc_config_def. +TEST_CASE("read_cli stores the log file path as a string", "[Config]") { + Slic3r::DynamicPrintAndCLIConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--logfile", "orca.log"}; + REQUIRE(config.read_cli(3, argv, &extra, &keys)); + REQUIRE(config.opt("logfile")->value == "orca.log"); +} + +TEST_CASE("read_cli accepts nil entries for a nullable vector option", "[Config]") { + Slic3r::DynamicPrintConfig config; + t_config_option_keys extra, keys; + const char* argv[] = {"orca-slicer", "--filament-retraction-length", "nil,2.5"}; + REQUIRE(config.read_cli(3, argv, &extra, &keys)); + auto* opt = config.opt("filament_retraction_length"); + REQUIRE(opt != nullptr); + REQUIRE(opt->values.size() == 2); + REQUIRE(opt->is_nil(0)); + REQUIRE_FALSE(opt->is_nil(1)); + REQUIRE_THAT(opt->values[1], Catch::Matchers::WithinAbs(2.5, 1e-9)); +} + +// get_at() returns values.front() for an out-of-range index, so calling it on an empty vector +// option is UB. filament_id and filament_is_support are unpopulated on a CLI from-scratch slice. +TEST_CASE("get_filament_type treats empty vector options as absent", "[Config][Filament]") +{ + DynamicPrintConfig config; + std::string displayed; + + SECTION("an empty filament_type yields no type at all") + { + config.set_key_value("filament_type", new ConfigOptionStrings()); + REQUIRE(config.get_filament_type(displayed, 0) == ""); + } + + SECTION("an empty filament_is_support falls back to the plain filament type") + { + config.set_key_value("filament_type", new ConfigOptionStrings({"PETG"})); + config.set_key_value("filament_is_support", new ConfigOptionBools()); + REQUIRE(config.get_filament_type(displayed, 0) == "PETG"); + REQUIRE(displayed == "PETG"); + } + + SECTION("a support filament with an empty filament_id resolves from the type alone") + { + config.set_key_value("filament_type", new ConfigOptionStrings({"PLA"})); + config.set_key_value("filament_is_support", new ConfigOptionBools({true})); + config.set_key_value("filament_id", new ConfigOptionStrings()); + REQUIRE(config.get_filament_type(displayed, 0) == "PLA-S"); + REQUIRE(displayed == "Sup.PLA"); + } + + SECTION("a populated filament_id still selects the support type by id") + { + config.set_key_value("filament_type", new ConfigOptionStrings({"PETG"})); + config.set_key_value("filament_is_support", new ConfigOptionBools({true})); + config.set_key_value("filament_id", new ConfigOptionStrings({"GFS00"})); + REQUIRE(config.get_filament_type(displayed, 0) == "PLA-S"); + REQUIRE(displayed == "Sup.PLA"); + } +} diff --git a/tests/libslic3r/test_preset_bundle_loading.cpp b/tests/libslic3r/test_preset_bundle_loading.cpp index 55c18bfa9e..c75a05949e 100644 --- a/tests/libslic3r/test_preset_bundle_loading.cpp +++ b/tests/libslic3r/test_preset_bundle_loading.cpp @@ -184,6 +184,44 @@ TEST_CASE("Printer extruder count tolerates missing nozzle diameter", "[Preset][ CHECK(bundle.get_printer_extruder_count() == 2); } +TEST_CASE("Selected printer uses its default or saved bed type", "[Preset][Bundle]") +{ + PresetBundle bundle; + Preset& printer = add_inmemory_preset(bundle.printers, "Test Printer"); + printer.is_system = true; + printer.config.option("printer_model")->value = "TEST-MODEL"; + printer.config.option("printer_variant")->value = "0.4"; + printer.config.option("default_bed_type")->value = "Engineering Plate"; + + AppConfig app_config; + app_config.set("curr_bed_type", std::to_string(static_cast(btPTE))); + PresetBundle::PresetPreferences preferred_selection; + BedType expected_bed_type; + + SECTION("New printer uses its symbolic default") { + expected_bed_type = btEP; + preferred_selection = {"TEST-MODEL", "0.4"}; + } + SECTION("Re-enabled printer uses its saved selection") { + expected_bed_type = btPC; + preferred_selection = {"TEST-MODEL", "0.4"}; + app_config.set_printer_setting("Test Printer", "curr_bed_type", + std::to_string(static_cast(expected_bed_type))); + } + SECTION("Existing printer keeps its saved selection after presets reload") { + expected_bed_type = btPCT; + app_config.set("presets", PRESET_PRINTER_NAME, "Test Printer"); + app_config.set_printer_setting("Test Printer", "curr_bed_type", + std::to_string(static_cast(expected_bed_type))); + } + + bundle.load_selections(app_config, preferred_selection); + bundle.export_selections(app_config); + + CHECK(bundle.project_config.opt_enum("curr_bed_type") == expected_bed_type); + CHECK(app_config.get_printer_setting("Test Printer", "curr_bed_type") == std::to_string(static_cast(expected_bed_type))); +} + TEST_CASE("find_preset resolves a system preset's renamed_from", "[Preset][Rename]") { RenameTestCollection coll;