mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 10:21:00 +00:00
Merge upstream/main into cad-mainline (530 commits)
Catches the fork up from449a4cf9fc(2026-06-28) tod6cb667b89(2026-07-24). Upstream touched 2326 files; 17 of them overlap the 164 this branch touches. 16 of the 17 auto-merged, including all three CMakeLists.txt, the build_all.yml CI workflow, and every GUI file. The CAD core never conflicts: CadDocument, SketchEngine, SketchSolver, McpControl and test_caddocument are files this fork adds, so upstream does not touch them. The one conflict, tests/libslic3r/test_3mf.cpp, was purely additive in all three hunks and is resolved as a union: our test pinning that store_bbs_3mf embeds the CAD recipe as Metadata/SnapOrca_cad.bin, upstream's multi-nozzle plate-metadata round-trip tests, and both sets of includes. All three were verified present after resolution rather than assumed. NOT BUILD-VERIFIED, for a reason that predates this merge and is not caused by it: this fork cannot be configured on nativedev at all. Its CMakeLists has required Eigen3 5.0.1 since before the merge (line 592 pre-merge), while the only deps image on the machine is snaporca-deps, built for snaporca's find_package(Eigen3 3.3). CMake fails at configure, so nothing compiles. That means this fork's Catch2 suite has never run. Every "suite green" figure recorded for M1-M8 was snaporca's suite; the ports were verified by patch-apply plus the CAD sources being byte-identical to snaporca's. Building an orca_cad deps image with Eigen 5.0.1 is what would finally close that gap. Pre-merge state is preserved at branch cad-mainline-pre-upstream-2026-07-25 (30d54f0074). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+75
-6
@@ -129,6 +129,23 @@ if (MINGW)
|
||||
set_target_properties(OrcaSlicer PROPERTIES PREFIX "")
|
||||
endif (MINGW)
|
||||
|
||||
# The GUI embeds the bundled shared libpython (libslic3r_gui -> pybind11::embed);
|
||||
# the dep recipe stamps it with an @rpath id / plain SONAME, so the executable
|
||||
# needs an rpath entry for each layout in the "Bundled Python/uv layout" map
|
||||
# further down. Dead entries are skipped by the loader.
|
||||
# OrcaSlicer_profile_validator links libslic3r only (no Python).
|
||||
if (APPLE)
|
||||
# A linker flag rather than the BUILD_RPATH property because the release
|
||||
# flow uses the Xcode generator, which does not honor BUILD_RPATH.
|
||||
target_link_options(OrcaSlicer PRIVATE "LINKER:-rpath,@executable_path/python/lib")
|
||||
elseif (UNIX AND NOT WIN32)
|
||||
set_target_properties(OrcaSlicer PROPERTIES
|
||||
# Build tree + AppImage, which packages the build-tree binary.
|
||||
BUILD_RPATH "$ORIGIN/python/lib;$ORIGIN/../lib/python/lib"
|
||||
# Flatpak (cmake --install).
|
||||
INSTALL_RPATH "$ORIGIN/../libpython/lib")
|
||||
endif ()
|
||||
|
||||
if (NOT WIN32 AND NOT APPLE)
|
||||
# Binary name on unix like systems (Linux, Unix)
|
||||
set_target_properties(OrcaSlicer PROPERTIES OUTPUT_NAME "orca-slicer")
|
||||
@@ -184,9 +201,12 @@ if (WIN32)
|
||||
if(MSVC)
|
||||
target_link_options(OrcaSlicer_app_gui PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
||||
endif()
|
||||
target_compile_definitions(OrcaSlicer_app_gui PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE)
|
||||
target_compile_definitions(OrcaSlicer_app_gui PRIVATE "$<$<NOT:$<CONFIG:RelWithDebInfo>>:SLIC3R_WRAPPER_NOCONSOLE>")
|
||||
add_dependencies(OrcaSlicer_app_gui OrcaSlicer)
|
||||
set_target_properties(OrcaSlicer_app_gui PROPERTIES OUTPUT_NAME "orca-slicer")
|
||||
set_target_properties(OrcaSlicer_app_gui PROPERTIES
|
||||
OUTPUT_NAME "orca-slicer"
|
||||
WIN32_EXECUTABLE "$<NOT:$<CONFIG:RelWithDebInfo>>"
|
||||
)
|
||||
target_link_libraries(OrcaSlicer_app_gui PRIVATE boost_headeronly)
|
||||
endif ()
|
||||
|
||||
@@ -194,6 +214,26 @@ endif ()
|
||||
set(output_dlls_Release "")
|
||||
set(output_dlls_Debug "")
|
||||
set(output_dlls_RelWithDebInfo "")
|
||||
# ---- Bundled Python/uv layout (canonical map) ---------------------------
|
||||
# The runtime and uv are staged three ways because packaging differs:
|
||||
# install() rules - Windows installer; on FHS/Flatpak they stage
|
||||
# only uv (Flatpak's runtime comes from the deps
|
||||
# build installing to /app/libpython; plain FHS
|
||||
# installs bundle no python runtime)
|
||||
# POST_BUILD copies (here) - build-tree runs, and the macOS .app/AppImage
|
||||
# flows, which package the build tree and
|
||||
# never run install()
|
||||
# packaging scripts - build_release_macos.sh (copies the .app),
|
||||
# build_linux_image.sh.in (assembles $APPDIR)
|
||||
# Final locations relative to the executable:
|
||||
# Windows: <exe dir>/python <resources>/tools/uv
|
||||
# macOS: Contents/MacOS/python Contents/MacOS/tools/uv
|
||||
# AppImage: $APPDIR/lib/python $APPDIR/resources/tools/uv
|
||||
# Flatpak: /app/libpython (dep prefix) /app/share/OrcaSlicer/tools/uv
|
||||
# Runtime lookup: PythonInterpreter::initialize() / bundled_uv_path().
|
||||
# libpython resolution: rpath on the OrcaSlicer target (above); the dep
|
||||
# recipe gives the bundled interpreter a self-relative rpath.
|
||||
# --------------------------------------------------------------------------
|
||||
if (WIN32)
|
||||
# This has to be a separate target due to the windows command line length limits
|
||||
add_custom_target(COPY_DLLS ALL DEPENDS OrcaSlicer)
|
||||
@@ -228,6 +268,12 @@ if (WIN32)
|
||||
VERBATIM
|
||||
)
|
||||
endif ()
|
||||
# copy libpython to the bin folder for Windows
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "$<TARGET_FILE_DIR:OrcaSlicer>/python"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_PREFIX_PATH}/libpython" "$<TARGET_FILE_DIR:OrcaSlicer>/python"
|
||||
COMMENT "Copying Python runtime into the build tree"
|
||||
VERBATIM)
|
||||
|
||||
|
||||
else ()
|
||||
@@ -237,33 +283,55 @@ else ()
|
||||
COMMAND ln -sf OrcaSlicer orca-slicer
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:OrcaSlicer>"
|
||||
VERBATIM)
|
||||
else ()
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:OrcaSlicer>"
|
||||
VERBATIM)
|
||||
endif ()
|
||||
if (XCODE)
|
||||
# Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level
|
||||
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources")
|
||||
set(BIN_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
set(BIN_CONF_DIR "Debug")
|
||||
else ()
|
||||
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/../resources")
|
||||
set(BIN_DIR "$<TARGET_FILE_DIR:OrcaSlicer>")
|
||||
endif ()
|
||||
if (CMAKE_MACOSX_BUNDLE)
|
||||
if (CMAKE_CONFIGURATION_TYPES)
|
||||
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/OrcaSlicer.app/Contents/Resources")
|
||||
set(BIN_DIR "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/OrcaSlicer.app/Contents/MacOS")
|
||||
else()
|
||||
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.app/Contents/Resources")
|
||||
set(BIN_DIR "${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.app/Contents/MacOS")
|
||||
endif()
|
||||
set(MACOSX_BUNDLE_ICON_FILE Icon.icns)
|
||||
set(MACOSX_BUNDLE_BUNDLE_NAME "OrcaSlicer")
|
||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${SoftFever_VERSION})
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "Copyright(C) 2022-2024 Li Jiang All Rights Reserved")
|
||||
if (XCODE)
|
||||
# Xcode's CodeSign phase fails on the bundled Python runtime's dotted
|
||||
# dirs (python3.12) under Contents/MacOS. Skip it for local dev builds
|
||||
# and let the linker ad-hoc sign; the shipped bundle is signed by the
|
||||
# Ninja/CI packaging path (build_release_macos.sh, build_orca.yml).
|
||||
set_target_properties(OrcaSlicer PROPERTIES XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO)
|
||||
endif()
|
||||
endif()
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${BIN_RESOURCES_DIR}"
|
||||
COMMENT "Symlinking the resources directory into the build tree"
|
||||
VERBATIM)
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "${BIN_DIR}/python"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_PREFIX_PATH}/libpython" "${BIN_DIR}/python"
|
||||
COMMENT "Copying Python runtime into the build tree"
|
||||
VERBATIM)
|
||||
# Stage uv next to the binary for the build-tree/.app flows, which never
|
||||
# run install() -- see the layout map above; lookup is bundled_uv_path().
|
||||
if(ORCA_BUNDLED_UV_EXECUTABLE AND EXISTS "${ORCA_BUNDLED_UV_EXECUTABLE}")
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${BIN_DIR}/tools/uv"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ORCA_BUNDLED_UV_EXECUTABLE}" "${BIN_DIR}/tools/uv/${ORCA_BUNDLED_UV_FILENAME}"
|
||||
COMMAND chmod +x "${BIN_DIR}/tools/uv/${ORCA_BUNDLED_UV_FILENAME}"
|
||||
COMMENT "Copying bundled uv into the build tree"
|
||||
VERBATIM)
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
# Slic3r binary install target. Default build type is release in case no CMAKE_BUILD_TYPE is provided.
|
||||
@@ -285,6 +353,7 @@ if (WIN32)
|
||||
install(TARGETS OrcaSlicer_app_gui RUNTIME DESTINATION ".")
|
||||
endif ()
|
||||
install(FILES ${output_dlls_${build_type}} DESTINATION ".")
|
||||
install(DIRECTORY "${CMAKE_PREFIX_PATH}/libpython/" DESTINATION "python")
|
||||
else ()
|
||||
install(TARGETS OrcaSlicer RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif ()
|
||||
|
||||
+130
-16
@@ -24,6 +24,8 @@
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
#include <csignal>
|
||||
#include <atomic>
|
||||
#include <new>
|
||||
|
||||
#if defined(__linux__) || defined(__LINUX__)
|
||||
#include <condition_variable>
|
||||
@@ -51,7 +53,6 @@ using namespace nlohmann;
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/GCode.hpp"
|
||||
#include "libslic3r/GCode/PostProcessor.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/ModelArrange.hpp"
|
||||
#include "libslic3r/Platform.hpp"
|
||||
@@ -1317,9 +1318,23 @@ int CLI::run(int argc, char **argv)
|
||||
return CLI_INVALID_PARAMS;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "finished setup params, argc="<< argc << std::endl;
|
||||
std::string temp_path = wxFileName::GetTempDir().utf8_str().data();
|
||||
std::string temp_path = per_user_temp_dir(wxFileName::GetTempDir().utf8_str().data(), per_user_temp_id());
|
||||
// Some consumers write into the temp root directly, so create it up front.
|
||||
try {
|
||||
boost::filesystem::create_directories(temp_path);
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "failed to create per-user temp dir " << temp_path << ": " << ex.what();
|
||||
}
|
||||
set_temporary_dir(temp_path);
|
||||
|
||||
// The Filament Track Switch flags are live-device state with no meaning in headless slicing;
|
||||
// default both off unless explicitly provided on the command line, so an old 3MF that had
|
||||
// them enabled still slices without the switch behavior.
|
||||
if (!m_extra_config.has("has_filament_switcher"))
|
||||
m_extra_config.set_key_value("has_filament_switcher", new ConfigOptionBool(false));
|
||||
if (!m_extra_config.has("enable_filament_dynamic_map"))
|
||||
m_extra_config.set_key_value("enable_filament_dynamic_map", new ConfigOptionBool(false));
|
||||
|
||||
m_extra_config.apply(m_config, true);
|
||||
m_extra_config.normalize_fdm();
|
||||
|
||||
@@ -1697,11 +1712,13 @@ int CLI::run(int argc, char **argv)
|
||||
const Vec3d &instance_offset = model_instance->get_offset();
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("instance %1% transform {%2%,%3%,%4%} at %5%:%6%")% model_object->name % instance_offset.x() % instance_offset.y() %instance_offset.z() % __FUNCTION__ % __LINE__<< std::endl;
|
||||
}*/
|
||||
current_printer_name = config.option<ConfigOptionString>("printer_settings_id")->value;
|
||||
current_process_name = config.option<ConfigOptionString>("print_settings_id")->value;
|
||||
// Read defensively — a 3mf missing preset ids (e.g. one produced
|
||||
// by a non-GUI writer) would otherwise crash here on the deref.
|
||||
if (const auto *o = config.option<ConfigOptionString>("printer_settings_id")) current_printer_name = o->value;
|
||||
if (const auto *o = config.option<ConfigOptionString>("print_settings_id")) current_process_name = o->value;
|
||||
current_printer_model = config.option<ConfigOptionString>("printer_model", true)->value;
|
||||
current_filaments_name = config.option<ConfigOptionStrings>("filament_settings_id")->values;
|
||||
current_extruder_count = config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
|
||||
if (const auto *o = config.option<ConfigOptionStrings>("filament_settings_id")) current_filaments_name = o->values;
|
||||
if (const auto *o = config.option<ConfigOptionFloats>("nozzle_diameter")) current_extruder_count = o->values.size();
|
||||
current_printer_variant_count = config.option<ConfigOptionStrings>("printer_extruder_variant", true)->values.size();
|
||||
current_print_variant_count = config.option<ConfigOptionStrings>("print_extruder_variant", true)->values.size();
|
||||
current_is_multi_extruder = current_extruder_count > 1;
|
||||
@@ -3865,13 +3882,13 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
|
||||
//travel_acceleration
|
||||
ConfigOptionFloat *travel_acceleration_option = m_print_config.option<ConfigOptionFloat>("travel_acceleration", true);
|
||||
ConfigOptionFloat *default_acceleration_option = m_print_config.option<ConfigOptionFloat>("default_acceleration");
|
||||
travel_acceleration_option->value = default_acceleration_option->value;
|
||||
ConfigOptionFloatsNullable *travel_acceleration_option = m_print_config.option<ConfigOptionFloatsNullable>("travel_acceleration", true);
|
||||
ConfigOptionFloatsNullable *default_acceleration_option = m_print_config.option<ConfigOptionFloatsNullable>("default_acceleration");
|
||||
travel_acceleration_option->values = default_acceleration_option->values;
|
||||
|
||||
ConfigOptionFloat *initial_layer_travel_acceleration_option = m_print_config.option<ConfigOptionFloat>("initial_layer_travel_acceleration", true);
|
||||
ConfigOptionFloat *initial_layer_acceleration_option = m_print_config.option<ConfigOptionFloat>("initial_layer_acceleration");
|
||||
initial_layer_travel_acceleration_option->value = initial_layer_acceleration_option->value;
|
||||
ConfigOptionFloatsNullable *initial_layer_travel_acceleration_option = m_print_config.option<ConfigOptionFloatsNullable>("initial_layer_travel_acceleration", true);
|
||||
ConfigOptionFloatsNullable *initial_layer_acceleration_option = m_print_config.option<ConfigOptionFloatsNullable>("initial_layer_acceleration");
|
||||
initial_layer_travel_acceleration_option->values = initial_layer_acceleration_option->values;
|
||||
}
|
||||
|
||||
auto get_print_sequence = [](Slic3r::GUI::PartPlate* plate, DynamicPrintConfig& print_config, bool &is_seq_print) {
|
||||
@@ -5931,6 +5948,72 @@ int CLI::run(int argc, char **argv)
|
||||
else
|
||||
filament_maps = part_plate->get_real_filament_maps(m_print_config);
|
||||
|
||||
// Multi-nozzle printers need the per-filament volume assignment as a grouping
|
||||
// input in the manual modes: synthesize it from the per-extruder flow types when
|
||||
// the caller did not provide one (an extruder whose nozzle stats span several
|
||||
// volume types keeps the per-filament choice), and require explicit maps in
|
||||
// nozzle-manual mode.
|
||||
auto max_nozzle_counts_opt = m_print_config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
|
||||
// Skip nil entries: a nullable-int nil is INT_MAX (> 1) and would otherwise falsely pass the gate.
|
||||
bool support_multi_nozzle =
|
||||
max_nozzle_counts_opt &&
|
||||
std::any_of(max_nozzle_counts_opt->values.begin(), max_nozzle_counts_opt->values.end(),
|
||||
[](int v) { return v > 1 && v != ConfigOptionIntsNullable::nil_value(); });
|
||||
if (support_multi_nozzle && (mode == fmmManual || mode == fmmNozzleManual) && (plate_to_slice != 0)) {
|
||||
// Orca: the grouping result is reconstructed purely from the passed maps in
|
||||
// nozzle-manual mode, so all of them must be present (there are no separate
|
||||
// per-nozzle CLI parameters to rebuild them from).
|
||||
if (mode == FilamentMapMode::fmmNozzleManual &&
|
||||
(!m_extra_config.has("filament_volume_map") || !m_extra_config.has("filament_nozzle_map") ||
|
||||
!m_extra_config.has("filament_map"))) {
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< boost::format("%1%, can not find filament_volume_map/filament_nozzle_map/filament_map under Nozzle Manual mode") % __LINE__;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, index + 1, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
|
||||
if (mode == fmmManual) {
|
||||
// Build the volume map when absent: filaments on a single-volume extruder
|
||||
// print with that extruder's volume; a mixed-volume extruder keeps the
|
||||
// per-filament choice (default Standard).
|
||||
std::vector<NozzleVolumeType> using_nozzle_volume_type = new_nozzle_volume_type;
|
||||
using_nozzle_volume_type.resize(new_extruder_count, nvtStandard);
|
||||
if (auto extruder_nozzle_stats_opt = m_print_config.option<ConfigOptionStrings>("extruder_nozzle_stats")) {
|
||||
auto nozzle_stats = get_extruder_nozzle_stats(extruder_nozzle_stats_opt->values);
|
||||
for (int e_index = 0; e_index < new_extruder_count && e_index < (int) nozzle_stats.size(); e_index++) {
|
||||
if (nozzle_stats[e_index].size() > 1) {
|
||||
using_nozzle_volume_type[e_index] = nvtHybrid;
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1% : extruder %2%, set nozzle_volume_type to hybrid ") % __LINE__ % (e_index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<int> &manual_volume_maps = m_extra_config.option<ConfigOptionInts>("filament_volume_map", true)->values;
|
||||
// default to standard flow
|
||||
manual_volume_maps.resize(filament_count, (int) (nvtStandard));
|
||||
for (int f_index = 0; f_index < filament_count && f_index < (int) filament_maps.size(); f_index++) {
|
||||
int f_extruder_index = filament_maps[f_index] - 1;
|
||||
if (f_extruder_index >= 0 && f_extruder_index < new_extruder_count &&
|
||||
using_nozzle_volume_type[f_extruder_index] != nvtHybrid) {
|
||||
manual_volume_maps[f_index] = int(using_nozzle_volume_type[f_extruder_index]);
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1% : filament %2% extruder %3%, set filament_volume_map to %4% ") % __LINE__ % (f_index + 1) % (f_extruder_index + 1) % manual_volume_maps[f_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_extra_config.has("filament_volume_map")) {
|
||||
part_plate->set_filament_volume_maps(m_extra_config.option<ConfigOptionInts>("filament_volume_map")->values);
|
||||
}
|
||||
if (m_extra_config.has("filament_nozzle_map")) {
|
||||
part_plate->set_filament_nozzle_maps(m_extra_config.option<ConfigOptionInts>("filament_nozzle_map")->values);
|
||||
}
|
||||
}
|
||||
else if (!support_multi_nozzle && (mode == fmmNozzleManual)) {
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< boost::format("%1%, Nozzle Manual mode not supported for %2%") % __LINE__ % new_printer_name;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, index + 1, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
|
||||
for (int index = 0; index < filament_maps.size(); index++)
|
||||
{
|
||||
int filament_extruder = filament_maps[index];
|
||||
@@ -6169,6 +6252,22 @@ int CLI::run(int argc, char **argv)
|
||||
BOOST_LOG_TRIVIAL(info) << "print::process: first time_using_cache is " << time_using_cache << " secs.";
|
||||
}
|
||||
if (printer_technology == ptFFF) {
|
||||
// Read the engine's final grouping back onto the plate so an exported
|
||||
// project (--export-3mf / gcode.3mf) carries the concrete maps in its
|
||||
// plate settings, matching what a GUI slice persists.
|
||||
// Orca: deliberately gated to multi-extruder printers so single-extruder
|
||||
// exports keep their plate settings unchanged.
|
||||
if (new_extruder_count > 1) {
|
||||
FilamentMapMode current_map_mode = print_fff->config().filament_map_mode.value;
|
||||
if (is_auto_filament_map_mode(current_map_mode)) {
|
||||
part_plate->set_filament_maps(print_fff->get_filament_maps());
|
||||
part_plate->set_filament_volume_maps(print_fff->get_filament_volume_maps());
|
||||
}
|
||||
if (current_map_mode != FilamentMapMode::fmmNozzleManual) {
|
||||
part_plate->set_filament_nozzle_maps(print_fff->get_filament_nozzle_maps());
|
||||
}
|
||||
}
|
||||
|
||||
std::string conflict_result = print_fff->get_conflict_string();
|
||||
if (!conflict_result.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": found slicing result conflict!"<< std::endl;
|
||||
@@ -7353,7 +7452,7 @@ bool CLI::export_project(Model *model, std::string& path, PlateDataPtrs &partpla
|
||||
bool success = false;
|
||||
|
||||
StoreParams store_params;
|
||||
store_params.path = path.c_str();
|
||||
store_params.path = path;
|
||||
store_params.model = model;
|
||||
store_params.plate_data_list = partplate_data;
|
||||
store_params.project_presets = project_presets;
|
||||
@@ -7511,6 +7610,9 @@ LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
|
||||
}*/
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
// Guards against a failed allocation inside the dump re-entering the new-handler.
|
||||
static std::atomic<bool> g_dump_in_progress{false};
|
||||
|
||||
extern "C" {
|
||||
__declspec(dllexport) int __stdcall orcaslicer_main(int argc, wchar_t **argv)
|
||||
{
|
||||
@@ -7529,10 +7631,22 @@ extern "C" {
|
||||
//AddVectoredExceptionHandler(1, CBaseException::UnhandledExceptionFilter);
|
||||
SET_DEFULTER_HANDLER();
|
||||
#endif
|
||||
// Dump before unwinding, while the stack still names what asked for the memory. Throwing
|
||||
// std::bad_alloc is standard-permitted here and is what reaches generic_exception_handle().
|
||||
std::set_new_handler([]() {
|
||||
int *a = nullptr;
|
||||
*a = 0;
|
||||
});
|
||||
if (!g_dump_in_progress.exchange(true)) {
|
||||
try {
|
||||
// A null EXCEPTION_POINTERS walks the calling thread as it stands.
|
||||
CBaseException base(GetCurrentProcess(), GetCurrentProcessId(), NULL, nullptr);
|
||||
base.ShowCallstack();
|
||||
} catch (...) {
|
||||
// A failed dump must not displace the std::bad_alloc owed to the caller.
|
||||
}
|
||||
// ObjParser recovers from std::bad_alloc, so let a later one dump again.
|
||||
g_dump_in_progress = false;
|
||||
}
|
||||
throw std::bad_alloc();
|
||||
});
|
||||
// Call the UTF8 main.
|
||||
return CLI().run(argc, argv_ptrs.data());
|
||||
}
|
||||
|
||||
@@ -263,6 +263,14 @@ int wmain(int argc, wchar_t **argv)
|
||||
_wsplitpath(path_to_exe, drive, dir, fname, ext);
|
||||
_wmakepath(path_to_exe, drive, dir, nullptr, nullptr);
|
||||
|
||||
wchar_t path_to_python[MAX_PATH + 1] = { 0 };
|
||||
wcscpy(path_to_python, path_to_exe);
|
||||
wcscat(path_to_python, L"python");
|
||||
DWORD python_attrs = GetFileAttributesW(path_to_python);
|
||||
if (python_attrs != INVALID_FILE_ATTRIBUTES && (python_attrs & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
SetDllDirectoryW(path_to_python);
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
// https://wiki.qt.io/Cross_compiling_Mesa_for_Windows
|
||||
// http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
// This single-TU executable links libslic3r, whose SVG/emboss objects (pulled in by the slice mode
|
||||
// below) reference the header-only nanosvg implementation. Provide it here BEFORE any libslic3r header:
|
||||
// several of them transitively include nanosvg.h without the implementation macro, and its include
|
||||
// guard would then suppress the implementation if the macro were defined afterwards. Same pattern as
|
||||
// the test mains.
|
||||
#define NANOSVG_IMPLEMENTATION
|
||||
#include "nanosvg/nanosvg.h"
|
||||
#define NANOSVGRAST_IMPLEMENTATION
|
||||
#include "nanosvg/nanosvgrast.h"
|
||||
|
||||
#include "libslic3r/GCode.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/log/core.hpp>
|
||||
#include <boost/log/expressions.hpp>
|
||||
#include <boost/log/sinks/sync_frontend.hpp>
|
||||
#include <boost/log/sinks/text_ostream_backend.hpp>
|
||||
#include <boost/core/null_deleter.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
@@ -83,6 +103,270 @@ void generate_custom_presets(PresetBundle* preset_bundle, AppConfig& app_config)
|
||||
|
||||
std::cout << "Custom presets generated successfully" << std::endl;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Vec2d printable_area_center(const DynamicPrintConfig &cfg)
|
||||
{
|
||||
const auto *opt = cfg.option<ConfigOptionPoints>("printable_area");
|
||||
if (opt == nullptr || opt->values.empty())
|
||||
return Vec2d(100., 100.);
|
||||
Vec2d lo = opt->values.front(), hi = opt->values.front();
|
||||
for (const Vec2d &p : opt->values) { lo = lo.cwiseMin(p); hi = hi.cwiseMax(p); }
|
||||
return 0.5 * (lo + hi);
|
||||
}
|
||||
|
||||
// Slice one centered cube that switches from filament 1 to filament 2 partway up, so exactly one
|
||||
// filament change fires, then export. The change drives the printer's own change_filament_gcode: on a
|
||||
// single-nozzle machine it rides the AMS prime tower (append_tcr), on a multi-nozzle machine it routes
|
||||
// through the nozzle swap (set_extruder / append_tcr2) - the engine picks the path from the printer's
|
||||
// topology, so one model covers both. An undefined placeholder in any shipped custom g-code throws
|
||||
// Slic3r::PlaceholderParserError from export.
|
||||
std::string slice_two_color_cube_and_export(const DynamicPrintConfig &cfg, bool is_bbl)
|
||||
{
|
||||
const Vec2d center = printable_area_center(cfg);
|
||||
TriangleMesh m = make_cube(10, 10, 10);
|
||||
m.translate(float(center.x() - 5.), float(center.y() - 5.), 0.f);
|
||||
|
||||
Model model;
|
||||
Print print;
|
||||
ModelObject *obj = model.add_object();
|
||||
obj->name = "cube"; // populates [input_filename_base] the way a loaded model does
|
||||
obj->add_volume(m);
|
||||
obj->add_instance();
|
||||
// Filament 2 is used only above z=4, so the upper layers carry a single filament change.
|
||||
DynamicPrintConfig range_config;
|
||||
range_config.set_key_value("extruder", new ConfigOptionInt(2));
|
||||
// Every range must carry a layer_height; use the process's own so a fine nozzle (e.g. 0.15 mm
|
||||
// printing ~0.1 mm layers) isn't forced to a height its extrusion width can't support - that
|
||||
// trips Flow::with_spacing.
|
||||
range_config.set_key_value("layer_height", new ConfigOptionFloat(cfg.opt_float("layer_height")));
|
||||
obj->layer_config_ranges[{4.0, 10.0}].assign_config(std::move(range_config));
|
||||
|
||||
print.is_BBL_printer() = is_bbl;
|
||||
obj->ensure_on_bed();
|
||||
print.auto_assign_extruders(obj);
|
||||
print.apply(model, cfg);
|
||||
print.validate();
|
||||
|
||||
// Process + export to a temp file, then read it back (the app's own export path is where the
|
||||
// custom *_gcode placeholders expand).
|
||||
print.set_status_silent();
|
||||
print.process();
|
||||
const fs::path tmp = fs::temp_directory_path() / fs::unique_path("orca-validate-%%%%-%%%%.gcode");
|
||||
print.export_gcode(tmp.string(), nullptr, nullptr);
|
||||
std::ifstream in(tmp.string());
|
||||
std::string out((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
|
||||
in.close();
|
||||
boost::system::error_code ec;
|
||||
fs::remove(tmp, ec);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Select the printer's OWN default process + filament (as the app does on a printer change) so we
|
||||
// slice with settings the printer actually ships, not the generic "Default Setting" that stays
|
||||
// selected because it is compatible with every printer.
|
||||
void select_printer_default_presets(PresetBundle &bundle)
|
||||
{
|
||||
const Preset &printer_preset = bundle.printers.get_selected_preset();
|
||||
const std::string def_print = printer_preset.config.opt_string("default_print_profile");
|
||||
if (!def_print.empty())
|
||||
bundle.prints.select_preset_by_name(def_print, /*force=*/true);
|
||||
if (const auto *def_fil = printer_preset.config.option<ConfigOptionStrings>("default_filament_profile");
|
||||
def_fil != nullptr && !def_fil->values.empty())
|
||||
bundle.filaments.select_preset_by_name(def_fil->values.front(), /*force=*/true);
|
||||
}
|
||||
|
||||
// The vendor/printer currently being sliced, stamped onto every engine log record by the sink below so
|
||||
// the interleaved [error] lines can be attributed to a profile. Updated once per loop iteration; safe as
|
||||
// a plain global because the sweep is single-threaded and synchronous (see slice_all_printers).
|
||||
static std::string g_slice_context;
|
||||
|
||||
// Route Boost.Log through a sink that prefixes every record with g_slice_context. Without this the
|
||||
// engine's [error] lines (emitted deep inside process()/export_gcode()) carry no printer context, so a
|
||||
// failing profile cannot be told apart from the ~1000 others in the sweep. Drops the default trivial
|
||||
// sink's timestamp/thread columns - noise here - in favour of the vendor/printer tag.
|
||||
void install_slice_context_log_sink()
|
||||
{
|
||||
namespace logging = boost::log;
|
||||
namespace sinks = boost::log::sinks;
|
||||
namespace expr = boost::log::expressions;
|
||||
|
||||
auto backend = boost::make_shared<sinks::text_ostream_backend>();
|
||||
backend->add_stream(boost::shared_ptr<std::ostream>(&std::clog, boost::null_deleter()));
|
||||
backend->auto_flush(true);
|
||||
|
||||
auto sink = boost::make_shared<sinks::synchronous_sink<sinks::text_ostream_backend>>(backend);
|
||||
sink->set_formatter([](const logging::record_view &rec, logging::formatting_ostream &strm) {
|
||||
strm << "[" << rec[logging::trivial::severity] << "]";
|
||||
if (!g_slice_context.empty())
|
||||
strm << " [" << g_slice_context << "]";
|
||||
strm << " " << rec[expr::smessage];
|
||||
});
|
||||
|
||||
logging::core::get()->remove_all_sinks(); // drop the default trivial sink so lines are not doubled
|
||||
logging::core::get()->add_sink(sink);
|
||||
}
|
||||
|
||||
// Slice-and-export a two-colour cube through every shipped printer (optionally scoped to one vendor via
|
||||
// -v). Unlike the static reference/placeholder checks, this expands every custom *_gcode - including
|
||||
// change_filament_gcode at the one filament change - against the printer's fully-resolved config, so
|
||||
// undefined-placeholder / invalid-flow bugs surface here. Reports every offending printer and returns 1
|
||||
// if any failed, 0 otherwise. When outdir is non-empty, each printer's g-code is also written there as
|
||||
// "<vendor>__<printer>.gcode" for manual inspection. The sweep is SEQUENTIAL by necessity:
|
||||
// Print::process() keeps process-global state, so slicing printers concurrently in one process races
|
||||
// even with per-slice Model+Print. Load in validation mode so the vendors are read straight from the -p
|
||||
// profiles dir (no data_dir/system tree) and -v scoping is honoured for free.
|
||||
int slice_all_printers(const std::string &vendor, const std::string &outdir)
|
||||
{
|
||||
install_slice_context_log_sink();
|
||||
|
||||
if (!outdir.empty()) {
|
||||
boost::system::error_code ec;
|
||||
fs::create_directories(outdir, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Could not create output directory \"" << outdir << "\": " << ec.message();
|
||||
std::cout << "Validation failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "Saving sliced g-code to " << outdir << std::endl;
|
||||
}
|
||||
|
||||
PresetBundle bundle;
|
||||
bundle.set_is_validation_mode(true);
|
||||
bundle.set_vendor_to_validate(vendor); // empty == all vendors
|
||||
AppConfig app_config;
|
||||
app_config.set("preset_folder", "default");
|
||||
try {
|
||||
bundle.load_presets(app_config, ForwardCompatibilitySubstitutionRule::Disable);
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << ex.what();
|
||||
std::cout << "Validation failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Enable every instantiable model/variant in AppConfig - system printers are hidden until enabled;
|
||||
// without this select_preset_by_name silently falls back to the "Default Printer".
|
||||
std::vector<std::pair<std::string, std::string>> printers; // (vendor name, preset name)
|
||||
for (const Preset &p : bundle.printers.get_presets()) {
|
||||
if (p.vendor == nullptr) continue; // skips the Default Printer
|
||||
const std::string model = p.config.opt_string("printer_model");
|
||||
const std::string variant = p.config.opt_string("printer_variant");
|
||||
if (model.empty() || variant.empty()) continue; // skip non-instantiable base/common configs
|
||||
app_config.set_variant(p.vendor->id, model, variant, true);
|
||||
printers.push_back({p.vendor->name, p.name});
|
||||
}
|
||||
bundle.load_installed_printers(app_config);
|
||||
|
||||
if (printers.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "No instantiable printer presets found"
|
||||
<< (vendor.empty() ? "" : " for vendor " + vendor);
|
||||
std::cout << "Validation failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "Slicing " << printers.size() << " printer preset(s)"
|
||||
<< (vendor.empty() ? "" : " for vendor " + vendor) << "..." << std::endl;
|
||||
|
||||
int failures = 0;
|
||||
for (const auto &[vendor_name, printer] : printers) {
|
||||
g_slice_context = vendor_name + " / " + printer; // tag every engine log line from this slice
|
||||
const bool selected = bundle.printers.select_preset_by_name(printer, /*force=*/true);
|
||||
if (!selected || bundle.printers.get_selected_preset_name() != printer) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Printer preset \"" << printer << "\" could not be selected";
|
||||
++failures;
|
||||
continue;
|
||||
}
|
||||
|
||||
select_printer_default_presets(bundle); // slice with the printer's shipped process/filament
|
||||
bundle.update_multi_material_filament_presets(); // size filament_presets to nozzle count
|
||||
bundle.update_compatible(PresetSelectCompatibleType::Always);
|
||||
|
||||
// Never slice with a generic default preset - that would validate stand-in settings, not the
|
||||
// real profile (a legit per-profile error).
|
||||
if (bundle.prints.get_selected_preset().is_default || bundle.filaments.get_selected_preset().is_default) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Printer \"" << printer << "\" fell back to a default preset (process=\""
|
||||
<< bundle.prints.get_selected_preset_name() << "\", filament=\""
|
||||
<< bundle.filaments.get_selected_preset_name() << "\")";
|
||||
++failures;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Grow to a 2nd filament so the cube can change colour; never shrink a multi-nozzle printer
|
||||
// below its nozzle count, or full_config()'s flush-volume matrix no longer matches validate().
|
||||
const size_t nozzles = bundle.printers.get_selected_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->size();
|
||||
bundle.set_num_filaments((unsigned int) std::max<size_t>(2, nozzles));
|
||||
|
||||
// Mirror the app's manual filament->nozzle assignment for a multi-nozzle BBL printer: put each
|
||||
// filament on its own nozzle and pin the map (fmmManual) so full_config() collapses every filament to
|
||||
// the variant of the nozzle it actually prints from, and the engine keeps that assignment instead of
|
||||
// auto-remapping it during process(). Without this the synthetic 2nd filament keeps nozzle 1's variant
|
||||
// while the auto map moves it to nozzle 2 - harmless, but on the one printer whose nozzles differ in
|
||||
// type (Direct Drive + Bowden) the mismatched lookup spams [error] lines. Single-nozzle and non-BBL
|
||||
// printers keep the default map (their toolchange rides the AMS/tool-changer path unchanged).
|
||||
const bool pin_filament_map = bundle.is_bbl_vendor() && nozzles > 1;
|
||||
if (pin_filament_map) {
|
||||
auto &fmap = bundle.project_config.option<ConfigOptionInts>("filament_map", true)->values;
|
||||
for (size_t i = 0; i < fmap.size(); ++i)
|
||||
fmap[i] = int(i % nozzles) + 1;
|
||||
}
|
||||
|
||||
DynamicPrintConfig cfg = bundle.full_config();
|
||||
cfg.set_key_value("enable_prime_tower", new ConfigOptionBool(true)); // force a purge tower so the change is detectable
|
||||
// The map above drives full_config()'s per-filament variant collapse; fmmManual on the sliced config
|
||||
// stops process() from auto-remapping filaments back onto a different nozzle (which would re-introduce
|
||||
// the variant mismatch this pinning avoids).
|
||||
if (pin_filament_map)
|
||||
cfg.set_key_value("filament_map_mode", new ConfigOptionEnum<FilamentMapMode>(fmmManual));
|
||||
|
||||
// full_config() grows filament_extruder_variant to one entry per filament, but because the synthetic
|
||||
// 2nd filament is a duplicate of the first (set_num_filaments copies the same preset), it leaves
|
||||
// filament_self_index at size 1. That makes update_values_to_printer_extruders_for_multiple_filaments
|
||||
// fail to resolve the 2nd filament's variant - a benign fallback that spams [error] lines. A real
|
||||
// 2-colour project ships filament_self_index = 1,2,...; mirror that so the sweep log stays clean. The
|
||||
// slice output is unaffected: the duplicated filament's per-variant values are identical to the first.
|
||||
if (auto *variants = cfg.option<ConfigOptionStrings>("filament_extruder_variant")) {
|
||||
auto &self_index = cfg.option<ConfigOptionInts>("filament_self_index", true)->values;
|
||||
if (self_index.size() != variants->size()) {
|
||||
self_index.resize(variants->size());
|
||||
for (size_t i = 0; i < self_index.size(); ++i)
|
||||
self_index[i] = int(i) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const std::string out = slice_two_color_cube_and_export(cfg, bundle.is_bbl_vendor());
|
||||
if (!outdir.empty() && !out.empty()) {
|
||||
const fs::path f = fs::path(outdir) / (sanitize_filename(vendor_name) + "__" + sanitize_filename(printer) + ".gcode");
|
||||
save_string_file(f, out);
|
||||
}
|
||||
if (out.empty() || out.find("G1") == std::string::npos) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Printer \"" << printer << "\" produced no g-code";
|
||||
++failures;
|
||||
} else if (out.find("CP TOOLCHANGE START") == std::string::npos) {
|
||||
// The filament change never rode the tower, so change_filament_gcode was not exercised.
|
||||
BOOST_LOG_TRIVIAL(error) << "Printer \"" << printer
|
||||
<< "\" sliced but the filament change never fired (no CP TOOLCHANGE START)";
|
||||
++failures;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Printer \"" << printer << "\" failed to slice: " << ex.what();
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
g_slice_context.clear();
|
||||
|
||||
if (failures > 0) {
|
||||
std::cout << failures << " of " << printers.size() << " printer preset(s) failed to slice" << std::endl;
|
||||
std::cout << "Validation failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "All " << printers.size() << " printer preset(s) sliced successfully" << std::endl;
|
||||
std::cout << "Validation completed successfully" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
po::options_description desc("Orca Profile Validator\nUsage");
|
||||
@@ -95,6 +379,8 @@ int main(int argc, char* argv[])
|
||||
#endif
|
||||
("vendor,v", po::value<std::string>()->default_value(""), "Vendor name. Optional, all profiles present in the folder will be validated if not specified")
|
||||
("generate_presets,g", po::value<bool>()->default_value(false), "Generate user presets for mock test")
|
||||
("slice,s", po::bool_switch()->default_value(false), "Slice a two-colour cube through every printer to expand all custom g-code (catches placeholder/flow errors that static checks miss). Off unless this flag is present.")
|
||||
("outdir,o", po::value<std::string>()->default_value(""), "With -s, also save each printer's g-code to this folder (as <vendor>__<printer>.gcode) for manual inspection. Optional.")
|
||||
("check_filament_subtypes,f", po::bool_switch()->default_value(false), "Also flag printers with duplicate (ambiguous) filament subtypes. Off unless this flag is present.")
|
||||
("log_level,l", po::value<int>()->default_value(2), "Log level. Optional, default is 2 (warning). Higher values produce more detailed logs.");
|
||||
// clang-format on
|
||||
@@ -119,6 +405,8 @@ int main(int argc, char* argv[])
|
||||
std::string vendor = vm["vendor"].as<std::string>();
|
||||
int log_level = vm["log_level"].as<int>();
|
||||
bool generate_user_preset = vm["generate_presets"].as<bool>();
|
||||
bool slice_mode = vm["slice"].as<bool>();
|
||||
std::string slice_outdir = vm["outdir"].as<std::string>();
|
||||
bool check_filament_subtypes = vm["check_filament_subtypes"].as<bool>();
|
||||
|
||||
// check if path is valid, and return error if not
|
||||
@@ -132,6 +420,12 @@ int main(int argc, char* argv[])
|
||||
// std::cout<<"log_level: "<<log_level<<std::endl;
|
||||
|
||||
set_data_dir(path);
|
||||
// Orca: the profiles folder lives at <resources>/profiles, so point resources_dir() at that
|
||||
// <resources> parent. Without this, resources_dir() is empty and slice mode's HRC lookup
|
||||
// (info/nozzle_info.json) resolves to a non-existent relative path and falls back to a
|
||||
// built-in table (logging a spurious parse error and dropping the E3D entry).
|
||||
if (fs::exists(fs::path(path).parent_path() / "info"))
|
||||
set_resources_dir(fs::path(path).parent_path().string());
|
||||
|
||||
auto user_dir = fs::path(Slic3r::data_dir()) / PRESET_USER_DIR;
|
||||
user_dir.make_preferred();
|
||||
@@ -139,6 +433,12 @@ int main(int argc, char* argv[])
|
||||
fs::create_directory(user_dir);
|
||||
|
||||
set_logging_level(log_level);
|
||||
|
||||
// Slice mode expands every printer's custom g-code by actually slicing (see slice_all_printers).
|
||||
// A distinct opt-in mode so the default static checks stay fast for every profile PR.
|
||||
if (slice_mode)
|
||||
return slice_all_printers(vendor, slice_outdir);
|
||||
|
||||
auto preset_bundle = new PresetBundle();
|
||||
// preset_bundle->setup_directories();
|
||||
preset_bundle->set_is_validation_mode(true);
|
||||
|
||||
@@ -171,6 +171,33 @@ echo -n "[9/9] Generating Linux app..."
|
||||
fi
|
||||
cp -fl "${ORIGINAL_BINARY_LOCATION}" "$BIN_DIR/@SLIC3R_APP_CMD@"
|
||||
|
||||
# Bundle the embedded Python runtime (interpreter + stdlib) that CMake staged
|
||||
# next to the binary. OrcaSlicer resolves PYTHONHOME at <resources_dir>/../lib/python,
|
||||
# which is $APPDIR/lib/python (resources_dir is $APPDIR/resources). Staging it here,
|
||||
# before the dependency-closure pass below, lets that pass also bundle the extension
|
||||
# modules' shared libraries; libpython itself is found via rpath ($ORIGIN entries set
|
||||
# in src/CMakeLists.txt and by the dep recipe). Without this the plugin system cannot start.
|
||||
PYTHON_RUNTIME_SRC="$(dirname "${ORIGINAL_BINARY_LOCATION}")/python"
|
||||
if [ -d "$PYTHON_RUNTIME_SRC" ]; then
|
||||
echo "Bundling Python runtime from ${PYTHON_RUNTIME_SRC} ..."
|
||||
copy_directory_if_present "$PYTHON_RUNTIME_SRC" "$LIB_DIR/python"
|
||||
else
|
||||
echo "Warning: bundled Python runtime not found at ${PYTHON_RUNTIME_SRC}; plugin support will be disabled in this AppImage."
|
||||
fi
|
||||
|
||||
# Bundle the uv executable (CMake staged it next to the binary) so the plugin
|
||||
# system can install Python package dependencies. bundled_uv_path() looks under
|
||||
# <resources_dir>/tools/uv == $APPDIR/resources/tools/uv -- the same layout the
|
||||
# install()-based packagers (Windows/Flatpak/FHS) use. Unlike the AppImage, those
|
||||
# run `make install`; this build copies artifacts, so stage uv explicitly.
|
||||
UV_RUNTIME_SRC="$(dirname "${ORIGINAL_BINARY_LOCATION}")/tools/uv"
|
||||
if [ -d "$UV_RUNTIME_SRC" ]; then
|
||||
echo "Bundling uv from ${UV_RUNTIME_SRC} ..."
|
||||
copy_directory_if_present "$UV_RUNTIME_SRC" "$APPDIR/resources/tools/uv"
|
||||
else
|
||||
echo "Warning: bundled uv not found at ${UV_RUNTIME_SRC}; Python package installation will be unavailable in this AppImage."
|
||||
fi
|
||||
|
||||
if [ "$BUNDLE_DESKTOP_STACK" = "1" ]; then
|
||||
GSTREAMER_PLUGIN_SOURCE_DIR="$(find_pkg_config_dir pluginsdir gstreamer-1.0 || true)"
|
||||
if [ -z "$GSTREAMER_PLUGIN_SOURCE_DIR" ]; then
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Loader generated by glad 2.0.8 on Tue Apr 7 00:45:10 2026
|
||||
* Loader generated by glad 2.0.8 on Thu Jul 16 03:42:05 2026
|
||||
*
|
||||
* SPDX-License-Identifier: (WTFPL OR CC0-1.0) AND Apache-2.0
|
||||
*
|
||||
* Generator: C/C++
|
||||
* Specification: gl
|
||||
* Extensions: 5
|
||||
* Extensions: 7
|
||||
*
|
||||
* APIs:
|
||||
* - gl:compatibility=4.6
|
||||
@@ -19,10 +19,10 @@
|
||||
* - ON_DEMAND = False
|
||||
*
|
||||
* Commandline:
|
||||
* --api='gl:compatibility=4.6' --extensions='GL_ARB_compatibility,GL_ARB_framebuffer_object,GL_EXT_framebuffer_object,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic' c --loader
|
||||
* --api='gl:compatibility=4.6' --extensions='GL_ARB_compatibility,GL_ARB_framebuffer_object,GL_EXT_framebuffer_blit,GL_EXT_framebuffer_multisample,GL_EXT_framebuffer_object,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic' c --loader
|
||||
*
|
||||
* Online:
|
||||
* http://glad.sh/#api=gl%3Acompatibility%3D4.6&extensions=GL_ARB_compatibility%2CGL_ARB_framebuffer_object%2CGL_EXT_framebuffer_object%2CGL_EXT_texture_compression_s3tc%2CGL_EXT_texture_filter_anisotropic&generator=c&options=LOADER
|
||||
* http://glad.sh/#api=gl%3Acompatibility%3D4.6&extensions=GL_ARB_compatibility%2CGL_ARB_framebuffer_object%2CGL_EXT_framebuffer_blit%2CGL_EXT_framebuffer_multisample%2CGL_EXT_framebuffer_object%2CGL_EXT_texture_compression_s3tc%2CGL_EXT_texture_filter_anisotropic&generator=c&options=LOADER
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -616,6 +616,8 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
|
||||
#define GL_DRAW_BUFFER9 0x882E
|
||||
#define GL_DRAW_FRAMEBUFFER 0x8CA9
|
||||
#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6
|
||||
#define GL_DRAW_FRAMEBUFFER_BINDING_EXT 0x8CA6
|
||||
#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9
|
||||
#define GL_DRAW_INDIRECT_BUFFER 0x8F3F
|
||||
#define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43
|
||||
#define GL_DRAW_PIXEL_TOKEN 0x0705
|
||||
@@ -746,6 +748,7 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC
|
||||
#define GL_FRAMEBUFFER_RENDERABLE 0x8289
|
||||
@@ -1106,6 +1109,7 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
|
||||
#define GL_MAX_RENDERBUFFER_SIZE 0x84E8
|
||||
#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8
|
||||
#define GL_MAX_SAMPLES 0x8D57
|
||||
#define GL_MAX_SAMPLES_EXT 0x8D57
|
||||
#define GL_MAX_SAMPLE_MASK_WORDS 0x8E59
|
||||
#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111
|
||||
#define GL_MAX_SHADER_STORAGE_BLOCK_SIZE 0x90DE
|
||||
@@ -1400,6 +1404,8 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
|
||||
#define GL_READ_BUFFER 0x0C02
|
||||
#define GL_READ_FRAMEBUFFER 0x8CA8
|
||||
#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA
|
||||
#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA
|
||||
#define GL_READ_FRAMEBUFFER_EXT 0x8CA8
|
||||
#define GL_READ_ONLY 0x88B8
|
||||
#define GL_READ_PIXELS 0x828C
|
||||
#define GL_READ_PIXELS_FORMAT 0x828D
|
||||
@@ -1437,6 +1443,7 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
|
||||
#define GL_RENDERBUFFER_RED_SIZE 0x8D50
|
||||
#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50
|
||||
#define GL_RENDERBUFFER_SAMPLES 0x8CAB
|
||||
#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB
|
||||
#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55
|
||||
#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55
|
||||
#define GL_RENDERBUFFER_WIDTH 0x8D42
|
||||
@@ -2150,6 +2157,10 @@ GLAD_API_CALL int GLAD_GL_VERSION_4_6;
|
||||
GLAD_API_CALL int GLAD_GL_ARB_compatibility;
|
||||
#define GL_ARB_framebuffer_object 1
|
||||
GLAD_API_CALL int GLAD_GL_ARB_framebuffer_object;
|
||||
#define GL_EXT_framebuffer_blit 1
|
||||
GLAD_API_CALL int GLAD_GL_EXT_framebuffer_blit;
|
||||
#define GL_EXT_framebuffer_multisample 1
|
||||
GLAD_API_CALL int GLAD_GL_EXT_framebuffer_multisample;
|
||||
#define GL_EXT_framebuffer_object 1
|
||||
GLAD_API_CALL int GLAD_GL_EXT_framebuffer_object;
|
||||
#define GL_EXT_texture_compression_s3tc 1
|
||||
@@ -2205,6 +2216,7 @@ typedef void (GLAD_API_PTR *PFNGLBLENDFUNCSEPARATEPROC)(GLenum sfactorRGB, GLenu
|
||||
typedef void (GLAD_API_PTR *PFNGLBLENDFUNCSEPARATEIPROC)(GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
|
||||
typedef void (GLAD_API_PTR *PFNGLBLENDFUNCIPROC)(GLuint buf, GLenum src, GLenum dst);
|
||||
typedef void (GLAD_API_PTR *PFNGLBLITFRAMEBUFFERPROC)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
typedef void (GLAD_API_PTR *PFNGLBLITFRAMEBUFFEREXTPROC)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
typedef void (GLAD_API_PTR *PFNGLBLITNAMEDFRAMEBUFFERPROC)(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
typedef void (GLAD_API_PTR *PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void * data, GLenum usage);
|
||||
typedef void (GLAD_API_PTR *PFNGLBUFFERSTORAGEPROC)(GLenum target, GLsizeiptr size, const void * data, GLbitfield flags);
|
||||
@@ -2881,6 +2893,7 @@ typedef GLint (GLAD_API_PTR *PFNGLRENDERMODEPROC)(GLenum mode);
|
||||
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEEXTPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
typedef void (GLAD_API_PTR *PFNGLRESUMETRANSFORMFEEDBACKPROC)(void);
|
||||
typedef void (GLAD_API_PTR *PFNGLROTATEDPROC)(GLdouble angle, GLdouble x, GLdouble y, GLdouble z);
|
||||
typedef void (GLAD_API_PTR *PFNGLROTATEFPROC)(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
|
||||
@@ -3318,6 +3331,8 @@ GLAD_API_CALL PFNGLBLENDFUNCIPROC glad_glBlendFunci;
|
||||
#define glBlendFunci glad_glBlendFunci
|
||||
GLAD_API_CALL PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer;
|
||||
#define glBlitFramebuffer glad_glBlitFramebuffer
|
||||
GLAD_API_CALL PFNGLBLITFRAMEBUFFEREXTPROC glad_glBlitFramebufferEXT;
|
||||
#define glBlitFramebufferEXT glad_glBlitFramebufferEXT
|
||||
GLAD_API_CALL PFNGLBLITNAMEDFRAMEBUFFERPROC glad_glBlitNamedFramebuffer;
|
||||
#define glBlitNamedFramebuffer glad_glBlitNamedFramebuffer
|
||||
GLAD_API_CALL PFNGLBUFFERDATAPROC glad_glBufferData;
|
||||
@@ -4670,6 +4685,8 @@ GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEEXTPROC glad_glRenderbufferStorageEXT;
|
||||
#define glRenderbufferStorageEXT glad_glRenderbufferStorageEXT
|
||||
GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample;
|
||||
#define glRenderbufferStorageMultisample glad_glRenderbufferStorageMultisample
|
||||
GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glad_glRenderbufferStorageMultisampleEXT;
|
||||
#define glRenderbufferStorageMultisampleEXT glad_glRenderbufferStorageMultisampleEXT
|
||||
GLAD_API_CALL PFNGLRESUMETRANSFORMFEEDBACKPROC glad_glResumeTransformFeedback;
|
||||
#define glResumeTransformFeedback glad_glResumeTransformFeedback
|
||||
GLAD_API_CALL PFNGLROTATEDPROC glad_glRotated;
|
||||
|
||||
+22
-1
@@ -44,6 +44,8 @@ int GLAD_GL_VERSION_4_5 = 0;
|
||||
int GLAD_GL_VERSION_4_6 = 0;
|
||||
int GLAD_GL_ARB_compatibility = 0;
|
||||
int GLAD_GL_ARB_framebuffer_object = 0;
|
||||
int GLAD_GL_EXT_framebuffer_blit = 0;
|
||||
int GLAD_GL_EXT_framebuffer_multisample = 0;
|
||||
int GLAD_GL_EXT_framebuffer_object = 0;
|
||||
int GLAD_GL_EXT_texture_compression_s3tc = 0;
|
||||
int GLAD_GL_EXT_texture_filter_anisotropic = 0;
|
||||
@@ -97,6 +99,7 @@ PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate = NULL;
|
||||
PFNGLBLENDFUNCSEPARATEIPROC glad_glBlendFuncSeparatei = NULL;
|
||||
PFNGLBLENDFUNCIPROC glad_glBlendFunci = NULL;
|
||||
PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer = NULL;
|
||||
PFNGLBLITFRAMEBUFFEREXTPROC glad_glBlitFramebufferEXT = NULL;
|
||||
PFNGLBLITNAMEDFRAMEBUFFERPROC glad_glBlitNamedFramebuffer = NULL;
|
||||
PFNGLBUFFERDATAPROC glad_glBufferData = NULL;
|
||||
PFNGLBUFFERSTORAGEPROC glad_glBufferStorage = NULL;
|
||||
@@ -773,6 +776,7 @@ PFNGLRENDERMODEPROC glad_glRenderMode = NULL;
|
||||
PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage = NULL;
|
||||
PFNGLRENDERBUFFERSTORAGEEXTPROC glad_glRenderbufferStorageEXT = NULL;
|
||||
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample = NULL;
|
||||
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glad_glRenderbufferStorageMultisampleEXT = NULL;
|
||||
PFNGLRESUMETRANSFORMFEEDBACKPROC glad_glResumeTransformFeedback = NULL;
|
||||
PFNGLROTATEDPROC glad_glRotated = NULL;
|
||||
PFNGLROTATEFPROC glad_glRotatef = NULL;
|
||||
@@ -2249,6 +2253,14 @@ static void glad_gl_load_GL_ARB_framebuffer_object( GLADuserptrloadfunc load, vo
|
||||
glad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC) load(userptr, "glRenderbufferStorage");
|
||||
glad_glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC) load(userptr, "glRenderbufferStorageMultisample");
|
||||
}
|
||||
static void glad_gl_load_GL_EXT_framebuffer_blit( GLADuserptrloadfunc load, void* userptr) {
|
||||
if(!GLAD_GL_EXT_framebuffer_blit) return;
|
||||
glad_glBlitFramebufferEXT = (PFNGLBLITFRAMEBUFFEREXTPROC) load(userptr, "glBlitFramebufferEXT");
|
||||
}
|
||||
static void glad_gl_load_GL_EXT_framebuffer_multisample( GLADuserptrloadfunc load, void* userptr) {
|
||||
if(!GLAD_GL_EXT_framebuffer_multisample) return;
|
||||
glad_glRenderbufferStorageMultisampleEXT = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) load(userptr, "glRenderbufferStorageMultisampleEXT");
|
||||
}
|
||||
static void glad_gl_load_GL_EXT_framebuffer_object( GLADuserptrloadfunc load, void* userptr) {
|
||||
if(!GLAD_GL_EXT_framebuffer_object) return;
|
||||
glad_glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC) load(userptr, "glBindFramebufferEXT");
|
||||
@@ -2366,6 +2378,8 @@ static int glad_gl_find_extensions_gl(void) {
|
||||
|
||||
GLAD_GL_ARB_compatibility = glad_gl_has_extension(exts, exts_i, "GL_ARB_compatibility");
|
||||
GLAD_GL_ARB_framebuffer_object = glad_gl_has_extension(exts, exts_i, "GL_ARB_framebuffer_object");
|
||||
GLAD_GL_EXT_framebuffer_blit = glad_gl_has_extension(exts, exts_i, "GL_EXT_framebuffer_blit");
|
||||
GLAD_GL_EXT_framebuffer_multisample = glad_gl_has_extension(exts, exts_i, "GL_EXT_framebuffer_multisample");
|
||||
GLAD_GL_EXT_framebuffer_object = glad_gl_has_extension(exts, exts_i, "GL_EXT_framebuffer_object");
|
||||
GLAD_GL_EXT_texture_compression_s3tc = glad_gl_has_extension(exts, exts_i, "GL_EXT_texture_compression_s3tc");
|
||||
GLAD_GL_EXT_texture_filter_anisotropic = glad_gl_has_extension(exts, exts_i, "GL_EXT_texture_filter_anisotropic");
|
||||
@@ -2451,6 +2465,8 @@ int gladLoadGLUserPtr( GLADuserptrloadfunc load, void *userptr) {
|
||||
|
||||
if (!glad_gl_find_extensions_gl()) return 0;
|
||||
glad_gl_load_GL_ARB_framebuffer_object(load, userptr);
|
||||
glad_gl_load_GL_EXT_framebuffer_blit(load, userptr);
|
||||
glad_gl_load_GL_EXT_framebuffer_multisample(load, userptr);
|
||||
glad_gl_load_GL_EXT_framebuffer_object(load, userptr);
|
||||
|
||||
|
||||
@@ -2574,7 +2590,9 @@ static void* glad_gl_dlopen_handle(void) {
|
||||
"libGL-1.so",
|
||||
#endif
|
||||
"libGL.so.1",
|
||||
"libGL.so"
|
||||
"libGL.so",
|
||||
"libEGL.so.1",
|
||||
"libEGL.so"
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -2597,6 +2615,9 @@ static struct _glad_gl_userptr glad_gl_build_userptr(void *handle) {
|
||||
#else
|
||||
userptr.gl_get_proc_address_ptr =
|
||||
(GLADglprocaddrfunc) glad_dlsym_handle(handle, "glXGetProcAddressARB");
|
||||
if (!userptr.gl_get_proc_address_ptr)
|
||||
userptr.gl_get_proc_address_ptr =
|
||||
(GLADglprocaddrfunc) glad_dlsym_handle(handle, "eglGetProcAddress");
|
||||
#endif
|
||||
|
||||
return userptr;
|
||||
|
||||
@@ -202,6 +202,10 @@ void AppConfig::set_defaults()
|
||||
if (get("seq_top_layer_only").empty())
|
||||
set("seq_top_layer_only", "1");
|
||||
|
||||
// ORCA: darken layers below the current one while scrubbing the preview (ported from preFlight)
|
||||
if (get("preview_dim_previous_layers").empty())
|
||||
set_bool("preview_dim_previous_layers", false);
|
||||
|
||||
if (get("filaments_area_preferred_count").empty())
|
||||
set("filaments_area_preferred_count", "10");
|
||||
|
||||
@@ -304,6 +308,10 @@ void AppConfig::set_defaults()
|
||||
if (get("show_3d_navigator").empty())
|
||||
set_bool("show_3d_navigator", true);
|
||||
|
||||
// Show the one-time "Filament Track Switch is ready" tip until it has been seen once.
|
||||
if (get("show_fila_switch_tips").empty())
|
||||
set_bool("show_fila_switch_tips", true);
|
||||
|
||||
if (get("show_plate_gridlines").empty())
|
||||
set_bool("show_plate_gridlines", true);
|
||||
|
||||
@@ -579,9 +587,16 @@ void AppConfig::set_defaults()
|
||||
if (get("enable_step_mesh_setting").empty()) {
|
||||
set_bool("enable_step_mesh_setting", true);
|
||||
}
|
||||
if (get("linear_defletion", "angle_defletion").empty()) {
|
||||
set("linear_defletion", "0.003");
|
||||
set("angle_defletion", "0.5");
|
||||
// Migrate legacy misspelled keys (linear_defletion/angle_defletion) to the corrected spelling.
|
||||
if (get("linear_deflection").empty() && !get("linear_defletion").empty())
|
||||
set("linear_deflection", get("linear_defletion"));
|
||||
if (get("angle_deflection").empty() && !get("angle_defletion").empty())
|
||||
set("angle_deflection", get("angle_defletion"));
|
||||
if (get("linear_deflection").empty()) {
|
||||
set("linear_deflection", "0.003");
|
||||
}
|
||||
if (get("angle_deflection").empty()) {
|
||||
set("angle_deflection", "0.5");
|
||||
}
|
||||
if (get("is_split_compound").empty()) {
|
||||
set_bool("is_split_compound", false);
|
||||
@@ -793,6 +808,10 @@ std::string AppConfig::load()
|
||||
preset_info.nozzle_volume_type = NozzleVolumeType(cali_it.value()["nozzle_volume_type"].get<int>());
|
||||
if (cali_it.value().contains("bed_type"))
|
||||
preset_info.bed_type = BedType(cali_it.value()["bed_type"].get<int>());
|
||||
if (cali_it.value().contains("nozzle_pos_id"))
|
||||
preset_info.nozzle_pos_id = cali_it.value()["nozzle_pos_id"].get<int>();
|
||||
if (cali_it.value().contains("nozzle_sn"))
|
||||
preset_info.nozzle_sn = cali_it.value()["nozzle_sn"].get<std::string>();
|
||||
cali_info.selected_presets.push_back(preset_info);
|
||||
}
|
||||
}
|
||||
@@ -950,6 +969,8 @@ void AppConfig::save()
|
||||
preset_json["extruder_id"] = filament_preset.extruder_id;
|
||||
preset_json["nozzle_volume_type"] = int(filament_preset.nozzle_volume_type);
|
||||
preset_json["bed_type"] = int(filament_preset.bed_type);
|
||||
preset_json["nozzle_pos_id"] = filament_preset.nozzle_pos_id;
|
||||
preset_json["nozzle_sn"] = filament_preset.nozzle_sn;
|
||||
preset_json["nozzle_diameter"] = filament_preset.nozzle_diameter;
|
||||
preset_json["filament_id"] = filament_preset.filament_id;
|
||||
preset_json["setting_id"] = filament_preset.setting_id;
|
||||
|
||||
@@ -1660,7 +1660,7 @@ void SkeletalTrapezoidation::propagateBeadingsDownward(edge_t* edge_to_peak, ptr
|
||||
}
|
||||
|
||||
|
||||
SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius) const
|
||||
SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius)
|
||||
{
|
||||
assert(ratio_left_to_whole >= 0.0 && ratio_left_to_whole <= 1.0);
|
||||
Beading ret = interpolate(left, ratio_left_to_whole, right);
|
||||
@@ -1684,6 +1684,12 @@ SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beadin
|
||||
{ // We cant adjust to fit the next edge because there is no previous one?!
|
||||
return ret;
|
||||
}
|
||||
// ret follows the thicker of left/right, which can hold fewer insets than left when bead
|
||||
// count and thickness disagree; skip the adjustment rather than index ret past its end.
|
||||
if (next_inset_idx >= coord_t(ret.toolpath_locations.size()))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
assert(next_inset_idx < coord_t(left.toolpath_locations.size()));
|
||||
assert(left.toolpath_locations[next_inset_idx] <= switching_radius);
|
||||
assert(left.toolpath_locations[next_inset_idx + 1] >= switching_radius);
|
||||
@@ -1703,7 +1709,7 @@ SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beadin
|
||||
}
|
||||
|
||||
|
||||
SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right) const
|
||||
SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right)
|
||||
{
|
||||
assert(ratio_left_to_whole >= 0.0 && ratio_left_to_whole <= 1.0);
|
||||
float ratio_right_to_whole = 1.0 - ratio_left_to_whole;
|
||||
|
||||
@@ -488,7 +488,7 @@ protected:
|
||||
* beads.
|
||||
* \return The beading at the interpolated location.
|
||||
*/
|
||||
Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius) const;
|
||||
static Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius);
|
||||
|
||||
/*!
|
||||
* Subroutine of \ref interpolate(const Beading&, Ratio, const Beading&, coord_t)
|
||||
@@ -501,7 +501,7 @@ protected:
|
||||
* \param right One of the beadings to interpolate between.
|
||||
* \return The beading at the interpolated location.
|
||||
*/
|
||||
Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right) const;
|
||||
static Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right);
|
||||
|
||||
/*!
|
||||
* Get the beading at a certain node of the skeletal graph, or create one if
|
||||
|
||||
@@ -125,7 +125,7 @@ public:
|
||||
template<class It, class = IteratorOnly<It> > BoundingBox3Base(It from, It to)
|
||||
{
|
||||
if (from == to)
|
||||
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
|
||||
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor.");
|
||||
|
||||
auto it = from;
|
||||
this->min = it->template cast<typename PointType::Scalar>();
|
||||
|
||||
+62
-147
@@ -34,14 +34,14 @@ static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const P
|
||||
}
|
||||
// BBS: generate brim area by objs
|
||||
static void append_and_translate(ExPolygons& dst, const ExPolygons& src,
|
||||
const PrintInstance& instance, const Print& print, std::map<ObjectID, ExPolygons>& brimAreaMap) {
|
||||
const PrintInstance& instance, size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
|
||||
ExPolygons srcShifted = src;
|
||||
Point instance_shift = instance.shift_without_plate_offset();
|
||||
for (size_t src_idx = 0; src_idx < srcShifted.size(); ++src_idx)
|
||||
srcShifted[src_idx].translate(instance_shift);
|
||||
srcShifted = diff_ex(srcShifted, dst);
|
||||
//expolygons_append(dst, temp2);
|
||||
expolygons_append(brimAreaMap[instance.print_object->id()], std::move(srcShifted));
|
||||
expolygons_append(brimAreaMap[{ instance.print_object->id(), instance_idx }], std::move(srcShifted));
|
||||
}
|
||||
|
||||
static void append_and_translate(Polygons &dst, const Polygons &src, const PrintInstance &instance) {
|
||||
@@ -61,7 +61,7 @@ static bool use_brim_efc_outline(const PrintObject &object)
|
||||
&& object.config().raft_layers.value == 0;
|
||||
}
|
||||
|
||||
//ORCA: Helper for snapping painted ears to the EFC outline.
|
||||
//ORCA: Helper for projecting painted ears to the EFC outline.
|
||||
static bool closest_point_on_expolygons(const ExPolygons &polygons, const Point &from, Point &closest_out)
|
||||
{
|
||||
double min_dist2 = std::numeric_limits<double>::max();
|
||||
@@ -69,23 +69,22 @@ static bool closest_point_on_expolygons(const ExPolygons &polygons, const Point
|
||||
|
||||
for (const ExPolygon &poly : polygons) {
|
||||
for (int i = 0; i < poly.num_contours(); ++i) {
|
||||
const Point *candidate = poly.contour_or_hole(i).closest_point(from);
|
||||
if (candidate == nullptr)
|
||||
continue;
|
||||
const int64_t dx = int64_t(candidate->x()) - int64_t(from.x());
|
||||
const int64_t dy = int64_t(candidate->y()) - int64_t(from.y());
|
||||
const double dist2 = double(dx * dx + dy * dy);
|
||||
if (dist2 < min_dist2) {
|
||||
min_dist2 = dist2;
|
||||
closest_out = *candidate;
|
||||
found = true;
|
||||
const Lines lines = poly.contour_or_hole(i).lines();
|
||||
for (const Line &line : lines) {
|
||||
Point candidate;
|
||||
const double dist2 = line.distance_to_squared(from, &candidate);
|
||||
if (dist2 < min_dist2) {
|
||||
min_dist2 = dist2;
|
||||
closest_out = candidate;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
//ORCA: Helper for matching painted ears to their original island before EFC snapping.
|
||||
//ORCA: Helper for matching painted ears to their original island before EFC projection.
|
||||
static int find_containing_expolygon_index(const ExPolygons &polygons, const Point &from)
|
||||
{
|
||||
for (size_t idx = 0; idx < polygons.size(); ++idx) {
|
||||
@@ -95,7 +94,7 @@ static int find_containing_expolygon_index(const ExPolygons &polygons, const Poi
|
||||
return -1;
|
||||
}
|
||||
|
||||
//ORCA: Keep painted ear snapping on the matching island when using EFC outline.
|
||||
//ORCA: Keep painted ear projection on the matching island when using EFC outline.
|
||||
static bool closest_point_on_matching_island(const ExPolygons &raw_outline, const ExPolygons &efc_outline, const Point &from, Point &closest_out)
|
||||
{
|
||||
const int island_idx = find_containing_expolygon_index(raw_outline, from);
|
||||
@@ -106,6 +105,7 @@ static bool closest_point_on_matching_island(const ExPolygons &raw_outline, cons
|
||||
}
|
||||
return closest_point_on_expolygons(efc_outline, from, closest_out);
|
||||
}
|
||||
|
||||
//ORCA: Use post-processed first-layer slices (including EFC) for brim outline.
|
||||
// Returns ExPolygons of the bottom layer after all first-layer modifiers
|
||||
// (including elephant foot compensation, if enabled) have been applied.
|
||||
@@ -358,11 +358,12 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi
|
||||
if (brim_ear_points.size() <= 0) {
|
||||
return mouse_ears_ex;
|
||||
}
|
||||
//ORCA: Painted ears can snap to the EFC-adjusted outline when enabled.
|
||||
//ORCA: Painted ears follow the EFC-adjusted outline when enabled, while
|
||||
// preserving their position along the selected outline segment.
|
||||
const bool use_efc_outline = use_brim_efc_outline(*object);
|
||||
const ExPolygons &raw_outline = object->layers().front()->lslices;
|
||||
//ORCA: Lazily computed EFC-adjusted bottom outline.
|
||||
//Stored separately so we can avoid recomputation unless EFC snapping is used.
|
||||
//Stored separately so we can avoid recomputation unless EFC projection is used.
|
||||
ExPolygons efc_outline_storage;
|
||||
const ExPolygons* efc_outline = nullptr;
|
||||
|
||||
@@ -390,17 +391,17 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi
|
||||
int32_t pt_x = scale_(pos.x());
|
||||
int32_t pt_y = scale_(pos.y());
|
||||
|
||||
//ORCA: Snap painted ears to the EFC-adjusted outline when enabled.
|
||||
//ORCA: Project painted ears to the EFC-adjusted outline when enabled.
|
||||
if (use_efc_outline) {
|
||||
if (efc_outline == nullptr) {
|
||||
//ORCA: Compute EFC-adjusted outline lazily for painted ear snapping.
|
||||
//ORCA: Compute the EFC-adjusted outline lazily for painted ear projection.
|
||||
efc_outline_storage = get_print_object_bottom_layer_expolygons(*object);
|
||||
efc_outline = &efc_outline_storage;
|
||||
}
|
||||
|
||||
if (!efc_outline->empty()) {
|
||||
Point closest_point;
|
||||
//ORCA: Snap within the matching island to avoid drifting to another island.
|
||||
//ORCA: Project within the matching island to avoid drifting to another island.
|
||||
if (closest_point_on_matching_island(
|
||||
raw_outline,
|
||||
*efc_outline,
|
||||
@@ -419,8 +420,7 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi
|
||||
|
||||
//BBS: create all brims
|
||||
static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
const float no_brim_offset, std::map<ObjectID, ExPolygons>& brimAreaMap,
|
||||
std::map<ObjectID, ExPolygons>& supportBrimAreaMap,
|
||||
const float no_brim_offset, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap,
|
||||
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders)
|
||||
{
|
||||
@@ -469,7 +469,6 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
|
||||
ExPolygons brim_area_object;
|
||||
ExPolygons no_brim_area_object;
|
||||
ExPolygons brim_area_support;
|
||||
ExPolygons no_brim_area_support;
|
||||
Polygons holes_object;
|
||||
Polygons holes_support;
|
||||
@@ -570,16 +569,18 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
append(no_brim_area_object, objectIsland);
|
||||
|
||||
brimToWrite.at(object->id()).obj = false;
|
||||
for (const PrintInstance& instance : object->instances()) {
|
||||
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
|
||||
const PrintInstance& instance = object->instances()[instance_idx];
|
||||
if (!brim_area_object.empty())
|
||||
append_and_translate(brim_area, brim_area_object, instance, print, brimAreaMap);
|
||||
append_and_translate(brim_area, brim_area_object, instance, instance_idx, brimAreaMap);
|
||||
append_and_translate(no_brim_area, no_brim_area_object, instance);
|
||||
append_and_translate(holes, holes_object, instance);
|
||||
append_and_translate(objectIslands, objectIsland, instance);
|
||||
|
||||
}
|
||||
if (brimAreaMap.find(object->id()) != brimAreaMap.end())
|
||||
expolygons_append(brim_area, brimAreaMap[object->id()]);
|
||||
for (const auto& [key, areas] : brimAreaMap)
|
||||
if (key.object_id == object->id())
|
||||
expolygons_append(brim_area, areas);
|
||||
}
|
||||
support_material_extruder = object->config().support_filament;
|
||||
if (support_material_extruder == 0 && object->has_support_material()) {
|
||||
@@ -591,32 +592,12 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
if (support_material_extruder == extruderNo && brimToWrite.at(object->id()).sup) {
|
||||
if (!object->support_layers().empty() && object->support_layers().front()->support_type==stInnerNormal) {
|
||||
for (const Polygon& support_contour : object->support_layers().front()->support_fills.polygons_covered_by_spacing()) {
|
||||
// Brim will not be generated for supports
|
||||
/*
|
||||
if (has_outer_brim) {
|
||||
append(brim_area_support, diff_ex(offset_ex(support_contour, brim_width + brim_offset, jtRound, SCALED_RESOLUTION), offset_ex(support_contour, brim_offset)));
|
||||
}
|
||||
if (has_inner_brim || has_outer_brim)
|
||||
append(no_brim_area_support, offset_ex(support_contour, 0));
|
||||
*/
|
||||
no_brim_area_support.emplace_back(support_contour);
|
||||
}
|
||||
}
|
||||
// BBS
|
||||
if (!object->support_layers().empty() && object->support_layers().front()->support_type == stInnerTree) {
|
||||
for (const ExPolygon &ex_poly : object->support_layers().front()->lslices) {
|
||||
// BBS: additional brim width will be added if adhesion area is too small without brim
|
||||
float brim_width_mod = ex_poly.area() / ex_poly.contour.length() < scaled_half_min_adh_length
|
||||
&& brim_width < scaled_flow_width ? brim_width + scaled_additional_brim_width : brim_width;
|
||||
brim_width_mod = floor(brim_width_mod / scaled_flow_width / 2) * scaled_flow_width * 2;
|
||||
// Brim will not be generated for supports
|
||||
/*
|
||||
if (has_outer_brim) {
|
||||
append(brim_area_support, diff_ex(offset_ex(ex_poly.contour, brim_width_mod + brim_offset, jtRound, SCALED_RESOLUTION), offset_ex(ex_poly.contour, brim_offset)));
|
||||
}
|
||||
if (has_inner_brim)
|
||||
append(brim_area_support, diff_ex(offset_ex(ex_poly.holes, -brim_offset), offset_ex(ex_poly.holes, -brim_width - brim_offset)));
|
||||
*/
|
||||
if (!has_outer_brim)
|
||||
append(no_brim_area_support, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes));
|
||||
if (!has_inner_brim && !has_outer_brim)
|
||||
@@ -629,13 +610,9 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
}
|
||||
brimToWrite.at(object->id()).sup = false;
|
||||
for (const PrintInstance& instance : object->instances()) {
|
||||
if (!brim_area_support.empty())
|
||||
append_and_translate(brim_area, brim_area_support, instance, print, supportBrimAreaMap);
|
||||
append_and_translate(no_brim_area, no_brim_area_support, instance);
|
||||
append_and_translate(holes, holes_support, instance);
|
||||
}
|
||||
if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end())
|
||||
expolygons_append(brim_area, supportBrimAreaMap[object->id()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -675,28 +652,27 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
|
||||
}
|
||||
|
||||
if (brimAreaMap.find(object->id()) != brimAreaMap.end()) {
|
||||
brimAreaMap[object->id()] = diff_ex(brimAreaMap[object->id()], extruder_no_brim_area);
|
||||
}
|
||||
for (auto& [key, areas] : brimAreaMap)
|
||||
if (key.object_id == object->id())
|
||||
areas = diff_ex(areas, extruder_no_brim_area);
|
||||
|
||||
if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end())
|
||||
supportBrimAreaMap[object->id()] = diff_ex(supportBrimAreaMap[object->id()], extruder_no_brim_area);
|
||||
}
|
||||
|
||||
brim_area.clear();
|
||||
for (const PrintObject* object : print.objects()) {
|
||||
// BBS: brim should be contacted to at least one object's island or brim area
|
||||
if (brimAreaMap.find(object->id()) != brimAreaMap.end()) {
|
||||
for (auto map_it = brimAreaMap.begin(); map_it != brimAreaMap.end(); ++map_it) {
|
||||
if (map_it->first.object_id != object->id())
|
||||
continue;
|
||||
|
||||
// find other objects' brim area
|
||||
ExPolygons otherExPolys;
|
||||
for (const PrintObject* otherObject : print.objects()) {
|
||||
if ((otherObject->id() != object->id()) && (brimAreaMap.find(otherObject->id()) != brimAreaMap.end())) {
|
||||
expolygons_append(otherExPolys, brimAreaMap[otherObject->id()]);
|
||||
}
|
||||
}
|
||||
for (const auto& [other_key, other_areas] : brimAreaMap)
|
||||
if (other_key != map_it->first)
|
||||
expolygons_append(otherExPolys, other_areas);
|
||||
|
||||
auto tempArea = brimAreaMap[object->id()];
|
||||
brimAreaMap[object->id()].clear();
|
||||
auto tempArea = map_it->second;
|
||||
map_it->second.clear();
|
||||
|
||||
for (int ia = 0; ia != tempArea.size(); ++ia) {
|
||||
// find this object's other brim area
|
||||
@@ -708,9 +684,9 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
if (!intersection_ex(offsetedTa, objectIslands).empty() ||
|
||||
!intersection_ex(offsetedTa, otherExPoly).empty() ||
|
||||
!intersection_ex(offsetedTa, otherExPolys).empty())
|
||||
brimAreaMap[object->id()].push_back(tempArea[ia]);
|
||||
map_it->second.push_back(tempArea[ia]);
|
||||
}
|
||||
expolygons_append(brim_area, brimAreaMap[object->id()]);
|
||||
expolygons_append(brim_area, map_it->second);
|
||||
}
|
||||
}
|
||||
return brim_area;
|
||||
@@ -889,19 +865,15 @@ ExtrusionEntityCollection makeBrimInfillFromPlateCoordinates(const ExPolygons& s
|
||||
//BBS: an overload of the orignal brim generator that generates the brim by obj and by extruders
|
||||
void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_area,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
|
||||
std::map<ObjectInstanceID, ExtrusionEntityCollection>& brimMapByInstance,
|
||||
std::vector<std::pair<ObjectID, unsigned int>> &objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders,
|
||||
std::map<ObjectID, ExPolygons>* objectBrimAreasOut,
|
||||
std::map<ObjectID, ExPolygons>* supportBrimAreasOut)
|
||||
std::map<ObjectInstanceID, ExPolygons>* objectBrimAreasByInstanceOut)
|
||||
{
|
||||
std::map<ObjectID, double> brim_width_map;
|
||||
std::map<ObjectID, ExPolygons> brimAreaMap;
|
||||
std::map<ObjectID, ExPolygons> supportBrimAreaMap;
|
||||
std::map<ObjectInstanceID, ExPolygons> brimAreaMap;
|
||||
Flow flow = print.brim_flow();
|
||||
const auto scaled_resolution = scaled<double>(print.config().resolution.value);
|
||||
ExPolygons islands_area_ex = outer_inner_brim_area(print,
|
||||
float(flow.scaled_spacing()), brimAreaMap, supportBrimAreaMap, objPrintVec, printExtruders);
|
||||
float(flow.scaled_spacing()), brimAreaMap, objPrintVec, printExtruders);
|
||||
|
||||
// BBS: Find boundingbox of the first layer
|
||||
for (const ObjectID printObjID : print.print_object_ids()) {
|
||||
@@ -923,14 +895,10 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
||||
ex_poly_translated.translate(instance.shift_without_plate_offset());
|
||||
bbx.merge(get_extents(ex_poly_translated));
|
||||
}
|
||||
if (supportBrimAreaMap.find(printObjID) != supportBrimAreaMap.end()) {
|
||||
for (const ExPolygon& ex_poly : supportBrimAreaMap.at(printObjID))
|
||||
bbx.merge(get_extents(ex_poly.contour));
|
||||
}
|
||||
if (brimAreaMap.find(printObjID) != brimAreaMap.end()) {
|
||||
for (const ExPolygon& ex_poly : brimAreaMap.at(printObjID))
|
||||
bbx.merge(get_extents(ex_poly.contour));
|
||||
}
|
||||
for (const auto& [key, areas] : brimAreaMap)
|
||||
if (key.object_id == printObjID)
|
||||
for (const ExPolygon& ex_poly : areas)
|
||||
bbx.merge(get_extents(ex_poly.contour));
|
||||
object->firstLayerObjectBrimBoundingBox = bbx;
|
||||
}
|
||||
|
||||
@@ -943,77 +911,24 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
||||
islands_area[iia].translate(plate_shift);
|
||||
|
||||
// Orca: keep translated brim footprints for skirt grouping.
|
||||
auto translate_area_map = [plate_shift](const std::map<ObjectID, ExPolygons>& src) {
|
||||
std::map<ObjectID, ExPolygons> dst = src;
|
||||
auto translate_area_map = [plate_shift](const auto& src) {
|
||||
auto dst = src;
|
||||
for (auto& [_, areas] : dst)
|
||||
for (ExPolygon& area : areas)
|
||||
area.translate(plate_shift);
|
||||
return dst;
|
||||
};
|
||||
if (objectBrimAreasOut != nullptr)
|
||||
*objectBrimAreasOut = translate_area_map(brimAreaMap);
|
||||
if (supportBrimAreasOut != nullptr)
|
||||
*supportBrimAreasOut = translate_area_map(supportBrimAreaMap);
|
||||
if (objectBrimAreasByInstanceOut != nullptr)
|
||||
*objectBrimAreasByInstanceOut = translate_area_map(brimAreaMap);
|
||||
|
||||
const bool has_per_object_skirt_or_shield = print.config().skirt_type == stPerObject &&
|
||||
(print.has_skirt() || print.has_infinite_skirt());
|
||||
const bool combine_brims = print.config().combine_brims.value &&
|
||||
!has_per_object_skirt_or_shield &&
|
||||
print.config().print_sequence != PrintSequence::ByObject;
|
||||
|
||||
if (!combine_brims) {
|
||||
// Orca: Generate brims separately when brims cannot be combined.
|
||||
for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) {
|
||||
if (!iter->second.empty()) {
|
||||
brimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
|
||||
};
|
||||
}
|
||||
for (auto iter = supportBrimAreaMap.begin(); iter != supportBrimAreaMap.end(); ++iter) {
|
||||
if (!iter->second.empty()) {
|
||||
supportBrimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Orca: Unified brim mode.
|
||||
ExPolygons all_brims_merged;
|
||||
std::vector<ObjectID> brim_object_ids;
|
||||
|
||||
// Add all object brims
|
||||
for (auto& [obj_id, brims] : brimAreaMap) {
|
||||
if (!brims.empty()) {
|
||||
expolygons_append(all_brims_merged, brims);
|
||||
brim_object_ids.push_back(obj_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!all_brims_merged.empty()) {
|
||||
// Merge all brims into a single continuous area
|
||||
all_brims_merged = union_ex(all_brims_merged);
|
||||
|
||||
// Apply a tiny morphological cleanup to reduce boolean-union micro-artifacts.
|
||||
const float brim_cleanup_delta = std::max(float(scaled_resolution), float(SCALED_EPSILON));
|
||||
all_brims_merged = offset2_ex(all_brims_merged, brim_cleanup_delta, -brim_cleanup_delta, jtRound, scaled_resolution);
|
||||
|
||||
// Generate infill once for the merged brim area.
|
||||
ExtrusionEntityCollection merged_brim = makeBrimInfill(all_brims_merged, print, islands_area);
|
||||
|
||||
// In unified mode, assign the merged brim to a deterministic carrier object.
|
||||
// Pick the first object in print order that actually contributed brim area.
|
||||
ObjectID carrier_id;
|
||||
bool carrier_found = false;
|
||||
for (const auto& [obj_id, _extruder] : objPrintVec) {
|
||||
if (std::find(brim_object_ids.begin(), brim_object_ids.end(), obj_id) != brim_object_ids.end()) {
|
||||
carrier_id = obj_id;
|
||||
carrier_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!carrier_found)
|
||||
carrier_id = brim_object_ids.front();
|
||||
|
||||
brimMap[carrier_id] = std::move(merged_brim);
|
||||
}
|
||||
// Orca: Generate brims per object instance. If Combine brims is enabled,
|
||||
// Print::_make_skirt() will join the touching ones.
|
||||
for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) {
|
||||
if (!iter->second.empty()) {
|
||||
ExtrusionEntityCollection brim = makeBrimInfill(iter->second, print, islands_area);
|
||||
brimMap[iter->first.object_id].append(brim.entities);
|
||||
brimMapByInstance.emplace(iter->first, std::move(brim));
|
||||
};
|
||||
}
|
||||
}
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define slic3r_Brim_hpp_
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ObjectID.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
#include<map>
|
||||
@@ -12,17 +13,15 @@ namespace Slic3r {
|
||||
class Print;
|
||||
class ExtrusionEntityCollection;
|
||||
class PrintTryCancel;
|
||||
class ObjectID;
|
||||
|
||||
// Produce brim lines around those objects, that have the brim enabled.
|
||||
// Collect islands_area to be merged into the final 1st layer convex hull.
|
||||
void make_brim(const Print& print, PrintTryCancel try_cancel,
|
||||
Polygons& islands_area, std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
|
||||
std::map<ObjectInstanceID, ExtrusionEntityCollection>& brimMapByInstance,
|
||||
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders,
|
||||
std::map<ObjectID, ExPolygons>* objectBrimAreasOut = nullptr,
|
||||
std::map<ObjectID, ExPolygons>* supportBrimAreasOut = nullptr);
|
||||
std::map<ObjectInstanceID, ExPolygons>* objectBrimAreasByInstanceOut = nullptr);
|
||||
|
||||
ExtrusionEntityCollection makeBrimInfill(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area);
|
||||
ExtrusionEntityCollection makeBrimInfillFromPlateCoordinates(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area);
|
||||
|
||||
@@ -19,6 +19,7 @@ if (TARGET OpenVDB::openvdb)
|
||||
endif()
|
||||
|
||||
option(BUILD_SHARED_LIBS "Build shared libs" OFF)
|
||||
option(USE_SLIC3R_CONSOLE_LOG "Enable console logging in RelWithDebInfo builds" OFF)
|
||||
|
||||
# Vendored SolveSpace constraint solver (2D sketch solver backbone).
|
||||
if (SLIC3R_CAD)
|
||||
@@ -226,11 +227,10 @@ set(lisbslic3r_sources
|
||||
GCode/FanMover.hpp
|
||||
GCode/GCodeProcessor.cpp
|
||||
GCode/GCodeProcessor.hpp
|
||||
GCode/ElegooGCodeProcessorHelper.cpp
|
||||
GCode.hpp
|
||||
GCode/PchipInterpolatorHelper.cpp
|
||||
GCode/PchipInterpolatorHelper.hpp
|
||||
GCode/PostProcessor.cpp
|
||||
GCode/PostProcessor.hpp
|
||||
GCode/PressureEqualizer.cpp
|
||||
GCode/PressureEqualizer.hpp
|
||||
GCode/PrintExtents.cpp
|
||||
@@ -475,6 +475,8 @@ set(lisbslic3r_sources
|
||||
FilamentGroup.cpp
|
||||
FilamentGroupUtils.hpp
|
||||
FilamentGroupUtils.cpp
|
||||
MultiNozzleUtils.hpp
|
||||
MultiNozzleUtils.cpp
|
||||
GCode/ToolOrderUtils.hpp
|
||||
GCode/ToolOrderUtils.cpp
|
||||
FlushVolPredictor.hpp
|
||||
@@ -524,8 +526,12 @@ set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
|
||||
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(SET CMP0011 NEW)
|
||||
# CGAL's config resets policies (cmake_minimum_required ...3.23), so a plain SET
|
||||
# can't reach it; the default opts its Boost lookup into BoostConfig (CMP0167).
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0167 NEW)
|
||||
find_package(CGAL REQUIRED)
|
||||
find_package(OpenCV REQUIRED core)
|
||||
unset(CMAKE_POLICY_DEFAULT_CMP0167)
|
||||
cmake_policy(POP)
|
||||
|
||||
add_library(libslic3r_cgal STATIC
|
||||
@@ -562,6 +568,9 @@ endif ()
|
||||
encoding_check(libslic3r)
|
||||
|
||||
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
|
||||
if (USE_SLIC3R_CONSOLE_LOG)
|
||||
target_compile_definitions(libslic3r PRIVATE $<$<CONFIG:RelWithDebInfo>:SLIC3R_CONSOLE_LOG>)
|
||||
endif()
|
||||
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_include_directories(libslic3r SYSTEM PUBLIC ${EXPAT_INCLUDE_DIRS})
|
||||
|
||||
@@ -571,7 +580,7 @@ find_package(OpenCASCADE REQUIRED)
|
||||
target_include_directories(libslic3r SYSTEM PUBLIC ${OpenCASCADE_INCLUDE_DIR})
|
||||
|
||||
find_package(JPEG REQUIRED)
|
||||
find_package(draco REQUIRED)
|
||||
find_package(Draco REQUIRED)
|
||||
|
||||
set(OCCT_LIBS
|
||||
TKBool
|
||||
|
||||
@@ -188,6 +188,18 @@ ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta)
|
||||
return results;
|
||||
}
|
||||
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta, Clipper2Lib::JoinType joinType)
|
||||
{
|
||||
Clipper2Lib::Paths64 subject = Slic3rExPolygons_to_Paths64(expolygons);
|
||||
Clipper2Lib::ClipperOffset offsetter;
|
||||
offsetter.AddPaths(subject, joinType, Clipper2Lib::EndType::Polygon);
|
||||
Clipper2Lib::PolyPath64 polytree;
|
||||
offsetter.Execute(delta, polytree);
|
||||
ExPolygons results = PolyTreeToExPolygons(std::move(polytree));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
ExPolygons offset2_ex_2(const ExPolygons& expolygons, double delta1, double delta2)
|
||||
{
|
||||
// 1st offset
|
||||
|
||||
@@ -15,6 +15,7 @@ Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Pol
|
||||
ExPolygons union_ex_2(const Polygons &expolygons);
|
||||
ExPolygons union_ex_2(const ExPolygons &expolygons);
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta);
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta, Clipper2Lib::JoinType joinType);
|
||||
ExPolygons offset2_ex_2(const ExPolygons &expolygons, double delta1, double delta2);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
@@ -930,6 +934,77 @@ Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip)
|
||||
{ return _clipper_pl_closed(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); }
|
||||
|
||||
// Orca: Sort and orient open polyline fragments produced by clipping `source` with
|
||||
// intersection_pl(), so that they run in the same order and direction as the source
|
||||
// polyline. Clipping creates new endpoints at the clip boundary, but it keeps the
|
||||
// interior source vertices intact, so a fragment's position on the source path is
|
||||
// recovered exactly by looking its vertices up in the source. Fragments without any
|
||||
// surviving source vertex lie on a single source segment, found by a nearest-segment
|
||||
// search.
|
||||
void restore_source_path_order(const Slic3r::Polyline &source, Slic3r::Polylines &fragments)
|
||||
{
|
||||
const Points &src = source.points;
|
||||
if (src.size() < 2 || fragments.empty())
|
||||
return;
|
||||
|
||||
std::unordered_map<Point, size_t, PointHash> source_index;
|
||||
source_index.reserve(src.size());
|
||||
for (size_t i = 0; i < src.size(); ++ i)
|
||||
source_index.emplace(src[i], i);
|
||||
|
||||
// Sort key: index of the source vertex where the fragment starts, then the signed
|
||||
// offset of the fragment's start from that vertex, to order multiple fragments cut
|
||||
// from one long source segment.
|
||||
std::vector<std::pair<size_t, double>> keys(fragments.size());
|
||||
for (size_t n = 0; n < fragments.size(); ++ n) {
|
||||
Polyline &pl = fragments[n];
|
||||
const size_t npos = size_t(-1);
|
||||
size_t front = npos;
|
||||
size_t back = npos;
|
||||
for (const Point &pt : pl.points)
|
||||
if (auto it = source_index.find(pt); it != source_index.end()) {
|
||||
front = it->second;
|
||||
break;
|
||||
}
|
||||
for (auto i = pl.points.rbegin(); i != pl.points.rend(); ++ i)
|
||||
if (auto it = source_index.find(*i); it != source_index.end()) {
|
||||
back = it->second;
|
||||
break;
|
||||
}
|
||||
Vec2crd source_dir;
|
||||
if (front == npos) {
|
||||
// All vertices were created by clipping, thus the whole fragment lies on a
|
||||
// single source segment. Find that segment.
|
||||
double best = std::numeric_limits<double>::max();
|
||||
for (size_t i = 0; i + 1 < src.size(); ++ i)
|
||||
if (double d = Line::distance_to_squared(pl.first_point(), src[i], src[i + 1]); d < best) {
|
||||
best = d;
|
||||
front = i;
|
||||
}
|
||||
back = front;
|
||||
source_dir = src[front + 1] - src[front];
|
||||
} else
|
||||
source_dir = src[std::min(back + 1, src.size() - 1)] - src[front > 0 ? front - 1 : 0];
|
||||
if (front > back) {
|
||||
pl.reverse();
|
||||
std::swap(front, back);
|
||||
} else if (front == back &&
|
||||
(pl.last_point() - pl.first_point()).cast<double>().dot(source_dir.cast<double>()) < 0.)
|
||||
pl.reverse();
|
||||
const Vec2crd seg = src[std::min(front + 1, src.size() - 1)] - src[front];
|
||||
keys[n] = { front, (pl.first_point() - src[front]).cast<double>().dot(seg.cast<double>()) };
|
||||
}
|
||||
|
||||
std::vector<size_t> order(fragments.size());
|
||||
std::iota(order.begin(), order.end(), size_t(0));
|
||||
std::sort(order.begin(), order.end(), [&keys](size_t a, size_t b) { return keys[a] < keys[b]; });
|
||||
Polylines sorted;
|
||||
sorted.reserve(fragments.size());
|
||||
for (size_t n : order)
|
||||
sorted.emplace_back(std::move(fragments[n]));
|
||||
fragments = std::move(sorted);
|
||||
}
|
||||
|
||||
Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip)
|
||||
{
|
||||
// convert Lines to Polylines
|
||||
|
||||
@@ -528,6 +528,10 @@ Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r
|
||||
Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::Polygon &clip);
|
||||
Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::ExPolygon &clip);
|
||||
|
||||
// Orca: Sort and orient open polyline fragments produced by clipping `source` with
|
||||
// intersection_pl(), so that they run in the same order and direction as the source polyline.
|
||||
void restore_source_path_order(const Slic3r::Polyline &source, Slic3r::Polylines &fragments);
|
||||
|
||||
inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip)
|
||||
{
|
||||
return _clipper_ln(ClipperLib::ctIntersection, subject, clip);
|
||||
|
||||
+163
-3
@@ -4,6 +4,7 @@
|
||||
#include "LocalesUtils.hpp"
|
||||
#include "Preset.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
@@ -36,6 +37,8 @@ using namespace nlohmann;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
std::function<std::string(std::string, std::string)> ConfigBase::resolve_capability_fn = nullptr;
|
||||
|
||||
//BBS: add json support
|
||||
//static const std::string CONFIG_VERSION_KEY = "version";
|
||||
//static const std::string CONFIG_NAME_KEY = "name";
|
||||
@@ -84,12 +87,12 @@ std::string escape_strings_cstyle(const std::vector<std::string> &strs)
|
||||
// Separate the strings.
|
||||
(*outptr ++) = ';';
|
||||
const std::string &str = strs[j];
|
||||
// Is the string simple or complex? Complex string contains spaces, tabs, new lines and other
|
||||
// escapable characters. Empty string shall be quoted as well, if it is the only string in strs.
|
||||
// Is the string simple or complex? Complex string contains spaces, tabs, semicolons, new lines
|
||||
// and other escapable characters. Empty string shall be quoted as well, if it is the only string in strs.
|
||||
bool should_quote = strs.size() == 1 && str.empty();
|
||||
for (size_t i = 0; i < str.size(); ++ i) {
|
||||
char c = str[i];
|
||||
if (c == ' ' || c == '\t' || c == '\\' || c == '"' || c == '\r' || c == '\n') {
|
||||
if (c == ' ' || c == '\t' || c == ';' || c == '\\' || c == '"' || c == '\r' || c == '\n') {
|
||||
should_quote = true;
|
||||
break;
|
||||
}
|
||||
@@ -685,6 +688,32 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
|
||||
return success;
|
||||
}
|
||||
|
||||
double ConfigBase::get_abs_value_at(const t_config_option_key &opt_key, size_t index) const
|
||||
{
|
||||
const ConfigOption *raw_opt = this->option(opt_key);
|
||||
assert(raw_opt != nullptr);
|
||||
if (raw_opt->type() == coFloats) {
|
||||
return static_cast<const ConfigOptionFloats*>(raw_opt)->get_at(index);
|
||||
}
|
||||
if (raw_opt->type() == coFloatsOrPercents) {
|
||||
const ConfigDef *def = this->def();
|
||||
if (def == nullptr) throw NoDefinitionException(opt_key);
|
||||
const ConfigOptionDef *opt_def = def->get(opt_key);
|
||||
assert(opt_def != nullptr);
|
||||
|
||||
if (opt_def->ratio_over.empty()) {
|
||||
return 0;
|
||||
} else {
|
||||
const ConfigOption *ratio_opt = this->option(opt_def->ratio_over);
|
||||
assert(ratio_opt->type() == coFloats);
|
||||
const ConfigOptionFloats *ratio_values = static_cast<const ConfigOptionFloats *>(ratio_opt);
|
||||
return static_cast<const ConfigOptionFloatsOrPercents *>(raw_opt)->get_at(index).get_abs_value(ratio_values->get_at(index));
|
||||
}
|
||||
}
|
||||
|
||||
throw ConfigurationError("ConfigBase::get_abs_value_at(): Not a valid option type for get_abs_value_at()");
|
||||
}
|
||||
|
||||
// Return an absolute value of a possibly relative config variable.
|
||||
// For example, return absolute infill extrusion width, either from an absolute value, or relative to the layer height.
|
||||
double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
|
||||
@@ -1460,6 +1489,29 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo
|
||||
return std::move(substitutions_ctxt.substitutions);
|
||||
}
|
||||
|
||||
std::optional<PluginCapabilityRef> parse_capability_ref(const std::string& value)
|
||||
{
|
||||
// Capability references are stored as "<plugin_name>;<cloud_uuid>;<capability_name>".
|
||||
// The cloud UUID is empty for local plugins (two consecutive semicolons).
|
||||
if (value.empty())
|
||||
return std::nullopt;
|
||||
|
||||
const size_t first = value.find(';');
|
||||
if (first == std::string::npos)
|
||||
return std::nullopt;
|
||||
const size_t second = value.find(';', first + 1);
|
||||
if (second == std::string::npos)
|
||||
return std::nullopt;
|
||||
|
||||
std::string name = value.substr(0, first);
|
||||
std::string uuid = value.substr(first + 1, second - first - 1);
|
||||
std::string capability_name = value.substr(second + 1);
|
||||
if (name.empty() || capability_name.empty())
|
||||
return std::nullopt;
|
||||
|
||||
return PluginCapabilityRef{ std::move(name), std::move(capability_name), std::move(uuid) };
|
||||
}
|
||||
|
||||
//BBS: add json support
|
||||
void ConfigBase::save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const
|
||||
{
|
||||
@@ -1496,6 +1548,18 @@ void ConfigBase::save_to_json(const std::string &file, const std::string &name,
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize the top-level "plugins" manifest: the individual plugin-backed options keep bare
|
||||
// capability names; the full "name;uuid;capability" references are derived here (same helper as
|
||||
// update_plugin_manifest). Only with a resolver (GUI); without one (CLI/headless) leave whatever
|
||||
// the "plugins" option already serialized above, so a round-trip never drops the manifest.
|
||||
if (resolve_capability_fn) {
|
||||
std::vector<std::string> unique_refs = this->collect_plugin_manifest();
|
||||
if (unique_refs.empty())
|
||||
j.erase("plugins");
|
||||
else
|
||||
j["plugins"] = unique_refs;
|
||||
}
|
||||
|
||||
boost::nowide::ofstream c;
|
||||
c.open(file, std::ios::out | std::ios::trunc);
|
||||
c << j.dump(1, '\t') << std::endl;
|
||||
@@ -1527,6 +1591,69 @@ void ConfigBase::null_nullables()
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigBase::save_plugin_collection(const std::string& opt_key, const ConfigOption* opt, std::vector<std::string>& plugin_refs) const {
|
||||
// Full plugin capability references ("name;uuid;capability") can only be derived through the
|
||||
// resolver registered by the GUI once plugins are loaded. In non-GUI/headless contexts (e.g.
|
||||
// the CLI) it stays null, so skip silently rather than calling an empty std::function.
|
||||
if (!resolve_capability_fn)
|
||||
return;
|
||||
|
||||
// A plugin-backed option declares its capability type via ConfigOptionDef::plugin_type (the same
|
||||
// metadata PluginResolver::find_option_for_capability scans). Deriving off the def rather than a
|
||||
// per-key branch keeps this generic across every plugin-backed option.
|
||||
const ConfigDef* def = this->def();
|
||||
const ConfigOptionDef* opt_def = def ? def->get(opt_key) : nullptr;
|
||||
if (opt_def == nullptr || !opt_def->is_plugin_backed())
|
||||
return;
|
||||
const std::string& type = opt_def->plugin_type;
|
||||
|
||||
// Resolve a single bare capability value into its full reference and append it, skipping unset
|
||||
// values, capabilities that could not be resolved (resolver returns ""), and duplicates already
|
||||
// collected (preserving insertion order).
|
||||
const auto append_ref = [&plugin_refs, &type](const std::string& capability_value) {
|
||||
if (capability_value.empty())
|
||||
return;
|
||||
std::string ref = resolve_capability_fn(capability_value, type);
|
||||
if (!ref.empty() && std::find(plugin_refs.begin(), plugin_refs.end(), ref) == plugin_refs.end())
|
||||
plugin_refs.emplace_back(std::move(ref));
|
||||
};
|
||||
|
||||
// Scalar options carry a single capability name; vector options carry a list. Same scalar/vector
|
||||
// dispatch as PluginResolver::find_option_for_capability.
|
||||
if (const auto* string_option = dynamic_cast<const ConfigOptionString*>(opt))
|
||||
append_ref(string_option->value);
|
||||
else if (const auto* vector_option = dynamic_cast<const ConfigOptionVectorBase*>(opt))
|
||||
for (const std::string& val : vector_option->vserialize())
|
||||
append_ref(val);
|
||||
}
|
||||
|
||||
std::vector<std::string> ConfigBase::collect_plugin_manifest() const
|
||||
{
|
||||
std::vector<std::string> refs;
|
||||
if (!resolve_capability_fn)
|
||||
return refs;
|
||||
|
||||
// Each plugin-backed option (ConfigOptionDef::is_plugin_backed) contributes its resolved
|
||||
// reference(s) via save_plugin_collection, which appends in order and skips duplicates, so no
|
||||
// second de-duplication pass is needed here.
|
||||
for (const std::string& opt_key : this->keys())
|
||||
if (const ConfigOption* opt = this->option(opt_key))
|
||||
this->save_plugin_collection(opt_key, opt, refs);
|
||||
return refs;
|
||||
}
|
||||
|
||||
void ConfigBase::update_plugin_manifest()
|
||||
{
|
||||
// Writes the derived manifest back into this config's "plugins" option (save_to_json writes the
|
||||
// same manifest into a JSON document instead), so an in-memory backend config carries a resolved
|
||||
// manifest even when the source preset was never serialized (picked-but-unsaved). Without a
|
||||
// resolver (CLI/headless) leave whatever manifest was loaded from disk untouched.
|
||||
if (!resolve_capability_fn)
|
||||
return;
|
||||
if (auto* manifest = this->option<ConfigOptionStrings>("plugins", true))
|
||||
manifest->values = this->collect_plugin_manifest();
|
||||
}
|
||||
|
||||
DynamicConfig::DynamicConfig(const ConfigBase& rhs, const t_config_option_keys& keys)
|
||||
{
|
||||
for (const t_config_option_key& opt_key : keys)
|
||||
@@ -1886,6 +2013,39 @@ t_config_option_keys DynamicConfig::equal(const DynamicConfig &other) const
|
||||
return equal;
|
||||
}
|
||||
|
||||
double& DynamicConfig::opt_float(const t_config_option_key &opt_key, unsigned int idx)
|
||||
{
|
||||
if (ConfigOptionFloats *opt_floats = dynamic_cast<ConfigOptionFloats *>(this->option(opt_key))) {
|
||||
return opt_floats->get_at(idx);
|
||||
} else {
|
||||
ConfigOptionFloatsNullable *opt_floats_nullable = dynamic_cast<ConfigOptionFloatsNullable *>(this->option(opt_key));
|
||||
assert(opt_floats_nullable != nullptr);
|
||||
return opt_floats_nullable->get_at(idx);
|
||||
}
|
||||
}
|
||||
const double& DynamicConfig::opt_float(const t_config_option_key &opt_key, unsigned int idx) const
|
||||
{
|
||||
if (const ConfigOptionFloats *opt_floats = dynamic_cast<const ConfigOptionFloats *>(this->option(opt_key))) {
|
||||
return opt_floats->get_at(idx);
|
||||
} else if (const ConfigOptionFloatsNullable *opt_floats_nullable = dynamic_cast<const ConfigOptionFloatsNullable *>(this->option(opt_key))) {
|
||||
return opt_floats_nullable->get_at(idx);
|
||||
} else {
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool DynamicConfig::opt_bool(const t_config_option_key &opt_key, unsigned int idx) const {
|
||||
if (const ConfigOptionBools *opts = dynamic_cast<const ConfigOptionBools *>(this->option(opt_key))) {
|
||||
return opts->get_at(idx) != 0;
|
||||
}
|
||||
else {
|
||||
const ConfigOptionBoolsNullable *opt_s = dynamic_cast<const ConfigOptionBoolsNullable *>(this->option(opt_key));
|
||||
assert(opt_s != nullptr);
|
||||
return opt_s->get_at(idx) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#include <cereal/types/polymorphic.hpp>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -30,8 +31,14 @@
|
||||
namespace Slic3r {
|
||||
struct FloatOrPercent
|
||||
{
|
||||
double value;
|
||||
bool percent;
|
||||
double value = 0;
|
||||
bool percent = false;
|
||||
|
||||
FloatOrPercent() {}
|
||||
FloatOrPercent(double value_, bool percent_) : value(value_), percent(percent_) { }
|
||||
|
||||
double get_abs_value(double ratio_over) const { return this->percent ? (ratio_over * this->value / 100) : this->value; }
|
||||
|
||||
private:
|
||||
friend class cereal::access;
|
||||
template<class Archive> void serialize(Archive& ar) { ar(this->value); ar(this->percent); }
|
||||
@@ -357,6 +364,7 @@ public:
|
||||
virtual void set_with_restore(const ConfigOptionVectorBase* rhs, std::vector<int>& restore_index, int stride) = 0;
|
||||
virtual void set_with_restore_2(const ConfigOptionVectorBase* rhs, std::vector<int>& restore_index, int start, int len, bool skip_error = false) = 0;
|
||||
virtual void set_only_diff(const ConfigOptionVectorBase* rhs, std::vector<int>& diff_index, int stride) = 0;
|
||||
virtual void set_to_index(const ConfigOptionVectorBase* rhs, std::vector<int>& dest_index, int stride) = 0;
|
||||
virtual void set_with_nil(const ConfigOptionVectorBase* rhs, const ConfigOptionVectorBase* inherits, int stride) = 0;
|
||||
// Resize the vector of values, copy the newly added values from opt_default if provided.
|
||||
virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0;
|
||||
@@ -580,6 +588,32 @@ public:
|
||||
throw ConfigurationError("ConfigOptionVector::set_only_diff(): Assigning an incompatible type");
|
||||
}
|
||||
|
||||
//set a item related with extruder variants when apply static config with dynamic config
|
||||
//rhs: item from dynamic config
|
||||
//dest_index: which index in this vector need to be used
|
||||
virtual void set_to_index(const ConfigOptionVectorBase* rhs, std::vector<int>& dest_index, int stride) override
|
||||
{
|
||||
if (rhs->type() == this->type()) {
|
||||
// Assign the first value of the rhs vector.
|
||||
auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
|
||||
T v = other->values.front();
|
||||
this->values.resize(dest_index.size() * stride, v);
|
||||
|
||||
for (size_t i = 0; i < dest_index.size(); i++) {
|
||||
if (dest_index[i] < 0)
|
||||
continue;
|
||||
for (size_t j = 0; j < size_t(stride); j++)
|
||||
{
|
||||
const size_t src_idx = size_t(dest_index[i]) * size_t(stride) + j;
|
||||
if (src_idx < other->values.size() && !other->is_nil(size_t(dest_index[i]) * size_t(stride)))
|
||||
this->values[i * size_t(stride) + j] = other->values[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
throw ConfigurationError("ConfigOptionVector::set_to_index(): Assigning an incompatible type");
|
||||
}
|
||||
|
||||
//set a item related with extruder variants when saving user config, set the non-diff value of some extruder to nill
|
||||
//this item has different value with inherit config
|
||||
//rhs: item from userconfig
|
||||
@@ -710,6 +744,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
// Apply an override option, possibly a nullable one.
|
||||
//default_index are 0 based
|
||||
bool apply_override(const ConfigOption *rhs, std::vector<int>& default_index) override {
|
||||
if (this->nullable())
|
||||
throw ConfigurationError("Cannot override a nullable ConfigOption.");
|
||||
@@ -745,8 +780,8 @@ public:
|
||||
this->values[i] = rhs_vec->values[i];
|
||||
modified = true;
|
||||
} else {
|
||||
if ((i < default_index.size()) && (default_index[i] - 1 < default_value.size()))
|
||||
this->values[i] = default_value[default_index[i] - 1];
|
||||
if ((i < default_index.size()) && (default_index[i] < default_value.size()))
|
||||
this->values[i] = default_value[default_index[i]];
|
||||
else
|
||||
this->values[i] = default_value[0];
|
||||
}
|
||||
@@ -2220,6 +2255,9 @@ public:
|
||||
legend,
|
||||
// Vector value, but edited as a single string.
|
||||
one_string,
|
||||
plugin_picker,
|
||||
// Raw JSON string value, edited through a dialog behind a button rather than in the row.
|
||||
plugin_config,
|
||||
};
|
||||
|
||||
// Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map.
|
||||
@@ -2436,6 +2474,13 @@ public:
|
||||
// "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon.
|
||||
// "show_value" - even if enum_values / enum_labels are set, still display the value, not the enum label.
|
||||
std::string gui_flags;
|
||||
// Capability type of a plugin-backed option, e.g. "slicing-pipeline" / "printer-connection"
|
||||
// (empty for ordinary options). GUIType::plugin_picker filters the plugin list by it, and it
|
||||
// resolves the option's "plugins" manifest reference; see is_plugin_backed().
|
||||
std::string plugin_type;
|
||||
// Whether this option holds plugin capability name(s) that feed the "plugins" manifest -- true
|
||||
// iff it declares a plugin_type. Setting plugin_type is the only step needed to add one.
|
||||
bool is_plugin_backed() const { return !plugin_type.empty(); }
|
||||
// Label of the GUI input field.
|
||||
// In case the GUI input fields are grouped in some views, the label defines a short label of a grouped value,
|
||||
// while full_label contains a label of a stand-alone field.
|
||||
@@ -2726,6 +2771,7 @@ public:
|
||||
void set_deserialize_strict(std::initializer_list<SetDeserializeItem> items)
|
||||
{ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(items, ctxt); }
|
||||
|
||||
double get_abs_value_at(const t_config_option_key &opt_key, size_t index) const;
|
||||
double get_abs_value(const t_config_option_key &opt_key) const;
|
||||
double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const;
|
||||
void setenv_() const;
|
||||
@@ -2748,14 +2794,29 @@ public:
|
||||
//BBS: add json support
|
||||
void save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const;
|
||||
|
||||
// Rebuild the in-memory "plugins" manifest (the "name;uuid;capability" references the plugin
|
||||
// dispatchers consume) from the plugin-backed options via the registered resolver. save_to_json()
|
||||
// derives the same manifest, but only when a preset is written to disk; a config assembled in
|
||||
// memory for the backend (PresetBundle::full_config -> Print::apply) must refresh it here or a
|
||||
// picked-but-unsaved plugin never resolves at slice/export time. No-op without a resolver.
|
||||
void update_plugin_manifest();
|
||||
|
||||
// Set all the nullable values to nils.
|
||||
void null_nullables();
|
||||
|
||||
static size_t load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions);
|
||||
|
||||
static void set_resolve_capability_fn(std::function<std::string(std::string, std::string)> fn) { resolve_capability_fn = fn; }
|
||||
private:
|
||||
// Set a configuration value from a string.
|
||||
bool set_deserialize_raw(const t_config_option_key& opt_key_src, const std::string& value, ConfigSubstitutionContext& substitutions, bool append);
|
||||
void save_plugin_collection(const std::string& opt_key, const ConfigOption* opt, std::vector<std::string>& plugin_refs) const;
|
||||
// Collect the de-duplicated "name;uuid;capability" plugin references derived from this config's
|
||||
// plugin-backed options via the resolver. Shared by save_to_json (serializes them into the JSON
|
||||
// manifest) and update_plugin_manifest (writes them back into the "plugins" option). Order is
|
||||
// preserved and empties are dropped; returns empty without a resolver (CLI/headless).
|
||||
std::vector<std::string> collect_plugin_manifest() const;
|
||||
|
||||
static std::function<std::string(std::string, std::string)> resolve_capability_fn;
|
||||
};
|
||||
|
||||
// Configuration store with dynamic number of configuration values.
|
||||
@@ -2900,13 +2961,17 @@ public:
|
||||
|
||||
double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; }
|
||||
const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
|
||||
double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
|
||||
const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
|
||||
double & opt_float(const t_config_option_key &opt_key, unsigned int idx);
|
||||
const double & opt_float(const t_config_option_key &opt_key, unsigned int idx) const;
|
||||
double & opt_float_nullable(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloatsNullable>(opt_key)->get_at(idx); }
|
||||
const double & opt_float_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloatsNullable *>(this->option(opt_key))->get_at(idx); }
|
||||
|
||||
int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
|
||||
int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
|
||||
int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
|
||||
int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(this->option(opt_key))->get_at(idx); }
|
||||
int& opt_int_nullable(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionIntsNullable>(opt_key)->get_at(idx);}
|
||||
const int & opt_int_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionIntsNullable*>(this->option(opt_key))->get_at(idx);}
|
||||
|
||||
// In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*.
|
||||
// Thus the virtual method getInt() is used to retrieve the enum value.
|
||||
@@ -2914,9 +2979,13 @@ public:
|
||||
ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast<ENUM>(this->option(opt_key)->getInt()); }
|
||||
// BBS
|
||||
int opt_enum(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionEnumsGeneric*>(this->option(opt_key))->get_at(idx); }
|
||||
int opt_enum_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionEnumsGenericNullable*>(this->option(opt_key))->get_at(idx); }
|
||||
|
||||
|
||||
bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
|
||||
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
|
||||
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const;
|
||||
bool opt_bool_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionBoolsNullable*>(this->option(opt_key))->get_at(idx);}
|
||||
|
||||
|
||||
// Command line processing
|
||||
bool read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys = nullptr);
|
||||
@@ -2984,6 +3053,15 @@ protected:
|
||||
void set_defaults();
|
||||
};
|
||||
|
||||
struct PluginCapabilityRef
|
||||
{
|
||||
std::string name;
|
||||
std::string capability_name;
|
||||
std::string uuid;
|
||||
};
|
||||
|
||||
std::optional<PluginCapabilityRef> parse_capability_ref(const std::string& value);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -13,11 +13,20 @@ Extruder::Extruder(unsigned int id, GCodeConfig *config, bool share_extruder) :
|
||||
{
|
||||
reset();
|
||||
|
||||
m_config_index = int(m_id);
|
||||
// cache values that are going to be called often
|
||||
m_e_per_mm3 = this->filament_flow_ratio();
|
||||
m_e_per_mm3 /= this->filament_crossection();
|
||||
}
|
||||
|
||||
void Extruder::set_config_index(int idx)
|
||||
{
|
||||
m_config_index = idx < 0 ? int(m_id) : idx;
|
||||
// keep the cached flow term reading the same column as the getters
|
||||
m_e_per_mm3 = this->filament_flow_ratio();
|
||||
m_e_per_mm3 /= this->filament_crossection();
|
||||
}
|
||||
|
||||
unsigned int Extruder::extruder_id() const
|
||||
{
|
||||
assert(m_config);
|
||||
@@ -162,28 +171,35 @@ double Extruder::filament_cost() const
|
||||
|
||||
double Extruder::filament_flow_ratio() const
|
||||
{
|
||||
return m_config->filament_flow_ratio.get_at(m_id);
|
||||
return m_config->filament_flow_ratio.get_at(m_config_index);
|
||||
}
|
||||
|
||||
// Return a "retract_before_wipe" percentage as a factor clamped to <0, 1>
|
||||
double Extruder::retract_before_wipe() const
|
||||
{
|
||||
return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_id) * 0.01));
|
||||
return std::clamp(m_config->retract_before_wipe.get_at(m_config_index) * 0.01, 0., 1.);
|
||||
}
|
||||
|
||||
// Orca:
|
||||
// Return a "retract_after_wipe" percentage as a factor clamped to <0, 1>
|
||||
double Extruder::retract_after_wipe() const
|
||||
{
|
||||
return std::min(std::clamp(m_config->retract_after_wipe.get_at(m_config_index) * 0.01, 0., 1.), 1. - retract_before_wipe());
|
||||
}
|
||||
|
||||
double Extruder::retraction_length() const
|
||||
{
|
||||
return m_config->retraction_length.get_at(m_id);
|
||||
return m_config->retraction_length.get_at(m_config_index);
|
||||
}
|
||||
|
||||
double Extruder::retract_lift() const
|
||||
{
|
||||
return m_config->z_hop.get_at(m_id);
|
||||
return m_config->z_hop.get_at(m_config_index);
|
||||
}
|
||||
|
||||
int Extruder::retract_speed() const
|
||||
{
|
||||
return int(floor(m_config->retraction_speed.get_at(m_id)+0.5));
|
||||
return int(floor(m_config->retraction_speed.get_at(m_config_index)+0.5));
|
||||
}
|
||||
|
||||
bool Extruder::use_firmware_retraction() const
|
||||
@@ -193,13 +209,13 @@ bool Extruder::use_firmware_retraction() const
|
||||
|
||||
int Extruder::deretract_speed() const
|
||||
{
|
||||
int speed = int(floor(m_config->deretraction_speed.get_at(m_id)+0.5));
|
||||
int speed = int(floor(m_config->deretraction_speed.get_at(m_config_index)+0.5));
|
||||
return (speed > 0) ? speed : this->retract_speed();
|
||||
}
|
||||
|
||||
double Extruder::retract_restart_extra() const
|
||||
{
|
||||
return m_config->retract_restart_extra.get_at(m_id);
|
||||
return m_config->retract_restart_extra.get_at(m_config_index);
|
||||
}
|
||||
|
||||
double Extruder::retract_length_toolchange() const
|
||||
@@ -214,6 +230,8 @@ double Extruder::retract_restart_extra_toolchange() const
|
||||
|
||||
double Extruder::travel_slope() const
|
||||
{
|
||||
// Orca: deliberately keyed by the physical extruder, not the filament column — this read
|
||||
// predates the per-variant merge and switching it would change existing multi-extruder output.
|
||||
return m_config->travel_slope.get_at(extruder_id()) * PI / 180;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,13 @@ public:
|
||||
|
||||
unsigned int id() const { return m_id; }
|
||||
|
||||
// Column of the per-variant filament/override arrays the getters read. Defaults to the
|
||||
// filament id (one column per filament); the g-code generator refreshes it on layer changes
|
||||
// and toolchanges when a per-layer nozzle grouping gives a filament several variant columns.
|
||||
int config_index() const { return m_config_index; }
|
||||
// idx < 0 resets to the filament id. Re-syncs the cached e_per_mm3 flow term.
|
||||
void set_config_index(int idx);
|
||||
|
||||
unsigned int extruder_id() const;
|
||||
double extrude(double dE);
|
||||
double retract(double length, double restart_extra);
|
||||
@@ -51,6 +58,10 @@ public:
|
||||
double retracted() const { return m_retracted; }
|
||||
// Get extra retraction planned after
|
||||
double restart_extra() const { return m_restart_extra; }
|
||||
// Share-aware retracted-length readers (for extruders shared between filaments), consumed by GCodeWriter::get_extruder_retracted_length.
|
||||
bool is_share_extruder() const { return m_share_extruder; }
|
||||
double get_single_retracted_length() const { return m_retracted; }
|
||||
double get_share_retracted_length() const { return m_share_retracted[extruder_id()]; }
|
||||
// Setters for the PlaceholderParser.
|
||||
// Set current extruder position. Only applicable with absolute extruder addressing.
|
||||
void set_position(double e) { m_E = e; }
|
||||
@@ -63,6 +74,8 @@ public:
|
||||
double filament_cost() const;
|
||||
double filament_flow_ratio() const;
|
||||
double retract_before_wipe() const;
|
||||
// Orca:
|
||||
double retract_after_wipe() const;
|
||||
double retraction_length() const;
|
||||
double retract_lift() const;
|
||||
int retract_speed() const;
|
||||
@@ -82,6 +95,8 @@ private:
|
||||
GCodeConfig *m_config;
|
||||
// Print-wide global ID of this extruder.
|
||||
unsigned int m_id;
|
||||
// Column into the per-variant filament/override arrays; equals m_id unless refreshed.
|
||||
int m_config_index{0};
|
||||
// Current state of the extruder axis, may be resetted if use_relative_e_distances.
|
||||
double m_E;
|
||||
// Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics.
|
||||
|
||||
@@ -96,12 +96,21 @@ inline bool is_solid_infill(ExtrusionRole role)
|
||||
|| role == erIroning;
|
||||
}
|
||||
|
||||
inline bool is_bridge(ExtrusionRole role) {
|
||||
inline bool is_bridge(ExtrusionRole role)
|
||||
{
|
||||
return role == erBridgeInfill
|
||||
|| role == erInternalBridgeInfill
|
||||
|| role == erOverhangPerimeter;
|
||||
}
|
||||
|
||||
// Orca
|
||||
inline bool is_support(ExtrusionRole role)
|
||||
{
|
||||
return role == erSupportMaterial
|
||||
|| role == erSupportMaterialInterface
|
||||
|| role == erSupportTransition;
|
||||
}
|
||||
|
||||
class ExtrusionEntity
|
||||
{
|
||||
public:
|
||||
|
||||
+932
-344
File diff suppressed because it is too large
Load Diff
+112
-46
@@ -13,7 +13,8 @@
|
||||
|
||||
const static int DEFAULT_CLUSTER_SIZE = 16;
|
||||
|
||||
const static int ABSOLUTE_FLUSH_GAP_TOLERANCE = 5;
|
||||
const static int ABSOLUTE_FLUSH_GAP_TOLERANCE = 10;
|
||||
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
@@ -52,12 +53,12 @@ namespace Slic3r
|
||||
|
||||
struct MemoryedGroup {
|
||||
MemoryedGroup() = default;
|
||||
MemoryedGroup(const std::vector<int>& group_, const int cost_, const int prefer_level_) :group(group_), cost(cost_), prefer_level(prefer_level_) {}
|
||||
MemoryedGroup(const std::vector<int>& group_, const double cost_, const int prefer_level_) :group(group_), cost(cost_), prefer_level(prefer_level_) {}
|
||||
bool operator>(const MemoryedGroup& other) const {
|
||||
return prefer_level < other.prefer_level || (prefer_level == other.prefer_level && cost > other.cost);
|
||||
}
|
||||
|
||||
int cost{ 0 };
|
||||
double cost{ 0 };
|
||||
int prefer_level{ 0 };
|
||||
std::vector<int>group;
|
||||
};
|
||||
@@ -75,6 +76,7 @@ namespace Slic3r
|
||||
std::vector<FilamentGroupUtils::FilamentInfo> filament_info;
|
||||
std::vector<std::string> filament_ids;
|
||||
std::vector<std::set<int>> unprintable_filaments;
|
||||
std::map<int, std::set<NozzleVolumeType>> unprintable_volumes;
|
||||
} model_info;
|
||||
|
||||
struct GroupInfo {
|
||||
@@ -82,40 +84,74 @@ namespace Slic3r
|
||||
double max_gap_threshold;
|
||||
FGMode mode;
|
||||
FGStrategy strategy;
|
||||
bool ignore_ext_filament; //wai gua filament
|
||||
bool ignore_ext_filament;
|
||||
bool has_filament_switcher = false;
|
||||
std::vector<int> filament_volume_map;
|
||||
} group_info;
|
||||
|
||||
struct MachineInfo {
|
||||
std::vector<int> max_group_size;
|
||||
std::vector<std::vector<FilamentGroupUtils::MachineFilamentInfo>> machine_filament_info;
|
||||
std::vector<std::pair<std::set<int>, int>> extruder_group_size;
|
||||
std::vector<bool> prefer_non_model_filament;
|
||||
int master_extruder_id;
|
||||
} machine_info;
|
||||
|
||||
struct SpeedInfo{
|
||||
std::unordered_map<int,std::unordered_map<int,double>> filament_print_time;
|
||||
double extruder_change_time;
|
||||
double filament_change_time;
|
||||
bool group_with_time;
|
||||
MultiNozzleUtils::FilamentChangeTimeParams change_time_params;
|
||||
std::vector<bool> ams_preload_enabled;
|
||||
} speed_info;
|
||||
|
||||
struct NozzleInfo {
|
||||
std::map<int, std::vector<int>> extruder_nozzle_list;
|
||||
std::vector<MultiNozzleUtils::NozzleInfo> nozzle_list;
|
||||
std::unordered_map<int, int> nozzle_status;
|
||||
} nozzle_info;
|
||||
};
|
||||
|
||||
std::vector<int> select_best_group_for_ams(const std::vector<std::vector<int>>& map_lists,
|
||||
std::vector<int> select_best_group_for_ams(const std::vector<std::vector<int>> &filament_to_nozzles,
|
||||
const std::vector<MultiNozzleUtils::NozzleInfo>& nozzle_list,
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<FilamentGroupUtils::FilamentInfo>& used_filament_info,
|
||||
const std::vector<std::vector<FilamentGroupUtils::MachineFilamentInfo>>& machine_filament_info,
|
||||
const bool has_filament_switcher = false,
|
||||
const double color_delta_threshold = 20);
|
||||
|
||||
std::vector<int> optimize_group_for_master_extruder(const std::vector<unsigned int>& used_filaments, const FilamentGroupContext& ctx, const std::vector<int>& filament_map);
|
||||
|
||||
bool can_swap_groups(const int extruder_id_0, const std::set<int>& group_0, const int extruder_id_1, const std::set<int>& group_1, const FilamentGroupContext& ctx);
|
||||
|
||||
std::vector<int> calc_filament_group_for_tpu(const std::set<int>& tpu_filaments, const int filament_nums, const int master_extruder_id);
|
||||
|
||||
class FlushDistanceEvaluator
|
||||
{
|
||||
public:
|
||||
FlushDistanceEvaluator(const FlushMatrix& flush_matrix,const std::vector<unsigned int>&used_filaments,const std::vector<std::vector<unsigned int>>& layer_filaments, double p = 0.65);
|
||||
FlushDistanceEvaluator(const std::vector<FlushMatrix>& flush_matrix,const std::vector<unsigned int>&used_filaments,const std::vector<std::vector<unsigned int>>& layer_filaments, double p = 0.65);
|
||||
~FlushDistanceEvaluator() = default;
|
||||
double get_distance(int idx_a, int idx_b) const;
|
||||
double get_distance(int idx_a, int idx_b, int extruder_id) const;
|
||||
private:
|
||||
std::vector<std::vector<float>>m_distance_matrix;
|
||||
std::vector<std::vector<std::vector<float>>>m_distance_matrix;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class TimeEvaluator
|
||||
{
|
||||
public:
|
||||
TimeEvaluator(const FilamentGroupContext::SpeedInfo& speed_info) : m_speed_info(speed_info) {}
|
||||
double get_estimated_time(const std::vector<int>& filament_map) const;
|
||||
private:
|
||||
FilamentGroupContext::SpeedInfo m_speed_info;
|
||||
};
|
||||
|
||||
// Search budget for the k-medoids clustering, an anytime search. Each restart is seeded from its
|
||||
// own index, so what it returns depends on how many restarts complete before the clock expires,
|
||||
// and therefore on the speed of the machine. A timeout_ms <= 0 removes the clock and bounds the
|
||||
// search by max_restarts alone.
|
||||
struct ClusteringBudget
|
||||
{
|
||||
int timeout_ms = 3000;
|
||||
int max_restarts = 30;
|
||||
};
|
||||
|
||||
class FilamentGroup
|
||||
{
|
||||
using MemoryedGroup = FilamentGroupUtils::MemoryedGroup;
|
||||
@@ -123,17 +159,25 @@ namespace Slic3r
|
||||
public:
|
||||
explicit FilamentGroup(const FilamentGroupContext& ctx_) :ctx(ctx_) {}
|
||||
public:
|
||||
void set_clustering_budget(const ClusteringBudget& budget) { m_clustering_budget = budget; }
|
||||
|
||||
std::vector<int> calc_filament_group(int * cost = nullptr);
|
||||
std::vector<std::vector<int>> get_memoryed_groups()const { return m_memoryed_groups; }
|
||||
|
||||
public:
|
||||
std::vector<int> calc_filament_group_for_match(int* cost = nullptr);
|
||||
std::vector<int> calc_filament_group_for_flush(int* cost = nullptr);
|
||||
|
||||
std::vector<int> calc_filament_group_for_tpu(int* cost = nullptr);
|
||||
private:
|
||||
std::vector<int> calc_min_flush_group(int* cost = nullptr);
|
||||
std::vector<int> calc_min_flush_group_by_enum(const std::vector<unsigned int>& used_filaments, int* cost = nullptr);
|
||||
std::vector<int> calc_min_flush_group_by_pam2(const std::vector<unsigned int>& used_filaments, int* cost = nullptr, int timeout_ms = 300);
|
||||
|
||||
std::vector<int> calc_group_by_enum(int k, const std::vector<unsigned int>& used_filaments,
|
||||
const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr);
|
||||
std::vector<int> calc_group_by_kmedoids(int k, const std::vector<unsigned int>& used_filaments,
|
||||
const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr);
|
||||
|
||||
std::map<int, int> rebuild_unprintables(const std::vector<unsigned int>& used_filaments, const std::map<int,int>& extruder_unprintables);
|
||||
std::unordered_map<int, std::vector<int>> rebuild_nozzle_unprintables(const std::vector<unsigned int>& used_filaments, const std::unordered_map<int, std::vector<int>>& extruder_unprintables, const std::vector<int>& filament_volume_map);
|
||||
|
||||
std::unordered_map<int, std::vector<int>> try_merge_filaments();
|
||||
void rebuild_context(const std::unordered_map<int, std::vector<int>>& merged_filaments);
|
||||
@@ -141,57 +185,79 @@ namespace Slic3r
|
||||
|
||||
private:
|
||||
FilamentGroupContext ctx;
|
||||
MemoryedGroupHeap m_memoryed_heap;
|
||||
std::vector<std::vector<int>> m_memoryed_groups;
|
||||
|
||||
ClusteringBudget m_clustering_budget;
|
||||
public:
|
||||
std::optional<std::function<bool(int, std::vector<int>&)>> get_custom_seq;
|
||||
};
|
||||
|
||||
|
||||
class KMediods2
|
||||
std::vector<int> calc_filament_group_for_manual_multi_nozzle(const std::vector<int>& filament_map_manual,const FilamentGroupContext& ctx);
|
||||
|
||||
std::vector<int> calc_filament_group_for_match_multi_nozzle(const FilamentGroupContext& ctx);
|
||||
|
||||
struct FilamentPlanRes
|
||||
{
|
||||
std::vector<int> fil_order;
|
||||
std::vector<int> fil_nozzle_match;
|
||||
};
|
||||
|
||||
std::vector<FilamentPlanRes> plan_filament_nozzle_mapping_and_order(const FilamentGroupContext& ctx);
|
||||
|
||||
|
||||
class KMediods
|
||||
{
|
||||
protected:
|
||||
using MemoryedGroupHeap = FilamentGroupUtils::MemoryedGroupHeap;
|
||||
using MemoryedGroup = FilamentGroupUtils::MemoryedGroup;
|
||||
|
||||
enum INIT_TYPE
|
||||
{
|
||||
Random = 0,
|
||||
Farthest
|
||||
};
|
||||
public:
|
||||
KMediods2(const int elem_count, const std::shared_ptr<FlushDistanceEvaluator>& evaluator, int default_group_id = 0) :
|
||||
m_evaluator{ evaluator },
|
||||
m_elem_count{ elem_count },
|
||||
m_default_group_id{ default_group_id }
|
||||
{
|
||||
m_max_cluster_size = std::vector<int>(m_k, DEFAULT_CLUSTER_SIZE);
|
||||
KMediods(const int k, const int elem_count, const std::shared_ptr<FlushDistanceEvaluator>& evaluator, int default_group_id = 0) {
|
||||
m_k = k;
|
||||
m_evaluator = evaluator;
|
||||
m_max_cluster_size = std::vector<int>(k, DEFAULT_CLUSTER_SIZE);
|
||||
m_elem_count = elem_count;
|
||||
m_default_group_id = default_group_id;
|
||||
}
|
||||
|
||||
// set max group size
|
||||
void set_max_cluster_size(const std::vector<int>& group_size) { m_max_cluster_size = group_size; }
|
||||
|
||||
// key stores elem idx, value stores the cluster id that elem cnanot be placed
|
||||
void set_unplaceable_limits(const std::map<int, int>& placeable_limits) { m_unplaceable_limits = placeable_limits; }
|
||||
void set_cluster_group_size(const std::vector<std::pair<std::set<int>,int>>& cluster_group_size);
|
||||
|
||||
void do_clustering(const FGStrategy& g_strategy,int timeout_ms = 100);
|
||||
// key stores elem, value stores the cluster id that the elem must be placed
|
||||
void set_placable_limits(const std::unordered_map<int, std::vector<int>>& placable_limits) { m_placeable_limits = placable_limits; }
|
||||
|
||||
// key stores elem, value stores the cluster id that the elem cannot be placed
|
||||
void set_unplacable_limits(const std::unordered_map<int, std::vector<int>>& unplacable_limits) { m_unplaceable_limits = unplacable_limits; }
|
||||
|
||||
void set_memory_threshold(double threshold) { memory_threshold = threshold; }
|
||||
MemoryedGroupHeap get_memoryed_groups()const { return memoryed_groups; }
|
||||
|
||||
std::vector<int>get_cluster_labels()const { return m_cluster_labels; }
|
||||
void do_clustering(const FilamentGroupContext& context, const ClusteringBudget& budget);
|
||||
std::vector<int> get_cluster_labels()const { return m_cluster_labels; }
|
||||
|
||||
private:
|
||||
std::vector<int>cluster_small_data(const std::map<int, int>& unplaceable_limits, const std::vector<int>& group_size);
|
||||
std::vector<int>assign_cluster_label(const std::vector<int>& center, const std::map<int, int>& unplaceable_limits, const std::vector<int>& group_size, const FGStrategy& strategy);
|
||||
int calc_cost(const std::vector<int>& labels, const std::vector<int>& medoids);
|
||||
protected:
|
||||
FilamentGroupUtils::MemoryedGroupHeap memoryed_groups;
|
||||
std::shared_ptr<FlushDistanceEvaluator> m_evaluator;
|
||||
std::map<int, int>m_unplaceable_limits;
|
||||
std::vector<int>m_cluster_labels;
|
||||
std::vector<int>m_max_cluster_size;
|
||||
bool have_enough_size(const std::vector<int>& cluster_size, const std::vector<std::pair<std::set<int>, int>>& cluster_group_size,int elem_count);
|
||||
// calculate cluster distance
|
||||
int calc_cost(const std::vector<int>& clusters, const std::vector<int>& cluster_centers, int cluster_id = -1);
|
||||
|
||||
const int m_k = 2;
|
||||
// get initial cluster center
|
||||
std::vector<int>init_cluster_center(const std::unordered_map<int, std::vector<int>>& placeable_limits, const std::unordered_map<int, std::vector<int>>& unplaceable_limits, const std::vector<int>& cluster_size, const std::vector<std::pair<std::set<int>, int>>& cluster_group_size, int seed);
|
||||
// assign each elem to the cluster
|
||||
std::vector<int> assign_cluster_label(const std::vector<int>& center, const std::unordered_map<int, std::vector<int>>& placeable_limits, const std::unordered_map<int, std::vector<int>>& unplaceable_limits, const std::vector<int>& group_size, const std::vector<std::pair<std::set<int>, int>>& cluster_group_size);
|
||||
|
||||
protected:
|
||||
MemoryedGroupHeap memoryed_groups;
|
||||
std::shared_ptr<FlushDistanceEvaluator>m_evaluator;
|
||||
std::unordered_map<int, std::vector<int>> m_unplaceable_limits; // key: filament, value: nozzle ids it cannot be assigned to
|
||||
std::unordered_map<int, std::vector<int>> m_placeable_limits; // key: filament, value: nozzle ids it must be assigned to
|
||||
std::vector<int>m_max_cluster_size; // max number of filaments each nozzle can hold
|
||||
std::vector<int>m_cluster_labels; // assignment result, resolved down to nozzle id
|
||||
std::vector<std::pair<std::set<int>,int>> m_cluster_group_size;
|
||||
std::vector<int> m_nozzle_to_extruder;
|
||||
|
||||
|
||||
int m_k;
|
||||
int m_elem_count;
|
||||
int m_default_group_id{ 0 };
|
||||
double memory_threshold{ 0 };
|
||||
|
||||
@@ -274,5 +274,70 @@ namespace FilamentGroupUtils
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int get_estimate_extruder_change_count(const std::vector<std::vector<unsigned int>> &layer_filaments, const MultiNozzleUtils::LayeredNozzleGroupResult &extruder_nozzle_info)
|
||||
{
|
||||
int ret = 0;
|
||||
for (size_t layer_id = 0; layer_id < layer_filaments.size(); ++layer_id) {
|
||||
int extruder_count = extruder_nozzle_info.get_used_extruders(layer_id).size();
|
||||
ret += (extruder_count - 1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int get_estimate_nozzle_change_count(const std::vector<std::vector<unsigned int>> &layer_filaments, const MultiNozzleUtils::LayeredNozzleGroupResult &extruder_nozzle_info)
|
||||
{
|
||||
int ret = 0;
|
||||
for (size_t layer_id = 0; layer_id < layer_filaments.size(); ++layer_id) {
|
||||
auto extruder_list = extruder_nozzle_info.get_used_extruders(layer_id);
|
||||
for (auto extruder_id : extruder_list) {
|
||||
int nozzle_count = extruder_nozzle_info.get_used_nozzles_in_extruder(extruder_id, layer_id).size();
|
||||
if (nozzle_count > 1) ret += (nozzle_count - 1);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::pair<int, int> get_estimate_extruder_filament_change_count(const MultiNozzleUtils::LayeredNozzleGroupResult &extruder_nozzle_info)
|
||||
{
|
||||
std::pair<int, int> ret{0,0};
|
||||
int layer_nums = extruder_nozzle_info.get_layer_filament_sequences().size();
|
||||
for (int layer_id = 0; layer_id < layer_nums; layer_id++) {
|
||||
std::vector<int> extruders = extruder_nozzle_info.get_used_extruders(layer_id);
|
||||
ret.first = extruders.size() - 1;
|
||||
|
||||
for (auto ext_id : extruders) {
|
||||
int nozzles = extruder_nozzle_info.get_used_nozzles_in_extruder(ext_id, layer_id).size();
|
||||
ret.second += nozzles;
|
||||
}
|
||||
ret.second = std::max(0, ret.second - ret.first);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::map<int,std::vector<int>> build_extruder_nozzle_list(const std::vector<MultiNozzleUtils::NozzleInfo>& nozzle_list)
|
||||
{
|
||||
std::map<int, std::vector<int>> ret;
|
||||
for (auto& nozzle : nozzle_list) {
|
||||
ret[nozzle.extruder_id].emplace_back(nozzle.group_id);
|
||||
}
|
||||
|
||||
for (auto& elem : ret)
|
||||
std::sort(elem.second.begin(), elem.second.end());
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<int> update_used_filament_values(const std::vector<int>& old_values, const std::vector<int>& new_values, const std::vector<unsigned int>& used_filaments)
|
||||
{
|
||||
std::vector<int> res = old_values;
|
||||
for (size_t i = 0; i < used_filaments.size(); ++i) {
|
||||
// Orca: guard against filament ids beyond the map sizes (possible with
|
||||
// mis-normalized per-filament arrays from CLI inputs); skip instead of UB.
|
||||
if (used_filaments[i] >= res.size() || used_filaments[i] >= new_values.size())
|
||||
continue;
|
||||
res[used_filaments[i]] = new_values[used_filaments[i]];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <exception>
|
||||
|
||||
#include "PrintConfig.hpp"
|
||||
#include "MultiNozzleUtils.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
@@ -31,6 +32,10 @@ namespace Slic3r
|
||||
Color color;
|
||||
std::string type;
|
||||
bool is_support;
|
||||
// How this filament is used across the model. Orca's shipping grouping
|
||||
// algorithm does not read it yet; defaulted so a default-built FilamentInfo
|
||||
// is deterministic. The nozzle-centric engine consumes it later.
|
||||
FilamentUsageType usage_type = FilamentUsageType::ModelOnly;
|
||||
};
|
||||
|
||||
struct MachineFilamentInfo: public FilamentInfo {
|
||||
@@ -80,6 +85,20 @@ namespace Slic3r
|
||||
void extract_unprintable_limit_indices(const std::vector<std::set<int>>& unprintable_elems, const std::vector<unsigned int>& used_filaments, std::unordered_map<int, std::vector<int>>& unplaceable_limits);
|
||||
|
||||
bool check_printable(const std::vector<std::set<int>>& groups, const std::map<int, int>& unprintable);
|
||||
|
||||
// Nozzle-centric grouping helpers. The estimate helpers read a LayeredNozzleGroupResult's
|
||||
// per-layer extruder/nozzle usage; the two builders support building the grouping context
|
||||
// (extruder->nozzle inventory) and writing back a resolved map onto only the used-filament
|
||||
// slots.
|
||||
int get_estimate_extruder_change_count(const std::vector<std::vector<unsigned int>>& layer_filaments, const MultiNozzleUtils::LayeredNozzleGroupResult& extruder_nozzle_info);
|
||||
|
||||
int get_estimate_nozzle_change_count(const std::vector<std::vector<unsigned int>>& layer_filaments, const MultiNozzleUtils::LayeredNozzleGroupResult& extruder_nozzle_info);
|
||||
|
||||
std::pair<int, int> get_estimate_extruder_filament_change_count(const MultiNozzleUtils::LayeredNozzleGroupResult& extruder_nozzle_info);
|
||||
|
||||
std::map<int, std::vector<int>> build_extruder_nozzle_list(const std::vector<MultiNozzleUtils::NozzleInfo>& nozzle_list);
|
||||
|
||||
std::vector<int> update_used_filament_values(const std::vector<int>& old_values, const std::vector<int>& new_values, const std::vector<unsigned int>& used_filaments);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+103
-18
@@ -57,6 +57,9 @@ double calculate_infill_rotation_angle(const PrintObject* object,
|
||||
if (template_string.empty()) {
|
||||
return Geometry::deg2rad(fixed_infill_angle);
|
||||
}
|
||||
// Convert the id to an index. Layer::id() counts the raft layers, object->layers() does not.
|
||||
const size_t first_object_layer_id = object->get_layer(0)->id();
|
||||
layer_id = layer_id > first_object_layer_id ? layer_id - first_object_layer_id : 0;
|
||||
double angle = 0.0;
|
||||
ConfigOptionFloats rotate_angles;
|
||||
const std::string search_string = "/NnZz$LlUuQq~^|#";
|
||||
@@ -75,6 +78,8 @@ double calculate_infill_rotation_angle(const PrintObject* object,
|
||||
double angle_start = 0;
|
||||
double limit_fill_z = object->get_layer(0)->bottom_z();
|
||||
double start_fill_z = limit_fill_z;
|
||||
// The raft height, or 0 without a raft.
|
||||
const double print_z_offset = object->slicing_parameters().object_print_z_min;
|
||||
bool _noop = false;
|
||||
auto fill_form = std::string::npos;
|
||||
bool _absolute = false;
|
||||
@@ -84,7 +89,8 @@ double calculate_infill_rotation_angle(const PrintObject* object,
|
||||
for (int i = 0; i <= layer_id; i++) {
|
||||
double fill_z = object->get_layer(i)->bottom_z();
|
||||
|
||||
if (limit_fill_z < object->get_layer(i)->slice_z) {
|
||||
// slice_z is measured from the bottom of the model, limit_fill_z from the build plate.
|
||||
if (limit_fill_z < object->get_layer(i)->slice_z + print_z_offset) {
|
||||
if (repeats) { // if repeats >0 then restore parameters for new iteration
|
||||
limit_fill_z += limit_fill_z - start_fill_z;
|
||||
start_fill_z = fill_z;
|
||||
@@ -272,6 +278,12 @@ struct SurfaceFillParams
|
||||
// For Gyroid: when true, use the parameterized "optimized" wave.
|
||||
bool gyroid_optimized = false;
|
||||
|
||||
CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface};
|
||||
bool separated_infills{false};
|
||||
|
||||
// Orca: forced print order of surface fill loops/fragments for center-based patterns.
|
||||
SurfaceFillOrder fill_order = SurfaceFillOrder::Default;
|
||||
|
||||
bool operator<(const SurfaceFillParams &rhs) const {
|
||||
#define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false;
|
||||
#define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false;
|
||||
@@ -301,8 +313,12 @@ struct SurfaceFillParams
|
||||
RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_2);
|
||||
RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis);
|
||||
RETURN_COMPARE_NON_EQUAL(infill_lock_depth);
|
||||
RETURN_COMPARE_NON_EQUAL(skin_infill_depth); RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
|
||||
RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
|
||||
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
|
||||
RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
|
||||
RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern);
|
||||
RETURN_COMPARE_NON_EQUAL(separated_infills);
|
||||
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, fill_order);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -329,7 +345,10 @@ struct SurfaceFillParams
|
||||
this->infill_lock_depth == rhs.infill_lock_depth &&
|
||||
this->skin_infill_depth == rhs.skin_infill_depth &&
|
||||
this->infill_overhang_angle == rhs.infill_overhang_angle &&
|
||||
this->gyroid_optimized == rhs.gyroid_optimized;
|
||||
this->center_of_surface_pattern == rhs.center_of_surface_pattern &&
|
||||
this->separated_infills == rhs.separated_infills &&
|
||||
this->gyroid_optimized == rhs.gyroid_optimized &&
|
||||
this->fill_order == rhs.fill_order;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -868,6 +887,8 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.lateral_lattice_angle_1 = region_config.lateral_lattice_angle_1;
|
||||
params.lateral_lattice_angle_2 = region_config.lateral_lattice_angle_2;
|
||||
params.infill_overhang_angle = region_config.infill_overhang_angle;
|
||||
params.center_of_surface_pattern = region_config.center_of_surface_pattern;
|
||||
params.separated_infills = region_config.separated_infills;
|
||||
if (params.pattern == ipLockedZag) {
|
||||
params.infill_lock_depth = scale_(region_config.infill_lock_depth);
|
||||
params.skin_infill_depth = scale_(region_config.skin_infill_depth);
|
||||
@@ -922,6 +943,14 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.extruder = region_config.bottom_surface_filament_id;
|
||||
else if (params.extrusion_role == erSolidInfill)
|
||||
params.extruder = region_config.internal_solid_filament_id;
|
||||
// Orca: forced fill order applies only to top/bottom surfaces filled with a
|
||||
// center-based pattern; everything else stays at Default to keep batching together.
|
||||
if (params.pattern == ipConcentric || params.pattern == ipArchimedeanChords || params.pattern == ipOctagramSpiral) {
|
||||
if (params.extrusion_role == erTopSolidInfill)
|
||||
params.fill_order = region_config.top_surface_fill_order.value;
|
||||
else if (params.extrusion_role == erBottomSurface)
|
||||
params.fill_order = region_config.bottom_surface_fill_order.value;
|
||||
}
|
||||
// Orca: apply fill multiline only for sparse infill
|
||||
params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1;
|
||||
|
||||
@@ -936,9 +965,16 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
region_config.sparse_infill_rotate_template.value);
|
||||
params.fixed_angle = !region_config.sparse_infill_rotate_template.value.empty();
|
||||
} else {
|
||||
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.solid_infill_direction.value,
|
||||
region_config.solid_infill_rotate_template.value);
|
||||
params.fixed_angle = !region_config.solid_infill_rotate_template.value.empty();
|
||||
const bool top_layer_direction_set = surface.is_top() && region_config.top_layer_direction.value >= 0.;
|
||||
const bool bottom_layer_direction_set = surface.is_bottom() && region_config.bottom_layer_direction.value >= 0.;
|
||||
if (top_layer_direction_set || bottom_layer_direction_set) {
|
||||
params.angle = Geometry::deg2rad(top_layer_direction_set ? region_config.top_layer_direction.value : region_config.bottom_layer_direction.value);
|
||||
params.fixed_angle = true;
|
||||
} else {
|
||||
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.solid_infill_direction.value,
|
||||
region_config.solid_infill_rotate_template.value);
|
||||
params.fixed_angle = !region_config.solid_infill_rotate_template.value.empty();
|
||||
}
|
||||
}
|
||||
params.bridge_angle = float(surface.bridge_angle);
|
||||
|
||||
@@ -960,15 +996,15 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
|
||||
params.role_speed = 0;
|
||||
if (params.extrusion_role == erBridgeInfill)
|
||||
params.role_speed = region_config.bridge_speed;
|
||||
params.role_speed = region_config.bridge_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erInternalBridgeInfill)
|
||||
params.role_speed = region_config.get_abs_value("internal_bridge_speed");
|
||||
params.role_speed = region_config.get_abs_value_at("internal_bridge_speed", layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erInternalInfill)
|
||||
params.role_speed = region_config.sparse_infill_speed;
|
||||
params.role_speed = region_config.sparse_infill_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erTopSolidInfill)
|
||||
params.role_speed = region_config.top_surface_speed;
|
||||
params.role_speed = region_config.top_surface_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erSolidInfill)
|
||||
params.role_speed = region_config.internal_solid_infill_speed;
|
||||
params.role_speed = region_config.internal_solid_infill_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
// Calculate flow spacing for infill pattern generation.
|
||||
if (surface.is_solid() || is_bridge) {
|
||||
params.spacing = params.flow.spacing();
|
||||
@@ -1301,6 +1337,22 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
auto ®ion_config = layerm->region().config();
|
||||
params.config = ®ion_config;
|
||||
params.pattern = surface_fill.params.pattern;
|
||||
params.fill_order = surface_fill.params.fill_order;
|
||||
|
||||
// Orca: Checking the filling of a centered surface by drawing for each model parts
|
||||
bool is_top_or_bottom = params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface;
|
||||
bool is_centered_infill = surface_fill.params.pattern == ipArchimedeanChords || surface_fill.params.pattern == ipOctagramSpiral;
|
||||
if (is_top_or_bottom) {
|
||||
params.center_of_surface_pattern = surface_fill.params.center_of_surface_pattern; // Orca: center of surface pattern
|
||||
}
|
||||
// Orca: Each_Model centers the pattern on each model part's bbox; Each_Surface / Each_Assembly
|
||||
// fall through to the default (whole-object) bounding box below.
|
||||
bool is_per_model_center = is_top_or_bottom && params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model && is_centered_infill;
|
||||
bool is_separate_infill = !is_top_or_bottom && surface_fill.params.separated_infills &&
|
||||
(
|
||||
is_separable_infill_pattern(surface_fill.params.pattern) ||
|
||||
params.config->solid_infill_rotate_template != "" ||
|
||||
params.config->sparse_infill_rotate_template != "" );
|
||||
|
||||
if( surface_fill.params.pattern == ipLockedZag ) {
|
||||
params.locked_zag = true;
|
||||
@@ -1325,7 +1377,36 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.can_reverse = false;
|
||||
for (ExPolygon& expoly : surface_fill.expolygons) {
|
||||
|
||||
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
||||
// Orca: separate infill / per-model pattern centering.
|
||||
//
|
||||
// Center the pattern on each connected body of the object independently, so every piece
|
||||
// is filled exactly as if it were sliced on its own: touching/overlapping parts merge
|
||||
// into one body sharing a center, while separate parts and disconnected islands (even
|
||||
// interleaved-but-not-touching ones, e.g. chain links) each get their own. The body each
|
||||
// island belongs to, and its full bounding box, were resolved in 3D by PrintObject::
|
||||
// infill() (lslices_separated_component_bboxes, aligned with this layer's lslices). We
|
||||
// match this fill region to the island it overlaps most, then re-use the whole-object
|
||||
// bounding box (origin-centered — identical extent to the default, so coverage and cost
|
||||
// are unchanged) re-centered on that body.
|
||||
if (is_per_model_center || is_separate_infill) {
|
||||
double best_overlap = 0.;
|
||||
BoundingBox best_component;
|
||||
for (size_t r = 0; r < this->lslices.size() && r < this->lslices_separated_component_bboxes.size(); ++ r) {
|
||||
const double overlap = area(intersection_ex(this->lslices[r], expoly));
|
||||
if (overlap > best_overlap) {
|
||||
best_overlap = overlap;
|
||||
best_component = this->lslices_separated_component_bboxes[r];
|
||||
}
|
||||
}
|
||||
if (best_component.defined) {
|
||||
const Point c = best_component.center();
|
||||
BoundingBox part_bbox = bbox; // origin-centered, whole-object extent (from above)
|
||||
part_bbox.translate(c.x(), c.y()); // re-center on this body
|
||||
f->set_bounding_box(part_bbox);
|
||||
}
|
||||
} // - End: separate infill / per-model pattern centering
|
||||
|
||||
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
||||
if (params.symmetric_infill_y_axis) {
|
||||
params.symmetric_y_axis = f->extended_object_bounding_box().center().x();
|
||||
expoly.symmetric_y(params.symmetric_y_axis);
|
||||
@@ -1599,16 +1680,20 @@ void Layer::make_ironing()
|
||||
ironing_params.height = default_layer_height * 0.01 * (!config.filament_ironing_flow.is_nil(extruder_idx)
|
||||
? config.filament_ironing_flow.get_at(extruder_idx)
|
||||
: config.ironing_flow);
|
||||
ironing_params.speed = (!config.filament_ironing_speed.is_nil(extruder_idx)
|
||||
? config.filament_ironing_speed.get_at(extruder_idx)
|
||||
: config.ironing_speed);
|
||||
double ironing_angle = (config.ironing_angle_fixed ? 0 : calculate_infill_rotation_angle(this->object(), this->id(), config.solid_infill_direction.value, config.solid_infill_rotate_template.value)) + config.ironing_angle * M_PI / 180.;
|
||||
ironing_params.speed = (!config.filament_ironing_speed.is_nil(extruder_idx)
|
||||
? config.filament_ironing_speed.get_at(extruder_idx)
|
||||
: config.ironing_speed);
|
||||
const bool top_layer_direction_set = config.top_layer_direction.value >= 0.;
|
||||
const double top_layer_base_angle = top_layer_direction_set ?
|
||||
Geometry::deg2rad(config.top_layer_direction.value) :
|
||||
calculate_infill_rotation_angle(this->object(), this->id(), config.solid_infill_direction.value, config.solid_infill_rotate_template.value);
|
||||
double ironing_angle = (config.ironing_angle_fixed ? 0. : top_layer_base_angle) + config.ironing_angle * M_PI / 180.;
|
||||
if (config.align_infill_direction_to_model) {
|
||||
auto m = this->object()->trafo().matrix();
|
||||
ironing_angle += atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
ironing_params.angle = ironing_angle;
|
||||
ironing_params.fixed_angle = config.ironing_angle_fixed || !config.solid_infill_rotate_template.value.empty();
|
||||
ironing_params.angle = ironing_angle;
|
||||
ironing_params.fixed_angle = config.ironing_angle_fixed || top_layer_direction_set || !config.solid_infill_rotate_template.value.empty();
|
||||
ironing_params.pattern = config.ironing_pattern;
|
||||
ironing_params.layerm = layerm;
|
||||
by_extruder.emplace_back(ironing_params);
|
||||
|
||||
@@ -165,7 +165,11 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
|
||||
// ORCA: special flag for flow rate calibration
|
||||
auto is_flow_calib = params.extrusion_role == erTopSolidInfill && this->print_object_config->has("calib_flowrate_topinfill_special_order") &&
|
||||
this->print_object_config->option("calib_flowrate_topinfill_special_order")->getBool();
|
||||
if (is_flow_calib) {
|
||||
// Orca: a forced surface fill order must survive the G-code path planner, which would
|
||||
// otherwise re-chain and possibly reverse the paths. The same applies to the flow rate
|
||||
// calibration's special toolpath order.
|
||||
const bool keep_fill_order = params.fill_order != SurfaceFillOrder::Default;
|
||||
if (is_flow_calib || keep_fill_order) {
|
||||
eec->no_sort = true;
|
||||
}
|
||||
size_t idx = eec->entities.size();
|
||||
@@ -180,7 +184,7 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
|
||||
params.extrusion_role,
|
||||
flow_mm3_per_mm, float(flow_width), params.flow.height());
|
||||
}
|
||||
if (!params.can_reverse || is_flow_calib) {
|
||||
if (!params.can_reverse || is_flow_calib || keep_fill_order) {
|
||||
for (size_t i = idx; i < eec->entities.size(); i++)
|
||||
eec->entities[i]->set_reverse();
|
||||
}
|
||||
|
||||
@@ -100,12 +100,17 @@ struct FillParams
|
||||
bool dont_sort{ false }; // do not sort the lines, just simply connect them
|
||||
bool can_reverse{true};
|
||||
|
||||
// Orca: forced print order of surface fill loops/fragments for center-based patterns
|
||||
// (Concentric, Archimedean Chords, Octagram Spiral). Default keeps shortest-path ordering.
|
||||
SurfaceFillOrder fill_order { SurfaceFillOrder::Default };
|
||||
|
||||
float horiz_move{0.0}; //move infill to get cross zag pattern
|
||||
bool symmetric_infill_y_axis{false};
|
||||
coord_t symmetric_y_axis{0};
|
||||
bool locked_zag{false};
|
||||
float infill_lock_depth{0.0};
|
||||
float skin_infill_depth{0.0};
|
||||
CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface};
|
||||
};
|
||||
static_assert(IsTriviallyCopyable<FillParams>::value, "FillParams class is not POD (and it should be - see constructor).");
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@ void FillConcentric::_fill_surface_single(
|
||||
// generate paths from the outermost to the innermost, to avoid
|
||||
// adhesion problems of the first central tiny loops
|
||||
loops = union_pt_chained_outside_in(loops);
|
||||
|
||||
// Orca: an outward fill order prints the innermost loops first instead.
|
||||
if (params.fill_order == SurfaceFillOrder::Outward)
|
||||
std::reverse(loops.begin(), loops.end());
|
||||
|
||||
// split paths using a nearest neighbor search
|
||||
size_t iPathFirst = polylines_out.size();
|
||||
@@ -108,6 +112,17 @@ void FillConcentric::_fill_surface_single(const FillParams& params,
|
||||
all_extrusions.emplace_back(&wall);
|
||||
}
|
||||
|
||||
// Orca: a forced fill order prints the loops in strictly monotonic depth order so
|
||||
// that surfaces broken up by holes or slots cannot hop outward and back inward.
|
||||
const bool forced_fill_order = params.fill_order != SurfaceFillOrder::Default;
|
||||
if (forced_fill_order) {
|
||||
const bool outward = params.fill_order == SurfaceFillOrder::Outward;
|
||||
std::stable_sort(all_extrusions.begin(), all_extrusions.end(),
|
||||
[outward](const Arachne::ExtrusionLine *a, const Arachne::ExtrusionLine *b) {
|
||||
return outward ? a->inset_idx > b->inset_idx : a->inset_idx < b->inset_idx;
|
||||
});
|
||||
}
|
||||
|
||||
// Split paths using a nearest neighbor search.
|
||||
size_t firts_poly_idx = thick_polylines_out.size();
|
||||
Point last_pos(0, 0);
|
||||
@@ -136,7 +151,8 @@ void FillConcentric::_fill_surface_single(const FillParams& params,
|
||||
if (j < thick_polylines_out.size())
|
||||
thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
|
||||
|
||||
reorder_by_shortest_traverse(thick_polylines_out);
|
||||
if (!forced_fill_order)
|
||||
reorder_by_shortest_traverse(thick_polylines_out);
|
||||
}
|
||||
else {
|
||||
Polylines polylines;
|
||||
|
||||
@@ -77,20 +77,24 @@ void FillPlanePath::_fill_surface_single(
|
||||
|
||||
//FIXME Vojtech: We are not sure whether the user expects the fill patterns on visible surfaces to be aligned across all the islands of a single layer.
|
||||
// One may align for this->centered() to align the patterns for Archimedean Chords and Octagram Spiral patterns.
|
||||
const bool align = params.density < 0.995;
|
||||
|
||||
// Orca: the old implementation became obsolete when it became possible to change the density of the top and bottom surfaces
|
||||
bool align = params.extrusion_role == ExtrusionRole::erInternalInfill;
|
||||
BoundingBox bounding_box;
|
||||
BoundingBox snug_bounding_box = get_extents(expolygon).inflated(SCALED_EPSILON);
|
||||
|
||||
// Expand the bounding box to avoid artifacts at the edges
|
||||
snug_bounding_box.offset(scale_(this->spacing)*params.multiline);
|
||||
snug_bounding_box.offset(scale_(this->spacing)*params.multiline);
|
||||
|
||||
// Rotated bounding box of the area to fill in with the pattern.
|
||||
BoundingBox bounding_box = align ?
|
||||
// Sparse infill needs to be aligned across layers. Align infill across layers using the object's bounding box.
|
||||
this->bounding_box.rotated(-direction.first) :
|
||||
// Solid infill does not need to be aligned across layers, generate the infill pattern
|
||||
// around the clipping expolygon only.
|
||||
snug_bounding_box;
|
||||
// Sparse infill (or Internal where align == true) needs to be aligned across layers. Align infill across layers using the object's bounding box.
|
||||
// Solid infill does not need to be aligned across layers, generate the infill pattern around the clipping expolygon only.
|
||||
if (align)
|
||||
bounding_box = this->bounding_box.rotated(-direction.first);
|
||||
else if (params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Surface)
|
||||
bounding_box = snug_bounding_box;
|
||||
else if (params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model)
|
||||
bounding_box = this->bounding_box.rotated(-direction.first);
|
||||
else
|
||||
bounding_box = extended_object_bounding_box();
|
||||
|
||||
Point shift = this->centered() ?
|
||||
bounding_box.center() :
|
||||
@@ -130,8 +134,12 @@ void FillPlanePath::_fill_surface_single(
|
||||
if (!polylines.empty()) {
|
||||
Polylines chained;
|
||||
if (params.dont_connect() || params.density > 0.5) {
|
||||
// ORCA: special flag for flow rate calibration
|
||||
auto is_flow_calib = params.extrusion_role == erTopSolidInfill &&
|
||||
// ORCA: special flag for flow rate calibration. The chords chained ahead of the
|
||||
// inside-out center spiral collide with it in opposing directions, raising a
|
||||
// tactile lip that the calibration reads. Only applies while the fill order is
|
||||
// Default, so it can be overridden from the calibration objects.
|
||||
auto is_flow_calib = params.fill_order == SurfaceFillOrder::Default &&
|
||||
params.extrusion_role == erTopSolidInfill &&
|
||||
this->print_object_config->has("calib_flowrate_topinfill_special_order") &&
|
||||
this->print_object_config->option("calib_flowrate_topinfill_special_order")->getBool() &&
|
||||
dynamic_cast<FillArchimedeanChords*>(this);
|
||||
@@ -149,12 +157,26 @@ void FillPlanePath::_fill_surface_single(
|
||||
|
||||
// Chain the other polylines
|
||||
polylines.erase(it);
|
||||
chained = chain_polylines(std::move(polylines));
|
||||
chained = chain_polylines(std::move(polylines), nullptr);
|
||||
|
||||
// Then add the center spiral back
|
||||
chained.push_back(std::move(center_spiral));
|
||||
} else if (params.fill_order != SurfaceFillOrder::Default) {
|
||||
// Orca: print the fragments in the order they appear along the generated
|
||||
// path, which runs from the center outwards. The Euclidean distance from
|
||||
// the center cannot be used for this: along the Octagram Spiral the radius
|
||||
// oscillates by far more than the ring spacing, so fragments of different
|
||||
// rings would interleave.
|
||||
restore_source_path_order(polyline, polylines);
|
||||
chained = std::move(polylines);
|
||||
if (params.fill_order == SurfaceFillOrder::Inward) {
|
||||
// The source path runs from the center outwards; flip everything for inward.
|
||||
std::reverse(chained.begin(), chained.end());
|
||||
for (Polyline &pl : chained)
|
||||
pl.reverse();
|
||||
}
|
||||
} else {
|
||||
chained = chain_polylines(std::move(polylines));
|
||||
chained = chain_polylines(std::move(polylines), nullptr);
|
||||
}
|
||||
} else
|
||||
connect_infill(std::move(polylines), expolygon, chained, this->spacing, params);
|
||||
|
||||
@@ -2739,13 +2739,19 @@ static void polylines_from_paths(const std::vector<MonotonicRegionLink> &path, c
|
||||
|
||||
// The extended bounding box of the whole object that covers any rotation of every layer.
|
||||
BoundingBox FillRectilinear::extended_object_bounding_box() const {
|
||||
// Build the extension around the box center. The transpose merge and the sqrt(2.) scaling
|
||||
// (which covers any possible rotation) are both defined about the origin, so a box that is not
|
||||
// origin-centered — e.g. a separated-infill box re-centered on a single assembly part — would be
|
||||
// distorted. Shift to the origin first and back afterwards; for the default origin-centered box
|
||||
// the two translations cancel and this is identical to the original behavior.
|
||||
const Point c = this->bounding_box.center();
|
||||
BoundingBox out = this->bounding_box;
|
||||
out.translate(-c.x(), -c.y());
|
||||
out.merge(Point(out.min.y(), out.min.x()));
|
||||
out.merge(Point(out.max.y(), out.max.x()));
|
||||
|
||||
// The bounding box is scaled by sqrt(2.) to ensure that the bounding box
|
||||
// covers any possible rotations.
|
||||
return out.scaled(sqrt(2.));
|
||||
out = out.scaled(sqrt(2.));
|
||||
out.translate(c.x(), c.y());
|
||||
return out;
|
||||
}
|
||||
|
||||
bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out)
|
||||
@@ -3098,8 +3104,11 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
|
||||
const coord_t d2 = coord_t(0.5 * period - d1);
|
||||
|
||||
// Align bounding box to the grid
|
||||
bb.merge(align_to_grid(bb.min, Point(period, period)));
|
||||
// Align bounding box to the grid, phased through the box center so separated infills align
|
||||
// each part on itself (grid_center is the origin for a standalone object / feature off).
|
||||
// Captured before the merge, which grows bb and would otherwise shift its center.
|
||||
const Point grid_center = bb.center();
|
||||
bb.merge(align_to_grid(bb.min, Point(period, period), grid_center));
|
||||
const coord_t xmin = bb.min.x();
|
||||
const coord_t xmax = bb.max.x();
|
||||
const coord_t ymin = bb.min.y();
|
||||
@@ -3146,11 +3155,17 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
flip_vertical = !flip_vertical;
|
||||
}
|
||||
|
||||
// transpose points for odd infill layers (taking infill combination into account)
|
||||
// transpose points for odd infill layers (taking infill combination into account).
|
||||
// Orca: mirror across the diagonal through grid_center (not the origin), so the swapped
|
||||
// layers stay aligned with the center-phased grid. For a standalone object / feature off,
|
||||
// grid_center is the origin and this is a plain x/y swap.
|
||||
if (infill_layer_id % 2 == 1) {
|
||||
for (Polyline& pl : polylines) {
|
||||
for (Point& p : pl.points) {
|
||||
std::swap(p.x(), p.y());
|
||||
const coord_t dx = p.x() - grid_center.x();
|
||||
const coord_t dy = p.y() - grid_center.y();
|
||||
p.x() = grid_center.x() + dy;
|
||||
p.y() = grid_center.y() + dx;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3341,6 +3356,14 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
break;
|
||||
}
|
||||
|
||||
// Orca: cases 1 & 2 build the pattern symmetrically around the origin, so on their own they
|
||||
// phase to the global origin and every part shares one grid. Shift the pattern onto the box
|
||||
// center this->bounding_box carries, so separated infills align each part on itself. The center
|
||||
// is the origin for a standalone object (or when the feature is off), making this a no-op there.
|
||||
if (Pattern_type != 0)
|
||||
for (Polyline &pl : polylines)
|
||||
pl.translate(rotate_vector.second);
|
||||
|
||||
// Apply multiline fill
|
||||
multiline_fill(polylines, params, spacing);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
|
||||
|
||||
static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key)
|
||||
{
|
||||
throw FlowErrorMissingVariable((boost::format(L("Failed to calculate line width of %1%. Cannot get value of \u201c%2%\u201d ")) % opt_key % dependent_opt_key).str());
|
||||
throw FlowErrorMissingVariable((boost::format("Failed to calculate line width of %1%. Cannot get value of \u201c%2%\u201d.") % opt_key % dependent_opt_key).str());
|
||||
}
|
||||
|
||||
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
|
||||
@@ -129,7 +129,7 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResol
|
||||
Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height)
|
||||
{
|
||||
if (height <= 0)
|
||||
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
|
||||
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width().");
|
||||
|
||||
float w;
|
||||
if (!width.percent && width.value <= 0.) {
|
||||
@@ -157,7 +157,7 @@ Flow Flow::with_spacing(float new_spacing) const
|
||||
assert(m_width >= m_height);
|
||||
out.m_width += new_spacing - m_spacing;
|
||||
if (out.m_width < out.m_height)
|
||||
throw Slic3r::InvalidArgument(L("Invalid spacing supplied to Flow::with_spacing(), check your layer height and extrusion width"));
|
||||
throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing(), check your layer height and extrusion width.");
|
||||
}
|
||||
out.m_spacing = new_spacing;
|
||||
return out;
|
||||
|
||||
@@ -230,8 +230,8 @@ static void getNamedSolids(const TopLoc_Location& location,
|
||||
}
|
||||
|
||||
//bool load_step(const char *path, Model *model, bool& is_cancel,
|
||||
// double linear_defletion/*=0.003*/,
|
||||
// double angle_defletion/*= 0.5*/,
|
||||
// double linear_deflection/*=0.003*/,
|
||||
// double angle_deflection/*= 0.5*/,
|
||||
// bool isSplitCompound,
|
||||
// ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn, long& mesh_face_num)
|
||||
//{
|
||||
@@ -288,7 +288,7 @@ static void getNamedSolids(const TopLoc_Location& location,
|
||||
// stl.resize(namedSolids.size());
|
||||
// tbb::parallel_for(tbb::blocked_range<size_t>(0, namedSolids.size()), [&](const tbb::blocked_range<size_t> &range) {
|
||||
// for (size_t i = range.begin(); i < range.end(); i++) {
|
||||
// BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_defletion, false, angle_defletion, true);
|
||||
// BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_deflection, false, angle_deflection, true);
|
||||
// // BBS: calculate total number of the nodes and triangles
|
||||
// int aNbNodes = 0;
|
||||
// int aNbTriangles = 0;
|
||||
@@ -511,8 +511,8 @@ Step::Step_Status Step::load()
|
||||
Step::Step_Status Step::mesh(Model* model,
|
||||
bool& is_cancel,
|
||||
bool isSplitCompound,
|
||||
double linear_defletion/*=0.003*/,
|
||||
double angle_defletion/*= 0.5*/)
|
||||
double linear_deflection/*=0.003*/,
|
||||
double angle_deflection/*= 0.5*/)
|
||||
|
||||
{
|
||||
bool task_result = false;
|
||||
@@ -544,7 +544,7 @@ Step::Step_Status Step::mesh(Model* model,
|
||||
stl.resize(namedSolids.size());
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, namedSolids.size()), [&](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t i = range.begin(); i < range.end(); i++) {
|
||||
BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_defletion, false, angle_defletion, true);
|
||||
BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_deflection, false, angle_deflection, true);
|
||||
// BBS: calculate total number of the nodes and triangles
|
||||
int aNbNodes = 0;
|
||||
int aNbTriangles = 0;
|
||||
@@ -689,15 +689,15 @@ void Step::clean_mesh_data()
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Step::get_triangle_num(double linear_defletion, double angle_defletion)
|
||||
unsigned int Step::get_triangle_num(double linear_deflection, double angle_deflection)
|
||||
{
|
||||
unsigned int tri_num = 0;
|
||||
try {
|
||||
Handle(StepProgressIncdicator) progress = new StepProgressIncdicator(m_stop_mesh);
|
||||
clean_mesh_data();
|
||||
IMeshTools_Parameters param;
|
||||
param.Deflection = linear_defletion;
|
||||
param.Angle = angle_defletion;
|
||||
param.Deflection = linear_deflection;
|
||||
param.Angle = angle_deflection;
|
||||
param.InParallel = true;
|
||||
for (int i = 0; i < m_name_solids.size(); ++i) {
|
||||
BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, param, progress->Start());
|
||||
@@ -719,7 +719,7 @@ unsigned int Step::get_triangle_num(double linear_defletion, double angle_deflet
|
||||
return tri_num;
|
||||
}
|
||||
|
||||
unsigned int Step::get_triangle_num_tbb(double linear_defletion, double angle_defletion)
|
||||
unsigned int Step::get_triangle_num_tbb(double linear_deflection, double angle_deflection)
|
||||
{
|
||||
unsigned int tri_num = 0;
|
||||
clean_mesh_data();
|
||||
@@ -727,7 +727,7 @@ unsigned int Step::get_triangle_num_tbb(double linear_defletion, double angle_de
|
||||
[&](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t i = range.begin(); i < range.end(); i++) {
|
||||
unsigned int solids_tri_num = 0;
|
||||
BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, linear_defletion, false, angle_defletion, true);
|
||||
BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, linear_deflection, false, angle_deflection, true);
|
||||
for (TopExp_Explorer anExpSF(m_name_solids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) {
|
||||
TopLoc_Location aLoc;
|
||||
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc);
|
||||
|
||||
@@ -38,8 +38,8 @@ struct NamedSolid
|
||||
//BBS: Load an step file into a provided model.
|
||||
extern bool load_step(const char *path, Model *model,
|
||||
bool& is_cancel,
|
||||
double linear_defletion = 0.003,
|
||||
double angle_defletion = 0.5,
|
||||
double linear_deflection = 0.003,
|
||||
double angle_deflection = 0.5,
|
||||
bool isSplitCompound = false,
|
||||
ImportStepProgressFn proFn = nullptr,
|
||||
StepIsUtf8Fn isUtf8Fn = nullptr,
|
||||
@@ -98,14 +98,14 @@ public:
|
||||
Step(std::string path, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
|
||||
~Step();
|
||||
Step_Status load();
|
||||
unsigned int get_triangle_num(double linear_defletion, double angle_defletion);
|
||||
unsigned int get_triangle_num_tbb(double linear_defletion, double angle_defletion);
|
||||
unsigned int get_triangle_num(double linear_deflection, double angle_deflection);
|
||||
unsigned int get_triangle_num_tbb(double linear_deflection, double angle_deflection);
|
||||
void clean_mesh_data();
|
||||
Step_Status mesh(Model* model,
|
||||
bool& is_cancel,
|
||||
bool isSplitCompound,
|
||||
double linear_defletion = 0.003,
|
||||
double angle_defletion = 0.5);
|
||||
double linear_deflection = 0.003,
|
||||
double angle_deflection = 0.5);
|
||||
|
||||
std::atomic<bool> m_stop_mesh{false};
|
||||
void update_process(int load_stage, int current, int total, bool& cancel);
|
||||
|
||||
@@ -348,6 +348,7 @@ static constexpr const char* OTHER_LAYERS_PRINT_SEQUENCE_NUMS_ATTR = "other_laye
|
||||
static constexpr const char* SPIRAL_VASE_MODE = "spiral_mode";
|
||||
static constexpr const char* FILAMENT_MAP_MODE_ATTR = "filament_map_mode";
|
||||
static constexpr const char* FILAMENT_MAP_ATTR = "filament_maps";
|
||||
static constexpr const char* FILAMENT_VOL_MAP_ATTR = "filament_volume_maps";
|
||||
static constexpr const char* LIMIT_FILAMENT_MAP_ATTR = "limit_filament_maps";
|
||||
static constexpr const char* GCODE_FILE_ATTR = "gcode_file";
|
||||
static constexpr const char* THUMBNAIL_FILE_ATTR = "thumbnail_file";
|
||||
@@ -700,6 +701,35 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
info.id = it->first;
|
||||
info.used_g = used_filament_g;
|
||||
info.used_m = used_filament_m;
|
||||
|
||||
// Stamp each filament's logical-nozzle assignment onto the saved 3mf so the device/monitor can
|
||||
// reconstruct it. This block runs for every print: reorder_extruders_for_minimum_flush_volume
|
||||
// runs unconditionally and stores a (non-null) 1-nozzle result even for a single-extruder print,
|
||||
// so result->nozzle_group_result is non-null here for single-nozzle printers too. The stamped
|
||||
// nozzle_diameter is the grouping result's rounded matching-key value; the 3mf writer decides the
|
||||
// final saved diameter (see has_multi_nozzle_extruder). group_id and volume_type are unaffected.
|
||||
if (result && result->nozzle_group_result) {
|
||||
auto nozzles_for_filament = result->nozzle_group_result->get_nozzles_for_filament(it->first);
|
||||
if (!nozzles_for_filament.empty()) {
|
||||
info.group_id.reserve(nozzles_for_filament.size());
|
||||
std::set<double> diameters;
|
||||
std::set<NozzleVolumeType> volume_types;
|
||||
for (const auto& nozzle : nozzles_for_filament) {
|
||||
info.group_id.emplace_back(nozzle.group_id);
|
||||
diameters.insert(string_to_double_decimal_point(nozzle.diameter));
|
||||
volume_types.insert(nozzle.volume_type);
|
||||
}
|
||||
std::sort(info.group_id.begin(), info.group_id.end());
|
||||
info.group_id.erase(std::unique(info.group_id.begin(), info.group_id.end()), info.group_id.end());
|
||||
if (!diameters.empty())
|
||||
info.nozzle_diameter = *diameters.begin();
|
||||
if (volume_types.size() > 1)
|
||||
info.nozzle_volume_type = get_nozzle_volume_type_string(nvtHybrid);
|
||||
else if (!volume_types.empty())
|
||||
info.nozzle_volume_type = get_nozzle_volume_type_string(*volume_types.begin());
|
||||
}
|
||||
}
|
||||
|
||||
auto model_volume_it = ps.model_volumes_per_extruder.find(it->first);
|
||||
auto support_volume_it = ps.support_volumes_per_extruder.find(it->first);
|
||||
info.used_for_object = model_volume_it != ps.model_volumes_per_extruder.end() && model_volume_it->second > EPSILON;
|
||||
@@ -707,6 +737,13 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
slice_filaments_info.push_back(info);
|
||||
}
|
||||
|
||||
// Carry the layer-aware grouping result into the plate so the 3mf writer can emit the <nozzle> tags
|
||||
// and the enable_filament_dynamic_map flag. Only a LayeredNozzleGroupResult (the slicer output) is
|
||||
// stored; a device-side StaticNozzleGroupResult loaded from a 3mf is not re-serialized here.
|
||||
auto layered_group_result = std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(result->nozzle_group_result);
|
||||
if (layered_group_result)
|
||||
nozzle_group_result = *layered_group_result;
|
||||
|
||||
/* only for test
|
||||
GCodeProcessorResult::SliceWarning sw;
|
||||
sw.msg = BED_TEMP_TOO_HIGH_THAN_FILAMENT;
|
||||
@@ -1284,6 +1321,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
bool _handle_start_config_warning(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_warning();
|
||||
|
||||
bool _handle_start_config_nozzle(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_nozzle();
|
||||
|
||||
//BBS: add plater config parse functions
|
||||
bool _handle_start_config_plater(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_plater();
|
||||
@@ -1619,8 +1659,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
plate->is_label_object_enabled = it->second->is_label_object_enabled;
|
||||
plate->skipped_objects = it->second->skipped_objects;
|
||||
plate->slice_filaments_info = it->second->slice_filaments_info;
|
||||
plate->nozzles_info = it->second->nozzles_info;
|
||||
plate->printer_model_id = it->second->printer_model_id;
|
||||
plate->nozzle_diameters = it->second->nozzle_diameters;
|
||||
plate->nozzle_volume_types = it->second->nozzle_volume_types;
|
||||
plate->filament_maps = it->second->filament_maps;
|
||||
plate->filament_change_sequence = it->second->filament_change_sequence;
|
||||
plate->nozzle_change_sequence = it->second->nozzle_change_sequence;
|
||||
@@ -2298,9 +2340,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
plate_data_list[it->first-1]->is_support_used = it->second->is_support_used;
|
||||
plate_data_list[it->first-1]->is_label_object_enabled = it->second->is_label_object_enabled;
|
||||
plate_data_list[it->first-1]->slice_filaments_info = it->second->slice_filaments_info;
|
||||
plate_data_list[it->first-1]->nozzles_info = it->second->nozzles_info;
|
||||
plate_data_list[it->first-1]->skipped_objects = it->second->skipped_objects;
|
||||
plate_data_list[it->first-1]->printer_model_id = it->second->printer_model_id;
|
||||
plate_data_list[it->first-1]->nozzle_diameters = it->second->nozzle_diameters;
|
||||
plate_data_list[it->first-1]->nozzle_volume_types = it->second->nozzle_volume_types;
|
||||
plate_data_list[it->first-1]->filament_maps = it->second->filament_maps;
|
||||
plate_data_list[it->first-1]->filament_change_sequence = it->second->filament_change_sequence;
|
||||
plate_data_list[it->first-1]->nozzle_change_sequence = it->second->nozzle_change_sequence;
|
||||
@@ -3478,6 +3522,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
res = _handle_start_config_filament(attributes, num_attributes);
|
||||
else if (::strcmp(SLICE_WARNING_TAG, name) == 0)
|
||||
res = _handle_start_config_warning(attributes, num_attributes);
|
||||
else if (::strcmp(NOZZLE_TAG, name) == 0)
|
||||
res = _handle_start_config_nozzle(attributes, num_attributes);
|
||||
else if (::strcmp(ASSEMBLE_TAG, name) == 0)
|
||||
res = _handle_start_assemble(attributes, num_attributes);
|
||||
else if (::strcmp(ASSEMBLE_ITEM_TAG, name) == 0)
|
||||
@@ -3512,6 +3558,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
res = _handle_end_config_plater();
|
||||
else if (::strcmp(FILAMENT_TAG, name) == 0)
|
||||
res = _handle_end_config_filament();
|
||||
else if (::strcmp(NOZZLE_TAG, name) == 0)
|
||||
res = _handle_end_config_nozzle();
|
||||
else if (::strcmp(INSTANCE_TAG, name) == 0)
|
||||
res = _handle_end_config_plater_instance();
|
||||
else if (::strcmp(ASSEMBLE_TAG, name) == 0)
|
||||
@@ -4469,6 +4517,21 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
m_curr_plater->config.set_key_value("filament_map", new ConfigOptionInts(filament_map));
|
||||
}
|
||||
}
|
||||
else if (key == FILAMENT_VOL_MAP_ATTR) {
|
||||
if (m_curr_plater){
|
||||
auto filament_volume_map = get_vector_from_string(value);
|
||||
for (size_t idx = 0; idx < filament_volume_map.size(); ++idx) {
|
||||
// The map feeds per-filament slot resolution and grouping. Clamp any
|
||||
// higher volume-type back to Standard(0) on load: Hybrid(2) is only an
|
||||
// in-memory grouping seed that is never persisted, and TPU High Flow(3)
|
||||
// is clamped with the same information loss on every load.
|
||||
if (filament_volume_map[idx] > 1) {
|
||||
filament_volume_map[idx] = 0;
|
||||
}
|
||||
}
|
||||
m_curr_plater->config.set_key_value("filament_volume_map", new ConfigOptionInts(filament_volume_map));
|
||||
}
|
||||
}
|
||||
else if (key == GCODE_FILE_ATTR)
|
||||
{
|
||||
m_curr_plater->gcode_file = value;
|
||||
@@ -4578,6 +4641,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
if (m_curr_plater)
|
||||
m_curr_plater->printer_model_id = value;
|
||||
}
|
||||
else if (key == NOZZLE_VOLUME_TYPE_ATTR)
|
||||
{
|
||||
if (m_curr_plater)
|
||||
m_curr_plater->nozzle_volume_types = value;
|
||||
}
|
||||
else if (key == NOZZLE_DIAMETERS_ATTR)
|
||||
{
|
||||
if (m_curr_plater)
|
||||
@@ -4631,6 +4699,41 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_start_config_nozzle(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
// Read the per-plate <nozzle> tags. Older 3mf without <nozzle> tags leave nozzles_info
|
||||
// empty; load_nozzle_infos_with_compatibility then rebuilds the list from the per-filament
|
||||
// group_id / filament_map on the device side.
|
||||
if (m_curr_plater) {
|
||||
// id="0" extruder_id="1" nozzle_diameter="0.4" volume_type="Standard"
|
||||
std::string id = bbs_get_attribute_value_string(attributes, num_attributes, "id");
|
||||
std::string extruder_id = bbs_get_attribute_value_string(attributes, num_attributes, "extruder_id");
|
||||
std::string nozzle_diameter= bbs_get_attribute_value_string(attributes, num_attributes, "nozzle_diameter");
|
||||
std::string volume_type = bbs_get_attribute_value_string(attributes, num_attributes, "volume_type");
|
||||
|
||||
auto volume_type_str_to_enum = ConfigOptionEnum<NozzleVolumeType>::get_enum_values();
|
||||
|
||||
MultiNozzleUtils::NozzleInfo nozzle_info;
|
||||
nozzle_info.group_id = atoi(id.c_str());
|
||||
nozzle_info.extruder_id = atoi(extruder_id.c_str()) - 1;
|
||||
nozzle_info.diameter = nozzle_diameter;
|
||||
|
||||
if (volume_type_str_to_enum.count(volume_type))
|
||||
nozzle_info.volume_type = NozzleVolumeType(volume_type_str_to_enum.at(volume_type));
|
||||
else
|
||||
nozzle_info.volume_type = NozzleVolumeType::nvtStandard;
|
||||
|
||||
m_curr_plater->nozzles_info.push_back(nozzle_info);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_end_config_nozzle()
|
||||
{
|
||||
// do nothing
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_start_config_warning(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
if (m_curr_plater) {
|
||||
@@ -5959,7 +6062,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
m_thumbnail_middle = iter->second;
|
||||
}
|
||||
boost::system::error_code ec;
|
||||
std::string filename = std::string(store_params.path);
|
||||
std::string filename = store_params.path;
|
||||
boost::filesystem::remove(filename + ".tmp", ec);
|
||||
|
||||
bool result = _save_model_to_file(filename + ".tmp", *store_params.model, store_params.plate_data_list, store_params.project_presets, store_params.config,
|
||||
@@ -8008,6 +8111,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << "\"/>\n";
|
||||
}
|
||||
|
||||
ConfigOptionInts* filament_volume_maps_opt = plate_data->config.option<ConfigOptionInts>("filament_volume_map");
|
||||
if (filament_map_mode_opt != nullptr && filament_volume_maps_opt != nullptr) {
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << FILAMENT_VOL_MAP_ATTR << "\" " << VALUE_ATTR << "=\"";
|
||||
const std::vector<int>& volume_values = filament_volume_maps_opt->values;
|
||||
for (int i = 0; i < volume_values.size(); ++i) {
|
||||
stream << volume_values[i];
|
||||
if (i != (volume_values.size() - 1))
|
||||
stream << " ";
|
||||
}
|
||||
stream << "\"/>\n";
|
||||
}
|
||||
|
||||
if (save_gcode)
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << GCODE_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << xml_escape(plate_data->gcode_file) << "\"/>\n";
|
||||
if (!plate_data->gcode_file.empty()) {
|
||||
@@ -8147,7 +8262,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
[](unsigned int filament_id) { return filament_id + 1; });
|
||||
|
||||
const std::string plate_key = "plate_" + std::to_string(idx + 1);
|
||||
sequence_json[plate_key]["sequence"] = filament_sequence;
|
||||
// Dynamic-map plates write the sequence under "filament_sequence"; every other plate (the
|
||||
// whole shipping fleet + H2C static mode) keeps the "sequence" key, so the saved 3mf is
|
||||
// byte-identical to the older format. The reader accepts both.
|
||||
const bool enable_dynamic_map = plate_data->nozzle_group_result && plate_data->nozzle_group_result->is_support_dynamic_nozzle_map();
|
||||
const std::string seq_key = enable_dynamic_map ? "filament_sequence" : "sequence";
|
||||
sequence_json[plate_key][seq_key] = filament_sequence;
|
||||
sequence_json[plate_key]["nozzle_sequence"] = plate_data->nozzle_change_sequence;
|
||||
sequence_json[plate_key]["optimal_assignment"] = plate_data->optimal_assignment;
|
||||
}
|
||||
@@ -8233,6 +8353,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
if (nozzle_diameter_option)
|
||||
nozzle_diameters_str = nozzle_diameter_option->serialize();
|
||||
|
||||
// True when any extruder carries a cluster of interchangeable nozzles (max nozzle count
|
||||
// > 1). Such an extruder's per-nozzle diameters are not expressible in the per-extruder
|
||||
// nozzle_diameter config, so the saved <filament>/<nozzle> diameters must come from the
|
||||
// grouping result. For a single-nozzle-per-extruder printer the true diameter is the raw
|
||||
// config value; the grouping result rounds it to the nearest of {0.2,0.4,0.6,0.8} for its
|
||||
// internal matching key, so reading that back would rewrite a non-standard nozzle
|
||||
// (e.g. 0.5 -> 0.4). Use this flag to keep the exact config value in that case.
|
||||
auto* extruder_max_nozzle_count_option = dynamic_cast<const ConfigOptionInts*>(config.option("extruder_max_nozzle_count"));
|
||||
const bool has_multi_nozzle_extruder = extruder_max_nozzle_count_option &&
|
||||
std::any_of(extruder_max_nozzle_count_option->values.begin(), extruder_max_nozzle_count_option->values.end(),
|
||||
[](int v) { return v > 1; });
|
||||
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PRINTER_MODEL_ID_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->printer_model_id << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NOZZLE_DIAMETERS_ATTR << "\" " << VALUE_ATTR << "=\"" << nozzle_diameters_str << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << TIMELAPSE_TYPE_ATTR << "\" " << VALUE_ATTR << "=\"" << timelapse_type << "\"/>\n";
|
||||
@@ -8242,7 +8374,15 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OUTSIDE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->toolpath_outside << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SUPPORT_USED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_support_used << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << LABEL_OBJECT_ENABLED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_label_object_enabled << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << ENABLE_FILAMENT_DYNAMIC_MAP_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << false << "\"/>\n";
|
||||
// Report the plate's dynamic-map state from the grouping result. The result is present
|
||||
// for the whole fleet (a static single-nozzle result too), so this if-branch is normally
|
||||
// taken; is_support_dynamic_nozzle_map() is false for any non-dynamic (static /
|
||||
// single-extruder) result ⇒ byte-identical to the previously hard-coded value. The else
|
||||
// is a defensive fallback for a missing result.
|
||||
if (plate_data && plate_data->nozzle_group_result)
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << ENABLE_FILAMENT_DYNAMIC_MAP_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << plate_data->nozzle_group_result->is_support_dynamic_nozzle_map() << "\"/>\n";
|
||||
else
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << ENABLE_FILAMENT_DYNAMIC_MAP_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << false << "\"/>\n";
|
||||
{
|
||||
bool has_filament_switcher = config.has("has_filament_switcher") ? config.opt_bool("has_filament_switcher") : false;
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << HAS_FILAMENT_SWITCHER_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << has_filament_switcher << "\"/>\n";
|
||||
@@ -8357,7 +8497,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
if (std::find(used_nozzle_groups.begin(), used_nozzle_groups.end(), nozzle_group_id) == used_nozzle_groups.end())
|
||||
used_nozzle_groups.push_back(nozzle_group_id);
|
||||
const std::string filament_nozzle_group_id = it->group_id.empty() ? std::to_string(nozzle_group_id) : join_int_list_comma(it->group_id);
|
||||
const double filament_nozzle_diameter = it->nozzle_diameter > 0.0 ? it->nozzle_diameter : get_nozzle_diameter(nozzle_group_id);
|
||||
// Single-nozzle extruders: exact config diameter; clusters keep the result's rounded
|
||||
// value (see has_multi_nozzle_extruder).
|
||||
const double filament_nozzle_diameter = (has_multi_nozzle_extruder && it->nozzle_diameter > 0.0)
|
||||
? it->nozzle_diameter : get_nozzle_diameter(nozzle_group_id);
|
||||
const std::string filament_nozzle_volume_type = it->nozzle_volume_type.empty() ? get_nozzle_volume_type(nozzle_group_id) : it->nozzle_volume_type;
|
||||
|
||||
stream << " <" << FILAMENT_TAG << " " << FILAMENT_ID_TAG << "=\"" << std::to_string(it->id + 1) << "\" "
|
||||
@@ -8377,12 +8520,26 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << " <" << SLICE_WARNING_TAG << " msg=\"" << it->msg << "\" level=\"" << std::to_string(it->level) << "\" error_code =\"" << it->error_code << "\" />\n";
|
||||
}
|
||||
|
||||
for (int nozzle_group_id : used_nozzle_groups) {
|
||||
stream << " <" << NOZZLE_TAG << " "
|
||||
<< "id=\"" << nozzle_group_id << "\" "
|
||||
<< "extruder_id=\"" << nozzle_group_id + 1 << "\" "
|
||||
<< "nozzle_diameter=\"" << get_nozzle_diameter_str(nozzle_group_id) << "\" "
|
||||
<< "volume_type=\"" << get_nozzle_volume_type(nozzle_group_id) << "\"/>\n";
|
||||
// Emit the <nozzle> tags from the grouping result. Single-nozzle-per-extruder printers
|
||||
// override the diameter with the exact config value (see has_multi_nozzle_extruder); the
|
||||
// else is a defensive fallback for a missing result.
|
||||
if (plate_data->nozzle_group_result) {
|
||||
auto used_nozzle_list = plate_data->nozzle_group_result->get_used_nozzles_in_extruder();
|
||||
for (auto& used_nozzle : used_nozzle_list) {
|
||||
if (!has_multi_nozzle_extruder && nozzle_diameter_option &&
|
||||
used_nozzle.extruder_id >= 0 && used_nozzle.extruder_id < (int) nozzle_diameter_option->values.size()) {
|
||||
used_nozzle.diameter = get_nozzle_diameter_str(used_nozzle.extruder_id);
|
||||
}
|
||||
stream << " <" << NOZZLE_TAG << " " << used_nozzle.serialize() << "/>\n";
|
||||
}
|
||||
} else {
|
||||
for (int nozzle_group_id : used_nozzle_groups) {
|
||||
stream << " <" << NOZZLE_TAG << " "
|
||||
<< "id=\"" << nozzle_group_id << "\" "
|
||||
<< "extruder_id=\"" << nozzle_group_id + 1 << "\" "
|
||||
<< "nozzle_diameter=\"" << get_nozzle_diameter_str(nozzle_group_id) << "\" "
|
||||
<< "volume_type=\"" << get_nozzle_volume_type(nozzle_group_id) << "\"/>\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!plate_data->layer_filaments.empty()) {
|
||||
@@ -9016,7 +9173,7 @@ bool store_bbs_3mf(StoreParams& store_params)
|
||||
// All export should use "C" locales for number formatting.
|
||||
CNumericLocalesSetter locales_setter;
|
||||
|
||||
if (store_params.path == nullptr || store_params.model == nullptr)
|
||||
if (store_params.path.empty() || store_params.model == nullptr)
|
||||
return false;
|
||||
|
||||
_BBS_3MF_Exporter exporter;
|
||||
|
||||
@@ -73,6 +73,7 @@ struct PlateData
|
||||
std::map<int, std::pair<int, int>> obj_inst_map;
|
||||
std::string printer_model_id;
|
||||
std::string nozzle_diameters;
|
||||
std::string nozzle_volume_types;
|
||||
std::string gcode_file;
|
||||
std::string gcode_file_md5;
|
||||
std::string thumbnail_file;
|
||||
@@ -102,6 +103,13 @@ struct PlateData
|
||||
std::vector<unsigned int> nozzle_change_sequence;
|
||||
std::vector<int> optimal_assignment;
|
||||
|
||||
// Multi-nozzle grouping surface. nozzles_info accumulates the <nozzle> tags read from a
|
||||
// gcode.3mf; nozzle_group_result is the slicer's per-filament→nozzle assignment carried into the
|
||||
// saved 3mf metadata (write) and reconstructed on load. Both are empty/nullopt for single-nozzle
|
||||
// prints, so the saved-3mf output for single-nozzle printers is byte-identical.
|
||||
std::vector<MultiNozzleUtils::NozzleInfo> nozzles_info;
|
||||
std::optional<MultiNozzleUtils::LayeredNozzleGroupResult> nozzle_group_result;
|
||||
|
||||
// Hexadecimal number,
|
||||
// the 0th digit corresponds to extruder 1
|
||||
// the 1th digit corresponds to extruder 2
|
||||
@@ -226,7 +234,7 @@ typedef std::map<int, PlateData*> PlateDataMaps;
|
||||
|
||||
struct StoreParams
|
||||
{
|
||||
const char* path;
|
||||
std::string path;
|
||||
Model* model = nullptr;
|
||||
PlateDataPtrs plate_data_list;
|
||||
int export_plate_idx = -1;
|
||||
|
||||
+1523
-454
File diff suppressed because it is too large
Load Diff
+72
-5
@@ -60,15 +60,20 @@ class Wipe {
|
||||
public:
|
||||
bool enable;
|
||||
Polyline path;
|
||||
|
||||
// Orca:
|
||||
struct RetractionValues{
|
||||
double retractLengthBeforeWipe;
|
||||
double retractLengthDuringWipe;
|
||||
double retraction_length_before_wipe = 0.;
|
||||
double retraction_length_during_wipe = 0.;
|
||||
double retraction_length_after_wipe = 0.;
|
||||
};
|
||||
|
||||
Wipe() : enable(false) {}
|
||||
bool has_path() const { return !this->path.points.empty(); }
|
||||
void reset_path() { this->path = Polyline(); }
|
||||
std::string wipe(GCode &gcodegen, double length, bool toolchange = false, bool is_last = false);
|
||||
|
||||
// Orca:
|
||||
RetractionValues calculateWipeRetractionLengths(GCode& gcodegen, bool toolchange);
|
||||
};
|
||||
|
||||
@@ -252,7 +257,8 @@ public:
|
||||
std::string travel_to(const Point& point, ExtrusionRole role, std::string comment, double z = DBL_MAX);
|
||||
bool needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type);
|
||||
std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift, bool apply_instantly = false, ExtrusionRole role = erNone);
|
||||
std::string unretract() { return m_writer.unlift() + m_writer.unretract(); }
|
||||
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
|
||||
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
|
||||
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1);
|
||||
bool is_BBL_Printer();
|
||||
WipeTowerType wipe_tower_type();
|
||||
@@ -263,6 +269,12 @@ public:
|
||||
// append full config to the given string
|
||||
static void append_full_config(const Print& print, std::string& str);
|
||||
|
||||
// Per-filament config-slot resolvers for the current layer (m_cur_layer_idx): the filament
|
||||
// resolver keys filament-indexed arrays, the nozzle resolver keys (extruder x volume-type)
|
||||
// slot arrays. Both degenerate to filament_id / extruder index on single-volume printers.
|
||||
size_t get_filament_config_index(int filament_id) const;
|
||||
size_t get_nozzle_config_index(int filament_id) const;
|
||||
|
||||
// Object and support extrusions of the same PrintObject at the same print_z.
|
||||
// public, so that it could be accessed by free helper functions from GCode.cpp
|
||||
struct LayerToPrint
|
||||
@@ -347,11 +359,13 @@ private:
|
||||
std::vector<coordf_t> &skirt_done);
|
||||
std::string generate_object_skirt_group(const Print &print,
|
||||
const PrintObject &object,
|
||||
size_t instance_id,
|
||||
const LayerTools &layer_tools,
|
||||
const Layer& layer,
|
||||
unsigned int extruder_id);
|
||||
std::string generate_object_brim(const Print &print,
|
||||
const PrintObject &object,
|
||||
size_t instance_id,
|
||||
bool first_layer);
|
||||
|
||||
LayerResult process_layer(
|
||||
@@ -392,7 +406,9 @@ private:
|
||||
|
||||
//BBS
|
||||
void check_placeholder_parser_failed();
|
||||
size_t cur_extruder_index() const;
|
||||
size_t get_extruder_id(unsigned int filament_id) const;
|
||||
void update_placeholder_parser_with_variant_params();
|
||||
|
||||
void set_last_pos(const Point &pos) { m_last_pos = Point3(pos, 0); m_last_pos_defined = true; }
|
||||
void set_last_pos(const Point3 &pos) { m_last_pos = pos; m_last_pos_defined = true; }
|
||||
@@ -401,6 +417,11 @@ private:
|
||||
std::string preamble();
|
||||
// BBS
|
||||
std::string change_layer(coordf_t print_z);
|
||||
// Bedslinger model: derive the Y-axis acceleration limit from the machine force/bed-mass config
|
||||
// and the mass already printed. Yields the min machine Y acceleration when the A2L config keys are
|
||||
// unset (i.e. every existing printer), so it is inert for them.
|
||||
void mass_load_limited_machine_acceleration(const PrintStatistics &curr_print_statistics, const Print &print,
|
||||
double &y_acceleration_limit_res, double &accumulated_mass_res);
|
||||
// Orca: pass the complete collection of region perimeters to the extrude loop to check whether the wipe before external loop
|
||||
// should be executed
|
||||
std::string extrude_entity(const ExtrusionEntity& entity,
|
||||
@@ -499,11 +520,22 @@ private:
|
||||
std::string extrude_infill(const Print& print, const std::vector<ObjectByExtruder::Island::Region>& by_region, bool ironing);
|
||||
std::string extrude_support(const ExtrusionEntityCollection& support_fills, const ExtrusionRole support_extrusion_role);
|
||||
|
||||
// Farthest-point timelapse: find the extrusion point farthest from camera (0,0)
|
||||
void compute_farthest_point(const std::vector<LayerToPrint> &layers, int most_used_extruder,
|
||||
const std::map<std::pair<const SupportLayer *, ExtrusionRole>, unsigned int> &support_filaments);
|
||||
// Build the per-layer timelapse snapshot g-code (safe-position or, when skip_pos_pick,
|
||||
// an inline photo at the current head position). Extracted from the former process_layer lambda so the
|
||||
// per-extrusion farthest-point hook (_extrude) can call it too. Identical to the old lambda when the
|
||||
// farthest-point subsystem is disabled (skip_pos_pick=false, m_farthest_point_timelapse.enabled=false).
|
||||
std::string generate_timelapse_gcode(const Print &print, coordf_t print_z, int most_used_extruder,
|
||||
const std::set<size_t> *layer_object_label_ids,
|
||||
const std::vector<const PrintObject*> *printed_objects,
|
||||
bool skip_pos_pick = false);
|
||||
|
||||
// BBS
|
||||
LiftType to_lift_type(ZHopType z_hop_types);
|
||||
|
||||
std::set<ObjectID> m_objsWithBrim; // indicates the objs with brim
|
||||
std::set<ObjectID> m_objSupportsWithBrim; // indicates the objs' supports with brim
|
||||
std::set<ObjectInstanceID> m_objsWithBrim; // indicates the object instances with brim
|
||||
// Cache for custom seam enforcers/blockers for each layer.
|
||||
SeamPlacer m_seam_placer;
|
||||
|
||||
@@ -555,6 +587,32 @@ private:
|
||||
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
|
||||
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;
|
||||
TimelapsePosPicker m_timelapse_pos_picker;
|
||||
|
||||
// Farthest-point timelapse context. Corexy-only refinement layered on top of the existing
|
||||
// timelapse_type. All fields default to the inert state; `enabled` is (re)computed each layer in
|
||||
// process_layer and is false whenever the farthest_point_timelapse config toggle is off, the printer
|
||||
// is i3 (psI3), or timelapse_type is not traditional — so every shipping printer that does not set the
|
||||
// toggle is identical to the previous path.
|
||||
struct FarthestPointTimelapseContext {
|
||||
// Whether farthest-point timelapse is active for this layer
|
||||
bool enabled{false};
|
||||
// The farthest extrusion point from camera (0,0) in global scaled coordinates (includes plate origin + inst.shift)
|
||||
Point farthest_point;
|
||||
// farthest_point converted to mm (gcode coordinate space, includes plate origin)
|
||||
Vec2d farthest_gcode_pos{0, 0};
|
||||
// Extruder index (0-based) that prints the farthest point
|
||||
int farthest_extruder_id{0};
|
||||
// Whether the farthest point is printed by the photo head (most_used_extruder)
|
||||
bool farthest_is_photo_head{false};
|
||||
// Whether inline timelapse gcode has already been inserted on this layer
|
||||
bool inserted_this_layer{false};
|
||||
// The extruder used most on this layer, chosen as the photo head
|
||||
int most_used_extruder{0};
|
||||
// Object labels for the current layer, used when inline timelapse is inserted from extrusion code.
|
||||
std::set<size_t> layer_object_label_ids;
|
||||
};
|
||||
FarthestPointTimelapseContext m_farthest_point_timelapse;
|
||||
|
||||
bool m_enable_loop_clipping;
|
||||
//resonance avoidance
|
||||
bool m_resonance_avoidance;
|
||||
@@ -594,6 +652,9 @@ private:
|
||||
float m_last_layer_z{ 0.0f };
|
||||
float m_max_layer_z{ 0.0f };
|
||||
float m_last_width{ 0.0f };
|
||||
// Bedslinger mass model: cumulative printed mass at the previous layer, used to derive
|
||||
// the current layer mass for the per-layer Y acceleration limit (curr_y_acceleration_limit).
|
||||
double m_last_layer_accumulated_mass{ 0.0 };
|
||||
|
||||
// Always check gcode placeholders when building in debug mode.
|
||||
#if !defined(NDEBUG)
|
||||
@@ -653,12 +714,18 @@ private:
|
||||
int m_start_gcode_filament = -1;
|
||||
std::string m_filament_instances_code;
|
||||
|
||||
// Object layer id of the layer being generated; keys the per-filament config-slot
|
||||
// resolvers. Distinct from m_layer_index (an export progress counter starting at -1).
|
||||
size_t m_cur_layer_idx{0};
|
||||
|
||||
std::set<unsigned int> m_initial_layer_extruders;
|
||||
std::vector<std::vector<unsigned int>> m_sorted_layer_filaments;
|
||||
// BBS
|
||||
int get_bed_temperature(const int extruder_id, const bool is_first_layer, const BedType bed_type) const;
|
||||
int get_highest_bed_temperature(const bool is_first_layer,const Print &print) const;
|
||||
|
||||
void update_layer_related_config(int layer_id);
|
||||
|
||||
double calc_max_volumetric_speed(const double layer_height, const double line_width, const std::string co_str);
|
||||
std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1);
|
||||
bool _needSAFC(const ExtrusionPath &path);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
|
||||
CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0), m_current_nozzle(0)
|
||||
{
|
||||
this->reset(gcodegen.writer().get_position());
|
||||
|
||||
@@ -37,7 +37,7 @@ void CoolingBuffer::reset(const Vec3d &position)
|
||||
m_current_pos[0] = float(position.x());
|
||||
m_current_pos[1] = float(position.y());
|
||||
m_current_pos[2] = float(position.z());
|
||||
m_current_pos[4] = float(m_config.travel_speed.value);
|
||||
m_current_pos[4] = float(m_config.travel_speed.get_at(m_current_nozzle));
|
||||
m_fan_speed = -1;
|
||||
m_additional_fan_speed = -1;
|
||||
m_current_fan_speed = -1;
|
||||
@@ -744,6 +744,13 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
int close_fan_the_first_x_layers = EXTRUDER_CONFIG(close_fan_the_first_x_layers);
|
||||
// Is the fan speed ramp enabled?
|
||||
int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer);
|
||||
// ORCA: explicit per-filament first-layer override (-1 = disabled, 0-100 = forced PWM percent on layer 0).
|
||||
// The override is only honoured when the "No cooling for the first" gate is 0; otherwise the gate would
|
||||
// force layers 1..N-1 to zero while the override sets layer 0 to a non-zero value, which is confusing
|
||||
// and non-monotonic. The UI greys out and resets the value in that case, but we also guard here so
|
||||
// legacy profiles loaded with both set are neutralised at the slicer level.
|
||||
int initial_layer_fan_speed = EXTRUDER_CONFIG(initial_layer_fan_speed);
|
||||
const bool has_initial_layer_override = initial_layer_fan_speed >= 0 && close_fan_the_first_x_layers <= 0;
|
||||
supp_interface_fan_speed = EXTRUDER_CONFIG(support_material_interface_fan_speed);
|
||||
|
||||
// ORCA: previously a silent override forced `close_fan_the_first_x_layers` from 0 up to 1 whenever a ramp
|
||||
@@ -751,7 +758,24 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
// That hid the user's literal "no cooling for the first 0 layers" setting and produced a non-zero starting
|
||||
// factor on the ramp denominator. The override has been removed: with N=0 and M>0 the ramp now genuinely
|
||||
// starts on layer 0 at a factor of 1/M and reaches 100% at layer M-1, matching the intent of the option.
|
||||
if (int(layer_id) >= close_fan_the_first_x_layers) {
|
||||
//
|
||||
// ORCA: First-layer hard override (`initial_layer_fan_speed`). When the user has set this option to a
|
||||
// value >= 0, layer 0 emits exactly that percentage so the entire first layer
|
||||
// is at one stable fan speed. The override wins over the `close_fan_the_first_x_layers` gate when
|
||||
// layer_id == 0. From layer 1 onwards the regular logic resumes.
|
||||
if (has_initial_layer_override && layer_id == 0) {
|
||||
fan_speed_new = initial_layer_fan_speed;
|
||||
overhang_fan_speed = initial_layer_fan_speed;
|
||||
overhang_fan_control = false;
|
||||
internal_bridge_fan_speed = initial_layer_fan_speed;
|
||||
internal_bridge_fan_control = false;
|
||||
supp_interface_fan_speed = initial_layer_fan_speed;
|
||||
supp_interface_fan_control = false;
|
||||
ironing_fan_speed = initial_layer_fan_speed;
|
||||
ironing_fan_control = false;
|
||||
// additional_fan_speed_new is left at its configured value (auxiliary fan is independent of the
|
||||
// part-cooling override).
|
||||
} else if (int(layer_id) >= close_fan_the_first_x_layers) {
|
||||
float fan_max_speed = EXTRUDER_CONFIG(fan_max_speed);
|
||||
float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time));
|
||||
float fan_cooling_layer_time = float(EXTRUDER_CONFIG(fan_cooling_layer_time));
|
||||
@@ -769,10 +793,25 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
//}
|
||||
overhang_fan_speed = EXTRUDER_CONFIG(overhang_fan_speed);
|
||||
if (int(layer_id) >= close_fan_the_first_x_layers && int(layer_id) + 1 < full_fan_speed_layer) {
|
||||
// Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer.
|
||||
float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers);
|
||||
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
|
||||
overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255);
|
||||
if (has_initial_layer_override && close_fan_the_first_x_layers == 0 && full_fan_speed_layer > 1) {
|
||||
// ORCA: Option-B anchored ramp. When the first-layer override is configured and there's
|
||||
// no "no cooling" gate, the ramp interpolates linearly from `initial_layer_fan_speed` on
|
||||
// layer 0 up to the computed target on layer `full_fan_speed_layer - 1` instead of
|
||||
// scaling the target by `factor` from zero. Layer 0 itself is handled by the override
|
||||
// branch above; this branch only runs for layer_id >= 1, but the formula uses t = 0 at
|
||||
// layer 0 conceptually so the curve is continuous. Guarantees a monotonic transition
|
||||
// even when the override is larger than the natural 1/M starting value.
|
||||
const float anchor = float(initial_layer_fan_speed);
|
||||
const float denom = float(full_fan_speed_layer - 1);
|
||||
const float t = float(int(layer_id)) / denom;
|
||||
fan_speed_new = std::clamp(int(anchor + t * (float(fan_speed_new) - anchor) + 0.5f), 0, 255);
|
||||
overhang_fan_speed = std::clamp(int(anchor + t * (float(overhang_fan_speed) - anchor) + 0.5f), 0, 255);
|
||||
} else {
|
||||
// Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer.
|
||||
float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers);
|
||||
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
|
||||
overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255);
|
||||
}
|
||||
}
|
||||
supp_interface_fan_speed = EXTRUDER_CONFIG(support_material_interface_fan_speed);
|
||||
supp_interface_fan_control = supp_interface_fan_speed >= 0;
|
||||
|
||||
@@ -25,7 +25,7 @@ class CoolingBuffer {
|
||||
public:
|
||||
CoolingBuffer(GCode &gcodegen);
|
||||
void reset(const Vec3d &position);
|
||||
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
|
||||
void set_current_extruder(unsigned int extruder_id, unsigned int nozzle_id) { m_current_extruder = extruder_id; m_current_nozzle = nozzle_id; }
|
||||
std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
|
||||
|
||||
private:
|
||||
@@ -55,6 +55,7 @@ private:
|
||||
// the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
|
||||
const PrintConfig &m_config;
|
||||
unsigned int m_current_extruder;
|
||||
unsigned int m_current_nozzle;
|
||||
//BBS: current fan speed
|
||||
int m_current_fan_speed;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
#include "GCodeProcessor.hpp"
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <string_view>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace {
|
||||
|
||||
bool equals_case_insensitive(std::string_view lhs, std::string_view rhs)
|
||||
{
|
||||
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin(), [](unsigned char l, unsigned char r) {
|
||||
return std::tolower(l) == std::tolower(r);
|
||||
});
|
||||
}
|
||||
|
||||
float get_clamped_param(const GCodeReader::GCodeLine& line, char axis, float default_value, float min_value, float max_value)
|
||||
{
|
||||
float value = default_value;
|
||||
line.has_value(axis, value);
|
||||
return std::clamp(value, min_value, max_value);
|
||||
}
|
||||
|
||||
float extrusion_time(float e_length, float feedrate)
|
||||
{
|
||||
return feedrate > 0.0f && e_length > 0.0f ? e_length / feedrate * 60.0f : 0.0f;
|
||||
}
|
||||
|
||||
float retract_time(float e_length)
|
||||
{
|
||||
static constexpr float retract_feedrate = 1800.0f;
|
||||
return extrusion_time(std::max(e_length, 0.0f), retract_feedrate);
|
||||
}
|
||||
|
||||
float s819_time(float e_length, float feedrate)
|
||||
{
|
||||
static constexpr float s819_tail_flush_length = 10.0f;
|
||||
static constexpr float s819_tail_feedrate = 400.0f;
|
||||
|
||||
const float tail_length = std::min(std::max(e_length, 0.0f), s819_tail_flush_length);
|
||||
const float main_length = std::max(e_length - tail_length, 0.0f);
|
||||
return extrusion_time(main_length, feedrate) + extrusion_time(tail_length, s819_tail_feedrate);
|
||||
}
|
||||
|
||||
float estimate_M6211_time_for_centauri_carbon(const GCodeReader::GCodeLine& line, float length, double current_x,
|
||||
double current_y)
|
||||
{
|
||||
static constexpr float max_segment_length = 73.0f;
|
||||
static constexpr float wipe_after_flush_time = 2.8f;
|
||||
static constexpr float main_feedrate = 500.0f;
|
||||
static constexpr float tail_feedrate = 400.0f;
|
||||
static constexpr float travel_feedrate = 5000.0f;
|
||||
static constexpr double parking_x = 256.0;
|
||||
static constexpr double parking_y = 0.0;
|
||||
|
||||
const float flush_length = std::clamp(length, 10.0f, 1000.0f);
|
||||
const float cool_time = get_clamped_param(line, 'P', 5000.0f, 0.0f, 20000.0f) * 0.001f;
|
||||
const float travel_time = static_cast<float>(std::abs(current_y - parking_y) + std::abs(current_x - parking_x)) /
|
||||
travel_feedrate * 60.0f;
|
||||
|
||||
// Initial time, including: material change, heating, etc.
|
||||
float m6211_time = 18.2f + travel_time;
|
||||
|
||||
float remaining_flush_length = std::max(flush_length, 0.0f);
|
||||
while (remaining_flush_length > 0.0f) {
|
||||
const float segment_length = std::min(remaining_flush_length, max_segment_length);
|
||||
remaining_flush_length -= segment_length;
|
||||
if (segment_length >= max_segment_length) {
|
||||
// Full segment: 3-phase extrusion (30+35+10=75mm) + retract
|
||||
m6211_time += extrusion_time(30.0f, main_feedrate) + extrusion_time(35.0f, main_feedrate) +
|
||||
extrusion_time(10.0f, tail_feedrate) + extrusion_time(2.0f, tail_feedrate) + cool_time +
|
||||
wipe_after_flush_time;
|
||||
} else {
|
||||
// Partial last segment: simple extrude at F500 + retract at F400
|
||||
m6211_time += extrusion_time(segment_length, main_feedrate) + extrusion_time(2.0f, tail_feedrate) + cool_time +
|
||||
wipe_after_flush_time;
|
||||
}
|
||||
}
|
||||
|
||||
return m6211_time;
|
||||
}
|
||||
|
||||
float estimate_M6211_time_for_centauri_carbon_2(const GCodeReader::GCodeLine& line, float length, float new_extruder_temp)
|
||||
{
|
||||
const float flush_length = std::clamp(length, 10.0f, 1000.0f);
|
||||
const float flush_length_single = get_clamped_param(line, 'K', 75.0f, 10.0f, 300.0f);
|
||||
const float old_filament_e_feedrate = get_clamped_param(line, 'M', 300.0f, 10.0f, 600.0f);
|
||||
const float new_filament_e_feedrate = get_clamped_param(line, 'N', 300.0f, 10.0f, 600.0f);
|
||||
const float cool_time = get_clamped_param(line, 'P', 3000.0f, 0.0f, 20000.0f) * 0.001f;
|
||||
|
||||
// The flush length of the old material, unit: mm
|
||||
static constexpr float e_flush_dist = 15.0f;
|
||||
// Wipe time after flush, in seconds
|
||||
static constexpr float wipe_after_flush_time = 5.0f;
|
||||
|
||||
const float flush_length_after_start = std::max(flush_length - e_flush_dist, 0.0f);
|
||||
const int flush_times = std::max(1, static_cast<int>(std::ceil(flush_length_after_start / flush_length_single)));
|
||||
const float flush_length_actual = flush_length_single;
|
||||
|
||||
// Initial time, including: material change, heating, moving, etc.
|
||||
float m6211_time = 31.0f;
|
||||
m6211_time += extrusion_time(std::min(e_flush_dist, flush_length), old_filament_e_feedrate);
|
||||
|
||||
const int intermediate_flush_times = flush_times - 1;
|
||||
const float intermediate_flush_time = s819_time(flush_length_actual, new_filament_e_feedrate) + retract_time(6.0f) + cool_time +
|
||||
wipe_after_flush_time;
|
||||
m6211_time += static_cast<float>(intermediate_flush_times) * intermediate_flush_time;
|
||||
m6211_time += s819_time(flush_length_actual, new_filament_e_feedrate * 0.8f) + retract_time(4.0f) + cool_time + wipe_after_flush_time;
|
||||
|
||||
static constexpr float cooling_rate = 1.36f;
|
||||
const float r_temp = get_clamped_param(line, 'R', new_extruder_temp + 20.0f, 185.0f, 350.0f);
|
||||
const float s_temp = get_clamped_param(line, 'S', 250.0f, 185.0f, 350.0f);
|
||||
|
||||
if (s_temp < r_temp)
|
||||
m6211_time += (r_temp - s_temp) / cooling_rate;
|
||||
|
||||
return m6211_time;
|
||||
}
|
||||
|
||||
float estimate_M6211_time(const GCodeReader::GCodeLine& line, std::string_view printer_model, float length, float new_extruder_temp, double current_x, double current_y)
|
||||
{
|
||||
if (equals_case_insensitive(printer_model, "Elegoo Centauri Carbon") || equals_case_insensitive(printer_model, "Elegoo Centauri")) {
|
||||
return estimate_M6211_time_for_centauri_carbon(line, length, current_x, current_y);
|
||||
} else if (equals_case_insensitive(printer_model, "Elegoo Centauri Carbon 2") ||
|
||||
equals_case_insensitive(printer_model, "Elegoo Centauri 2")) {
|
||||
return estimate_M6211_time_for_centauri_carbon_2(line, length, new_extruder_temp);
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void GCodeProcessor::process_elegoo_M6211(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
float length = 0.0f;
|
||||
if (!line.has_value('L', length) || length <= 0.0f)
|
||||
return;
|
||||
|
||||
float t = -1.0f;
|
||||
if (!line.has_value('T', t) || t < 0.0f)
|
||||
return;
|
||||
|
||||
const int filament_id = static_cast<int>(std::round(t));
|
||||
if (filament_id < 0 || filament_id >= m_result.filaments_count)
|
||||
return;
|
||||
|
||||
const int extruder_id = m_filament_maps[filament_id];
|
||||
|
||||
float new_extruder_temp = 0.0f;
|
||||
if (line.has_value('S', new_extruder_temp)) {
|
||||
if (extruder_id >= 0 && static_cast<size_t>(extruder_id) < m_extruder_temps.size())
|
||||
m_extruder_temps[static_cast<size_t>(extruder_id)] = new_extruder_temp;
|
||||
}
|
||||
|
||||
const float m6211_time = estimate_M6211_time(line, m_printer_model, length, new_extruder_temp,
|
||||
m_start_position[X], m_start_position[Y]);
|
||||
const int curr_filament_id = get_filament_id(false);
|
||||
const bool is_first_extrusion = (curr_filament_id == -1) || (filament_id == curr_filament_id);
|
||||
|
||||
m_time_processor.filament_unload_times = 0;
|
||||
m_time_processor.filament_load_times = m6211_time;
|
||||
process_filament_change(filament_id);
|
||||
|
||||
if (extruder_id >= 0 && static_cast<size_t>(extruder_id) < m_remaining_volume.size()) {
|
||||
const float remaining_volume = static_cast<size_t>(extruder_id) < m_nozzle_volume.size() ?
|
||||
m_nozzle_volume[extruder_id] :
|
||||
0.0f;
|
||||
const float filament_diameter = static_cast<size_t>(filament_id) < m_result.filament_diameters.size() ?
|
||||
m_result.filament_diameters[filament_id] :
|
||||
m_result.filament_diameters.back();
|
||||
const float area_filament_cross_section = static_cast<float>(M_PI) * sqr(0.5f * filament_diameter);
|
||||
const float volume_flushed_filament = area_filament_cross_section * length;
|
||||
|
||||
if (volume_flushed_filament >= remaining_volume) {
|
||||
if (!is_first_extrusion)
|
||||
m_used_filaments.update_flush_per_filament(curr_filament_id, remaining_volume);
|
||||
|
||||
m_used_filaments.update_flush_per_filament(filament_id, volume_flushed_filament - remaining_volume);
|
||||
m_remaining_volume[extruder_id] = 0.0f;
|
||||
} else {
|
||||
m_used_filaments.update_flush_per_filament(filament_id, volume_flushed_filament);
|
||||
m_remaining_volume[extruder_id] -= volume_flushed_filament;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/CustomGCode.hpp"
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
@@ -43,6 +44,23 @@ class Print;
|
||||
Count
|
||||
};
|
||||
|
||||
// Classifies why a wipe-tower / change_filament / time-lapse region is safe to relocate a
|
||||
// pre-heat M104 into, for the pre-heat/pre-cool injector. The shipping time_lapse_gcode
|
||||
// template (timelapse-on by default) emits SKIPPABLE_* on essentially every slice, so the
|
||||
// "timelapse" payload -> stTimelapse classification is exercised widely.
|
||||
enum SkipType
|
||||
{
|
||||
stTimelapse,
|
||||
stHeadWrapDetect,
|
||||
stOther,
|
||||
stNone
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string_view, SkipType> skip_type_map{
|
||||
{"timelapse", SkipType::stTimelapse},
|
||||
{"head_wrap_detect", SkipType::stHeadWrapDetect}
|
||||
};
|
||||
|
||||
struct PrintEstimatedStatistics
|
||||
{
|
||||
enum class ETimeMode : unsigned char
|
||||
@@ -77,6 +95,10 @@ class Print;
|
||||
|
||||
std::array<Mode, static_cast<size_t>(ETimeMode::Count)> modes;
|
||||
unsigned int total_filament_changes;
|
||||
// Number of filament changes that actually re-flush a nozzle (a filament-in-nozzle change
|
||||
// onto a non-empty nozzle), tracked only by the richer multi-nozzle hotend-change time model.
|
||||
// Stays 0 for single-nozzle printers (X1/P1/A1/H2S/A2L), which never enter the two-arg model.
|
||||
unsigned int total_flush_filament_changes;
|
||||
unsigned int total_extruder_changes;
|
||||
float total_filament_load_time;
|
||||
float total_filament_unload_time;
|
||||
@@ -101,6 +123,7 @@ class Print;
|
||||
flush_per_filament.clear();
|
||||
used_filaments_per_role.clear();
|
||||
total_filament_changes = 0;
|
||||
total_flush_filament_changes = 0;
|
||||
total_extruder_changes = 0;
|
||||
total_filament_load_time = 0.0f;
|
||||
total_filament_unload_time = 0.0f;
|
||||
@@ -166,6 +189,14 @@ class Print;
|
||||
ConflictResultOpt conflict_result;
|
||||
GCodeCheckResult gcode_check_result;
|
||||
FilamentPrintableResult filament_printable_reuslt;
|
||||
// The per-filament -> logical-nozzle grouping the slicer computed for this
|
||||
// result, surfaced onto the object the device GUI reads
|
||||
// (plater->background_process().get_current_gcode_result()). Populated only from
|
||||
// Print::get_layered_nozzle_group_result() (ToolOrdering's static L/R + rack subset);
|
||||
// default-empty (null) and read by no g-code emitter, so it is invisible in the emitted
|
||||
// g-code. Consumed by the print-dispatch nozzle mapping (DevNozzleMappingCtrl) via
|
||||
// DevUtilBackend::GetNozzleGroupResult.
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> nozzle_group_result;
|
||||
float initial_layer_time;
|
||||
|
||||
struct SettingsIds
|
||||
@@ -263,6 +294,14 @@ class Print;
|
||||
std::vector<SliceWarning> warnings;
|
||||
int nozzle_hrc;
|
||||
std::vector<NozzleType> nozzle_type;
|
||||
// Per-extruder physical hotend type. Fed to the pre-heat injector's TimeProcessContext
|
||||
// (mixed-type X2D workaround). Populated in apply_config; unused until the injector side-pass
|
||||
// consumes it.
|
||||
std::vector<ExtruderType> extruder_types;
|
||||
// Machine-slot layout of the per-variant printer arrays (one entry per (extruder x
|
||||
// volume-type) slot). Populated in apply_config; keys the per-slot machine-limit lookup.
|
||||
std::vector<std::string> printer_extruder_variant;
|
||||
std::vector<int> printer_extruder_id;
|
||||
// first key stores filaments, second keys stores the layer ranges(enclosed) that use the filaments
|
||||
std::unordered_map<std::vector<unsigned int>, std::vector<std::pair<int, int>>,FilamentSequenceHash> layer_filaments;
|
||||
std::vector<unsigned int> nozzle_change_sequence;
|
||||
@@ -271,6 +310,11 @@ class Print;
|
||||
// first key stores `from` filament, second keys stores the `to` filament
|
||||
std::map<std::pair<int,int>, int > filament_change_count_map;
|
||||
|
||||
// Accumulated print time spent inside SKIPPABLE regions, per skip type. Populated by the time
|
||||
// estimator; consumed only downstream. The shipping time_lapse_gcode template emits SKIPPABLE_*
|
||||
// widely, so this is typically populated (stTimelapse) on most slices.
|
||||
std::unordered_map<SkipType, float> skippable_part_time;
|
||||
|
||||
BedType bed_type = BedType::btCount;
|
||||
void reset();
|
||||
|
||||
@@ -304,11 +348,20 @@ class Print;
|
||||
gcode_check_result = other.gcode_check_result;
|
||||
limit_filament_maps = other.limit_filament_maps;
|
||||
filament_printable_reuslt = other.filament_printable_reuslt;
|
||||
// Orca: copy the shared grouping result so a copied result keeps it (shared_ptr =>
|
||||
// memory-safe), rather than leaving a stale pointer on the target. No g-code effect either way.
|
||||
nozzle_group_result = other.nozzle_group_result;
|
||||
// Keep the per-extruder hotend types on a copied result (injector input).
|
||||
extruder_types = other.extruder_types;
|
||||
printer_extruder_variant = other.printer_extruder_variant;
|
||||
printer_extruder_id = other.printer_extruder_id;
|
||||
layer_filaments = other.layer_filaments;
|
||||
filament_change_sequence = other.filament_change_sequence;
|
||||
nozzle_change_sequence = other.nozzle_change_sequence;
|
||||
optimal_assignment = other.optimal_assignment;
|
||||
filament_change_count_map = other.filament_change_count_map;
|
||||
// Keep the SKIPPABLE per-type time on a copied result.
|
||||
skippable_part_time = other.skippable_part_time;
|
||||
initial_layer_time = other.initial_layer_time;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
time = other.time;
|
||||
@@ -319,6 +372,75 @@ class Print;
|
||||
void unlock() const { result_mutex.unlock(); }
|
||||
};
|
||||
|
||||
// First-pass usage-block descriptors for the pre-heat/pre-cool injector. FilamentUsageBlock
|
||||
// records the [lower,upper) output-line-id span a single filament occupies; ExtruderUsageBlcok
|
||||
// (the "Blcok" typo is intentional) records the span an extruder is active in, with the start/end
|
||||
// filament + logical-nozzle ids and the post-extrusion (pre-switch) partial-free sub-range. Built
|
||||
// during run_post_process, consumed only by the injector side-pass under the enable_pre_heating gate.
|
||||
namespace ExtruderPreHeating
|
||||
{
|
||||
struct FilamentUsageBlock
|
||||
{
|
||||
int filament_id;
|
||||
int extruder_id;
|
||||
int nozzle_id;
|
||||
unsigned int lower_gcode_id;
|
||||
unsigned int upper_gcode_id; // [lower_gcode_id,upper_gcode_id) uses current filament , upper gcode id will be set after finding next block
|
||||
FilamentUsageBlock(int filament_id_, int extruder_id_, int nozzle_id_, unsigned int lower_gcode_id_, unsigned int upper_gcode_id_) :filament_id(filament_id_), extruder_id(extruder_id_), nozzle_id(nozzle_id_), lower_gcode_id(lower_gcode_id_), upper_gcode_id(upper_gcode_id_) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Describle the usage of a exturder in a section
|
||||
*
|
||||
* The strucutre stores the start and end lines of the sections as well as
|
||||
* the filament used at the beginning and end of the section.
|
||||
* Post extrusion means the final extrusion before switching to the next extruder.
|
||||
*
|
||||
* Simplified GCode Flow:
|
||||
* 1.Extruder Change Block (ext0 switch to ext1)
|
||||
* 2.Extruder Usage Block (use ext1 to print)
|
||||
* 3.Extruder Change Block (ext1 switch to ext0)
|
||||
* 4.Extruder Usage Block (use ext0 to print)
|
||||
* 5.Extruder Change Block (ext0 switch to ex1)
|
||||
* ...
|
||||
*
|
||||
* So the construct of extruder usage block relys on two extruder change block
|
||||
*/
|
||||
struct ExtruderUsageBlcok
|
||||
{
|
||||
int extruder_id = -1;
|
||||
unsigned int start_id = -1;
|
||||
unsigned int end_id = -1;
|
||||
int start_filament = -1;
|
||||
int end_filament = -1;
|
||||
int start_nozzle_id = -1;
|
||||
int end_nozzle_id = -1;
|
||||
unsigned int post_extrusion_start_id = -1;
|
||||
unsigned int post_extrusion_end_id = -1;
|
||||
bool ignore_cooling_before_tower = false;
|
||||
|
||||
void initialize_step_1(int extruder_id_, int start_id_, int start_filament_, int start_nozzle_id_) {
|
||||
extruder_id = extruder_id_;
|
||||
start_id = start_id_;
|
||||
start_filament = start_filament_;
|
||||
start_nozzle_id = start_nozzle_id_;
|
||||
};
|
||||
void initialize_step_2(int post_extrusion_start_id_) {
|
||||
post_extrusion_start_id = post_extrusion_start_id_;
|
||||
}
|
||||
void initialize_step_3(int end_id_, int end_filament_, int post_extrusion_end_id_, int end_nozzle_id_) {
|
||||
end_id = end_id_;
|
||||
end_filament = end_filament_;
|
||||
post_extrusion_end_id = post_extrusion_end_id_;
|
||||
end_nozzle_id = end_nozzle_id_;
|
||||
}
|
||||
void reset() {
|
||||
*this = ExtruderUsageBlcok();
|
||||
}
|
||||
ExtruderUsageBlcok() = default;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
class CommandProcessor {
|
||||
public:
|
||||
@@ -347,6 +469,24 @@ class Print;
|
||||
static const std::string VFlush_Start_Tag;
|
||||
static const std::string VFlush_End_Tag;
|
||||
static const std::string External_Purge_Tag;
|
||||
public:
|
||||
// Orca: SKIPPABLE region tags, stored as static strings (the FLUSH idiom above) rather than
|
||||
// a CustomETags/CustomTags array. Public so the emission sites (WipeTower / change_filament
|
||||
// path) can reference them single-sourced.
|
||||
static const std::string Skippable_Start_Tag;
|
||||
static const std::string Skippable_End_Tag;
|
||||
static const std::string Skippable_Type_Tag;
|
||||
// Orca: usage-block builder markers (MACHINE_START_GCODE_END / MACHINE_END_GCODE_START /
|
||||
// NOZZLE_CHANGE_START / NOZZLE_CHANGE_END / CP_TOOLCHANGE_WIPE), stored as static strings (the
|
||||
// FLUSH/SKIPPABLE idiom above) rather than extending the Reserved_Tags arrays — these are
|
||||
// multi-nozzle markers only ever emitted by BBL-printer paths. Public so the emission sites can
|
||||
// reference them single-sourced. The MACHINE_*_GCODE_* emission (GCode.cpp, gated
|
||||
// enable_pre_heating) activates the usage-block builder.
|
||||
static const std::string Machine_Start_GCode_End_Tag;
|
||||
static const std::string Machine_End_GCode_Start_Tag;
|
||||
static const std::string Nozzle_Change_Start_Tag;
|
||||
static const std::string Nozzle_Change_End_Tag;
|
||||
static const std::string Toolchange_Wipe_Tag;
|
||||
public:
|
||||
enum class ETags : unsigned char
|
||||
{
|
||||
@@ -455,6 +595,9 @@ class Print;
|
||||
|
||||
EMoveType move_type{ EMoveType::Noop };
|
||||
ExtrusionRole role{ erNone };
|
||||
// SKIPPABLE tag classification stamped onto each time block. Feeds skippable_part_time
|
||||
// and the injector's SKIPPABLE relocation. stNone unless inside a SKIPPABLE_* region.
|
||||
SkipType skippable_type{ SkipType::stNone };
|
||||
unsigned int move_id{ 0 };
|
||||
unsigned int g1_line_id{ 0 };
|
||||
unsigned int remaining_internal_g1_lines{ 0 };
|
||||
@@ -560,9 +703,24 @@ class Print;
|
||||
//BBS: prepare stage time before print model, including start gcode time and mostly same with start gcode time
|
||||
float prepare_time;
|
||||
|
||||
// Orca: extra time (e.g. a filament-change delay) that can't be attributed to a
|
||||
// matching block on this pass is buffered here and retried on a later pass, so it
|
||||
// is never folded into an unrelated move. On the final pass no later pass remains,
|
||||
// so any still-unmatched remainder is added to the machine total (never to a move
|
||||
// vertex) instead of being dropped, keeping get_time() consistent with the
|
||||
// filament-change statistics. Orca-only EOF hardening; BambuStudio drops it.
|
||||
using AdditionalBufferBlock = std::pair<EMoveType, float>;
|
||||
using AdditionalBuffer = std::vector<AdditionalBufferBlock>;
|
||||
AdditionalBuffer m_additional_time_buffer;
|
||||
|
||||
void reset();
|
||||
|
||||
void calculate_time(GCodeProcessorResult& result, PrintEstimatedStatistics::ETimeMode mode, size_t keep_last_n_blocks = 0, float additional_time = 0.0f);
|
||||
// Merge adjacent buffer entries that target the same move type.
|
||||
static AdditionalBuffer merge_adjacent_additional_time_blocks(const AdditionalBuffer& buffer);
|
||||
|
||||
// additional_time is attributed to the first block matching target_move_type
|
||||
// (EMoveType::Noop matches any block, i.e. the first processed block).
|
||||
void calculate_time(GCodeProcessorResult& result, PrintEstimatedStatistics::ETimeMode mode, size_t keep_last_n_blocks = 0, float additional_time = 0.0f, EMoveType target_move_type = EMoveType::Noop, bool is_final = false);
|
||||
};
|
||||
|
||||
struct UsedFilaments // filaments per ColorChange
|
||||
@@ -609,6 +767,25 @@ class Print;
|
||||
|
||||
struct TimeProcessor
|
||||
{
|
||||
// Orca: the insert-line taxonomy + the ordered map of lines the pre-heat/pre-cool injector
|
||||
// splices into the finished g-code, keyed by output-line id. Orca keeps its single-pass
|
||||
// run_post_process (M73 / filament stats / ActualSpeedMove / Backtrace /
|
||||
// machine_tool_change_time) intact and applies this map in a separate, gated ADDITIVE
|
||||
// second file-rewrite pass (run_second_pass_injection); with an empty map that pass is a
|
||||
// byte-for-byte identity rewrite. The map is populated by the PreCoolingInjector.
|
||||
enum InsertLineType
|
||||
{
|
||||
PlaceholderReplace,
|
||||
TimePredict,
|
||||
FilamentChangePredict,
|
||||
ExtruderChangePredict,
|
||||
PreCooling,
|
||||
PreHeating,
|
||||
};
|
||||
|
||||
// first key is line id, second key is content
|
||||
using InsertedLinesMap = std::map<unsigned int, std::vector<std::pair<std::string, InsertLineType>>>;
|
||||
|
||||
struct Planner
|
||||
{
|
||||
// Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks.
|
||||
@@ -636,6 +813,117 @@ class Print;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
// The pre-cool / pre-heat injection engine. It consumes the already-computed per-move time
|
||||
// substrate (moves[i].time[valid_machine_id] / .gcode_id) and the first-pass usage blocks to
|
||||
// locate idle-hotend windows, then emits M632/M400/M104/M633 lines into a
|
||||
// TimeProcessor::InsertedLinesMap that the additive second file-rewrite pass
|
||||
// (run_second_pass_injection) splices into the finished g-code. It is constructed and run ONLY
|
||||
// when m_enable_pre_heating — single-nozzle printers (X1/P1/A1/H2S, flag false) never reach it.
|
||||
// Every input is a const reference bundled from GCodeProcessor members; the injector never
|
||||
// mutates GCodeProcessor state.
|
||||
class PreCoolingInjector {
|
||||
public:
|
||||
struct ExtruderFreeBlock {
|
||||
unsigned int free_lower_gcode_id;
|
||||
unsigned int free_upper_gcode_id;
|
||||
unsigned int partial_free_lower_id; // range of extrusion in wipe tower; without a wipe tower
|
||||
unsigned int partial_free_upper_id; // partial_free lower/upper equal free_lower_gcode_id
|
||||
int last_filament_id;
|
||||
int next_filament_id;
|
||||
int last_nozzle_id;
|
||||
int next_nozzle_id;
|
||||
int extruder_id; // partition key for the pre-heat/pre-cool region (extruder or hotend), not
|
||||
// necessarily a real extruder id
|
||||
bool ignore_cooling_before_tower = false;
|
||||
};
|
||||
|
||||
void process_pre_cooling_and_heating(TimeProcessor::InsertedLinesMap& inserted_operation_lines);
|
||||
void build_extruder_free_blocks(const std::vector<ExtruderPreHeating::FilamentUsageBlock>& filament_usage_blocks, const std::vector<ExtruderPreHeating::ExtruderUsageBlcok>& extruder_usage_blocks);
|
||||
|
||||
PreCoolingInjector(
|
||||
const std::vector<GCodeProcessorResult::MoveVertex>& moves_,
|
||||
const std::vector<std::string>& filament_types_,
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult& nozzle_group_result_,
|
||||
const std::vector<int>& filament_nozzle_temps_,
|
||||
const std::vector<int>& filament_nozzle_temps_initial_layer_,
|
||||
const std::vector<int>& physical_extruder_map_,
|
||||
int valid_machine_id_,
|
||||
float inject_time_threshold_,
|
||||
bool handle_hotend_as_extruder_,
|
||||
bool has_filament_switcher_,
|
||||
const std::vector<int>& pre_cooling_temp_,
|
||||
const std::vector<double>& cooling_rate_,
|
||||
const std::vector<double>& heating_rate_,
|
||||
const std::vector<std::pair<unsigned int, unsigned int>>& skippable_blocks_,
|
||||
const std::vector<int>& extruder_max_nozzle_count_,
|
||||
const std::vector<double>& filament_preheat_temperature_delta_,
|
||||
const std::vector<double>& filament_max_temperature_drop_when_ec_,
|
||||
unsigned int machine_start_gcode_end_id_,
|
||||
unsigned int machine_end_gcode_start_id_,
|
||||
const std::vector<ExtruderType>& extruder_types_,
|
||||
const std::vector<double>& nozzle_diameter_
|
||||
) :
|
||||
moves(moves_),
|
||||
filament_types(filament_types_),
|
||||
nozzle_group_result(nozzle_group_result_),
|
||||
filament_nozzle_temps(filament_nozzle_temps_),
|
||||
filament_nozzle_temps_initial_layer(filament_nozzle_temps_initial_layer_),
|
||||
physical_extruder_map(physical_extruder_map_),
|
||||
valid_machine_id(valid_machine_id_),
|
||||
inject_time_threshold(inject_time_threshold_),
|
||||
handle_hotend_as_extruder(handle_hotend_as_extruder_),
|
||||
has_filament_switcher(has_filament_switcher_),
|
||||
filament_pre_cooling_temps(pre_cooling_temp_),
|
||||
cooling_rate(cooling_rate_),
|
||||
heating_rate(heating_rate_),
|
||||
skippable_blocks(skippable_blocks_),
|
||||
extruder_max_nozzle_count(extruder_max_nozzle_count_),
|
||||
filament_preheat_temperature_delta(filament_preheat_temperature_delta_),
|
||||
filament_max_temperature_drop_when_ec(filament_max_temperature_drop_when_ec_),
|
||||
machine_start_gcode_end_id(machine_start_gcode_end_id_),
|
||||
machine_end_gcode_start_id(machine_end_gcode_start_id_),
|
||||
extruder_types(extruder_types_),
|
||||
nozzle_diameter(nozzle_diameter_)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<ExtruderFreeBlock> m_extruder_free_blocks;
|
||||
const std::vector<GCodeProcessorResult::MoveVertex>& moves;
|
||||
const std::vector<std::string>& filament_types;
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult& nozzle_group_result;
|
||||
const std::vector<int>& filament_nozzle_temps;
|
||||
const std::vector<int>& filament_nozzle_temps_initial_layer;
|
||||
const std::vector<int>& physical_extruder_map;
|
||||
const int valid_machine_id;
|
||||
const float inject_time_threshold;
|
||||
const bool handle_hotend_as_extruder;
|
||||
const bool has_filament_switcher;
|
||||
const std::vector<double>& cooling_rate;
|
||||
const std::vector<double>& heating_rate;
|
||||
const std::vector<int>& filament_pre_cooling_temps; // target cooling temp during post extrusion
|
||||
const std::vector<std::pair<unsigned int, unsigned int>>& skippable_blocks;
|
||||
const std::vector<int>& extruder_max_nozzle_count;
|
||||
const std::vector<double>& filament_preheat_temperature_delta;
|
||||
const std::vector<double>& filament_max_temperature_drop_when_ec;
|
||||
const unsigned int machine_start_gcode_end_id;
|
||||
const unsigned int machine_end_gcode_start_id;
|
||||
const std::vector<ExtruderType>& extruder_types;
|
||||
const std::vector<double>& nozzle_diameter;
|
||||
|
||||
void inject_cooling_heating_command(
|
||||
TimeProcessor::InsertedLinesMap& inserted_operation_lines,
|
||||
const ExtruderFreeBlock& free_block,
|
||||
float curr_temp,
|
||||
float target_temp,
|
||||
bool pre_cooling,
|
||||
bool pre_heating
|
||||
);
|
||||
|
||||
void build_by_filament_blocks(const std::vector<ExtruderPreHeating::FilamentUsageBlock>& filament_usage_blocks);
|
||||
void build_by_extruder_blocks(const std::vector<ExtruderPreHeating::ExtruderUsageBlcok>& extruder_usage_blocks);
|
||||
};
|
||||
public:
|
||||
class SeamsDetector
|
||||
{
|
||||
@@ -780,12 +1068,61 @@ class Print;
|
||||
bool m_flushing; // mark a section with real flush
|
||||
bool m_virtual_flushing; // mark a section with virtual flush, only for statistics
|
||||
bool m_wipe_tower;
|
||||
// Current-section SKIPPABLE state. Set by process_tags when inside a SKIPPABLE_* region;
|
||||
// stamped onto each TimeBlock. The shipping time_lapse_gcode template emits SKIPPABLE_*
|
||||
// widely, so these commonly go active (true / stTimelapse) and stamp blocks on most slices.
|
||||
bool m_skippable{false};
|
||||
SkipType m_skippable_type{SkipType::stNone};
|
||||
int m_object_label_id{-1};
|
||||
float m_print_z{0.0f};
|
||||
std::vector<float> m_remaining_volume;
|
||||
ExtruderTemps m_filament_nozzle_temp;
|
||||
ExtruderTemps m_filament_nozzle_temp_first_layer;
|
||||
std::vector<int> m_physical_extruder_map;
|
||||
// Multi-nozzle context state. Per-extruder max (sub-)nozzle count; >1 marks a multi-nozzle
|
||||
// extruder. Input for the pre-heat/filament-change-time injection model; not yet consumed by
|
||||
// Orca's time estimator, so it is inert for existing printers.
|
||||
std::vector<int> m_extruder_max_nozzle_count{1};
|
||||
// Pre-heat / pre-cool injector estimator inputs. Populated from the config in apply_config
|
||||
// (both overloads) and cleared in reset(), so the PreCoolingInjector has its inputs in place.
|
||||
// Consumed only by the injector two-pass side-pass, gated on m_enable_pre_heating.
|
||||
std::vector<std::string> m_filament_types;
|
||||
std::vector<double> m_nozzle_diameter;
|
||||
std::vector<double> m_hotend_cooling_rate{ 2.f };
|
||||
std::vector<double> m_hotend_heating_rate{ 2.f };
|
||||
std::vector<int> m_filament_pre_cooling_temp{ 0 };
|
||||
std::vector<double> m_filament_preheat_temperature_delta;
|
||||
bool m_enable_pre_heating{ false };
|
||||
bool m_handle_hotend_as_extruder{ false };
|
||||
bool m_has_filament_switcher{ false };
|
||||
// [start,end] output-line-id ranges of each SKIPPABLE region, collected during
|
||||
// run_post_process. The injector relocates pre-heat M104s out of these ranges. The shipping
|
||||
// time_lapse_gcode template emits SKIPPABLE_* widely, so on a timelapse-on slice this is
|
||||
// populated with many timelapse ranges (not empty) — the consumer must expect the common
|
||||
// timelapse case, not only H2C/A2L wipe-tower ranges.
|
||||
std::vector<std::pair<unsigned int, unsigned int>> m_skippable_blocks;
|
||||
// First-pass usage blocks, built in run_post_process and stored on the member so the
|
||||
// injector side-pass can consume them. Filled only when m_enable_pre_heating — single-nozzle
|
||||
// printers (X1/P1/A1/H2S) never build them. They depend on the MACHINE_*_GCODE_* /
|
||||
// NOZZLE_CHANGE_* emission the builder keys off.
|
||||
std::vector<ExtruderPreHeating::FilamentUsageBlock> m_filament_blocks;
|
||||
std::vector<ExtruderPreHeating::ExtruderUsageBlcok> m_extruder_blocks;
|
||||
unsigned int m_machine_start_gcode_end_line_id{ (unsigned int) (-1) };
|
||||
unsigned int m_machine_end_gcode_start_line_id{ (unsigned int) (-1) };
|
||||
// Set when the MACHINE_END_GCODE_START tag is seen during the streaming parse; tells
|
||||
// process_M400 to skip post-print end-gcode dwells (air purification, timelapse, sound)
|
||||
// so they don't inflate the M73 estimate. BBS excludes them in calculate_time(is_final).
|
||||
bool m_skip_end_gcode_delays{ false };
|
||||
// Tracks, during the stream, which filament sits in each physical nozzle and which nozzle each
|
||||
// extruder currently carries. Written by both branches of the two-arg process_filament_change
|
||||
// (the fallback branch does occupancy bookkeeping only); read by the richer change-time model
|
||||
// and by the per-slot machine-limit resolution. Single-nozzle printers never populate it.
|
||||
MultiNozzleUtils::NozzleStatusRecorder m_nozzle_status_recorder;
|
||||
// Nozzle grouping context for slot resolution during the streaming pass. Set before the
|
||||
// replay begins (see initialize_from_context); deliberately separate from
|
||||
// m_result.nozzle_group_result, which is handed over only after the stream for the
|
||||
// pre-heat injector's second pass and gates the richer change-time model.
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> m_nozzle_group_result;
|
||||
bool m_manual_filament_change;
|
||||
|
||||
//BBS: x, y offset for gcode generated
|
||||
@@ -810,6 +1147,9 @@ class Print;
|
||||
std::vector<unsigned char> m_last_filament_id;
|
||||
std::vector<unsigned char> m_filament_id;
|
||||
unsigned char m_extruder_id;
|
||||
// Cached get_machine_config_idx() value; its inputs (active extruder + recorder occupancy)
|
||||
// change only on filament-change events, where it is recomputed.
|
||||
int m_machine_config_idx{0};
|
||||
ExtruderColors m_extruder_colors;
|
||||
ExtruderTemps m_extruder_temps;
|
||||
bool m_is_XL_printer = false;
|
||||
@@ -831,6 +1171,7 @@ class Print;
|
||||
float m_preheat_time;
|
||||
int m_preheat_steps;
|
||||
bool m_disable_m73;
|
||||
std::string m_printer_model;
|
||||
|
||||
enum class EProducer
|
||||
{
|
||||
@@ -860,6 +1201,11 @@ class Print;
|
||||
public:
|
||||
GCodeProcessor();
|
||||
void init_filament_maps_and_nozzle_type_when_import_only_gcode();
|
||||
// Reprocessing an already-generated g-code (from-previous / imported g-code) does not rebuild
|
||||
// the per-filament nozzle grouping the multi-nozzle device GUI needs. Surface it onto the
|
||||
// result: keep an already-seeded grouping (from initialize_from_context), otherwise synthesize
|
||||
// a default one from the filament map so the result is never left without it.
|
||||
void ensure_nozzle_group_result(int min_filament_count);
|
||||
// check whether the gcode path meets the filament_map grouping requirements
|
||||
bool check_multi_extruder_gcode_valid(const int extruder_size,
|
||||
const Pointfs plate_printable_area,
|
||||
@@ -871,6 +1217,11 @@ class Print;
|
||||
const std::vector<std::set<int>>& unprintable_filament_types );
|
||||
void apply_config(const PrintConfig& config);
|
||||
void set_print(Print* print) { m_print = print; }
|
||||
// Hand the nozzle grouping context to the estimator BEFORE the streaming replay, so the
|
||||
// per-slot machine-limit resolution can follow the active nozzle. Null is fine (slot 0).
|
||||
void initialize_from_context(const std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase>& nozzle_group_result) {
|
||||
m_nozzle_group_result = nozzle_group_result;
|
||||
}
|
||||
|
||||
DynamicConfig export_config_for_render() const;
|
||||
|
||||
@@ -1069,35 +1420,72 @@ class Print;
|
||||
// Unload the current filament into the MK3 MMU2 unit at the end of print.
|
||||
void process_M702(const GCodeReader::GCodeLine& line);
|
||||
|
||||
//Used for Elegoo printer to change tool head
|
||||
void process_M6211(const GCodeReader::GCodeLine& line);
|
||||
void process_elegoo_M6211(const GCodeReader::GCodeLine& line);
|
||||
|
||||
void process_SYNC(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Processes T line (Select Tool)
|
||||
void process_T(const GCodeReader::GCodeLine& line);
|
||||
void process_T(const std::string_view command);
|
||||
// T variant carrying the H<nozzle> logical-nozzle id parsed off the command line. -1 = absent.
|
||||
void process_T(const std::string_view command, int nozzle_id);
|
||||
void process_M1020(const GCodeReader::GCodeLine &line);
|
||||
|
||||
void process_M622(const GCodeReader::GCodeLine &line);
|
||||
void process_M623(const GCodeReader::GCodeLine &line);
|
||||
|
||||
void process_filament_change(int id);
|
||||
// Richer hotend-change time model distinguishing extruder-switch / nozzle-in-extruder change /
|
||||
// filament-in-nozzle change. Self-gated: for single-nozzle printers it delegates to
|
||||
// process_filament_change(int) so their time estimate — hence exported g-code — is unchanged.
|
||||
void process_filament_change(int id, int nozzle_id);
|
||||
// Destination nozzle of a filament change: the explicit H<nozzle> id when given, else the
|
||||
// filament's first nozzle in the grouping. Shared by the change-time model and the
|
||||
// fallback-path occupancy bookkeeping.
|
||||
std::optional<MultiNozzleUtils::NozzleInfo> resolve_target_nozzle(
|
||||
const MultiNozzleUtils::NozzleGroupResultBase &group, int id, int nozzle_id) const;
|
||||
// Machine slot of the nozzle currently mounted in the active extruder (0 when no grouping
|
||||
// context / unknown extruder — the single-slot layout). Cached in m_machine_config_idx,
|
||||
// recomputed on filament-change events.
|
||||
int get_machine_config_idx() const;
|
||||
// True only for multi-nozzle-capable printers (H2C cluster, or a dual/multi-extruder machine
|
||||
// like H2D/X2D): the gate that admits the richer two-arg hotend-change time model. False for
|
||||
// every single-extruder single-nozzle printer (X1/P1/A1/H2S/A2L).
|
||||
bool use_multi_nozzle_change_time_model() const;
|
||||
|
||||
// post process the file with the given filename to:
|
||||
// 1) add remaining time lines M73 and update moves' gcode ids accordingly
|
||||
// 2) update used filament data
|
||||
void run_post_process();
|
||||
|
||||
// Additive second file-rewrite pass. Splices the pre-heat/pre-cool injector's InsertedLinesMap
|
||||
// into the finished g-code and re-shifts every move's gcode_id by the number of inserted lines
|
||||
// before it. Runs only when m_enable_pre_heating, AFTER run_post_process, so single-nozzle
|
||||
// printers (X1/P1/A1/H2S) never enter it; with an empty map it is a byte-for-byte identity rewrite.
|
||||
void run_second_pass_injection();
|
||||
// Shift each move's gcode_id by the count of injector lines inserted before it. No-op when the
|
||||
// map is empty.
|
||||
void handle_offsets_of_second_process(const TimeProcessor::InsertedLinesMap& inserted_operation_lines);
|
||||
|
||||
//BBS: different path_type is only used for arc move
|
||||
void store_move_vertex(EMoveType type, EMovePathType path_type = EMovePathType::Noop_move, bool internal_only = false);
|
||||
|
||||
void set_extrusion_role(ExtrusionRole role);
|
||||
// Resolve the SKIPPABLE_TYPE payload to a SkipType.
|
||||
void set_skippable_type(const std::string_view type);
|
||||
|
||||
float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const;
|
||||
float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const;
|
||||
// Machine limit arrays are indexed by time mode only: [0]=Normal, [1]=Stealth.
|
||||
// Do NOT add an extruder_id parameter — OrcaSlicer does not use BambuStudio's
|
||||
// per-nozzle machine limits (filament_map_2 / get_config_idx_for_filament).
|
||||
// Speed/acceleration limit arrays are slot-major with two mode entries per machine slot:
|
||||
// [slot*2 + mode], slot from get_machine_config_idx() (0 = the only slot on single-variant
|
||||
// printers, whose arrays hold just [Normal, Stealth]). The 2-arg forms read slot 0 and stay
|
||||
// exactly the historical mode-only lookup; jerk and the accelerations below are mode-only.
|
||||
float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int machine_idx) const;
|
||||
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis, int machine_idx) const;
|
||||
float get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis, float acceleration) const;
|
||||
float get_axis_max_jerk_with_jd(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
@@ -1115,10 +1503,10 @@ class Print;
|
||||
void process_custom_gcode_time(CustomGCode::Type code);
|
||||
void process_filaments(CustomGCode::Type code);
|
||||
|
||||
void calculate_time(GCodeProcessorResult& result, size_t keep_last_n_blocks = 0, float additional_time = 0.0f);
|
||||
void calculate_time(GCodeProcessorResult& result, size_t keep_last_n_blocks = 0, float additional_time = 0.0f, EMoveType target_move_type = EMoveType::Noop, bool is_final = false);
|
||||
|
||||
// Simulates firmware st_synchronize() call
|
||||
void simulate_st_synchronize(float additional_time = 0.0f);
|
||||
void simulate_st_synchronize(float additional_time = 0.0f, EMoveType target_move_type = EMoveType::Noop);
|
||||
|
||||
void update_estimated_times_stats();
|
||||
|
||||
|
||||
@@ -1,379 +0,0 @@
|
||||
#include "PostProcessor.hpp"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/nowide/cstdlib.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
// BBS
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
// The standard Windows includes.
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
|
||||
// This routine appends the given argument to a command line such that CommandLineToArgvW will return the argument string unchanged.
|
||||
// Arguments in a command line should be separated by spaces; this function does not add these spaces.
|
||||
// Argument - Supplies the argument to encode.
|
||||
// CommandLine - Supplies the command line to which we append the encoded argument string.
|
||||
static void quote_argv_winapi(const std::wstring &argument, std::wstring &commmand_line_out)
|
||||
{
|
||||
// Don't quote unless we actually need to do so --- hopefully avoid problems if programs won't parse quotes properly.
|
||||
if (argument.empty() == false && argument.find_first_of(L" \t\n\v\"") == argument.npos)
|
||||
commmand_line_out.append(argument);
|
||||
else {
|
||||
commmand_line_out.push_back(L'"');
|
||||
for (auto it = argument.begin(); ; ++ it) {
|
||||
unsigned number_backslashes = 0;
|
||||
while (it != argument.end() && *it == L'\\') {
|
||||
++ it;
|
||||
++ number_backslashes;
|
||||
}
|
||||
if (it == argument.end()) {
|
||||
// Escape all backslashes, but let the terminating double quotation mark we add below be interpreted as a metacharacter.
|
||||
commmand_line_out.append(number_backslashes * 2, L'\\');
|
||||
break;
|
||||
} else if (*it == L'"') {
|
||||
// Escape all backslashes and the following double quotation mark.
|
||||
commmand_line_out.append(number_backslashes * 2 + 1, L'\\');
|
||||
commmand_line_out.push_back(*it);
|
||||
} else {
|
||||
// Backslashes aren't special here.
|
||||
commmand_line_out.append(number_backslashes, L'\\');
|
||||
commmand_line_out.push_back(*it);
|
||||
}
|
||||
}
|
||||
commmand_line_out.push_back(L'"');
|
||||
}
|
||||
}
|
||||
|
||||
static DWORD execute_process_winapi(const std::wstring &command_line)
|
||||
{
|
||||
// Extract the current environment to be passed to the child process.
|
||||
std::wstring envstr;
|
||||
{
|
||||
wchar_t *env = GetEnvironmentStrings();
|
||||
assert(env != nullptr);
|
||||
const wchar_t* var = env;
|
||||
size_t totallen = 0;
|
||||
size_t len;
|
||||
while ((len = wcslen(var)) > 0) {
|
||||
totallen += len + 1;
|
||||
var += len + 1;
|
||||
}
|
||||
envstr = std::wstring(env, totallen);
|
||||
FreeEnvironmentStrings(env);
|
||||
}
|
||||
|
||||
STARTUPINFOW startup_info;
|
||||
memset(&startup_info, 0, sizeof(startup_info));
|
||||
startup_info.cb = sizeof(STARTUPINFO);
|
||||
#if 0
|
||||
startup_info.dwFlags = STARTF_USESHOWWINDOW;
|
||||
startup_info.wShowWindow = SW_HIDE;
|
||||
#endif
|
||||
PROCESS_INFORMATION process_info;
|
||||
if (! ::CreateProcessW(
|
||||
nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */,
|
||||
CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info))
|
||||
throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError())));
|
||||
::WaitForSingleObject(process_info.hProcess, INFINITE);
|
||||
ULONG rc = 0;
|
||||
::GetExitCodeProcess(process_info.hProcess, &rc);
|
||||
::CloseHandle(process_info.hThread);
|
||||
::CloseHandle(process_info.hProcess);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Run the script. If it is a perl script, run it through the bundled perl interpreter.
|
||||
// If it is a batch file, run it through the cmd.exe.
|
||||
// Otherwise run it directly.
|
||||
static int run_script(const std::string &script, const std::string &gcode, std::string &/*std_err*/)
|
||||
{
|
||||
// Unpack the argument list provided by the user.
|
||||
int nArgs;
|
||||
LPWSTR *szArglist = CommandLineToArgvW(boost::nowide::widen(script).c_str(), &nArgs);
|
||||
if (szArglist == nullptr || nArgs <= 0) {
|
||||
// CommandLineToArgvW failed. Maybe the command line escapment is invalid?
|
||||
throw Slic3r::RuntimeError(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path.");
|
||||
}
|
||||
|
||||
std::wstring command_line;
|
||||
std::wstring command = szArglist[0];
|
||||
if (! boost::filesystem::exists(boost::filesystem::path(command)))
|
||||
throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
|
||||
if (boost::iends_with(command, L".pl")) {
|
||||
// This is a perl script. Run it through the perl interpreter.
|
||||
// The current process may be slic3r.exe or slic3r-console.exe.
|
||||
// Find the path of the process:
|
||||
wchar_t wpath_exe[_MAX_PATH + 1];
|
||||
::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH);
|
||||
boost::filesystem::path path_exe(wpath_exe);
|
||||
boost::filesystem::path path_perl = path_exe.parent_path() / "perl" / "perl.exe";
|
||||
if (! boost::filesystem::exists(path_perl)) {
|
||||
LocalFree(szArglist);
|
||||
throw Slic3r::RuntimeError(std::string("Perl interpreter ") + path_perl.string() + " does not exist.");
|
||||
}
|
||||
// Replace it with the current perl interpreter.
|
||||
quote_argv_winapi(boost::nowide::widen(path_perl.string()), command_line);
|
||||
command_line += L" ";
|
||||
} else if (boost::iends_with(command, ".bat")) {
|
||||
// Run a batch file through the command line interpreter.
|
||||
command_line = L"cmd.exe /C ";
|
||||
}
|
||||
|
||||
for (int i = 0; i < nArgs; ++ i) {
|
||||
quote_argv_winapi(szArglist[i], command_line);
|
||||
command_line += L" ";
|
||||
}
|
||||
LocalFree(szArglist);
|
||||
quote_argv_winapi(boost::nowide::widen(gcode), command_line);
|
||||
return (int)execute_process_winapi(command_line);
|
||||
}
|
||||
|
||||
#else
|
||||
// POSIX
|
||||
|
||||
#include <cstdlib> // getenv()
|
||||
#include <sstream>
|
||||
#include <boost/process.hpp>
|
||||
|
||||
namespace process = boost::process;
|
||||
|
||||
static int run_script(const std::string &script, const std::string &gcode, std::string &std_err)
|
||||
{
|
||||
// Try to obtain user's default shell
|
||||
const char *shell = ::getenv("SHELL");
|
||||
if (shell == nullptr) { shell = "/bin/sh"; }
|
||||
|
||||
// Quote and escape the gcode path argument
|
||||
std::string command { script };
|
||||
command.append(" '");
|
||||
for (char c : gcode) {
|
||||
if (c == '\'') { command.append("'\\''"); }
|
||||
else { command.push_back(c); }
|
||||
}
|
||||
command.push_back('\'');
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script, shell: %1%, command: %2%") % shell % command;
|
||||
|
||||
process::ipstream istd_err;
|
||||
process::child child(shell, "-c", command, process::std_err > istd_err);
|
||||
|
||||
std_err.clear();
|
||||
std::string line;
|
||||
|
||||
while (child.running() && std::getline(istd_err, line)) {
|
||||
std_err.append(line);
|
||||
std_err.push_back('\n');
|
||||
}
|
||||
|
||||
child.wait();
|
||||
return child.exit_code();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#define L(s) (s)
|
||||
#define _(s) Slic3r::I18N::translate(s)
|
||||
|
||||
// BBS
|
||||
void gcode_add_line_number(const std::string& path, const DynamicPrintConfig& config)
|
||||
{
|
||||
const ConfigOptionBool* opt = config.opt<ConfigOptionBool>("gcode_add_line_number");
|
||||
if (!opt->getBool())
|
||||
return;
|
||||
|
||||
auto gcode_file = boost::filesystem::path(path);
|
||||
if (!boost::filesystem::exists(gcode_file))
|
||||
return;
|
||||
|
||||
std::fstream fs;
|
||||
std::string new_gcode;
|
||||
fs.open(gcode_file.c_str(), std::fstream::in | std::fstream::out);
|
||||
|
||||
size_t line_number = 1;
|
||||
std::string gcode_line;
|
||||
while (std::getline(fs, gcode_line)) {
|
||||
char num_str[128];
|
||||
memset(num_str, 0, sizeof(num_str));
|
||||
snprintf(num_str, sizeof(num_str), "%zd", line_number);
|
||||
new_gcode += std::string("N") + num_str + " " + gcode_line + "\n";
|
||||
line_number++;
|
||||
}
|
||||
|
||||
fs.clear();
|
||||
fs.seekp(0, std::ios_base::beg);
|
||||
fs.write(new_gcode.c_str(), new_gcode.length());
|
||||
fs.close();
|
||||
}
|
||||
|
||||
// Run post processing script / scripts if defined.
|
||||
// Returns true if a post-processing script was executed.
|
||||
// Returns false if no post-processing script was defined.
|
||||
// Throws an exception on error.
|
||||
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
|
||||
// For a "File" target, a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
|
||||
// In that case the caller is responsible to delete the temp file created.
|
||||
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
|
||||
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
|
||||
// The post-processing script may change the output_name.
|
||||
bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config)
|
||||
{
|
||||
const auto *post_process = config.opt<ConfigOptionStrings>("post_process");
|
||||
if (// likely running in SLA mode
|
||||
post_process == nullptr ||
|
||||
// no post-processing script
|
||||
post_process->values.empty())
|
||||
return false;
|
||||
|
||||
std::string path;
|
||||
if (make_copy) {
|
||||
// Don't run the post-processing script on the input file, it will be memory mapped by the G-code viewer.
|
||||
// Make a copy.
|
||||
path = src_path + ".pp";
|
||||
// First delete an old file if it exists.
|
||||
try {
|
||||
if (boost::filesystem::exists(path))
|
||||
boost::filesystem::remove(path);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting an old temporary file %1% before running a post-processing script: %2%", path, err.what());
|
||||
}
|
||||
// Second make a copy.
|
||||
std::string error_message;
|
||||
if (copy_file(src_path, path, error_message, false) != SUCCESS)
|
||||
throw Slic3r::RuntimeError(Slic3r::format("Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%", src_path, error_message));
|
||||
} else {
|
||||
// Don't make a copy of the G-code before running the post-processing script.
|
||||
path = src_path;
|
||||
}
|
||||
|
||||
auto delete_copy = [&path, &src_path, make_copy]() {
|
||||
if (make_copy)
|
||||
try {
|
||||
if (boost::filesystem::exists(path))
|
||||
boost::filesystem::remove(path);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a temporary copy %1% of a G-code file %2% : %3%", path, src_path, err.what());
|
||||
}
|
||||
};
|
||||
|
||||
auto gcode_file = boost::filesystem::path(path);
|
||||
if (! boost::filesystem::exists(gcode_file))
|
||||
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
|
||||
|
||||
// Store print configuration into environment variables.
|
||||
config.setenv_();
|
||||
// Let the post-processing script know the target host ("File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...)
|
||||
boost::nowide::setenv("SLIC3R_PP_HOST", host.c_str(), 1);
|
||||
// Let the post-processing script know the final file name. For "File" host, it is a full path of the target file name and its location, for example pointing to an SD card.
|
||||
// For "PrusaLink" or "OctoPrint", it is a file name optionally with a directory on the target host.
|
||||
boost::nowide::setenv("SLIC3R_PP_OUTPUT_NAME", output_name.c_str(), 1);
|
||||
|
||||
// Path to an optional file that the post-processing script may create and populate it with a single line containing the output_name replacement.
|
||||
std::string path_output_name = path + ".output_name";
|
||||
auto remove_output_name_file = [&path_output_name, &src_path]() {
|
||||
try {
|
||||
if (boost::filesystem::exists(path_output_name))
|
||||
boost::filesystem::remove(path_output_name);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a file %1% carrying the final name / path of a G-code file %2%: %3%", path_output_name, src_path, err.what());
|
||||
}
|
||||
};
|
||||
// Remove possible stalled path_output_name of the previous run.
|
||||
remove_output_name_file();
|
||||
|
||||
try {
|
||||
for (const std::string &scripts : post_process->values) {
|
||||
std::vector<std::string> lines;
|
||||
boost::split(lines, scripts, boost::is_any_of("\r\n"));
|
||||
for (std::string script : lines) {
|
||||
// Ignore empty post processing script lines.
|
||||
boost::trim(script);
|
||||
if (script.empty())
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
|
||||
std::string std_err;
|
||||
const int result = run_script(script, gcode_file.string(), std_err);
|
||||
if (result != 0) {
|
||||
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
|
||||
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
delete_copy();
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
if (! boost::filesystem::exists(gcode_file)) {
|
||||
const std::string msg = (boost::format(_(L(
|
||||
"Post-processing script %1% failed.\n\n"
|
||||
"The post-processing script is expected to change the G-code file %2% in place, but the G-code file was deleted and likely saved under a new name.\n"
|
||||
"Please adjust the post-processing script to change the G-code in place and consult the manual on how to optionally rename the post-processed G-code file.\n")))
|
||||
% script % path).str();
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (boost::filesystem::exists(path_output_name)) {
|
||||
try {
|
||||
// Read a single line from path_output_name, which should contain the new output name of the post-processed G-code.
|
||||
boost::nowide::fstream f;
|
||||
f.open(path_output_name, std::ios::in);
|
||||
std::string new_output_name;
|
||||
std::getline(f, new_output_name);
|
||||
f.close();
|
||||
|
||||
if (host == "File") {
|
||||
namespace fs = boost::filesystem;
|
||||
fs::path op(new_output_name);
|
||||
if (op.is_relative() && op.has_filename() && op.parent_path().empty()) {
|
||||
// Is this just a filename? Make it an absolute path.
|
||||
auto outpath = fs::path(output_name).parent_path();
|
||||
outpath /= op.string();
|
||||
new_output_name = outpath.string();
|
||||
}
|
||||
else {
|
||||
if (! op.is_absolute() || ! op.has_filename())
|
||||
throw Slic3r::RuntimeError("Unable to parse desired new path from output name file");
|
||||
}
|
||||
if (! fs::exists(fs::path(new_output_name).parent_path()))
|
||||
throw Slic3r::RuntimeError(Slic3r::format("Output directory does not exist: %1%",
|
||||
fs::path(new_output_name).parent_path().string()));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Post-processing script changed the file name from " << output_name << " to " << new_output_name;
|
||||
output_name = new_output_name;
|
||||
} catch (const std::exception &err) {
|
||||
throw Slic3r::RuntimeError(Slic3r::format("run_post_process_scripts: Failed reading a file %1% "
|
||||
"carrying the final name / path of a G-code file: %2%",
|
||||
path_output_name, err.what()));
|
||||
}
|
||||
remove_output_name_file();
|
||||
}
|
||||
} catch (...) {
|
||||
remove_output_name_file();
|
||||
delete_copy();
|
||||
throw;
|
||||
}
|
||||
|
||||
src_path = std::move(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef slic3r_GCode_PostProcessor_hpp_
|
||||
#define slic3r_GCode_PostProcessor_hpp_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Run post processing script / scripts if defined.
|
||||
// Returns true if a post-processing script was executed.
|
||||
// Returns false if no post-processing script was defined.
|
||||
// Throws an exception on error.
|
||||
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
|
||||
// If make_copy, then a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
|
||||
// In that case the caller is responsible to delete the temp file created.
|
||||
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
|
||||
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
|
||||
// The post-processing script may change the output_name.
|
||||
extern bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config);
|
||||
|
||||
inline bool run_post_process_scripts(std::string &src_path, const DynamicPrintConfig &config)
|
||||
{
|
||||
std::string src_path_name = src_path;
|
||||
return run_post_process_scripts(src_path, false, "File", src_path_name, config);
|
||||
}
|
||||
|
||||
// BBS
|
||||
extern void gcode_add_line_number(const std::string &path, const DynamicPrintConfig &config);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GCode_PostProcessor_hpp_ */
|
||||
@@ -361,7 +361,8 @@ namespace Slic3r {
|
||||
* @param safe_areas A collection of extended polygons defining the safe areas.
|
||||
* @return Point The nearest point within the safe areas or the default timelapse position if no safe areas exist.
|
||||
*/
|
||||
Point pick_pos_internal(const Point& curr_pos, const ExPolygons& safe_areas, const ExPolygons& path_collision_area, bool detect_path_collision)
|
||||
Point pick_pos_internal(const Point& curr_pos, const ExPolygons& safe_areas, const ExPolygons& path_collision_area, bool detect_path_collision,
|
||||
const std::optional<Point>& farthest_point = std::nullopt)
|
||||
{
|
||||
struct CandidatePoint
|
||||
{
|
||||
@@ -381,7 +382,11 @@ namespace Slic3r {
|
||||
std::priority_queue<CandidatePoint> max_heap;
|
||||
|
||||
const double candidate_point_segment = scale_(5), weight_of_camera=1./3.;
|
||||
auto penaltyFunc = [&weight_of_camera](const Point &curr_post, const Point &CameraPos, const Point &candidatet) -> double {
|
||||
auto penaltyFunc = [&weight_of_camera, &farthest_point](const Point &curr_post, const Point &CameraPos, const Point &candidatet) -> double {
|
||||
if (farthest_point.has_value()) {
|
||||
// Farthest-point timelapse: prefer candidate closest to the farthest point (L1 norm)
|
||||
return (farthest_point.value() - candidatet).cwiseAbs().sum();
|
||||
}
|
||||
// move distance + Camera occlusion penalty function
|
||||
double ret_pen = (curr_post - candidatet).cwiseAbs().sum() - weight_of_camera * (CameraPos - candidatet).cwiseAbs().sum();
|
||||
return ret_pen;
|
||||
@@ -523,7 +528,7 @@ namespace Slic3r {
|
||||
path_collision_area = union_ex(layer_slices_without_curr, rod_limit_areas);
|
||||
}
|
||||
|
||||
return pick_pos_internal(center_p, safe_area,path_collision_area, by_object);
|
||||
return pick_pos_internal(center_p, safe_area,path_collision_area, by_object, ctx.farthest_point);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -610,4 +615,50 @@ namespace Slic3r {
|
||||
return *m_all_layer_pos;
|
||||
}
|
||||
|
||||
// Whether the head can travel to X0 without crossing any other instance that is
|
||||
// taller than the current print position.
|
||||
bool TimelapsePosPicker::get_is_clear_to_x0(const PosPickCtx &ctx)
|
||||
{
|
||||
bool by_object = m_print_seq == PrintSequence::ByObject;
|
||||
std::vector<const PrintObject *> object_list = get_object_list(ctx.printed_objects);
|
||||
|
||||
auto range_intersect = [](int left1, int right1, int left2, int right2) {
|
||||
if (left1 <= left2 && left2 <= right1) return true;
|
||||
if (left2 <= left1 && left1 <= right2) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
ExPolygons unclear_area;
|
||||
const Layer *layer = ctx.curr_layer;
|
||||
float z_target = layer->print_z;
|
||||
float z_low = layer->print_z - 0.5;
|
||||
float z_high = layer->print_z + 0.5;
|
||||
|
||||
for (auto &obj : object_list) {
|
||||
for (auto &instance : obj->instances()) {
|
||||
auto instance_bbox = get_real_instance_bbox(instance);
|
||||
bool is_curr_obj = ( obj == object_list.back() ) || ( !by_object ),
|
||||
higher_than_curr_pos = instance_bbox.max.z() > z_target;
|
||||
if (!is_curr_obj && range_intersect(instance_bbox.min.z(), instance_bbox.max.z(), z_low, z_high)) {
|
||||
ExPolygon expoly;
|
||||
expoly.contour = {{scale_(instance_bbox.min.x()), scale_(instance_bbox.min.y())},
|
||||
{scale_(instance_bbox.max.x()), scale_(instance_bbox.min.y())},
|
||||
{scale_(instance_bbox.max.x()), scale_(instance_bbox.max.y())},
|
||||
{scale_(instance_bbox.min.x()), scale_(instance_bbox.max.y())}};
|
||||
expoly.contour = expand_object_projection(expoly.contour, by_object, higher_than_curr_pos);
|
||||
unclear_area.emplace_back(std::move(expoly));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Point curr_pos_in_plate = {ctx.curr_pos.x() - scale_(m_plate_offset.x()), ctx.curr_pos.y() - scale_(m_plate_offset.y())};
|
||||
for (const ExPolygon &expoly : unclear_area) {
|
||||
BoundingBox bbox = expoly.contour.bounding_box();
|
||||
if (curr_pos_in_plate.y() < bbox.min.y() || curr_pos_in_plate.y() > bbox.max.y()) continue;
|
||||
if (bbox.min.x() <= curr_pos_in_plate.x()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,9 @@ namespace Slic3r {
|
||||
int picture_extruder_id; // the extruder id to take picture
|
||||
int curr_extruder_id;
|
||||
std::optional<std::vector<const PrintObject*>> printed_objects; // printed objects, only have value in by object mode
|
||||
// Farthest-point timelapse: plate-relative scaled point; when set, pick_pos_internal
|
||||
// biases the picked snapshot position toward this point (nullopt → legacy camera-occlusion loss).
|
||||
std::optional<Point> farthest_point;
|
||||
};
|
||||
|
||||
// data are stored without plate offset
|
||||
@@ -32,6 +35,9 @@ namespace Slic3r {
|
||||
~TimelapsePosPicker() = default;
|
||||
|
||||
Point pick_pos(const PosPickCtx& ctx);
|
||||
// Is the path to X0 clear of other (taller) instances? Drives the
|
||||
// `clear_to_x0` timelapse-gcode variable (g39 clamping detection).
|
||||
bool get_is_clear_to_x0(const PosPickCtx& ctx);
|
||||
void init(const Print* print, const Point& plate_offset);
|
||||
void reset();
|
||||
private:
|
||||
|
||||
@@ -7,13 +7,100 @@
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
// ==================== MaxFlowWithLowerBounds ====================
|
||||
struct MaxFlowWithLowerBounds {
|
||||
public:
|
||||
|
||||
void add_edge(int from, int to, int capacity);
|
||||
|
||||
bool bfs();
|
||||
int dfs(int u, int f);
|
||||
int solve(std::vector<int>& matching);
|
||||
|
||||
public:
|
||||
std::vector<int> l_nodes;
|
||||
std::vector<int> r_nodes;
|
||||
std::vector<Edge> edges;
|
||||
std::vector<std::vector<int>> adj;
|
||||
std::vector<int> level;
|
||||
std::vector<int> it;
|
||||
|
||||
int total_nodes{ -1 };
|
||||
int source_id{ -1 };
|
||||
int sink_id{ -1 };
|
||||
};
|
||||
|
||||
void MaxFlowWithLowerBounds::add_edge(int from, int to, int capacity)
|
||||
{
|
||||
adj[from].emplace_back(edges.size());
|
||||
edges.emplace_back(from, to, capacity, 0);
|
||||
// also add the reverse residual edge with zero capacity
|
||||
adj[to].emplace_back(edges.size());
|
||||
edges.emplace_back(to, from, 0, 0);
|
||||
}
|
||||
|
||||
bool MaxFlowWithLowerBounds::bfs() {
|
||||
level.assign(total_nodes, -1);
|
||||
std::queue<int> q;
|
||||
q.push(source_id);
|
||||
level[source_id] = 0;
|
||||
|
||||
while (!q.empty()) {
|
||||
int u = q.front(); q.pop();
|
||||
for (int eid : adj[u]) {
|
||||
Edge &e = edges[eid];
|
||||
if (e.flow < e.capacity && level[e.to] == -1) {
|
||||
level[e.to] = level[u] + 1;
|
||||
q.push(e.to);
|
||||
}
|
||||
}
|
||||
}
|
||||
return level[sink_id] != -1;
|
||||
}
|
||||
|
||||
int MaxFlowWithLowerBounds::dfs(int u, int f) {
|
||||
if (u == sink_id) return f;
|
||||
for (int &i = it[u]; i < (int)adj[u].size(); ++i) {
|
||||
int eid = adj[u][i];
|
||||
Edge &e = edges[eid];
|
||||
if (e.flow < e.capacity && level[e.to] == level[u] + 1) {
|
||||
int pushed = dfs(e.to, std::min(f, e.capacity - e.flow));
|
||||
if (pushed > 0) {
|
||||
e.flow += pushed;
|
||||
edges[eid ^ 1].flow -= pushed;
|
||||
return pushed;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MaxFlowWithLowerBounds::solve(std::vector<int>& matching) {
|
||||
int flow = 0;
|
||||
while (bfs()) {
|
||||
it.assign(total_nodes, 0);
|
||||
while (int pushed = dfs(source_id, MaxFlowGraph::INF))
|
||||
flow += pushed;
|
||||
}
|
||||
|
||||
int L = l_nodes.size();
|
||||
int R = r_nodes.size();
|
||||
// collect l-r matches
|
||||
matching.resize(l_nodes.size(), MaxFlowGraph::INVALID_ID);
|
||||
for (int u = 0; u < L; ++u) {
|
||||
for (int eid : adj[u]) {
|
||||
Edge &e = edges[eid];
|
||||
if (e.flow > 0 && e.to >= L && e.to < L + R) {
|
||||
matching[e.from] = e.to - L;
|
||||
}
|
||||
}
|
||||
}
|
||||
return flow;
|
||||
}
|
||||
|
||||
// ==================== MinCostMaxFlow ====================
|
||||
struct MinCostMaxFlow {
|
||||
public:
|
||||
struct Edge {
|
||||
int from, to, capacity, cost, flow;
|
||||
Edge(int u, int v, int cap, int cst) : from(u), to(v), capacity(cap), cost(cst), flow(0) {}
|
||||
};
|
||||
|
||||
std::vector<int> solve();
|
||||
void add_edge(int from, int to, int capacity, int cost);
|
||||
bool spfa(int source, int sink);
|
||||
@@ -107,15 +194,10 @@ namespace Slic3r
|
||||
{
|
||||
if (l_nodes[idx_in_left] == -1) {
|
||||
return 0;
|
||||
//TODO: test more here
|
||||
int sum = 0;
|
||||
for (int i = 0; i < matrix.size(); ++i)
|
||||
sum += matrix[i][idx_in_right];
|
||||
sum /= matrix.size();
|
||||
return -sum;
|
||||
}
|
||||
|
||||
return matrix[l_nodes[idx_in_left]][r_nodes[idx_in_right]];
|
||||
float val = matrix[l_nodes[idx_in_left]][r_nodes[idx_in_right]];
|
||||
return std::min(static_cast<int>(val), MaxFlowGraph::MCMF_MAX_EDGE_COST);
|
||||
}
|
||||
|
||||
|
||||
@@ -123,27 +205,40 @@ namespace Slic3r
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits,
|
||||
const std::vector<int>& u_capacity,
|
||||
const std::vector<int>& v_capacity)
|
||||
const std::vector<int>& v_capacity,
|
||||
const std::vector<std::pair<std::set<int>,int>>& v_group_capacity)
|
||||
{
|
||||
assert(u_capacity.empty() || u_capacity.size() == u_nodes.size());
|
||||
assert(v_capacity.empty() || v_capacity.size() == v_nodes.size());
|
||||
l_nodes = u_nodes;
|
||||
r_nodes = v_nodes;
|
||||
total_nodes = u_nodes.size() + v_nodes.size() + 2;
|
||||
total_nodes = u_nodes.size() + v_nodes.size() + v_group_capacity.size() + 2;
|
||||
source_id = total_nodes - 2;
|
||||
sink_id = total_nodes - 1;
|
||||
|
||||
adj.resize(total_nodes);
|
||||
|
||||
std::vector<int>v_node_to(v_nodes.size(), sink_id);
|
||||
for (size_t gid = 0; gid < v_group_capacity.size(); ++gid) {
|
||||
for (auto vid : v_group_capacity[gid].first)
|
||||
v_node_to[vid] = l_nodes.size() + r_nodes.size() + gid;
|
||||
}
|
||||
|
||||
// add edge from source to left nodes
|
||||
for (int idx = 0; idx < l_nodes.size(); ++idx) {
|
||||
int capacity = u_capacity.empty() ? 1 : u_capacity[idx];
|
||||
add_edge(source_id, idx, capacity);
|
||||
}
|
||||
// add edge from right nodes to sink node
|
||||
// add edge from right nodes to v_node_to(sink node or temp group node)
|
||||
for (int idx = 0; idx < r_nodes.size(); ++idx) {
|
||||
int capacity = v_capacity.empty() ? 1 : v_capacity[idx];
|
||||
add_edge(l_nodes.size() + idx, sink_id, capacity);
|
||||
add_edge(l_nodes.size() + idx, v_node_to[idx], capacity);
|
||||
}
|
||||
|
||||
// add edge from temp group node to sink node
|
||||
for (int idx = 0; idx < v_group_capacity.size(); ++idx) {
|
||||
int capacity = v_group_capacity[idx].second;
|
||||
add_edge(l_nodes.size() + r_nodes.size() + idx, sink_id, capacity);
|
||||
}
|
||||
|
||||
// add edge from left nodes to right nodes
|
||||
@@ -269,6 +364,301 @@ namespace Slic3r
|
||||
return m_solver->solve();
|
||||
}
|
||||
|
||||
// ==================== GeneralMinCostLowerBoundsSolver ====================
|
||||
GeneralMinCostLowerBoundsSolver::~GeneralMinCostLowerBoundsSolver() = default;
|
||||
|
||||
GeneralMinCostLowerBoundsSolver::GeneralMinCostLowerBoundsSolver(const std::vector<FlushMatrix> &matrix_,
|
||||
const std::vector<int> &u_nodes,
|
||||
const std::vector<int> &v_nodes,
|
||||
const std::vector<int> &v_nodes_group,
|
||||
const std::unordered_map<int, std::vector<int>> &uv_link_limits,
|
||||
const std::unordered_map<int, std::vector<int>> &uv_unlink_limits)
|
||||
{
|
||||
flush_matrix = matrix_;
|
||||
l_nodes = u_nodes;
|
||||
r_nodes = v_nodes;
|
||||
r_nodes_group = v_nodes_group;
|
||||
m_uv_link_limits = uv_link_limits;
|
||||
m_uv_unlink_limits = uv_unlink_limits;
|
||||
num_groups = *std::max_element(r_nodes_group.begin(), r_nodes_group.end()) + 1;
|
||||
|
||||
m_solver_lower_bounds = std::make_unique<MaxFlowWithLowerBounds>();
|
||||
m_solver_min_cost = std::make_unique<MinCostMaxFlow>();
|
||||
}
|
||||
|
||||
std::vector<int> GeneralMinCostLowerBoundsSolver::solve()
|
||||
{
|
||||
// group nodes that do not need a lower-bound constraint
|
||||
std::unordered_set<int> no_lower_group;
|
||||
for (int i = 0; i < r_nodes.size(); i++) {
|
||||
if (r_nodes[i] >= 0)
|
||||
no_lower_group.insert(r_nodes_group[i]);
|
||||
}
|
||||
|
||||
// 1. build the lower-bound network graph
|
||||
build_feasible_graph(no_lower_group);
|
||||
|
||||
// 2. compute the max flow
|
||||
int need = 0;
|
||||
for (int d : demand)
|
||||
if (d > 0) need += d;
|
||||
std::vector<int> feasible_matching;
|
||||
int pushed_flow = m_solver_lower_bounds->solve(feasible_matching);
|
||||
assert(need == pushed_flow);
|
||||
|
||||
// 3. convert the lower-bound max-flow network into a min-cost-max-flow network
|
||||
build_graph_with_feasible_result();
|
||||
// 4. compute the min-cost max-flow
|
||||
auto min_cost_matching = m_solver_min_cost->solve();
|
||||
|
||||
return min_cost_matching;
|
||||
}
|
||||
|
||||
void GeneralMinCostLowerBoundsSolver::build_feasible_graph(const std::unordered_set<int> &no_lower_groups)
|
||||
{
|
||||
m_solver_lower_bounds->l_nodes = l_nodes;
|
||||
m_solver_lower_bounds->r_nodes = r_nodes;
|
||||
m_solver_lower_bounds->total_nodes = l_nodes.size() + r_nodes.size() + num_groups + 2;
|
||||
|
||||
m_solver_lower_bounds->source_id = m_solver_lower_bounds->total_nodes - 2;
|
||||
m_solver_lower_bounds->sink_id = m_solver_lower_bounds->total_nodes - 1;
|
||||
m_solver_lower_bounds->adj.resize(m_solver_lower_bounds->total_nodes);
|
||||
demand.resize(m_solver_lower_bounds->total_nodes, 0);
|
||||
|
||||
const int L = m_solver_lower_bounds->l_nodes.size();
|
||||
const int R = m_solver_lower_bounds->r_nodes.size();
|
||||
|
||||
// source -> l
|
||||
for (int i = 0; i < L; ++i)
|
||||
m_solver_lower_bounds->add_edge(m_solver_lower_bounds->source_id, i, 1);
|
||||
|
||||
// u -> v (with link/unlink limits)
|
||||
for (int i = 0; i < L; ++i) {
|
||||
if (auto it = m_uv_link_limits.find(i); it != m_uv_link_limits.end()) {
|
||||
for (int j : it->second)
|
||||
m_solver_lower_bounds->add_edge(i, L + j, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::optional<std::vector<int>> unlink_limits;
|
||||
if (auto it = m_uv_unlink_limits.find(i); it != m_uv_unlink_limits.end())
|
||||
unlink_limits = it->second;
|
||||
|
||||
for (int j = 0; j < R; ++j) {
|
||||
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
|
||||
continue;
|
||||
m_solver_lower_bounds->add_edge(i, L + j, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// r -> group
|
||||
for (int j = 0; j < R; ++j) {
|
||||
int g = r_nodes_group[j];
|
||||
m_solver_lower_bounds->add_edge(L + j, L + R + g, 1);
|
||||
}
|
||||
|
||||
// group -> sink (lower bound = 1)
|
||||
for (int g = 0; g < num_groups; ++g) {
|
||||
if (no_lower_groups.count(g))
|
||||
m_solver_lower_bounds->add_edge(L + R + g, m_solver_lower_bounds->sink_id, R);
|
||||
else
|
||||
add_edge_with_lower_bound(L + R + g, m_solver_lower_bounds->sink_id, 1, R, 0);
|
||||
}
|
||||
|
||||
max_flow_edges = m_solver_lower_bounds->edges.size();
|
||||
|
||||
// support lower bounds, add super source super sink
|
||||
super_source = m_solver_lower_bounds->total_nodes++;
|
||||
super_sink = m_solver_lower_bounds->total_nodes++;
|
||||
|
||||
m_solver_lower_bounds->adj.resize(m_solver_lower_bounds->total_nodes);
|
||||
demand.resize(m_solver_lower_bounds->total_nodes, 0);
|
||||
|
||||
for (int i = 0; i < super_source; ++i) {
|
||||
if (demand[i] > 0) {
|
||||
m_solver_lower_bounds->add_edge(super_source, i, demand[i]);
|
||||
} else if (demand[i] < 0) {
|
||||
m_solver_lower_bounds->add_edge(i, super_sink, -demand[i]);
|
||||
}
|
||||
}
|
||||
m_solver_lower_bounds->add_edge(m_solver_lower_bounds->sink_id, m_solver_lower_bounds->source_id, MaxFlowGraph::INF);
|
||||
source_id = m_solver_lower_bounds->source_id;
|
||||
sink_id = m_solver_lower_bounds->sink_id;
|
||||
m_solver_lower_bounds->source_id = super_source;
|
||||
m_solver_lower_bounds->sink_id = super_sink;
|
||||
}
|
||||
|
||||
void GeneralMinCostLowerBoundsSolver::build_graph_with_feasible_result()
|
||||
{
|
||||
for (auto&lb:lower_bound_edges){
|
||||
m_solver_lower_bounds->edges[lb.edge_id].flow += lb.lower;
|
||||
m_solver_lower_bounds->edges[lb.edge_id ^ 1].flow -= lb.lower;
|
||||
}
|
||||
|
||||
m_solver_min_cost->l_nodes = m_solver_lower_bounds->l_nodes;
|
||||
m_solver_min_cost->r_nodes = m_solver_lower_bounds->r_nodes;
|
||||
|
||||
m_solver_min_cost->source_id = source_id;
|
||||
m_solver_min_cost->sink_id = sink_id;
|
||||
m_solver_min_cost->total_nodes = sink_id + 1;
|
||||
|
||||
m_solver_min_cost->edges = m_solver_lower_bounds->edges;
|
||||
m_solver_min_cost->edges.erase(m_solver_min_cost->edges.begin() + max_flow_edges, m_solver_min_cost->edges.end());
|
||||
|
||||
m_solver_min_cost->adj = m_solver_lower_bounds->adj;
|
||||
m_solver_min_cost->adj.resize(m_solver_min_cost->total_nodes);
|
||||
for (auto &node_edges : m_solver_min_cost->adj) {
|
||||
node_edges.erase(std::remove_if(node_edges.begin(), node_edges.end(), [this](int val) {return val >= this->max_flow_edges;}), node_edges.end());
|
||||
}
|
||||
|
||||
|
||||
for (auto& e : m_solver_min_cost->edges) {
|
||||
int L = m_solver_min_cost->l_nodes.size();
|
||||
int R = m_solver_min_cost->r_nodes.size();
|
||||
|
||||
if (e.from < L && e.to >= L && e.to < L + R) {
|
||||
int idx_in_left = e.from;
|
||||
int idx_in_right = e.to - L;
|
||||
int group_id = r_nodes_group[idx_in_right];
|
||||
|
||||
if (r_nodes[idx_in_right] == -1) continue;
|
||||
e.cost = flush_matrix[group_id][l_nodes[idx_in_left]][r_nodes[idx_in_right]];
|
||||
}
|
||||
}
|
||||
}
|
||||
void GeneralMinCostLowerBoundsSolver::add_edge_with_lower_bound(int from, int to, int lower, int upper, int cost)
|
||||
{
|
||||
int eid = m_solver_lower_bounds->edges.size();
|
||||
m_solver_lower_bounds->add_edge(from, to, upper - lower);
|
||||
|
||||
lower_bound_edges.push_back({eid, lower});
|
||||
demand[from] -= lower;
|
||||
demand[to] += lower;
|
||||
}
|
||||
|
||||
// ==================== GroupMinCostFlowSolver ====================
|
||||
GroupMinCostFlowSolver::~GroupMinCostFlowSolver() = default;
|
||||
|
||||
GroupMinCostFlowSolver::GroupMinCostFlowSolver(const std::vector<FlushMatrix> &matrix_,
|
||||
const std::vector<int> &u_nodes,
|
||||
const std::vector<int> &v_nodes,
|
||||
const std::vector<int> &v_nodes_group,
|
||||
const std::unordered_map<int, std::vector<int>> &uv_link_limits,
|
||||
const std::unordered_map<int, std::vector<int>> &uv_unlink_limits)
|
||||
{
|
||||
flush_matrix = matrix_;
|
||||
l_nodes = u_nodes;
|
||||
r_nodes = v_nodes;
|
||||
r_nodes_group = v_nodes_group;
|
||||
m_uv_link_limits = uv_link_limits;
|
||||
m_uv_unlink_limits = uv_unlink_limits;
|
||||
num_groups = *std::max_element(r_nodes_group.begin(), r_nodes_group.end()) + 1;
|
||||
|
||||
m_solver = std::make_unique<MinCostMaxFlow>();
|
||||
build_graph();
|
||||
}
|
||||
|
||||
int GroupMinCostFlowSolver::get_flush_cost(int l_idx, int r_idx)
|
||||
{
|
||||
if (r_nodes[r_idx] == -1)
|
||||
return 0;
|
||||
int group_id = r_nodes_group[r_idx];
|
||||
return (int)flush_matrix[group_id][l_nodes[l_idx]][r_nodes[r_idx]];
|
||||
}
|
||||
|
||||
void GroupMinCostFlowSolver::build_graph()
|
||||
{
|
||||
const int L = (int)l_nodes.size();
|
||||
const int R = (int)r_nodes.size();
|
||||
const int G = num_groups;
|
||||
|
||||
m_solver->l_nodes = l_nodes;
|
||||
m_solver->r_nodes = r_nodes;
|
||||
m_solver->total_nodes = L + R + G + 2;
|
||||
m_solver->source_id = L + R + G;
|
||||
m_solver->sink_id = L + R + G + 1;
|
||||
m_solver->adj.resize(m_solver->total_nodes);
|
||||
|
||||
int max_flush = 0;
|
||||
for (const auto &mat : flush_matrix)
|
||||
for (const auto &row : mat)
|
||||
for (float v : row)
|
||||
max_flush = std::max(max_flush, (int)v);
|
||||
int bonus = max_flush * L + 1;
|
||||
|
||||
// source -> l_i
|
||||
for (int i = 0; i < L; ++i)
|
||||
m_solver->add_edge(m_solver->source_id, i, 1, 0);
|
||||
|
||||
// l_i -> r_j (with link/unlink limits)
|
||||
for (int i = 0; i < L; ++i) {
|
||||
if (auto it = m_uv_link_limits.find(i); it != m_uv_link_limits.end()) {
|
||||
for (int j : it->second)
|
||||
m_solver->add_edge(i, L + j, 1, get_flush_cost(i, j));
|
||||
continue;
|
||||
}
|
||||
|
||||
std::optional<std::vector<int>> unlink_limits;
|
||||
if (auto it = m_uv_unlink_limits.find(i); it != m_uv_unlink_limits.end())
|
||||
unlink_limits = it->second;
|
||||
|
||||
for (int j = 0; j < R; ++j) {
|
||||
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
|
||||
continue;
|
||||
m_solver->add_edge(i, L + j, 1, get_flush_cost(i, j));
|
||||
}
|
||||
}
|
||||
|
||||
// r_j -> group_g
|
||||
// Compute per-nozzle incoming edge count as capacity upper bound.
|
||||
// When unlink_limits restrict multiple filaments to the same nozzle,
|
||||
// capacity=1 would block valid assignments. Using the actual in-degree
|
||||
// allows the necessary flow while still preserving nozzle-level balance
|
||||
// (a nozzle with fewer forced filaments keeps a tighter cap).
|
||||
// The first unit carries a small nozzle-bonus to encourage spreading
|
||||
// filaments across distinct nozzles within the same group.
|
||||
int nozzle_bonus = max_flush + 1;
|
||||
std::vector<int> r_in_degree(R, 0);
|
||||
for (int i = 0; i < L; ++i) {
|
||||
if (auto it = m_uv_link_limits.find(i); it != m_uv_link_limits.end()) {
|
||||
for (int j : it->second)
|
||||
r_in_degree[j]++;
|
||||
continue;
|
||||
}
|
||||
std::optional<std::vector<int>> unlink_limits;
|
||||
if (auto it = m_uv_unlink_limits.find(i); it != m_uv_unlink_limits.end())
|
||||
unlink_limits = it->second;
|
||||
for (int j = 0; j < R; ++j) {
|
||||
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
|
||||
continue;
|
||||
r_in_degree[j]++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < R; ++j) {
|
||||
int g = r_nodes_group[j];
|
||||
int cap = std::max(r_in_degree[j], 1);
|
||||
// First unit gets -nozzle_bonus to prefer using distinct nozzles
|
||||
m_solver->add_edge(L + j, L + R + g, 1, -nozzle_bonus);
|
||||
if (cap > 1)
|
||||
m_solver->add_edge(L + j, L + R + g, cap - 1, 0);
|
||||
}
|
||||
|
||||
// group_g -> sink (split: first unit gets -bonus, rest gets 0)
|
||||
// bonus >> nozzle_bonus, so group coverage always takes priority
|
||||
for (int g = 0; g < G; ++g) {
|
||||
m_solver->add_edge(L + R + g, m_solver->sink_id, 1, -bonus);
|
||||
if (L > 1)
|
||||
m_solver->add_edge(L + R + g, m_solver->sink_id, L - 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> GroupMinCostFlowSolver::solve()
|
||||
{
|
||||
return m_solver->solve();
|
||||
}
|
||||
|
||||
// ==================== MinFlushFlowSolver ====================
|
||||
MinFlushFlowSolver::~MinFlushFlowSolver()
|
||||
{
|
||||
}
|
||||
@@ -277,7 +667,8 @@ namespace Slic3r
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits,
|
||||
const std::vector<int>& u_capacity,
|
||||
const std::vector<int>& v_capacity)
|
||||
const std::vector<int>& v_capacity,
|
||||
const std::vector<std::pair<std::set<int>,int>>&v_group_capacity)
|
||||
{
|
||||
assert(u_capacity.empty() || u_capacity.size() == u_nodes.size());
|
||||
assert(v_capacity.empty() || v_capacity.size() == v_nodes.size());
|
||||
@@ -286,13 +677,19 @@ namespace Slic3r
|
||||
m_solver->l_nodes = u_nodes;
|
||||
m_solver->r_nodes = v_nodes;
|
||||
|
||||
m_solver->total_nodes = u_nodes.size() + v_nodes.size() + 2;
|
||||
m_solver->total_nodes = u_nodes.size() + v_nodes.size() + v_group_capacity.size() + 2;
|
||||
|
||||
m_solver->source_id =m_solver->total_nodes - 2;
|
||||
m_solver->sink_id = m_solver->total_nodes - 1;
|
||||
|
||||
m_solver->adj.resize(m_solver->total_nodes);
|
||||
|
||||
std::vector<int> v_node_to(v_nodes.size(), m_solver->sink_id);
|
||||
for (size_t gid = 0; gid < v_group_capacity.size(); ++gid) {
|
||||
for (auto vid : v_group_capacity[gid].first)
|
||||
v_node_to[vid] = m_solver->l_nodes.size() + m_solver->r_nodes.size() + gid;
|
||||
}
|
||||
|
||||
// add edge from source to left nodes,cost to 0
|
||||
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
|
||||
int capacity = u_capacity.empty() ? 1 : u_capacity[i];
|
||||
@@ -301,7 +698,12 @@ namespace Slic3r
|
||||
// add edge from right nodes to sink,cost to 0
|
||||
for (int i = 0; i < m_solver->r_nodes.size(); ++i) {
|
||||
int capacity = v_capacity.empty() ? 1 : v_capacity[i];
|
||||
m_solver->add_edge(m_solver->l_nodes.size() + i, m_solver->sink_id, capacity, 0);
|
||||
m_solver->add_edge(m_solver->l_nodes.size() + i, v_node_to[i], capacity, 0);
|
||||
}
|
||||
// add edge from temp group node to sink node
|
||||
for(int i=0;i<v_group_capacity.size();++i){
|
||||
int capacity = v_group_capacity[i].second;
|
||||
m_solver->add_edge(m_solver->l_nodes.size() + m_solver->r_nodes.size() + i, m_solver->sink_id, capacity, 0);
|
||||
}
|
||||
// add edge from left node to right nodes
|
||||
for (int i = 0; i < m_solver->l_nodes.size(); ++i) {
|
||||
@@ -602,12 +1004,135 @@ namespace Slic3r
|
||||
}
|
||||
|
||||
|
||||
// Single-nozzle flush-minimizing reorder over one filament set / one flush matrix, with an
|
||||
// optional seed filament. Extracted from the group loop so the multi-nozzle reorder can call it
|
||||
// per physical nozzle.
|
||||
// TODO: add custom sequence
|
||||
static int reorder_filaments_for_minimum_flush_volume_base(const std::vector<unsigned int>& filament_lists,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments,
|
||||
const FlushMatrix& flush_matrix,
|
||||
const std::function<bool(int, std::vector<int>&)> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>>* filament_sequences,
|
||||
std::optional<unsigned int> initial_filament_id = std::nullopt)
|
||||
{
|
||||
constexpr int max_n_with_forcast = 5;
|
||||
using uint128_t = boost::multiprecision::uint128_t;
|
||||
|
||||
if (filament_sequences) {
|
||||
filament_sequences->clear();
|
||||
filament_sequences->reserve(layer_filaments.size());
|
||||
}
|
||||
auto filament_list_to_hash_key = [](const std::vector<unsigned int>& curr_layer_filaments, const std::vector<unsigned int>& next_layer_filaments,
|
||||
const std::optional<unsigned int>& prev_filament, bool use_forcast) -> uint128_t {
|
||||
uint128_t hash_key = 0;
|
||||
// 31-0 bit define current layer extruder,63-32 bit define next layer extruder,95~64 define prev extruder
|
||||
if (prev_filament) hash_key |= (uint128_t(1) << (64 + *prev_filament));
|
||||
|
||||
if (use_forcast) {
|
||||
for (auto item : next_layer_filaments) { hash_key |= (uint128_t(1) << (32 + item)); }
|
||||
}
|
||||
|
||||
for (auto item : curr_layer_filaments) { hash_key |= (uint128_t(1) << item); }
|
||||
return hash_key;
|
||||
};
|
||||
|
||||
int cost = 0;
|
||||
std::map<size_t, std::vector<unsigned int>> custom_layer_sequence_map;
|
||||
std::unordered_map<uint128_t, std::pair<float, std::vector<unsigned int>>> caches;
|
||||
std::unordered_set<unsigned int> filament_sets(filament_lists.begin(), filament_lists.end());
|
||||
std::optional<unsigned int> curr_filament_id;
|
||||
// use the provided initial filament id as the starting state when it is valid
|
||||
if (initial_filament_id.has_value() && *initial_filament_id < flush_matrix.size()) {
|
||||
curr_filament_id = initial_filament_id;
|
||||
}
|
||||
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer){
|
||||
const auto& curr_lf = layer_filaments[layer];
|
||||
std::vector<int> custom_filament_seq;
|
||||
if (get_custom_seq && get_custom_seq(layer, custom_filament_seq) && !custom_filament_seq.empty()) {
|
||||
std::vector<unsigned int> unsign_custom_extruder_seq;
|
||||
for (int extruder : custom_filament_seq) {
|
||||
unsigned int unsign_extruder = static_cast<unsigned int>(extruder) - 1;
|
||||
auto it = std::find(layer_filaments[layer].begin(), layer_filaments[layer].end(), unsign_extruder);
|
||||
if (it != layer_filaments[layer].end())
|
||||
unsign_custom_extruder_seq.emplace_back(unsign_extruder);
|
||||
}
|
||||
assert(layer_filaments[layer].size() == unsign_custom_extruder_seq.size());
|
||||
|
||||
custom_layer_sequence_map[layer] = unsign_custom_extruder_seq;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer) {
|
||||
const auto& curr_lf = layer_filaments[layer];
|
||||
|
||||
if(auto iter = custom_layer_sequence_map.find(layer); iter != custom_layer_sequence_map.end()){
|
||||
auto sequence_in_group = collect_filaments_in_groups<unsigned int>(std::unordered_set<unsigned int>(filament_lists.begin(),filament_lists.end()), iter->second);
|
||||
|
||||
std::optional<unsigned int> prev = curr_filament_id;
|
||||
for (auto& f: sequence_in_group){
|
||||
if(prev)
|
||||
cost += flush_matrix[*prev][f];
|
||||
prev = f;
|
||||
}
|
||||
|
||||
if(!sequence_in_group.empty()){
|
||||
curr_filament_id = sequence_in_group.back();
|
||||
}
|
||||
|
||||
if(filament_sequences)
|
||||
filament_sequences->emplace_back(sequence_in_group);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> filament_used = collect_filaments_in_groups<unsigned int>(filament_sets, curr_lf);
|
||||
std::vector<unsigned int> next_lf;
|
||||
if (layer + 1 < layer_filaments.size()) next_lf = layer_filaments[layer + 1];
|
||||
std::vector<unsigned int> filament_used_next_layer = collect_filaments_in_groups<unsigned int>(filament_sets, next_lf);
|
||||
|
||||
// Enable inter-layer forecast: when choosing filament ordering for current layer,
|
||||
// also consider next layer's filament set to minimize inter-layer transition flush.
|
||||
// solve_extruder_order_with_forcast() tries all permutations of curr+next layer
|
||||
// and picks the ordering that minimizes total flush across both layers.
|
||||
// This avoids expensive inter-layer transitions (e.g. ending layer with F2 when
|
||||
// next layer starts with F3, costing flush[F2→F3], instead of ending with F3
|
||||
// which gives flush[F3→F3]=0). Limited to ≤5 filaments due to O(N!×M!) complexity.
|
||||
// The per-nozzle base reorder does not use the inter-layer forecast. This function drives
|
||||
// BBL multi-extruder grouping cost and H2C ordering, so keeping it false avoids perturbing
|
||||
// existing H2D/H2C output.
|
||||
bool use_forcast = false;
|
||||
float tmp_cost = 0;
|
||||
std::vector<unsigned int> sequence;
|
||||
uint128_t hash_key = filament_list_to_hash_key(filament_used, filament_used_next_layer, curr_filament_id, use_forcast);
|
||||
if (auto iter = caches.find(hash_key); iter != caches.end()) {
|
||||
tmp_cost = iter->second.first;
|
||||
sequence = iter->second.second;
|
||||
}
|
||||
else {
|
||||
sequence = get_extruders_order(flush_matrix, filament_used, filament_used_next_layer, curr_filament_id, use_forcast, &tmp_cost);
|
||||
caches[hash_key] = { tmp_cost,sequence };
|
||||
}
|
||||
|
||||
if (filament_sequences)
|
||||
filament_sequences->emplace_back(sequence);
|
||||
|
||||
if (!sequence.empty())
|
||||
curr_filament_id = sequence.back();
|
||||
|
||||
cost += tmp_cost;
|
||||
}
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
int reorder_filaments_for_minimum_flush_volume(const std::vector<unsigned int>& filament_lists,
|
||||
const std::vector<int>& filament_maps,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments,
|
||||
const std::vector<FlushMatrix>& flush_matrix,
|
||||
std::optional<std::function<bool(int, std::vector<int>&)>> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>>* filament_sequences)
|
||||
std::vector<std::vector<unsigned int>>* filament_sequences,
|
||||
const std::unordered_map<int, int>& nozzle_status)
|
||||
{
|
||||
//only when layer filament num <= 5,we do forcast
|
||||
constexpr int max_n_with_forcast = 5;
|
||||
@@ -670,6 +1195,12 @@ namespace Slic3r
|
||||
if (groups[idx].empty())
|
||||
continue;
|
||||
std::optional<unsigned int>current_extruder_id;
|
||||
// seed the group (nozzle) with the filament already loaded, if nozzle_status supplies one
|
||||
if (auto it = nozzle_status.find(static_cast<int>(idx)); it != nozzle_status.end() && it->second >= 0) {
|
||||
unsigned int initial_fil = static_cast<unsigned int>(it->second);
|
||||
if (initial_fil < flush_matrix[idx].size())
|
||||
current_extruder_id = initial_fil;
|
||||
}
|
||||
|
||||
std::unordered_map<uint128_t, std::pair<float, std::vector<unsigned int>>> caches;
|
||||
|
||||
@@ -775,4 +1306,174 @@ namespace Slic3r
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
int reorder_filaments_for_multi_nozzle_extruder(const std::vector<unsigned int>& filament_lists,
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult& nozzle_group_result,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments,
|
||||
const std::vector<FlushMatrix>& flush_matrix,
|
||||
const std::function<bool(int, std::vector<int>&)> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>>* filament_sequences,
|
||||
const MultiNozzleUtils::NozzleStatusRecorder& initial_status)
|
||||
{
|
||||
std::map<int,std::set<unsigned int>> nozzle_filament_groups;
|
||||
std::map<int,std::set<int>> extruder_to_nozzle;
|
||||
|
||||
for(auto filament_idx : filament_lists){
|
||||
auto nozzle_info = nozzle_group_result.get_nozzle_for_filament(filament_idx, -1);
|
||||
if (!nozzle_info)
|
||||
continue;
|
||||
nozzle_filament_groups[nozzle_info->group_id].insert(filament_idx);
|
||||
extruder_to_nozzle[nozzle_info->extruder_id].insert(nozzle_info->group_id);
|
||||
}
|
||||
|
||||
std::map<size_t, std::vector<unsigned int>>custom_layer_sequence_map;// save the filament sequences of custom layer
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer){
|
||||
const auto& curr_lf = layer_filaments[layer];
|
||||
std::vector<int> custom_filament_seq;
|
||||
if (get_custom_seq && get_custom_seq(layer, custom_filament_seq) && !custom_filament_seq.empty()) {
|
||||
std::vector<unsigned int> unsign_custom_extruder_seq;
|
||||
for (int extruder : custom_filament_seq) {
|
||||
unsigned int unsign_extruder = static_cast<unsigned int>(extruder) - 1;
|
||||
auto it = std::find(layer_filaments[layer].begin(), layer_filaments[layer].end(), unsign_extruder);
|
||||
if (it != layer_filaments[layer].end())
|
||||
unsign_custom_extruder_seq.emplace_back(unsign_extruder);
|
||||
}
|
||||
assert(layer_filaments[layer].size() == unsign_custom_extruder_seq.size());
|
||||
|
||||
custom_layer_sequence_map[layer] = unsign_custom_extruder_seq;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::map<int, std::vector<std::vector<unsigned int>>> nozzle_filament_sequences;
|
||||
bool store_sequence = filament_sequences != nullptr;
|
||||
|
||||
int cost = 0;
|
||||
for(auto& group : nozzle_filament_groups){
|
||||
int nozzle_id = group.first;
|
||||
auto& filament_in_nozzle = group.second;
|
||||
|
||||
int extruder_id = 0;
|
||||
for(auto& [ext, nozzle_set] : extruder_to_nozzle){
|
||||
if(nozzle_set.count(nozzle_id)){
|
||||
extruder_id = ext;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(filament_in_nozzle.empty())
|
||||
continue;
|
||||
|
||||
std::vector<unsigned int> filament_vec_in_nozzle(filament_in_nozzle.begin(), filament_in_nozzle.end());
|
||||
|
||||
int initial_fil = initial_status.get_filament_in_nozzle(nozzle_id);
|
||||
std::optional<unsigned int> initial_fil_id = (initial_fil >= 0 && initial_fil < flush_matrix[extruder_id].size())? std::optional<unsigned int>(initial_fil) : std::nullopt;
|
||||
|
||||
std::vector<std::vector<unsigned int>> filament_seq;
|
||||
cost += reorder_filaments_for_minimum_flush_volume_base(filament_vec_in_nozzle, layer_filaments, flush_matrix[extruder_id], get_custom_seq,
|
||||
store_sequence ? &filament_seq : nullptr, initial_fil_id);
|
||||
if(store_sequence)
|
||||
nozzle_filament_sequences.emplace(nozzle_id, std::move(filament_seq));
|
||||
|
||||
}
|
||||
|
||||
if(!store_sequence)
|
||||
return cost;
|
||||
|
||||
std::vector<int> extruders;
|
||||
std::map<int, std::vector<int>> nozzles_per_extruder;
|
||||
for (auto& [extruder_id, nozzle_set] : extruder_to_nozzle) {
|
||||
extruders.push_back(extruder_id);
|
||||
nozzles_per_extruder[extruder_id] = std::vector<int>(
|
||||
nozzle_set.begin(), nozzle_set.end()
|
||||
);
|
||||
}
|
||||
|
||||
filament_sequences->clear();
|
||||
filament_sequences->resize(layer_filaments.size());
|
||||
|
||||
// No filament in filament_lists resolved to a nozzle in nozzle_group_result
|
||||
// (e.g. a degenerate input where a layer references a filament index outside the range's
|
||||
// grouping map). Emit each layer's filaments in their given order so the caller still gets a
|
||||
// valid per-layer sequence, and skip the cross-nozzle reorder. Guards the unchecked
|
||||
// max_element(extruders) below, which would dereference end() on an empty range.
|
||||
if (extruders.empty()) {
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer)
|
||||
(*filament_sequences)[layer] = layer_filaments[layer];
|
||||
return cost;
|
||||
}
|
||||
|
||||
auto get_extruder_for_filament = [nozzle_group_result](unsigned int filament_idx) {
|
||||
auto nozzle = nozzle_group_result.get_nozzle_for_filament(filament_idx, -1);
|
||||
if (!nozzle)
|
||||
return -1;
|
||||
return nozzle->extruder_id;
|
||||
};
|
||||
|
||||
auto get_nozzle_idx_for_filament = [nozzles_per_extruder, nozzle_group_result](unsigned int filament_idx)->int {
|
||||
auto nozzle = nozzle_group_result.get_nozzle_for_filament(filament_idx, -1);
|
||||
if (!nozzle)
|
||||
return -1;
|
||||
return std::find(nozzles_per_extruder.at(nozzle->extruder_id).begin(), nozzles_per_extruder.at(nozzle->extruder_id).end(), nozzle->group_id) - nozzles_per_extruder.at(nozzle->extruder_id).begin();
|
||||
};
|
||||
|
||||
int initial_extruder = initial_status.get_current_extruder_id();
|
||||
int last_extruder_idx = (initial_extruder >= 0 && initial_extruder < extruders.size())? initial_extruder : 0;
|
||||
// set size to max extruder_id in case extruder_id is not continuous
|
||||
std::vector<int> last_nozzle_idx(*std::max_element(extruders.begin(),extruders.end()) + 1,0);
|
||||
for (int ext_id = 0; ext_id < static_cast<int>(last_nozzle_idx.size()); ext_id++) {
|
||||
int initial_nozzle = initial_status.get_nozzle_in_extruder(ext_id);
|
||||
auto ext_nozzles = nozzles_per_extruder[ext_id];
|
||||
auto it = std::find(ext_nozzles.begin(), ext_nozzles.end(), initial_nozzle);
|
||||
if (it != ext_nozzles.end())
|
||||
last_nozzle_idx[ext_id] = static_cast<int>(std::distance(ext_nozzles.begin(), it));
|
||||
}
|
||||
|
||||
for (size_t layer = 0; layer < layer_filaments.size(); ++layer) {
|
||||
auto& out_seq = (*filament_sequences)[layer];
|
||||
|
||||
if (custom_layer_sequence_map.find(layer) != custom_layer_sequence_map.end()) {
|
||||
out_seq = custom_layer_sequence_map[layer];
|
||||
if (!out_seq.empty()) {
|
||||
last_extruder_idx = get_extruder_for_filament(out_seq.back());
|
||||
for (auto filament : out_seq) {
|
||||
int cur_ext_id = get_extruder_for_filament(filament);
|
||||
last_nozzle_idx[cur_ext_id] = get_nozzle_idx_for_filament(filament);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (last_extruder_idx == -1)
|
||||
last_extruder_idx = 0;
|
||||
|
||||
int curr_last_extruder_idx = last_extruder_idx;
|
||||
auto curr_last_nozzle_idx = last_nozzle_idx;
|
||||
for (int i = 0; i < extruders.size(); ++i) {
|
||||
int extruder_id = extruders[(last_extruder_idx + i) % extruders.size()];
|
||||
auto& base_nozzles = nozzles_per_extruder[extruder_id];
|
||||
|
||||
bool has_seq = false;
|
||||
if (last_nozzle_idx[extruder_id] == -1)
|
||||
last_nozzle_idx[extruder_id] = 0;
|
||||
|
||||
for (int j = 0; j < base_nozzles.size(); ++j) {
|
||||
int nozzle_idx = (last_nozzle_idx[extruder_id] + j) % base_nozzles.size();
|
||||
int nozzle_id = base_nozzles[nozzle_idx];
|
||||
const auto& frag = nozzle_filament_sequences[nozzle_id][layer];
|
||||
if (frag.empty())
|
||||
continue;
|
||||
has_seq = true;
|
||||
curr_last_nozzle_idx[extruder_id] = nozzle_idx;
|
||||
out_seq.insert(out_seq.end(), frag.begin(), frag.end());
|
||||
}
|
||||
|
||||
if (has_seq)
|
||||
curr_last_extruder_idx = extruder_id;
|
||||
}
|
||||
last_extruder_idx = curr_last_extruder_idx;
|
||||
last_nozzle_idx = curr_last_nozzle_idx;
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include "../MultiNozzleUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -15,21 +18,27 @@ using FlushMatrix = std::vector<std::vector<float>>;
|
||||
namespace MaxFlowGraph {
|
||||
const int INF = std::numeric_limits<int>::max();
|
||||
const int INVALID_ID = -1;
|
||||
// Upper bound for MCMF edge cost to prevent int overflow in SPFA causing infinite loops
|
||||
constexpr int MCMF_MAX_EDGE_COST = 10000000;
|
||||
}
|
||||
|
||||
// Namespace-scope edge shared by the max-flow / min-cost-max-flow solvers below.
|
||||
// The default cost keeps the plain max-flow solvers (which never read cost) source-compatible.
|
||||
struct Edge
|
||||
{
|
||||
int from, to, capacity, cost, flow;
|
||||
Edge(int u, int v, int cap, int cst = 0) : from(u), to(v), capacity(cap), cost(cst), flow(0) {}
|
||||
};
|
||||
|
||||
class MaxFlowSolver
|
||||
{
|
||||
private:
|
||||
struct Edge {
|
||||
int from, to, capacity, flow;
|
||||
Edge(int u, int v, int cap) :from(u), to(v), capacity(cap), flow(0) {}
|
||||
};
|
||||
public:
|
||||
MaxFlowSolver(const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {},
|
||||
const std::vector<int>& u_capacity = {},
|
||||
const std::vector<int>& v_capacity = {}
|
||||
const std::vector<int>& v_capacity = {},
|
||||
const std::vector<std::pair<std::set<int>, int>>& v_group_capacity = {}
|
||||
);
|
||||
std::vector<int> solve();
|
||||
|
||||
@@ -47,6 +56,7 @@ private:
|
||||
|
||||
|
||||
struct MinCostMaxFlow;
|
||||
struct MaxFlowWithLowerBounds;
|
||||
|
||||
class GeneralMinCostSolver
|
||||
{
|
||||
@@ -61,6 +71,84 @@ private:
|
||||
std::unique_ptr<MinCostMaxFlow> m_solver;
|
||||
};
|
||||
|
||||
class GeneralMinCostLowerBoundsSolver
|
||||
{
|
||||
public:
|
||||
GeneralMinCostLowerBoundsSolver(
|
||||
const std::vector<FlushMatrix> &matrix_,
|
||||
const std::vector<int>& u_nodes,
|
||||
const std::vector<int>& v_nodes,
|
||||
const std::vector<int>& v_nodes_group,
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {});
|
||||
|
||||
std::vector<int> solve();
|
||||
~GeneralMinCostLowerBoundsSolver();
|
||||
|
||||
private:
|
||||
void build_feasible_graph(const std::unordered_set<int>& no_lower_groups);
|
||||
|
||||
void build_graph_with_feasible_result();
|
||||
|
||||
void add_edge_with_lower_bound(int from, int to, int lower, int upper, int cost);
|
||||
|
||||
int get_distance(const int idx_in_left,const int idx_in_right);
|
||||
|
||||
private:
|
||||
std::unique_ptr<MaxFlowWithLowerBounds> m_solver_lower_bounds;
|
||||
std::unique_ptr<MinCostMaxFlow> m_solver_min_cost;
|
||||
|
||||
std::vector<FlushMatrix> flush_matrix;
|
||||
std::vector<int> l_nodes;
|
||||
std::vector<int> r_nodes;
|
||||
std::vector<int> r_nodes_group;
|
||||
std::unordered_map<int, std::vector<int>> m_uv_link_limits;
|
||||
std::unordered_map<int, std::vector<int>> m_uv_unlink_limits;
|
||||
int num_groups = 0;
|
||||
|
||||
// support lower bounds
|
||||
struct LowerBoundEdge{
|
||||
int edge_id;
|
||||
int lower;
|
||||
};
|
||||
|
||||
std::vector<int> demand;
|
||||
std::vector<LowerBoundEdge> lower_bound_edges;
|
||||
|
||||
int super_source = -1;
|
||||
int super_sink = -1;
|
||||
int source_id = -1;
|
||||
int sink_id = -1;
|
||||
int max_flow_edges = 0;
|
||||
};
|
||||
|
||||
class GroupMinCostFlowSolver
|
||||
{
|
||||
public:
|
||||
GroupMinCostFlowSolver(
|
||||
const std::vector<FlushMatrix> &matrix_,
|
||||
const std::vector<int> &u_nodes,
|
||||
const std::vector<int> &v_nodes,
|
||||
const std::vector<int> &v_nodes_group,
|
||||
const std::unordered_map<int, std::vector<int>> &uv_link_limits = {},
|
||||
const std::unordered_map<int, std::vector<int>> &uv_unlink_limits = {});
|
||||
|
||||
std::vector<int> solve();
|
||||
~GroupMinCostFlowSolver();
|
||||
|
||||
private:
|
||||
void build_graph();
|
||||
int get_flush_cost(int l_idx, int r_idx);
|
||||
|
||||
std::unique_ptr<MinCostMaxFlow> m_solver;
|
||||
std::vector<FlushMatrix> flush_matrix;
|
||||
std::vector<int> l_nodes;
|
||||
std::vector<int> r_nodes;
|
||||
std::vector<int> r_nodes_group;
|
||||
std::unordered_map<int, std::vector<int>> m_uv_link_limits;
|
||||
std::unordered_map<int, std::vector<int>> m_uv_unlink_limits;
|
||||
int num_groups = 0;
|
||||
};
|
||||
|
||||
class MinFlushFlowSolver
|
||||
{
|
||||
@@ -71,7 +159,8 @@ public:
|
||||
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
|
||||
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {},
|
||||
const std::vector<int>& u_capacity = {},
|
||||
const std::vector<int>& v_capacity = {}
|
||||
const std::vector<int>& v_capacity = {},
|
||||
const std::vector<std::pair<std::set<int>, int>>& v_group_capacity = {}
|
||||
);
|
||||
std::vector<int> solve();
|
||||
~MinFlushFlowSolver();
|
||||
@@ -108,7 +197,19 @@ int reorder_filaments_for_minimum_flush_volume(const std::vector<unsigned int> &
|
||||
const std::vector<std::vector<unsigned int>> &layer_filaments,
|
||||
const std::vector<FlushMatrix> &flush_matrix,
|
||||
std::optional<std::function<bool(int, std::vector<int> &)>> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>> *filament_sequences);
|
||||
std::vector<std::vector<unsigned int>> *filament_sequences,
|
||||
const std::unordered_map<int, int>& nozzle_status = {});
|
||||
|
||||
// Order filaments within a per-nozzle grouping result (multi-nozzle extruders). Threads a
|
||||
// NozzleStatusRecorder describing the initial physical nozzle occupancy so the reorder can reward
|
||||
// keeping an already-loaded filament in place.
|
||||
int reorder_filaments_for_multi_nozzle_extruder(const std::vector<unsigned int>& filament_lists,
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult& nozzle_group_result,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments,
|
||||
const std::vector<FlushMatrix>& flush_matrix,
|
||||
const std::function<bool(int,std::vector<int>&)> get_custom_seq,
|
||||
std::vector<std::vector<unsigned int>> * filament_sequences,
|
||||
const MultiNozzleUtils::NozzleStatusRecorder& initial_status = {});
|
||||
|
||||
}
|
||||
#endif // !TOOL_ORDER_UTILS_HPP
|
||||
|
||||
+1027
-172
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include "../FilamentGroup.hpp"
|
||||
#include "../MultiNozzleUtils.hpp"
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
@@ -98,19 +99,23 @@ private:
|
||||
struct FilamentChangeStats
|
||||
{
|
||||
int filament_flush_weight{0};
|
||||
// flush_filament_change_count counts filament changes that actually flush a physical nozzle.
|
||||
// It replaces the former (dead, never populated) extruder_change_count. For single-nozzle-per-
|
||||
// extruder printers it equals the per-extruder filament_change_count, so GUI stat displays are
|
||||
// unchanged.
|
||||
int flush_filament_change_count{0};
|
||||
int filament_change_count{0};
|
||||
int extruder_change_count{0};
|
||||
|
||||
void clear(){
|
||||
filament_flush_weight = 0;
|
||||
filament_change_count = 0;
|
||||
extruder_change_count = 0;
|
||||
flush_filament_change_count = 0;
|
||||
}
|
||||
|
||||
FilamentChangeStats& operator+=(const FilamentChangeStats& other) {
|
||||
this->filament_flush_weight += other.filament_flush_weight;
|
||||
this->filament_change_count += other.filament_change_count;
|
||||
this->extruder_change_count += other.extruder_change_count;
|
||||
this->flush_filament_change_count += other.flush_filament_change_count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -118,7 +123,7 @@ struct FilamentChangeStats
|
||||
FilamentChangeStats ret;
|
||||
ret.filament_flush_weight = this->filament_flush_weight + other.filament_flush_weight;
|
||||
ret.filament_change_count = this->filament_change_count + other.filament_change_count;
|
||||
ret.extruder_change_count = this->extruder_change_count + other.extruder_change_count;
|
||||
ret.flush_filament_change_count = this->flush_filament_change_count + other.flush_filament_change_count;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -236,12 +241,44 @@ public:
|
||||
bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().has_wipe_tower; }
|
||||
|
||||
int get_most_used_extruder() const { return most_used_extruder; }
|
||||
|
||||
// Logical (extruder, nozzle) grouping of the used filaments, built during reorder.
|
||||
// For single-nozzle printers this is one logical nozzle per extruder (nozzle id == extruder id).
|
||||
// Consumed by GCode (get_nozzle_id / get_first_nozzle_for_filament).
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult &get_layered_nozzle_group_result() const { return m_nozzle_group_result; }
|
||||
|
||||
// Physical nozzle occupancy threading for the sequential (by-object) selector regroup: the
|
||||
// setter seeds both the initial recorder (the state the per-layer plan starts from) and the
|
||||
// running recorder (read back after sort_and_build_data via get_nozzle_status()), so each
|
||||
// object's plan continues from the nozzle state the previous object ended with.
|
||||
const MultiNozzleUtils::NozzleStatusRecorder &get_nozzle_status() const { return m_nozzle_status; }
|
||||
void set_nozzle_status(const MultiNozzleUtils::NozzleStatusRecorder &status) { m_initial_nozzle_status = status; m_nozzle_status = status; }
|
||||
/*
|
||||
* called in single extruder mode, the value in map are all 0
|
||||
* called in dual extruder mode, the value in map will be 0 or 1
|
||||
* 0 based group id
|
||||
*/
|
||||
static std::vector<int> get_recommended_filament_maps(const std::vector<std::vector<unsigned int>>& layer_filaments, const Print* print,const FilamentMapMode mode, const std::vector<std::set<int>>& physical_unprintables, const std::vector<std::set<int>>& geometric_unprintables);
|
||||
// Nozzle-centric grouping. Returns a nozzle-aware LayeredNozzleGroupResult instead of a plain
|
||||
// extruder-level std::vector<int>. Callers derive the 0/1-based extruder map via
|
||||
// result.get_extruder_map(). unprintable_volumes / nozzle_status default empty for the static
|
||||
// path; the per-layer engine supplies non-empty values.
|
||||
static MultiNozzleUtils::LayeredNozzleGroupResult get_recommended_filament_maps(const std::vector<std::vector<unsigned int>>& layer_filaments, const Print* print,const FilamentMapMode mode, const std::vector<std::set<int>>& physical_unprintables, const std::vector<std::set<int>>& geometric_unprintables, const std::map<int, std::set<NozzleVolumeType>>& unprintable_volumes = {}, const std::unordered_map<int, int>& nozzle_status = {});
|
||||
|
||||
// Wrap stitched per-layer filament->nozzle maps from a sequential (by-object) selector regroup
|
||||
// into one print-wide result. nozzle_map_per_layer / layer_filaments / layer_sequences are the
|
||||
// per-object planned layers concatenated in print order; nozzle_map_per_layer is taken by value
|
||||
// and normalized in place. The nozzle list is rebuilt from the print's grouping context. Returns
|
||||
// an empty result when the wrap fails. Lives here (not in Print) to reach the file-local
|
||||
// grouping-context builder.
|
||||
static MultiNozzleUtils::LayeredNozzleGroupResult build_sequential_group_result(
|
||||
Print* print,
|
||||
std::vector<std::vector<int>> nozzle_map_per_layer,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments,
|
||||
const std::vector<std::vector<unsigned int>>& layer_sequences,
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<std::set<int>>& physical_unprintables,
|
||||
const std::vector<std::set<int>>& geometric_unprintables,
|
||||
const std::map<int, std::set<NozzleVolumeType>>& unprintable_volumes);
|
||||
|
||||
// should be called after doing reorder
|
||||
FilamentChangeStats get_filament_change_stats(FilamentChangeMode mode);
|
||||
@@ -283,6 +320,13 @@ private:
|
||||
FilamentChangeStats m_stats_by_single_extruder;
|
||||
FilamentChangeStats m_stats_by_multi_extruder_curr;
|
||||
FilamentChangeStats m_stats_by_multi_extruder_best;
|
||||
MultiNozzleUtils::LayeredNozzleGroupResult m_nozzle_group_result;
|
||||
// Physical nozzle occupancy threaded through the per-layer selector regroup.
|
||||
// m_initial_nozzle_status seeds the first combo range (empty for a fresh slice — there is no
|
||||
// device continuation state); m_nozzle_status carries the running state out of the plan. Inert
|
||||
// for every printer except an H2C profile that enables the filament selector (is_dynamic_group_reorder).
|
||||
MultiNozzleUtils::NozzleStatusRecorder m_initial_nozzle_status;
|
||||
MultiNozzleUtils::NozzleStatusRecorder m_nozzle_status;
|
||||
|
||||
int most_used_extruder;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
@@ -1472,7 +1473,7 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
|
||||
m_bridging(10.f),
|
||||
m_no_sparse_layers(config.wipe_tower_no_sparse_layers),
|
||||
m_gcode_flavor(config.gcode_flavor),
|
||||
m_travel_speed(config.travel_speed),
|
||||
m_travel_speed(config.travel_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
|
||||
m_current_tool(initial_tool),
|
||||
//wipe_volumes(flush_matrix)
|
||||
m_enable_timelapse_print(config.timelapse_type.value == TimelapseType::tlSmooth),
|
||||
@@ -1493,11 +1494,26 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
|
||||
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value)
|
||||
{
|
||||
m_flat_ironing = (m_flat_ironing && m_use_gap_wall);
|
||||
|
||||
// Prime-tower heating during wipe. m_is_multiple_nozzle mirrors the gate used in ToolOrdering/GCode
|
||||
// (std::any_of extruder_max_nozzle_count > 1); it is false for every current printer, so the
|
||||
// heating-during-wipe logic in toolchange_wipe_new is inert.
|
||||
m_hotend_heating_rate = config.hotend_heating_rate.values;
|
||||
m_physical_extruder_map = config.physical_extruder_map.values;
|
||||
m_is_multiple_nozzle = std::any_of(config.extruder_max_nozzle_count.values.begin(),
|
||||
config.extruder_max_nozzle_count.values.end(),
|
||||
[](int v) { return v > 1; });
|
||||
|
||||
// Per-extruder printable-height clamp. Empty for single-extruder printers
|
||||
// (extruder_printable_height = []), so is_valid_last_layer is inert there.
|
||||
m_printable_height = config.extruder_printable_height.values;
|
||||
m_last_layer_id.assign(config.nozzle_diameter.size(), -1);
|
||||
|
||||
// Read absolute value of first layer speed, if given as percentage,
|
||||
// it is taken over following default. Speeds from config are not
|
||||
// easily accessible here.
|
||||
const float default_speed = 60.f;
|
||||
m_first_layer_speed = config.get_abs_value("initial_layer_speed");
|
||||
m_first_layer_speed = config.initial_layer_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool));
|
||||
if (m_first_layer_speed == 0.f) // just to make sure autospeed doesn't break it.
|
||||
m_first_layer_speed = default_speed / 2.f;
|
||||
|
||||
@@ -1543,6 +1559,10 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
//while (m_filpar.size() < idx+1) // makes sure the required element is in the vector
|
||||
m_filpar.push_back(FilamentParameters());
|
||||
|
||||
// Orca: one row per filament, indexed by the raw filament id. Under a per-layer nozzle
|
||||
// grouping the per-variant arrays may hold several columns per filament; the tower has no
|
||||
// layer dimension here, so it keeps the filament's first column (tower x per-layer
|
||||
// grouping is a documented follow-up).
|
||||
m_filpar[idx].material = config.filament_type.get_at(idx);
|
||||
m_filpar[idx].is_soluble = config.wipe_tower_filament == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_filament - 1));
|
||||
// BBS
|
||||
@@ -1558,8 +1578,12 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
}
|
||||
m_filpar[idx].tower_interface_pre_extrusion_dist = config.filament_tower_interface_pre_extrusion_dist.get_at(idx);
|
||||
m_filpar[idx].tower_interface_pre_extrusion_length = config.filament_tower_interface_pre_extrusion_length.get_at(idx);
|
||||
// PETG pre-extrusion offset reuses the tower-interface pre-extrusion distance. Only read by the
|
||||
// has_filament_switcher-gated PETG branch in get_next_pos (inert fleet-wide).
|
||||
m_filpar[idx].petg_pre_extrusion_offset_dist = config.filament_tower_interface_pre_extrusion_dist.get_at(idx);
|
||||
m_filpar[idx].tower_ironing_area = config.filament_tower_ironing_area.get_at(idx);
|
||||
m_filpar[idx].tower_interface_purge_length = config.filament_tower_interface_purge_volume.get_at(idx);
|
||||
m_filpar[idx].filament_cooling_before_tower = config.filament_cooling_before_tower.get_at(idx);
|
||||
|
||||
// If this is a single extruder MM printer, we will use all the SE-specific config values.
|
||||
// Otherwise, the defaults will be used to turn off the SE stuff.
|
||||
@@ -1585,6 +1609,64 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
if (max_vol_speed!= 0.f)
|
||||
m_filpar[idx].max_e_speed = (max_vol_speed / filament_area());
|
||||
|
||||
// Vortek H2C: carousel-specific ramming, precool, and reverse travel parameters
|
||||
{
|
||||
// Ramming speed: .first = extruder change, .second = nozzle change (carousel)
|
||||
// Use the dedicated ramming volumetric speed, falling back to max_vol_speed only when
|
||||
// the setting is nil/-1.
|
||||
float ramming_vol_speed = float(config.filament_ramming_volumetric_speed.get_at(idx));
|
||||
if (config.filament_ramming_volumetric_speed.is_nil(idx) || is_approx(config.filament_ramming_volumetric_speed.get_at(idx), -1.))
|
||||
ramming_vol_speed = max_vol_speed;
|
||||
m_filpar[idx].max_e_ramming_speed.first = (ramming_vol_speed / filament_area());
|
||||
|
||||
float ramming_vol_speed_nc = float(config.filament_ramming_volumetric_speed_nc.get_at(idx));
|
||||
if (config.filament_ramming_volumetric_speed_nc.is_nil(idx) || is_approx(config.filament_ramming_volumetric_speed_nc.get_at(idx), -1.))
|
||||
ramming_vol_speed_nc = max_vol_speed;
|
||||
m_filpar[idx].max_e_ramming_speed.second = (ramming_vol_speed_nc / filament_area());
|
||||
}
|
||||
{
|
||||
// Precool target temp: .first = extruder change, .second = nozzle change (carousel)
|
||||
// Precool is only active when enable_pre_heating is on; otherwise no precool temp/timing is
|
||||
// applied and the downstream precool_t stays 0, matching printers with pre-heating disabled.
|
||||
m_filpar[idx].precool_target_temp = {0, 0};
|
||||
if (config.enable_pre_heating.value) {
|
||||
if (!config.filament_pre_cooling_temperature.is_nil(idx) && config.filament_pre_cooling_temperature.get_at(idx) != 0)
|
||||
m_filpar[idx].precool_target_temp.first = config.filament_pre_cooling_temperature.get_at(idx);
|
||||
if (!config.filament_pre_cooling_temperature_nc.is_nil(idx) && config.filament_pre_cooling_temperature_nc.get_at(idx) != 0)
|
||||
m_filpar[idx].precool_target_temp.second = config.filament_pre_cooling_temperature_nc.get_at(idx);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Precool timing: (nozzle_temp - precool_temp) / hotend_cooling_rate
|
||||
int extruder_count = m_is_multi_extruder ? 2 : 1; // H2C = 2 extruders
|
||||
float nozzle_temp = float(config.nozzle_temperature.is_nil(idx) ? 0 : config.nozzle_temperature.get_at(idx));
|
||||
float nozzle_temp_fl = float(config.nozzle_temperature_initial_layer.is_nil(idx) ? nozzle_temp : config.nozzle_temperature_initial_layer.get_at(idx));
|
||||
m_filpar[idx].precool_t.first.resize(extruder_count, 0.f);
|
||||
m_filpar[idx].precool_t.second.resize(extruder_count, 0.f);
|
||||
m_filpar[idx].precool_t_first_layer.first.resize(extruder_count, 0.f);
|
||||
m_filpar[idx].precool_t_first_layer.second.resize(extruder_count, 0.f);
|
||||
std::vector<double> cooling_rates = config.hotend_cooling_rate.values;
|
||||
for (int i = 0; i < extruder_count && i < (int)cooling_rates.size(); i++) {
|
||||
if (cooling_rates[i] < EPSILON) continue;
|
||||
if (m_filpar[idx].precool_target_temp.first != 0) {
|
||||
m_filpar[idx].precool_t.first[i] = std::max(0.f, nozzle_temp - float(m_filpar[idx].precool_target_temp.first)) / float(cooling_rates[i]);
|
||||
m_filpar[idx].precool_t_first_layer.first[i] = std::max(0.f, nozzle_temp_fl - float(m_filpar[idx].precool_target_temp.first)) / float(cooling_rates[i]);
|
||||
}
|
||||
if (m_filpar[idx].precool_target_temp.second != 0) {
|
||||
m_filpar[idx].precool_t.second[i] = std::max(0.f, nozzle_temp - float(m_filpar[idx].precool_target_temp.second)) / float(cooling_rates[i]);
|
||||
m_filpar[idx].precool_t_first_layer.second[i] = std::max(0.f, nozzle_temp_fl - float(m_filpar[idx].precool_target_temp.second)) / float(cooling_rates[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ramming travel time: .first = extruder change, .second = nozzle change (carousel)
|
||||
m_filpar[idx].ramming_travel_time = {0.f, 0.f};
|
||||
if (!config.filament_ramming_travel_time.is_nil(idx))
|
||||
m_filpar[idx].ramming_travel_time.first = float(config.filament_ramming_travel_time.get_at(idx));
|
||||
if (!config.filament_ramming_travel_time_nc.is_nil(idx))
|
||||
m_filpar[idx].ramming_travel_time.second = float(config.filament_ramming_travel_time_nc.get_at(idx));
|
||||
}
|
||||
|
||||
m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
|
||||
m_nozzle_change_perimeter_width = 2*m_perimeter_width;
|
||||
// BBS: remove useless config
|
||||
@@ -1651,6 +1733,27 @@ Vec2f WipeTower::get_next_pos(const WipeTower::box_coordinates &cleaning_box, fl
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
// Shift the wipe start outward for a PETG pre-extrusion on filament-switcher devices, clamped to the
|
||||
// shared printable bed. Gated on m_has_filament_switcher, which is false for the whole shipping fleet
|
||||
// (no profile sets the key), so is_petg_pre_extrusion is always false and res is returned unchanged.
|
||||
// The tower-interface contact branch is deliberately NOT applied here (enable_tower_interface_features
|
||||
// DOES ship on H2C/X2D; applying it would change their g-code); is_contact_pre_extrusion is computed
|
||||
// only as the guard that gives the contact path priority over PETG.
|
||||
bool is_contact_pre_extrusion = interface_layer && m_enable_tower_interface_features;
|
||||
bool is_petg_pre_extrusion = !is_contact_pre_extrusion && is_petg_filament(m_current_tool) && m_has_filament_switcher;
|
||||
if (is_petg_pre_extrusion) {
|
||||
Vec2f stop_pos = res;
|
||||
float offset_dist = m_filpar[m_current_tool].petg_pre_extrusion_offset_dist;
|
||||
auto printer_bbx = unscaled(get_extents(m_shared_print_bed)); // BoundingBoxBase<Vec2d>
|
||||
printer_bbx.translate((-m_wipe_tower_pos - m_rib_offset).cast<double>());
|
||||
if (stop_pos.x() < m_wipe_tower_width / 2.f)
|
||||
stop_pos = Vec2f(stop_pos.x() - offset_dist, stop_pos.y());
|
||||
else
|
||||
stop_pos = Vec2f(stop_pos.x() + offset_dist, stop_pos.y());
|
||||
if (stop_pos.x() < printer_bbx.min[0]) stop_pos.x() = printer_bbx.min[0];
|
||||
if (stop_pos.x() > printer_bbx.max[0]) stop_pos.x() = printer_bbx.max[0];
|
||||
res = stop_pos;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1848,7 +1951,7 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change(int old_filament_id, int
|
||||
.set_initial_tool(m_current_tool)
|
||||
.set_extrusion_flow(m_extrusion_flow)
|
||||
.set_y_shift(m_y_shift + (new_filament_id != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth() : 0.f))
|
||||
.append("; Nozzle change start\n");
|
||||
.append(format_nozzle_change_tag(true, old_filament_id, new_filament_id));
|
||||
|
||||
box_coordinates cleaning_box(Vec2f(m_perimeter_width, m_perimeter_width), m_wipe_tower_width - 2 * m_perimeter_width,
|
||||
(new_filament_id != (unsigned int) (-1) ? wipe_depth + m_depth_traversed - m_perimeter_width : m_wipe_tower_depth - m_perimeter_width));
|
||||
@@ -1924,7 +2027,7 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change(int old_filament_id, int
|
||||
}
|
||||
}
|
||||
|
||||
writer.append("; Nozzle change end\n");
|
||||
writer.append(format_nozzle_change_tag(false, old_filament_id, new_filament_id));
|
||||
|
||||
result.start_pos = writer.start_pos_rotated();
|
||||
result.end_pos = writer.pos();
|
||||
@@ -2501,6 +2604,19 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in
|
||||
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||
depth += nozzle_change_depth;
|
||||
}
|
||||
if (nozzle_change_depth == 0
|
||||
&& !m_filament_nozzle_map.empty()
|
||||
&& old_tool < m_filament_nozzle_map.size() && new_tool < m_filament_nozzle_map.size()
|
||||
&& m_filament_nozzle_map[old_tool] != m_filament_nozzle_map[new_tool]) {
|
||||
double e_flow = nozzle_change_extrusion_flow(layer_height_par);
|
||||
double length = m_filaments_change_length[old_tool] / e_flow;
|
||||
int nozzle_change_line_count = length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width) + 1;
|
||||
if (has_tpu_filament())
|
||||
nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||
else
|
||||
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||
depth += nozzle_change_depth;
|
||||
}
|
||||
WipeTowerInfo::ToolChange tool_change = WipeTowerInfo::ToolChange(old_tool, new_tool, depth, 0.f, 0.f, wipe_volume, length_to_extrude, purge_volume);
|
||||
tool_change.nozzle_change_depth = nozzle_change_depth;
|
||||
m_plan.back().tool_changes.push_back(tool_change);
|
||||
@@ -2645,6 +2761,18 @@ bool WipeTower::is_tpu_filament(int filament_id) const
|
||||
return m_filpar[filament_id].material == "TPU";
|
||||
}
|
||||
|
||||
bool WipeTower::is_petg_filament(int filament_id) const
|
||||
{
|
||||
return m_filpar[filament_id].material == "PETG";
|
||||
}
|
||||
|
||||
bool WipeTower::is_need_reverse_travel(int filament_id, bool extruder_change) const
|
||||
{
|
||||
if (extruder_change)
|
||||
return m_filpar[filament_id].ramming_travel_time.first > EPSILON;
|
||||
return m_filpar[filament_id].ramming_travel_time.second > EPSILON;
|
||||
}
|
||||
|
||||
// BBS: consider both soluable and support properties
|
||||
// Return index of first toolchange that switches to non-soluble and non-support extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
@@ -2717,6 +2845,9 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer)
|
||||
float spacing = m_layer_info->extra_spacing;
|
||||
if (has_tpu_filament() && m_layer_info->extra_spacing < m_tpu_fixed_spacing) spacing = 1;
|
||||
float nozzle_change_depth = tool_change.nozzle_change_depth * spacing;
|
||||
// Drop the nozzle-change depth on an extruder's final layer above its printable height
|
||||
// (inert unless is_valid_last_layer clamps, i.e. multi-extruder near Z-max).
|
||||
if (!is_valid_last_layer(old_filament, m_cur_layer_id, layer.z)) nozzle_change_depth = 0.f;
|
||||
//float nozzle_change_depth = tool_change.nozzle_change_depth * (has_tpu_filament() ? m_tpu_fixed_spacing : layer.extra_spacing);
|
||||
auto* block = get_block_by_category(m_filpar[new_filament].category, false);
|
||||
if (!block)
|
||||
@@ -2760,7 +2891,17 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer)
|
||||
WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool solid_toolchange,bool solid_nozzlechange)
|
||||
{
|
||||
m_nozzle_change_result.gcode.clear();
|
||||
if (!m_filament_map.empty() && new_tool < m_filament_map.size() && m_filament_map[m_current_tool] != m_filament_map[new_tool]) {
|
||||
// Skip the cross-extruder nozzle change (ramming) on an extruder's final layer above its printable
|
||||
// height. is_valid_last_layer is inert unless multi-extruder near Z-max.
|
||||
if (!m_filament_map.empty() && new_tool < m_filament_map.size() && m_filament_map[m_current_tool] != m_filament_map[new_tool]
|
||||
&& is_valid_last_layer(m_current_tool, m_cur_layer_id, m_z_pos)) {
|
||||
m_nozzle_change_result = nozzle_change_new(m_current_tool, new_tool, solid_nozzlechange);
|
||||
}
|
||||
if (m_nozzle_change_result.gcode.empty()
|
||||
&& !m_filament_nozzle_map.empty()
|
||||
&& m_current_tool < m_filament_nozzle_map.size() && new_tool < m_filament_nozzle_map.size()
|
||||
&& m_filament_nozzle_map[m_current_tool] != m_filament_nozzle_map[new_tool]
|
||||
&& is_valid_last_layer(m_current_tool, m_cur_layer_id, m_z_pos)) {
|
||||
m_nozzle_change_result = nozzle_change_new(m_current_tool, new_tool, solid_nozzlechange);
|
||||
}
|
||||
|
||||
@@ -2927,20 +3068,40 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id,
|
||||
}
|
||||
|
||||
float nz_extrusion_flow = nozzle_change_extrusion_flow(m_layer_height);
|
||||
float nozzle_change_speed = 60.0f * m_filpar[m_current_tool].max_e_speed / nz_extrusion_flow;
|
||||
nozzle_change_speed = solid_infill ? 40.f * 60.f : nozzle_change_speed;//If the contact layers belong to different categories, then reduce the speed.
|
||||
bool extruder_change = !is_in_same_extruder(old_filament_id, new_filament_id);
|
||||
float max_e_ramming = extruder_change
|
||||
? m_filpar[m_current_tool].max_e_ramming_speed.first
|
||||
: m_filpar[m_current_tool].max_e_ramming_speed.second;
|
||||
if (max_e_ramming < EPSILON) max_e_ramming = m_filpar[m_current_tool].max_e_speed; // fallback
|
||||
float nozzle_change_speed = 60.0f * max_e_ramming / nz_extrusion_flow;
|
||||
nozzle_change_speed = solid_infill ? 40.f * 60.f : nozzle_change_speed;
|
||||
|
||||
if (is_tpu_filament(m_current_tool)) {
|
||||
nozzle_change_speed *= 0.25;
|
||||
}
|
||||
float bridge_speed = std::min(60.0f * m_filpar[m_current_tool].max_e_speed / nozzle_change_extrusion_flow(0.2), nozzle_change_speed); // limit the bridge speed by add flow
|
||||
float bridge_speed = std::min(60.0f * max_e_ramming / nozzle_change_extrusion_flow(0.2), nozzle_change_speed);
|
||||
|
||||
WipeTowerWriter writer(m_layer_height, m_nozzle_change_perimeter_width, m_gcode_flavor, m_filpar);
|
||||
writer.set_extrusion_flow(nz_extrusion_flow)
|
||||
.set_z(m_z_pos)
|
||||
.set_initial_tool(m_current_tool)
|
||||
.set_y_shift(m_y_shift + (new_filament_id != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth() : 0.f))
|
||||
.append("; Nozzle change start\n");
|
||||
.append(format_nozzle_change_tag(true, old_filament_id, new_filament_id));
|
||||
|
||||
if (!extruder_change && m_is_multiple_nozzle) {
|
||||
writer.append("M632 S" + std::to_string(new_filament_id) + " M N\n");
|
||||
// Use m_physical_extruder_map for heater index (matches format_line_M104 in add_M104_by_requirement)
|
||||
if (m_filpar[m_current_tool].precool_target_temp.second != 0) {
|
||||
int logical_ext = m_filament_map.empty() ? 0 : m_filament_map[m_current_tool] - 1;
|
||||
int phys_ext = (logical_ext >= 0 && logical_ext < (int)m_physical_extruder_map.size())
|
||||
? m_physical_extruder_map[logical_ext] : logical_ext;
|
||||
writer.append("M400\n");
|
||||
writer.append("M104 T" + std::to_string(phys_ext) + " S" +
|
||||
std::to_string(m_filpar[m_current_tool].precool_target_temp.second) + " N0\n");
|
||||
writer.append("M106 S255\n");
|
||||
}
|
||||
writer.append("M633\n");
|
||||
}
|
||||
|
||||
WipeTowerBlock* block = get_block_by_category(m_filpar[old_filament_id].category, false);
|
||||
if (!block) {
|
||||
@@ -2965,6 +3126,23 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id,
|
||||
dy = solid_infill ? m_nozzle_change_perimeter_width : dy;
|
||||
nozzle_change_line_count = solid_infill ? std::numeric_limits<int>::max() : nozzle_change_line_count;
|
||||
m_left_to_right = true;
|
||||
|
||||
if (extruder_change) {
|
||||
float ramming_length = nozzle_change_line_count * (xr - xl);
|
||||
int extruder_id = m_filament_map.empty() ? 0 : m_filament_map[m_current_tool] - 1;
|
||||
float precool_t = (extruder_id >= 0 && extruder_id < (int)m_filpar[m_current_tool].precool_t.first.size())
|
||||
? m_filpar[m_current_tool].precool_t.first[extruder_id] : 0.f;
|
||||
float precool_t_fl = (extruder_id >= 0 && extruder_id < (int)m_filpar[m_current_tool].precool_t_first_layer.first.size())
|
||||
? m_filpar[m_current_tool].precool_t_first_layer.first[extruder_id] : 0.f;
|
||||
float per_cooling_max_speed = nozzle_change_speed;
|
||||
if (is_first_layer() && precool_t_fl > EPSILON)
|
||||
per_cooling_max_speed = ramming_length / precool_t_fl * 60.f;
|
||||
else if (precool_t > EPSILON)
|
||||
per_cooling_max_speed = ramming_length / precool_t * 60.f;
|
||||
if (nozzle_change_speed > per_cooling_max_speed) nozzle_change_speed = per_cooling_max_speed;
|
||||
if (bridge_speed > per_cooling_max_speed) bridge_speed = per_cooling_max_speed;
|
||||
}
|
||||
|
||||
int real_nozzle_change_line_count = 0;
|
||||
bool need_change_flow = false;
|
||||
for (int i = 0; true; ++i) {
|
||||
@@ -2997,9 +3175,40 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id,
|
||||
block->last_nozzle_change_id = old_filament_id;
|
||||
|
||||
NozzleChangeResult result;
|
||||
if (is_tpu_filament(m_current_tool)) {
|
||||
if (!extruder_change && m_is_multiple_nozzle) {
|
||||
writer.append("M632 S" + std::to_string(new_filament_id) + " M N\n");
|
||||
}
|
||||
|
||||
if (is_need_reverse_travel(m_current_tool, extruder_change)) {
|
||||
bool left_to_right = !m_left_to_right;
|
||||
int tpu_line_count = (real_nozzle_change_line_count + 2 - 1) / 2; // nozzle_change_line_count / 2 round up
|
||||
int tpu_line_count = real_nozzle_change_line_count;
|
||||
float reverse_speed = nozzle_change_speed * 2; // reverse travel runs at double the nozzle-change speed
|
||||
float rt_time = extruder_change ? m_filpar[m_current_tool].ramming_travel_time.first
|
||||
: m_filpar[m_current_tool].ramming_travel_time.second;
|
||||
float need_reverse_travel_dis = rt_time * reverse_speed / 60.f;
|
||||
float real_travel_dis = tpu_line_count * (xr - xl - 2 * m_perimeter_width);
|
||||
if (real_travel_dis < need_reverse_travel_dis)
|
||||
reverse_speed *= real_travel_dis / need_reverse_travel_dis;
|
||||
writer.travel(writer.x(), writer.y() + dy/2);
|
||||
|
||||
for (int i = 0; true; ++i) {
|
||||
need_reverse_travel_dis -= (xr - xl - 2 * m_perimeter_width);
|
||||
float offset_dis = 0.f;
|
||||
if (need_reverse_travel_dis < 0)
|
||||
offset_dis = -need_reverse_travel_dis;
|
||||
if (left_to_right)
|
||||
writer.travel(xr - m_perimeter_width - offset_dis, writer.y(), reverse_speed);
|
||||
else
|
||||
writer.travel(xl + m_perimeter_width + offset_dis, writer.y(), reverse_speed);
|
||||
if (need_reverse_travel_dis < EPSILON) break;
|
||||
if (i == tpu_line_count - 1)
|
||||
break;
|
||||
writer.travel(writer.x(), writer.y() - dy);
|
||||
left_to_right = !left_to_right;
|
||||
}
|
||||
} else if (is_tpu_filament(m_current_tool)) {
|
||||
bool left_to_right = !m_left_to_right;
|
||||
int tpu_line_count = (real_nozzle_change_line_count + 2 - 1) / 2;
|
||||
nozzle_change_speed *= 2;
|
||||
writer.travel(writer.x(), writer.y() - m_nozzle_change_perimeter_width);
|
||||
|
||||
@@ -3024,12 +3233,15 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id,
|
||||
}
|
||||
}
|
||||
|
||||
writer.append("; Nozzle change end\n");
|
||||
if (!extruder_change && m_is_multiple_nozzle) writer.append("M633\n");
|
||||
|
||||
writer.append(format_nozzle_change_tag(false, old_filament_id, new_filament_id));
|
||||
|
||||
result.start_pos = writer.start_pos_rotated();
|
||||
result.origin_start_pos = initial_position;
|
||||
result.end_pos = writer.pos_rotated();
|
||||
result.gcode = writer.gcode();
|
||||
result.is_extruder_change = extruder_change;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -3411,23 +3623,100 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
|
||||
x_to_wipe = solid_tool_toolchange ? std::numeric_limits<float>::max(): x_to_wipe;
|
||||
float target_speed = is_first_layer() ? std::min(m_first_layer_speed * 60.f, 4800.f) : 4800.f;
|
||||
target_speed = solid_tool_toolchange ? 20.f * 60.f : target_speed;
|
||||
float wipe_speed = 0.33f * target_speed;
|
||||
// Nominal wipe-speed schedule. The applied wipe_speed is nominal_speed * speed_factor; speed_factor
|
||||
// stays 1.0 unless the H2C prime-tower heating-during-wipe model below slows the wipe so the hotend
|
||||
// can reach temperature (nominal_speed == wipe_speed when speed_factor == 1, i.e. single-nozzle).
|
||||
float nominal_speed = 0.33f * target_speed;
|
||||
|
||||
m_left_to_right = ((m_cur_layer_id + 3) % 4 >= 2);
|
||||
|
||||
bool is_from_up = (m_cur_layer_id % 2 == 1);
|
||||
|
||||
// Prime-tower heating during wipe. Everything here is gated on m_is_multiple_nozzle (false for every
|
||||
// current printer); the lambdas emit nothing until add_M104_by_requirement's gate opens, so the
|
||||
// single-nozzle wipe is untouched.
|
||||
// WipeSpeedMap mirrors the nominal schedule above and is read only by estimate_wipe_time. It is a
|
||||
// std::array (stack, no per-call heap allocation); values depend on runtime target_speed so it
|
||||
// cannot be static const.
|
||||
const std::array<float, 5> WipeSpeedMap{0.33f * target_speed, 0.375f * target_speed, 0.458f * target_speed,
|
||||
0.875f * target_speed, std::min(target_speed, 0.875f * target_speed + 50.f)};
|
||||
auto estimate_wipe_time = [&cleaning_box, &x_to_wipe, &xr, &xl, &dy, &WipeSpeedMap, &solid_tool_toolchange]() -> float {
|
||||
int n = std::ceil(x_to_wipe / (xr - xl));
|
||||
if (solid_tool_toolchange) n = (cleaning_box.lu[1] - cleaning_box.ld[1]) / dy;
|
||||
float one_line_len = xr - xl;
|
||||
float time = std::numeric_limits<float>::max();
|
||||
if (n <= 1)
|
||||
time = one_line_len / WipeSpeedMap[0];
|
||||
else if (n <= 2)
|
||||
time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1];
|
||||
else if (n <= 3)
|
||||
time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1] + one_line_len / WipeSpeedMap[2];
|
||||
else if (n <= 4)
|
||||
time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1] + one_line_len / WipeSpeedMap[2] + one_line_len / WipeSpeedMap[3];
|
||||
else {
|
||||
time = one_line_len / WipeSpeedMap[0] + one_line_len / WipeSpeedMap[1] + one_line_len / WipeSpeedMap[2] + one_line_len / WipeSpeedMap[3];
|
||||
time += (n - 4) * one_line_len / WipeSpeedMap[4];
|
||||
}
|
||||
return time * 60.f;
|
||||
};
|
||||
// Emit the arriving-hotend pre-heat inside the M632/M633 nozzle-change barrier. `M632 S<tool>[ H<nozzle>]
|
||||
// M N` opens the barrier (M = firmware nozzle-change flag, N = slicer generated), the M104 sets the
|
||||
// arriving hotend temp, and `M633` closes it. H2C's grouping is static (no dynamic nozzle map), so the
|
||||
// H<nozzle> field is omitted (a dynamic nozzle map would supply a real nozzle id, a static map -1 =>no
|
||||
// H). The counterproductive fan-on (M106 S255) used for departing-tool cooldown is intentionally
|
||||
// omitted, since this is a pre-HEAT of the arriving tool. The whole helper is only ever called from
|
||||
// add_M104_by_requirement, which is gated on m_is_multiple_nozzle (extruder_max_nozzle_count>1) => H2C
|
||||
// only; every other printer's wipe tower is untouched.
|
||||
// BBS: extruder change preheat uses M400 + M104 WITHOUT M632/M633 barrier.
|
||||
// M632 barriers are only for carousel nozzle changes (emitted in nozzle_change_new/ramming).
|
||||
auto format_line_M104 = [this](int target_temp, int target_extruder = -1, bool wait_for_moves = true, const std::string &comment = "") {
|
||||
std::string buffer;
|
||||
if (wait_for_moves)
|
||||
buffer += "M400\n";
|
||||
buffer += "M104";
|
||||
if (target_extruder != -1 && target_extruder < (int) m_physical_extruder_map.size())
|
||||
buffer += (" T" + std::to_string(m_physical_extruder_map[target_extruder]));
|
||||
buffer += " S" + std::to_string(target_temp) + " N0"; // N0 means the gcode is generated by the slicer
|
||||
if (!comment.empty()) buffer += " ;" + comment;
|
||||
buffer += '\n';
|
||||
return buffer;
|
||||
};
|
||||
// m_is_multiple_nozzle gate needed because Orca calls toolchange_wipe_new for ALL printers (BBS has it H2C-only).
|
||||
bool should_heating = m_is_multiple_nozzle && m_filpar[m_current_tool].filament_cooling_before_tower > EPSILON &&
|
||||
!solid_tool_toolchange && !is_first_layer();
|
||||
auto add_M104_by_requirement = [&writer, &format_line_M104, &should_heating, this]() {
|
||||
if (m_filpar[m_current_tool].filament_cooling_before_tower < EPSILON) return;
|
||||
if (!should_heating) return;
|
||||
float target_temp = is_first_layer() ? m_filpar[m_current_tool].nozzle_temperature_initial_layer : m_filpar[m_current_tool].nozzle_temperature;
|
||||
writer.append(format_line_M104(target_temp, m_filament_map[m_current_tool] - 1));
|
||||
};
|
||||
float speed_factor = 1.f;
|
||||
if (should_heating) {
|
||||
// The heating-slowdown scaling is disabled — no additional heating time is required, so
|
||||
// speed_factor stays 1.0. The structure and estimate_wipe_time/WipeSpeedMap are retained for
|
||||
// future H2C tuning; the divide-by-zero/bounds guard is preserved in the commented body below.
|
||||
// int extruder_id = m_filament_map[m_current_tool] - 1;
|
||||
// if (extruder_id >= 0 && extruder_id < (int) m_hotend_heating_rate.size() && m_hotend_heating_rate[extruder_id] > 0.) {
|
||||
// float estimate_time = estimate_wipe_time();
|
||||
// float heat_time = m_filpar[m_current_tool].filament_cooling_before_tower / m_hotend_heating_rate[extruder_id];
|
||||
// if (estimate_time < heat_time) speed_factor = estimate_time / heat_time;
|
||||
// }
|
||||
(void) estimate_wipe_time; // retain scaffolding above without an unused-lambda warning
|
||||
}
|
||||
float wipe_speed = nominal_speed * speed_factor;
|
||||
|
||||
// now the wiping itself:
|
||||
for (int i = 0; true; ++i) {
|
||||
if (i != 0) {
|
||||
if (wipe_speed < 0.34f * target_speed)
|
||||
wipe_speed = 0.375f * target_speed;
|
||||
else if (wipe_speed < 0.377 * target_speed)
|
||||
wipe_speed = 0.458f * target_speed;
|
||||
else if (wipe_speed < 0.46f * target_speed)
|
||||
wipe_speed = 0.875f * target_speed;
|
||||
if (nominal_speed < 0.34f * target_speed)
|
||||
nominal_speed = 0.375f * target_speed;
|
||||
else if (nominal_speed < 0.377 * target_speed)
|
||||
nominal_speed = 0.458f * target_speed;
|
||||
else if (nominal_speed < 0.46f * target_speed)
|
||||
nominal_speed = 0.875f * target_speed;
|
||||
else
|
||||
wipe_speed = std::min(target_speed, wipe_speed + 50.f);
|
||||
nominal_speed = std::min(target_speed, nominal_speed + 50.f);
|
||||
wipe_speed = nominal_speed * speed_factor;
|
||||
}
|
||||
|
||||
bool need_change_flow = need_thick_bridge_flow(writer.y());
|
||||
@@ -3453,6 +3742,7 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
|
||||
} else
|
||||
writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 240.);
|
||||
writer.retract(-retract_length, retract_speed);
|
||||
add_M104_by_requirement(); // Pre-heat the arriving hotend during the wipe
|
||||
writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed);
|
||||
} else {
|
||||
float dx = xl - wipe_tower_wall_infill_overlap * m_perimeter_width - writer.pos().x();
|
||||
@@ -3468,9 +3758,11 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
|
||||
}else
|
||||
writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 240.);
|
||||
writer.retract(-retract_length, retract_speed);
|
||||
add_M104_by_requirement(); // Pre-heat the arriving hotend during the wipe
|
||||
writer.extrude(xl - wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed);
|
||||
}
|
||||
} else {
|
||||
if (i == 0) add_M104_by_requirement(); // Pre-heat the arriving hotend during the wipe
|
||||
if (m_left_to_right)
|
||||
writer.extrude(xr + wipe_tower_wall_infill_overlap * m_perimeter_width, writer.y(), wipe_speed);
|
||||
else
|
||||
@@ -3571,6 +3863,59 @@ bool WipeTower::is_in_same_extruder(int filament_id_1, int filament_id_2)
|
||||
return m_filament_map[filament_id_1] == m_filament_map[filament_id_2];
|
||||
}
|
||||
|
||||
std::string WipeTower::format_nozzle_change_tag(bool start, int old_filament_id, int new_filament_id) const
|
||||
{
|
||||
const std::string &tag = start ? GCodeProcessor::Nozzle_Change_Start_Tag : GCodeProcessor::Nozzle_Change_End_Tag;
|
||||
int old_nozzle = (old_filament_id >= 0 && old_filament_id < (int)m_filament_nozzle_map.size())
|
||||
? m_filament_nozzle_map[old_filament_id] : -1;
|
||||
int new_nozzle = (new_filament_id >= 0 && new_filament_id < (int)m_filament_nozzle_map.size())
|
||||
? m_filament_nozzle_map[new_filament_id] : -1;
|
||||
char buff[96];
|
||||
snprintf(buff, sizeof(buff), ";%s OF%d NF%d ON%d NN%d\n", tag.c_str(), old_filament_id, new_filament_id, old_nozzle, new_nozzle);
|
||||
return std::string(buff);
|
||||
}
|
||||
|
||||
// Per-extruder printable-height clamp: is an extruder still allowed to print on this wipe-tower layer,
|
||||
// or is it its final layer above the extruder's printable height?
|
||||
// Orca: the arriving extruder id is resolved as m_filament_map[tool]-1 (1-based map, layer-static),
|
||||
// because Orca's wipe tower is extruder-level rather than tracking a per-layer nozzle map (the same
|
||||
// idiom the pre-heat path uses in toolchange_wipe_new). Gated on m_is_multi_extruder so that
|
||||
// single-extruder printers (including ones whose extruder_printable_height defaults to {0}) always
|
||||
// return true and leave wipe-tower g-code unchanged.
|
||||
bool WipeTower::is_valid_last_layer(int tool, int layer_id, double layer_z) const
|
||||
{
|
||||
if (!m_is_multi_extruder)
|
||||
return true;
|
||||
int extruder_id = (tool >= 0 && tool < (int) m_filament_map.size()) ? m_filament_map[tool] - 1 : -1;
|
||||
if (extruder_id < 0 || extruder_id >= (int) m_printable_height.size() || extruder_id >= (int) m_last_layer_id.size())
|
||||
return true;
|
||||
if (m_last_layer_id[extruder_id] == layer_id && layer_z > m_printable_height[extruder_id])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Records, per extruder, the last wipe-tower layer index that uses it, so is_valid_last_layer can
|
||||
// recognise the extruder's final layer. Inert for single-extruder printers (early return);
|
||||
// bounds-checked because m_filament_map may be empty/short.
|
||||
void WipeTower::set_nozzle_last_layer_id()
|
||||
{
|
||||
if (!m_is_multi_extruder)
|
||||
return;
|
||||
for (int idx = 0; idx < (int) m_plan.size(); ++idx) {
|
||||
const auto &info = m_plan[idx];
|
||||
for (const auto &tc : info.tool_changes) {
|
||||
int old_tool = (int) tc.old_tool;
|
||||
int new_tool = (int) tc.new_tool;
|
||||
int old_ext = (old_tool >= 0 && old_tool < (int) m_filament_map.size()) ? m_filament_map[old_tool] - 1 : -1;
|
||||
int new_ext = (new_tool >= 0 && new_tool < (int) m_filament_map.size()) ? m_filament_map[new_tool] - 1 : -1;
|
||||
if (old_ext >= 0 && old_ext < (int) m_last_layer_id.size())
|
||||
m_last_layer_id[old_ext] = idx;
|
||||
if (new_ext >= 0 && new_ext < (int) m_last_layer_id.size())
|
||||
m_last_layer_id[new_ext] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WipeTower::reset_block_status()
|
||||
{
|
||||
for (auto &block : m_wipe_tower_blocks) {
|
||||
@@ -3728,6 +4073,19 @@ void WipeTower::plan_tower_new()
|
||||
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||
depth += nozzle_change_depth;
|
||||
}
|
||||
if (nozzle_change_depth == 0
|
||||
&& !m_filament_nozzle_map.empty()
|
||||
&& toolchange.old_tool < (int)m_filament_nozzle_map.size() && toolchange.new_tool < (int)m_filament_nozzle_map.size()
|
||||
&& m_filament_nozzle_map[toolchange.old_tool] != m_filament_nozzle_map[toolchange.new_tool]) {
|
||||
double e_flow = nozzle_change_extrusion_flow(m_plan[idx].height);
|
||||
double length = m_filaments_change_length[toolchange.old_tool] / e_flow;
|
||||
int nozzle_change_line_count = length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width) + 1;
|
||||
if (has_tpu_filament())
|
||||
nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||
else
|
||||
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||
depth += nozzle_change_depth;
|
||||
}
|
||||
toolchange.nozzle_change_depth = nozzle_change_depth;
|
||||
toolchange.required_depth = depth;
|
||||
}
|
||||
@@ -3797,6 +4155,7 @@ void WipeTower::plan_tower_new()
|
||||
}
|
||||
|
||||
update_all_layer_depth(max_depth);
|
||||
set_nozzle_last_layer_id(); // record per-extruder last layer for is_valid_last_layer
|
||||
float diagonal = sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width);
|
||||
m_rib_length = std::max({m_rib_length, diagonal});
|
||||
m_rib_length += m_extra_rib_length;
|
||||
@@ -3916,9 +4275,12 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
int candidate_id = -1;
|
||||
for (size_t idx = 0; idx < layer.tool_changes.size(); ++idx) {
|
||||
if (idx == 0) {
|
||||
if (layer.tool_changes[idx].old_tool == wall_filament_id)
|
||||
// An extruder's last-layer filament above its printable height cannot supply the
|
||||
// outer wall. is_valid_last_layer is inert unless it clamps.
|
||||
if (layer.tool_changes[idx].old_tool == wall_filament_id && is_valid_last_layer(layer.tool_changes[idx].old_tool, m_cur_layer_id, layer.z))
|
||||
return wall_filament_id;
|
||||
else if (m_filpar[layer.tool_changes[idx].old_tool].category == m_filpar[wall_filament_id].category) {
|
||||
else if (m_filpar[layer.tool_changes[idx].old_tool].category == m_filpar[wall_filament_id].category &&
|
||||
is_valid_last_layer(layer.tool_changes[idx].old_tool, m_cur_layer_id, layer.z)) {
|
||||
candidate_id = layer.tool_changes[idx].old_tool;
|
||||
}
|
||||
}
|
||||
@@ -4017,6 +4379,10 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
finish_layer_filament = wall_idx;
|
||||
}
|
||||
|
||||
// Cancel a block on the last layer above its extruder's printable height.
|
||||
// is_valid_last_layer is inert unless multi-extruder near Z-max.
|
||||
if (!is_valid_last_layer(finish_layer_filament, m_cur_layer_id, layer.z)) continue;
|
||||
|
||||
ToolChangeResult finish_block_tcr;
|
||||
if (interface_solid || (block.solid_infill[m_cur_layer_id] && block.filament_adhesiveness_category != m_filament_categories[finish_layer_filament])) {
|
||||
interface_solid = interface_solid && !((block.solid_infill[m_cur_layer_id] && block.filament_adhesiveness_category != m_filament_categories[finish_layer_filament]));//noly reduce speed when
|
||||
|
||||
@@ -58,6 +58,7 @@ public:
|
||||
Vec2f origin_start_pos; // not rotated
|
||||
|
||||
std::vector<Vec2f> wipe_path;
|
||||
bool is_extruder_change{true};
|
||||
};
|
||||
|
||||
struct ToolChangeResult
|
||||
@@ -309,10 +310,19 @@ public:
|
||||
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
||||
|
||||
void set_filament_map(const std::vector<int> &filament_map) { m_filament_map = filament_map; }
|
||||
// Vortek H2C: filament_id → physical nozzle_id for carousel rotation detection
|
||||
void set_filament_nozzle_map(const std::vector<int> &nozzle_map) { m_filament_nozzle_map = nozzle_map; }
|
||||
|
||||
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
|
||||
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
||||
|
||||
// Orca: has_filament_switcher is not a static PrintConfig member, so it is pushed in from Print
|
||||
// via a setter rather than read in the ctor. Device-set only.
|
||||
void set_has_filament_switcher(bool v) { m_has_filament_switcher = v; }
|
||||
// The region every extruder can reach, used to clamp the PETG pre-extrusion offset to the
|
||||
// printable bed.
|
||||
void set_shared_print_bed(const Polygons &bed) { m_shared_print_bed = bed; }
|
||||
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
int category;
|
||||
@@ -341,8 +351,20 @@ public:
|
||||
float wipe_dist;
|
||||
float tower_interface_pre_extrusion_dist = 0.f;
|
||||
float tower_interface_pre_extrusion_length = 0.f;
|
||||
// Outward shift of the wipe start for a PETG pre-extrusion on filament-switcher devices;
|
||||
// set from filament_tower_interface_pre_extrusion_dist.
|
||||
float petg_pre_extrusion_offset_dist = 0.f;
|
||||
float tower_ironing_area = 4.f;
|
||||
float tower_interface_purge_length = 0.f;
|
||||
// Distance (in mm of filament) that a hotend is allowed to pre-cool before the
|
||||
// tower is reached; drives the prime-tower heating-during-wipe model (multi-nozzle only).
|
||||
float filament_cooling_before_tower = 0.f;
|
||||
// .first = extruder change, .second = nozzle change (carousel)
|
||||
std::pair<float,float> max_e_ramming_speed{0.f, 0.f};
|
||||
std::pair<float,float> ramming_travel_time{0.f, 0.f};
|
||||
std::pair<int,int> precool_target_temp{0, 0};
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t;
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t_first_layer;
|
||||
};
|
||||
|
||||
|
||||
@@ -382,6 +404,8 @@ public:
|
||||
void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false);
|
||||
int get_filament_category(int filament_id);
|
||||
bool is_in_same_extruder(int filament_id_1, int filament_id_2);
|
||||
// Vortek H2C: format BBS-compatible NOZZLE_CHANGE_START/END tag with OF/NF/ON/NN payload
|
||||
std::string format_nozzle_change_tag(bool start, int old_filament_id, int new_filament_id) const;
|
||||
void reset_block_status();
|
||||
int get_wall_filament_for_all_layer();
|
||||
// for generate new wipe tower
|
||||
@@ -440,6 +464,7 @@ private:
|
||||
size_t m_cur_layer_id;
|
||||
NozzleChangeResult m_nozzle_change_result;
|
||||
std::vector<int> m_filament_map;
|
||||
std::vector<int> m_filament_nozzle_map; // Vortek H2C: filament_id → physical nozzle_id
|
||||
bool m_has_tpu_filament{false};
|
||||
bool m_is_multi_extruder{false};
|
||||
bool m_use_gap_wall{false};
|
||||
@@ -462,6 +487,22 @@ private:
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
|
||||
// Multi-nozzle prime-tower heating during wipe. m_is_multiple_nozzle gates the whole
|
||||
// feature; it is false for every current (single-nozzle) printer (extruder_max_nozzle_count
|
||||
// defaults to 1), so the pre-heat/pre-cool path is inert and wipe-tower g-code is unchanged.
|
||||
bool m_is_multiple_nozzle = false;
|
||||
std::vector<double> m_hotend_heating_rate; // config.hotend_heating_rate (deg/s per extruder)
|
||||
std::vector<int> m_physical_extruder_map; // logical extruder -> physical tool number (M104 T param)
|
||||
|
||||
// Per-extruder printable-height clamp. m_printable_height = config.extruder_printable_height
|
||||
// (per-extruder Z limit; empty for single-extruder printers, [320,325] for H2D). m_last_layer_id
|
||||
// records, per extruder, the last wipe-tower layer that uses it. is_valid_last_layer() is gated on
|
||||
// m_is_multi_extruder so single-extruder wipe-tower g-code is unchanged; the clamp only bites a
|
||||
// multi-extruder wipe tower whose final per-extruder layer exceeds that extruder's printable
|
||||
// height (near the Z limit).
|
||||
std::vector<double> m_printable_height;
|
||||
std::vector<int> m_last_layer_id;
|
||||
|
||||
// Bed properties
|
||||
enum {
|
||||
RectangularBed,
|
||||
@@ -501,6 +542,11 @@ private:
|
||||
bool m_flat_ironing=false;
|
||||
bool m_enable_tower_interface_features=false;
|
||||
bool m_enable_tower_interface_cooldown_during_tower=false;
|
||||
// Filament-switcher device flag + shared printable bed for the PETG pre-extrusion offset.
|
||||
// m_has_filament_switcher is false for the whole shipping fleet (no profile sets the key), so
|
||||
// the PETG branch in get_next_pos never runs -> no change fleet-wide.
|
||||
bool m_has_filament_switcher=false;
|
||||
Polygons m_shared_print_bed;
|
||||
bool m_prev_layer_had_interface=false;
|
||||
bool m_current_layer_has_interface=false;
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
@@ -520,6 +566,8 @@ private:
|
||||
void save_on_last_wipe();
|
||||
|
||||
bool is_tpu_filament(int filament_id) const;
|
||||
bool is_petg_filament(int filament_id) const;
|
||||
bool is_need_reverse_travel(int filament_id, bool extruder_change) const;
|
||||
|
||||
// BBS
|
||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||
@@ -586,6 +634,12 @@ private:
|
||||
const box_coordinates &cleaning_box,
|
||||
float wipe_volume);
|
||||
void get_wall_skip_points(const WipeTowerInfo &layer);
|
||||
|
||||
// Per-extruder printable-height clamp (see m_printable_height). is_valid_last_layer returns
|
||||
// false only for a multi-extruder wipe tower's final per-extruder layer that exceeds that
|
||||
// extruder's printable height; returns true (no clamp) in every other case.
|
||||
bool is_valid_last_layer(int tool, int layer_id, double layer_z) const;
|
||||
void set_nozzle_last_layer_id();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1262,9 +1262,9 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
|
||||
m_bridging(float(config.wipe_tower_bridging)),
|
||||
m_no_sparse_layers(config.wipe_tower_no_sparse_layers),
|
||||
m_gcode_flavor(config.gcode_flavor),
|
||||
m_travel_speed(config.travel_speed),
|
||||
m_infill_speed(default_region_config.sparse_infill_speed),
|
||||
m_perimeter_speed(default_region_config.inner_wall_speed),
|
||||
m_travel_speed(config.travel_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
|
||||
m_infill_speed(default_region_config.sparse_infill_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
|
||||
m_perimeter_speed(default_region_config.inner_wall_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
|
||||
m_current_tool(initial_tool),
|
||||
wipe_volumes(wiping_matrix), m_wipe_tower_max_purge_speed(float(config.wipe_tower_max_purge_speed)),
|
||||
m_enable_arc_fitting(config.enable_arc_fitting),
|
||||
@@ -1280,7 +1280,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
|
||||
// it is taken over following default. Speeds from config are not
|
||||
// easily accessible here.
|
||||
const float default_speed = 60.f;
|
||||
m_first_layer_speed = config.initial_layer_speed;
|
||||
m_first_layer_speed = config.initial_layer_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool));
|
||||
if (m_first_layer_speed == 0.f) // just to make sure autospeed doesn't break it.
|
||||
m_first_layer_speed = default_speed / 2.f;
|
||||
|
||||
@@ -1333,6 +1333,10 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config)
|
||||
//while (m_filpar.size() < idx+1) // makes sure the required element is in the vector
|
||||
m_filpar.push_back(FilamentParameters());
|
||||
|
||||
// Orca: one row per filament, indexed by the raw filament id. Under a per-layer nozzle
|
||||
// grouping the per-variant arrays may hold several columns per filament; the tower has no
|
||||
// layer dimension here, so it keeps the filament's first column (tower x per-layer
|
||||
// grouping is a documented follow-up).
|
||||
m_filpar[idx].material = config.filament_type.get_at(idx);
|
||||
if (m_wipe_tower_filament > 0)
|
||||
m_filpar[idx].is_soluble = (idx != size_t(m_wipe_tower_filament - 1));
|
||||
|
||||
+242
-77
@@ -2,6 +2,8 @@
|
||||
#include "CustomGCode.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Line.hpp"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
@@ -28,6 +30,39 @@ bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor)
|
||||
void GCodeWriter::apply_print_config(const PrintConfig &print_config)
|
||||
{
|
||||
this->config.apply(print_config, true);
|
||||
|
||||
// Some machine limits are stride-2 (normal, silent) pairs, here we extract the value that will be used,
|
||||
// which is always normal mode at the moment
|
||||
// TODO: support silent? Any printer actually have that?
|
||||
auto get_machine_limits = [](const std::string key, const ConfigOptionFloats& opt) -> std::vector<double> {
|
||||
unsigned int stride = 1;
|
||||
unsigned int offset = 0;
|
||||
if (printer_options_with_variant_2.count(key) > 0) {
|
||||
stride = 2;
|
||||
// offset = <TODO: current print mode>;
|
||||
}
|
||||
|
||||
std::vector<double> results;
|
||||
results.reserve(opt.values.size() / stride);
|
||||
|
||||
for (unsigned int i = offset; i < opt.values.size(); i += stride) {
|
||||
results.emplace_back(opt.values[i]);
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
auto rounded = [](std::vector<double>&& vec) -> std::vector<double>&&{
|
||||
std::transform(vec.cbegin(), vec.cend(), vec.begin(), [](const double v) { return std::round(v); });
|
||||
return std::move(vec);
|
||||
};
|
||||
auto to_uint = [](const std::vector<double>& vec) {
|
||||
std::vector<unsigned int> r;
|
||||
std::transform(vec.begin(), vec.end(), std::back_inserter(r), [](const double v) { return static_cast<unsigned int>(v); });
|
||||
return r;
|
||||
};
|
||||
#define LIMITS(OPT) get_machine_limits(#OPT, print_config.OPT)
|
||||
#define LIMITS_UINT(OPT) to_uint(rounded(LIMITS(OPT)))
|
||||
|
||||
m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
|
||||
bool use_mach_limits = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware ||
|
||||
print_config.gcode_flavor.value == gcfKlipper || print_config.gcode_flavor.value == gcfRepRapFirmware;
|
||||
@@ -35,29 +70,118 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
|
||||
// For Klipper, SET_VELOCITY_LIMIT ACCEL= applies to all moves, so the effective cap
|
||||
// is the minimum of the extruding limit and the per-axis X/Y limits.
|
||||
// This ensures user-configured Motion Ability limits are honoured (#12244).
|
||||
unsigned int extruding_limit = std::lrint(print_config.machine_max_acceleration_extruding.values.front());
|
||||
auto extruding_limit = LIMITS_UINT(machine_max_acceleration_extruding);
|
||||
if (print_config.gcode_flavor.value == gcfKlipper) {
|
||||
unsigned int x_limit = std::lrint(print_config.machine_max_acceleration_x.values.front());
|
||||
unsigned int y_limit = std::lrint(print_config.machine_max_acceleration_y.values.front());
|
||||
if (x_limit > 0) extruding_limit = std::min(extruding_limit, x_limit);
|
||||
if (y_limit > 0) extruding_limit = std::min(extruding_limit, y_limit);
|
||||
auto x_limit = LIMITS_UINT(machine_max_acceleration_x);
|
||||
auto y_limit = LIMITS_UINT(machine_max_acceleration_y);
|
||||
|
||||
for (size_t i = 0; i < extruding_limit.size(); i++) {
|
||||
if (x_limit[i] > 0) extruding_limit[i] = std::min(extruding_limit[i], x_limit[i]);
|
||||
if (y_limit[i] > 0) extruding_limit[i] = std::min(extruding_limit[i], y_limit[i]);
|
||||
}
|
||||
}
|
||||
m_max_acceleration = extruding_limit;
|
||||
m_max_acceleration = std::move(extruding_limit);
|
||||
} else {
|
||||
m_max_acceleration = 0;
|
||||
m_max_acceleration.clear();
|
||||
}
|
||||
if (use_mach_limits && supports_separate_travel_acceleration(print_config.gcode_flavor.value)) {
|
||||
m_max_travel_acceleration = LIMITS_UINT(machine_max_acceleration_travel);
|
||||
} else {
|
||||
m_max_travel_acceleration.clear();
|
||||
}
|
||||
m_max_travel_acceleration = static_cast<unsigned int>(
|
||||
std::round((use_mach_limits && supports_separate_travel_acceleration(print_config.gcode_flavor.value)) ?
|
||||
print_config.machine_max_acceleration_travel.values.front() :
|
||||
0));
|
||||
if (use_mach_limits) {
|
||||
m_max_jerk_x = std::lrint(print_config.machine_max_jerk_x.values.front());
|
||||
m_max_jerk_y = std::lrint(print_config.machine_max_jerk_y.values.front());
|
||||
m_max_junction_deviation = (print_config.machine_max_junction_deviation.values.front());
|
||||
};
|
||||
m_max_jerk_z = print_config.machine_max_jerk_z.values.front();
|
||||
m_max_jerk_e = print_config.machine_max_jerk_e.values.front();
|
||||
m_max_jerk_x = rounded(LIMITS(machine_max_jerk_x));
|
||||
m_max_jerk_y = rounded(LIMITS(machine_max_jerk_y));
|
||||
m_max_junction_deviation = LIMITS(machine_max_junction_deviation);
|
||||
} else {
|
||||
m_max_jerk_x.clear();
|
||||
m_max_jerk_y.clear();
|
||||
m_max_junction_deviation.clear();
|
||||
}
|
||||
m_max_jerk_z = LIMITS(machine_max_jerk_z);
|
||||
m_max_jerk_e = LIMITS(machine_max_jerk_e);
|
||||
m_resolution = print_config.resolution.value;
|
||||
#undef LIMITS
|
||||
#undef LIMITS_UINT
|
||||
// Orca: capture the printable area(s) so a spiral lift can be skipped when its
|
||||
// circle would leave the boundary and collide with the print limits. Full polygons
|
||||
// are stored (not a bounding box) so the check stays correct for non-rectangular
|
||||
// beds, and per-extruder areas are kept so printers with different boundaries per
|
||||
// extruder use the right limit for whichever extruder is active.
|
||||
auto to_scaled_polygon = [](const Pointfs &pts) {
|
||||
Polygon poly;
|
||||
poly.points.reserve(pts.size());
|
||||
for (const Vec2d &p : pts)
|
||||
poly.points.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y())));
|
||||
poly.make_counter_clockwise();
|
||||
return poly;
|
||||
};
|
||||
|
||||
m_bed_printable_area.points.clear();
|
||||
m_extruder_printable_areas.clear();
|
||||
|
||||
if (print_config.printable_area.values.size() >= 3)
|
||||
m_bed_printable_area = to_scaled_polygon(print_config.printable_area.values);
|
||||
|
||||
const std::vector<Pointfs> &extruder_areas = print_config.extruder_printable_area.values;
|
||||
if (!extruder_areas.empty()) {
|
||||
m_extruder_printable_areas.resize(extruder_areas.size());
|
||||
for (size_t i = 0; i < extruder_areas.size(); ++i) {
|
||||
if (extruder_areas[i].size() < 3) {
|
||||
// No dedicated area for this extruder: it can reach the whole bed.
|
||||
m_extruder_printable_areas[i] = m_bed_printable_area;
|
||||
continue;
|
||||
}
|
||||
Polygon extruder_poly = to_scaled_polygon(extruder_areas[i]);
|
||||
if (m_bed_printable_area.points.size() < 3) {
|
||||
m_extruder_printable_areas[i] = std::move(extruder_poly);
|
||||
continue;
|
||||
}
|
||||
// The reachable area is the extruder area clipped to the bed. Bed shapes are
|
||||
// convex in practice, so keep the largest resulting contour.
|
||||
Polygons clipped = intersection(extruder_poly, m_bed_printable_area);
|
||||
const Polygon *largest = nullptr;
|
||||
double best_area = 0.;
|
||||
for (const Polygon &p : clipped) {
|
||||
double a = std::abs(p.area());
|
||||
if (a > best_area) { best_area = a; largest = &p; }
|
||||
}
|
||||
m_extruder_printable_areas[i] = largest ? *largest : std::move(extruder_poly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Polygon *GCodeWriter::active_printable_area() const
|
||||
{
|
||||
if (const Extruder *e = this->filament()) {
|
||||
size_t id = e->extruder_id();
|
||||
if (id < m_extruder_printable_areas.size() && m_extruder_printable_areas[id].points.size() >= 3)
|
||||
return &m_extruder_printable_areas[id];
|
||||
}
|
||||
if (m_bed_printable_area.points.size() >= 3)
|
||||
return &m_bed_printable_area;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool GCodeWriter::spiral_lift_fits_printable_area(const Vec2d ¢er, double radius) const
|
||||
{
|
||||
const Polygon *area = this->active_printable_area();
|
||||
if (area == nullptr)
|
||||
return true; // Boundary unknown: don't restrict (preserve previous behavior).
|
||||
|
||||
const Point c = Point::new_scale(center.x(), center.y());
|
||||
const double r_scaled = scale_(radius);
|
||||
const double r2 = r_scaled * r_scaled;
|
||||
|
||||
// The spiral traces a full circle of `radius` around `center`, so the center must lie
|
||||
// inside the printable area and every edge must be at least `radius` away from it.
|
||||
if (!area->contains(c))
|
||||
return false;
|
||||
const Points &pts = area->points;
|
||||
for (size_t i = 0, n = pts.size(); i < n; ++i)
|
||||
if (Line::distance_to_squared(c, pts[i], pts[(i + 1) % n]) < r2)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GCodeWriter::set_extruders(std::vector<unsigned int> extruder_ids)
|
||||
@@ -66,6 +190,7 @@ void GCodeWriter::set_extruders(std::vector<unsigned int> extruder_ids)
|
||||
m_filament_extruders.clear();
|
||||
//ORCA: Reset current extruder ID and clear pointers to prevent dangling pointers when extruders are recreated.
|
||||
m_curr_extruder_id = -1;
|
||||
m_cached_extruder_idx = 0;
|
||||
std::fill(m_curr_filament_extruder.begin(), m_curr_filament_extruder.end(), nullptr);
|
||||
m_filament_extruders.reserve(extruder_ids.size());
|
||||
for (unsigned int extruder_id : extruder_ids)
|
||||
@@ -212,14 +337,18 @@ std::string GCodeWriter::set_chamber_temperature(int temperature, bool wait)
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
#define EXTRUDER_LIMIT(OPT) \
|
||||
(filament() ? ((OPT).size() <= filament()->extruder_id() ? 0 : (OPT)[filament()->extruder_id()]) : \
|
||||
((OPT).empty() ? 0 : *std::max_element((OPT).cbegin(), (OPT).cend())))
|
||||
|
||||
// copied from PrusaSlicer
|
||||
std::string GCodeWriter::set_acceleration_internal(Acceleration type, unsigned int acceleration)
|
||||
{
|
||||
// Clamp the acceleration to the allowed maximum.
|
||||
if (type == Acceleration::Print && m_max_acceleration > 0 && acceleration > m_max_acceleration)
|
||||
acceleration = m_max_acceleration;
|
||||
if (type == Acceleration::Travel && m_max_travel_acceleration > 0 && acceleration > m_max_travel_acceleration)
|
||||
acceleration = m_max_travel_acceleration;
|
||||
if (type == Acceleration::Print && EXTRUDER_LIMIT(m_max_acceleration) > 0 && acceleration > EXTRUDER_LIMIT(m_max_acceleration))
|
||||
acceleration = EXTRUDER_LIMIT(m_max_acceleration);
|
||||
if (type == Acceleration::Travel && EXTRUDER_LIMIT(m_max_travel_acceleration) > 0 && acceleration > EXTRUDER_LIMIT(m_max_travel_acceleration))
|
||||
acceleration = EXTRUDER_LIMIT(m_max_travel_acceleration);
|
||||
|
||||
// Are we setting travel acceleration for a flavour that supports separate travel and print acc?
|
||||
bool separate_travel = (type == Acceleration::Travel && supports_separate_travel_acceleration(this->config.gcode_flavor));
|
||||
@@ -262,10 +391,10 @@ std::string GCodeWriter::set_jerk_xy(double jerk)
|
||||
std::ostringstream gcode;
|
||||
if (FLAVOR_IS(gcfKlipper)) {
|
||||
// Clamp the jerk to the allowed maximum.
|
||||
if (m_max_jerk_x > 0 && jerk > m_max_jerk_x)
|
||||
jerk = m_max_jerk_x;
|
||||
if (m_max_jerk_y > 0 && jerk > m_max_jerk_y)
|
||||
jerk = m_max_jerk_y;
|
||||
if (EXTRUDER_LIMIT(m_max_jerk_x) > 0 && jerk > EXTRUDER_LIMIT(m_max_jerk_x))
|
||||
jerk = EXTRUDER_LIMIT(m_max_jerk_x);
|
||||
if (EXTRUDER_LIMIT(m_max_jerk_y) > 0 && jerk > EXTRUDER_LIMIT(m_max_jerk_y))
|
||||
jerk = EXTRUDER_LIMIT(m_max_jerk_y);
|
||||
|
||||
gcode << "SET_VELOCITY_LIMIT SQUARE_CORNER_VELOCITY=" << jerk;
|
||||
|
||||
@@ -274,12 +403,12 @@ std::string GCodeWriter::set_jerk_xy(double jerk)
|
||||
double jerk_xy = jerk;
|
||||
|
||||
// Clamp against the X machine limit
|
||||
if (m_max_jerk_x > 0 && jerk_xy > m_max_jerk_x)
|
||||
jerk_xy = m_max_jerk_x;
|
||||
if (EXTRUDER_LIMIT(m_max_jerk_x) > 0 && jerk_xy > EXTRUDER_LIMIT(m_max_jerk_x))
|
||||
jerk_xy = EXTRUDER_LIMIT(m_max_jerk_x);
|
||||
|
||||
// Clamp against the Y machine limit as well to be safe
|
||||
if (m_max_jerk_y > 0 && jerk_xy > m_max_jerk_y)
|
||||
jerk_xy = m_max_jerk_y;
|
||||
if (EXTRUDER_LIMIT(m_max_jerk_y) > 0 && jerk_xy > EXTRUDER_LIMIT(m_max_jerk_y))
|
||||
jerk_xy = EXTRUDER_LIMIT(m_max_jerk_y);
|
||||
|
||||
// Output the lowest safe limit using ONLY the X parameter
|
||||
gcode << "M207 X" << jerk_xy;
|
||||
@@ -287,16 +416,16 @@ std::string GCodeWriter::set_jerk_xy(double jerk)
|
||||
double jerk_x = jerk;
|
||||
double jerk_y = jerk;
|
||||
// Clamp the axis jerk to the allowed maximum.
|
||||
if (m_max_jerk_x > 0 && jerk > m_max_jerk_x)
|
||||
jerk_x = m_max_jerk_x;
|
||||
if (m_max_jerk_y > 0 && jerk > m_max_jerk_y)
|
||||
jerk_y = m_max_jerk_y;
|
||||
if (EXTRUDER_LIMIT(m_max_jerk_x) > 0 && jerk > EXTRUDER_LIMIT(m_max_jerk_x))
|
||||
jerk_x = EXTRUDER_LIMIT(m_max_jerk_x);
|
||||
if (EXTRUDER_LIMIT(m_max_jerk_y) > 0 && jerk > EXTRUDER_LIMIT(m_max_jerk_y))
|
||||
jerk_y = EXTRUDER_LIMIT(m_max_jerk_y);
|
||||
|
||||
gcode << "M205 X" << jerk_x << " Y" << jerk_y;
|
||||
}
|
||||
//the is_bbl check should be in the else statement above so that it doesn't inadverently added Z & E to klipper
|
||||
if (m_is_bbl_printers)
|
||||
gcode << std::setprecision(2) << " Z" << m_max_jerk_z << " E" << m_max_jerk_e;
|
||||
gcode << std::setprecision(2) << " Z" << EXTRUDER_LIMIT(m_max_jerk_z) << " E" << EXTRUDER_LIMIT(m_max_jerk_e);
|
||||
|
||||
if (GCodeWriter::full_gcode_comment) gcode << " ; adjust jerk";
|
||||
gcode << "\n";
|
||||
@@ -312,8 +441,8 @@ std::string GCodeWriter::set_accel_and_jerk(unsigned int acceleration, double je
|
||||
throw std::runtime_error(_u8L("set_accel_and_jerk() is only supported by Klipper"));
|
||||
|
||||
// Clamp the acceleration to the allowed maximum.
|
||||
if (m_max_acceleration > 0 && acceleration > m_max_acceleration)
|
||||
acceleration = m_max_acceleration;
|
||||
if (EXTRUDER_LIMIT(m_max_acceleration) > 0 && acceleration > EXTRUDER_LIMIT(m_max_acceleration))
|
||||
acceleration = EXTRUDER_LIMIT(m_max_acceleration);
|
||||
|
||||
bool is_empty = true;
|
||||
std::ostringstream gcode;
|
||||
@@ -327,10 +456,10 @@ std::string GCodeWriter::set_accel_and_jerk(unsigned int acceleration, double je
|
||||
is_empty = false;
|
||||
}
|
||||
// Clamp the jerk to the allowed maximum.
|
||||
if (m_max_jerk_x > 0 && jerk > m_max_jerk_x)
|
||||
jerk = m_max_jerk_x;
|
||||
if (m_max_jerk_y > 0 && jerk > m_max_jerk_y)
|
||||
jerk = m_max_jerk_y;
|
||||
if (EXTRUDER_LIMIT(m_max_jerk_x) > 0 && jerk > EXTRUDER_LIMIT(m_max_jerk_x))
|
||||
jerk = EXTRUDER_LIMIT(m_max_jerk_x);
|
||||
if (EXTRUDER_LIMIT(m_max_jerk_y) > 0 && jerk > EXTRUDER_LIMIT(m_max_jerk_y))
|
||||
jerk = EXTRUDER_LIMIT(m_max_jerk_y);
|
||||
|
||||
if (jerk > 0.01 && !is_approx(jerk, m_last_jerk)) {
|
||||
gcode << " SQUARE_CORNER_VELOCITY=" << jerk;
|
||||
@@ -351,13 +480,13 @@ std::string GCodeWriter::set_accel_and_jerk(unsigned int acceleration, double je
|
||||
|
||||
std::string GCodeWriter::set_junction_deviation(double junction_deviation){
|
||||
std::ostringstream gcode;
|
||||
if (FLAVOR_IS(gcfMarlinFirmware) && m_max_junction_deviation > 0 && junction_deviation > 0) {
|
||||
if (FLAVOR_IS(gcfMarlinFirmware) && EXTRUDER_LIMIT(m_max_junction_deviation) > 0 && junction_deviation > 0) {
|
||||
// Clamp the junction deviation to the allowed maximum.
|
||||
gcode << "M205 J";
|
||||
if (junction_deviation <= m_max_junction_deviation) {
|
||||
if (junction_deviation <= EXTRUDER_LIMIT(m_max_junction_deviation)) {
|
||||
gcode << std::fixed << std::setprecision(3) << junction_deviation;
|
||||
} else {
|
||||
gcode << std::fixed << std::setprecision(3) << m_max_junction_deviation;
|
||||
gcode << std::fixed << std::setprecision(3) << EXTRUDER_LIMIT(m_max_junction_deviation);
|
||||
}
|
||||
if (GCodeWriter::full_gcode_comment) {
|
||||
gcode << " ; Junction Deviation";
|
||||
@@ -546,36 +675,36 @@ std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, boo
|
||||
|
||||
std::string GCodeWriter::toolchange_prefix() const
|
||||
{
|
||||
std::string gcode = "T";
|
||||
// Orca: the manual-filament-change tag must stay ahead of the flavor selection so
|
||||
// MMU manual-change handling keeps working.
|
||||
if (config.manual_filament_change)
|
||||
gcode = ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Manual_Tool_Change) + "T";
|
||||
else {
|
||||
if (m_is_bbl_printers)
|
||||
gcode = "M1020 S";
|
||||
else {
|
||||
if (FLAVOR_IS(gcfMakerWare))
|
||||
gcode = "M135 T";
|
||||
else if (FLAVOR_IS(gcfSailfish))
|
||||
gcode = "M108 T";
|
||||
}
|
||||
}
|
||||
return gcode;
|
||||
return ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Manual_Tool_Change) + "T";
|
||||
return FLAVOR_IS(gcfMakerWare) ? "M135 T" :
|
||||
FLAVOR_IS(gcfSailfish) ? "M108 T" : "T";
|
||||
}
|
||||
|
||||
std::string GCodeWriter::toolchange(unsigned int filament_id)
|
||||
std::string GCodeWriter::toolchange(unsigned int filament_id, int nozzle_id)
|
||||
{
|
||||
// set the new extruder
|
||||
auto filament_extruder_iter = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(), [filament_id](const Extruder &e) { return e.id() < filament_id; });
|
||||
assert(filament_extruder_iter != m_filament_extruders.end() && filament_extruder_iter->id() == filament_id);
|
||||
m_curr_extruder_id = filament_extruder_iter->extruder_id();
|
||||
m_curr_filament_extruder[m_curr_extruder_id] = &*filament_extruder_iter;
|
||||
m_cached_extruder_idx = get_extruder_index(this->config, filament_id);
|
||||
|
||||
// return the toolchange command
|
||||
// if we are running a single-extruder setup, just set the extruder and return nothing
|
||||
std::ostringstream gcode;
|
||||
// Orca: also emit for non-BBL single-extruder multi-filament setups (MMU-style).
|
||||
if (this->multiple_extruders || (this->config.filament_diameter.values.size() > 1 && !is_bbl_printers())) {
|
||||
// Orca: call toolchange_prefix() to get the correct command prefix based on the configuration and flavor.
|
||||
gcode << this->toolchange_prefix() << filament_id;
|
||||
// Orca: manual filament change keeps its tag line even on BBL machines, so the
|
||||
// M1020 form must not shadow it. nozzle_id is signed: the null-safe nozzle
|
||||
// lookup legitimately yields -1 ("no specific nozzle"), matching the literal
|
||||
// H-1 the stock change templates emit; an unsigned would wrap.
|
||||
if (m_is_bbl_printers && !config.manual_filament_change)
|
||||
gcode << "M1020 S" << filament_id << " H" << nozzle_id;
|
||||
else
|
||||
gcode << this->toolchange_prefix() << filament_id;
|
||||
if (GCodeWriter::full_gcode_comment)
|
||||
gcode << " ; change extruder";
|
||||
gcode << "\n";
|
||||
@@ -584,6 +713,25 @@ std::string GCodeWriter::toolchange(unsigned int filament_id)
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
// Current parked-retract length of the filament's extruder, share-aware. m_filament_extruders is
|
||||
// sorted by id (see toolchange), so a lower_bound lookup finds the entry; unknown filament ids
|
||||
// degrade to 0 rather than dereferencing end().
|
||||
double GCodeWriter::get_extruder_retracted_length(const int filament_id)
|
||||
{
|
||||
double res = 0.0;
|
||||
auto filament_extruder_iter = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(),
|
||||
[filament_id](const Extruder &e) { return (int) e.id() < filament_id; });
|
||||
if (filament_extruder_iter == m_filament_extruders.end() || (int) filament_extruder_iter->id() != filament_id)
|
||||
return res;
|
||||
|
||||
if (filament_extruder_iter->is_share_extruder())
|
||||
res = filament_extruder_iter->get_share_retracted_length();
|
||||
else
|
||||
res = filament_extruder_iter->get_single_retracted_length();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker)
|
||||
{
|
||||
assert(F > 0.);
|
||||
@@ -610,7 +758,7 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xy(point_on_plate);
|
||||
auto speed = m_is_first_layer
|
||||
? this->config.get_abs_value("initial_layer_travel_speed") : this->config.travel_speed.value;
|
||||
? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) : this->config.travel_speed.get_at(m_cached_extruder_idx);
|
||||
w.emit_f(speed * 60.0);
|
||||
//BBS
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
@@ -663,14 +811,19 @@ std::string GCodeWriter::eager_lift(const LiftType type) {
|
||||
}
|
||||
|
||||
// BBS: spiral lift only safe with known position
|
||||
// TODO: check the arc will move within bed area
|
||||
if (type == LiftType::SpiralLift && this->is_current_position_clear()) {
|
||||
double radius = target_lift / (2 * PI * atan(filament()->travel_slope()));
|
||||
// static spiral alignment when no move in x,y plane.
|
||||
// spiral centra is a radius distance to the right (y=0)
|
||||
// spiral centra is a radius distance to the right (y=0)
|
||||
Vec2d ij_offset = { radius, 0 };
|
||||
if (target_lift > 0) {
|
||||
// Orca: keep the spiral inside the active extruder's printable area, otherwise
|
||||
// fall back to a normal lift to avoid colliding with the print boundary. m_pos
|
||||
// includes the plate offset, so remove it to match the printable area coordinates.
|
||||
const Vec2d spiral_center = { m_pos.x() - m_x_offset + ij_offset.x(), m_pos.y() - m_y_offset + ij_offset.y() };
|
||||
if (target_lift > 0 && this->spiral_lift_fits_printable_area(spiral_center, radius)) {
|
||||
lift_move = this->_spiral_travel_to_z(m_pos(2) + target_lift, ij_offset, "spiral lift Z");
|
||||
} else if (target_lift > 0) {
|
||||
lift_move = _travel_to_z(m_pos(2) + target_lift, "normal lift Z");
|
||||
}
|
||||
}
|
||||
//BBS: if position is unknown use normal lift
|
||||
@@ -696,7 +849,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
|
||||
// BBS
|
||||
Vec3d dest_point = point;
|
||||
auto travel_speed =
|
||||
m_is_first_layer ? this->config.get_abs_value("initial_layer_travel_speed") : this->config.travel_speed.value;
|
||||
m_is_first_layer ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) : this->config.travel_speed.get_at(m_cached_extruder_idx);
|
||||
//BBS: a z_hop need to be handle when travel
|
||||
if (std::abs(m_to_lift) > EPSILON) {
|
||||
assert(std::abs(m_lifted) < EPSILON);
|
||||
@@ -725,7 +878,15 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
|
||||
double radius = delta(2) / (2 * PI * atan(this->filament()->travel_slope()));
|
||||
Vec2d ij_offset = radius * delta_no_z.normalized();
|
||||
ij_offset = { -ij_offset(1), ij_offset(0) };
|
||||
slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z");
|
||||
// Orca: only perform the spiral lift if its full circle stays inside the
|
||||
// printable area of the active extruder, otherwise fall back to a normal
|
||||
// lift to avoid colliding with the print boundary. `source` is already in
|
||||
// bed coordinates (plate offset removed), matching the printable area.
|
||||
const Vec2d spiral_center = { source.x() + ij_offset.x(), source.y() + ij_offset.y() };
|
||||
if (this->spiral_lift_fits_printable_area(spiral_center, radius))
|
||||
slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z");
|
||||
else
|
||||
slop_move = _travel_to_z(target.z(), "normal lift Z");
|
||||
}
|
||||
//BBS: SlopeLift
|
||||
else if (m_to_lift_type == LiftType::SlopeLift &&
|
||||
@@ -793,13 +954,13 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
|
||||
{
|
||||
//force to move xy first then z after filament change
|
||||
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
|
||||
w.emit_f(this->config.travel_speed.value * 60.0);
|
||||
w.emit_f(this->config.travel_speed.get_at(m_cached_extruder_idx) * 60.0);
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
out_string = w.string() + _travel_to_z(point_on_plate.z(), comment);
|
||||
} else {
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(point_on_plate);
|
||||
w.emit_f(this->config.travel_speed.value * 60.0);
|
||||
w.emit_f(this->config.travel_speed.get_at(m_cached_extruder_idx) * 60.0);
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
out_string = w.string();
|
||||
}
|
||||
@@ -832,10 +993,10 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
|
||||
{
|
||||
m_pos(2) = z;
|
||||
|
||||
double speed = this->config.travel_speed_z.value;
|
||||
double speed = this->config.travel_speed_z.get_at(m_cached_extruder_idx);
|
||||
if (speed == 0.) {
|
||||
speed = m_is_first_layer ? this->config.get_abs_value("initial_layer_travel_speed")
|
||||
: this->config.travel_speed.value;
|
||||
speed = m_is_first_layer ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx)
|
||||
: this->config.travel_speed.get_at(m_cached_extruder_idx);
|
||||
}
|
||||
|
||||
GCodeG1Formatter w;
|
||||
@@ -849,11 +1010,11 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
|
||||
std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment)
|
||||
{
|
||||
std::string output;
|
||||
double speed = this->config.travel_speed_z.value;
|
||||
double speed = this->config.travel_speed_z.get_at(m_cached_extruder_idx);
|
||||
|
||||
if (speed == 0.) {
|
||||
speed = m_is_first_layer ? this->config.get_abs_value("initial_layer_travel_speed")
|
||||
: this->config.travel_speed.value;
|
||||
speed = m_is_first_layer ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx)
|
||||
: this->config.travel_speed.get_at(m_cached_extruder_idx);
|
||||
}
|
||||
|
||||
if (!this->config.enable_arc_fitting) { // Orca: if arc fitting is disabled, approximate the arc with small linear segments
|
||||
@@ -1053,7 +1214,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::unretract()
|
||||
std::string GCodeWriter::unretract(float extra_retract)
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
@@ -1069,7 +1230,9 @@ std::string GCodeWriter::unretract()
|
||||
//BBS
|
||||
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
|
||||
GCodeG1Formatter w;
|
||||
w.emit_e(filament()->E());
|
||||
// extra_retract over-extrudes for the PETG pre-extrusion; 0 by
|
||||
// default -> identical to the plain deretract E position.
|
||||
w.emit_e(filament()->E() + extra_retract);
|
||||
w.emit_f(filament()->deretract_speed() * 60.);
|
||||
//BBS
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, " ; unretract");
|
||||
@@ -1202,8 +1365,9 @@ std::string GCodeWriter::set_extruder(unsigned int filament_id)
|
||||
auto filament_ext_it = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(), [filament_id](const Extruder &e) { return e.id() < filament_id; });
|
||||
unsigned int extruder_id = filament_ext_it->extruder_id();
|
||||
assert(filament_ext_it != m_filament_extruders.end() && filament_ext_it->id() == filament_id);
|
||||
//TODO: optmize here, pass extruder_id to toolchange
|
||||
return this->need_toolchange(filament_id) ? this->toolchange(filament_id) : "";
|
||||
// Orca: writer-only context (calibration paths) has no nozzle grouping; the
|
||||
// filament's own extruder id is the correct degenerate nozzle value.
|
||||
return this->need_toolchange(filament_id) ? this->toolchange(filament_id, (int) extruder_id) : "";
|
||||
}
|
||||
|
||||
void GCodeWriter::init_extruder(unsigned int filament_id)
|
||||
@@ -1213,6 +1377,7 @@ void GCodeWriter::init_extruder(unsigned int filament_id)
|
||||
assert(filament_extruder_iter != m_filament_extruders.end() && filament_extruder_iter->id() == filament_id);
|
||||
m_curr_extruder_id = filament_extruder_iter->extruder_id();
|
||||
m_curr_filament_extruder[m_curr_extruder_id] = &*filament_extruder_iter;
|
||||
m_cached_extruder_idx = get_extruder_index(this->config, filament_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <charconv>
|
||||
#include "Extruder.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "GCode/CoolingBuffer.hpp"
|
||||
|
||||
@@ -19,6 +20,7 @@ public:
|
||||
GCodeWriter() :
|
||||
multiple_extruders(false), m_curr_filament_extruder(MAXIMUM_EXTRUDER_NUMBER, nullptr),
|
||||
m_curr_extruder_id (-1),
|
||||
m_cached_extruder_idx(0),
|
||||
m_single_extruder_multi_material(false),
|
||||
m_last_acceleration(0), m_max_acceleration(0),m_last_travel_acceleration(0), m_max_travel_acceleration(0),
|
||||
m_last_jerk(0), m_max_jerk_x(0), m_max_jerk_y(0),
|
||||
@@ -66,10 +68,13 @@ public:
|
||||
bool need_toolchange(unsigned int filament_id) const;
|
||||
std::string set_extruder(unsigned int filament_id);
|
||||
void init_extruder(unsigned int filament_id);
|
||||
// Current parked-retract length of a filament's extruder (share-aware). Used for the
|
||||
// new_extruder_retracted_length change-filament placeholder. Returns 0 if the filament is unknown.
|
||||
double get_extruder_retracted_length(const int filament_id);
|
||||
// Prefix of the toolchange G-code line, to be used by the CoolingBuffer to separate sections of the G-code
|
||||
// printed with the same extruder.
|
||||
std::string toolchange_prefix() const;
|
||||
std::string toolchange(unsigned int filament_id);
|
||||
std::string toolchange(unsigned int filament_id, int nozzle_id);
|
||||
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string());
|
||||
// SoftFever NOTE: the returned speed is mm/minute
|
||||
double get_current_speed() const { return m_current_speed;}
|
||||
@@ -83,7 +88,9 @@ public:
|
||||
std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
||||
std::string retract(bool before_wipe = false, double retract_length = 0);
|
||||
std::string retract_for_toolchange(bool before_wipe = false, double retract_length = 0);
|
||||
std::string unretract();
|
||||
// extra_retract adds a small over-extrusion to the deretract move (PETG pre-extrusion).
|
||||
// Default 0 -> byte-identical to the plain deretract.
|
||||
std::string unretract(float extra_retract = 0.f);
|
||||
// do lift instantly
|
||||
std::string eager_lift(const LiftType type);
|
||||
// record a lift request, do realy lift in next travel
|
||||
@@ -126,6 +133,8 @@ public:
|
||||
const bool is_bbl_printers() const {return m_is_bbl_printers;}
|
||||
void set_is_first_layer(bool bval) { m_is_first_layer = bval; }
|
||||
GCodeFlavor get_gcode_flavor() const { return config.gcode_flavor; }
|
||||
void invalidate_acceleration() { m_last_acceleration = 0; m_last_travel_acceleration = 0; }
|
||||
void invalidate_jerk() { m_last_jerk = 0; }
|
||||
|
||||
// Returns whether this flavor supports separate print and travel acceleration.
|
||||
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
|
||||
@@ -135,22 +144,24 @@ public:
|
||||
bool m_single_extruder_multi_material;
|
||||
std::vector<Extruder*> m_curr_filament_extruder;
|
||||
int m_curr_extruder_id;
|
||||
unsigned int m_last_acceleration;
|
||||
unsigned int m_last_travel_acceleration;
|
||||
unsigned int m_max_travel_acceleration;
|
||||
// Motion uses the global/base process variant until a filament becomes active.
|
||||
size_t m_cached_extruder_idx;
|
||||
unsigned int m_last_acceleration;
|
||||
unsigned int m_last_travel_acceleration;
|
||||
std::vector<unsigned int> m_max_travel_acceleration;
|
||||
|
||||
// Limit for setting the acceleration, to respect the machine limits set for the Marlin firmware.
|
||||
// If set to zero, the limit is not in action.
|
||||
unsigned int m_max_acceleration;
|
||||
double m_max_jerk_x;
|
||||
double m_max_jerk_y;
|
||||
double m_last_jerk;
|
||||
double m_max_jerk_z;
|
||||
double m_max_jerk_e;
|
||||
double m_max_junction_deviation;
|
||||
// If set to zero, the limit is not in action. Indexed by 0-based physical nozzle id.
|
||||
std::vector<unsigned int> m_max_acceleration;
|
||||
std::vector<double> m_max_jerk_x;
|
||||
std::vector<double> m_max_jerk_y;
|
||||
double m_last_jerk;
|
||||
std::vector<double> m_max_jerk_z;
|
||||
std::vector<double> m_max_jerk_e;
|
||||
std::vector<double> m_max_junction_deviation;
|
||||
|
||||
unsigned int m_travel_acceleration;
|
||||
unsigned int m_travel_jerk;
|
||||
// unsigned int m_travel_acceleration;
|
||||
// unsigned int m_travel_jerk;
|
||||
|
||||
|
||||
//BBS
|
||||
@@ -173,6 +184,14 @@ public:
|
||||
|
||||
// Orca: slicing resolution in mm
|
||||
double m_resolution = 0.01;
|
||||
// Orca: printable area polygons (scaled, bed coordinates) used to keep spiral lifts
|
||||
// from colliding with the print boundary. m_extruder_printable_areas holds the
|
||||
// per-extruder reachable area (intersected with the bed) when a printer defines
|
||||
// different boundaries per extruder; m_bed_printable_area is the global fallback.
|
||||
// Storing full polygons (rather than a bounding box) keeps the check correct for
|
||||
// non-rectangular beds such as delta/circular printers.
|
||||
Polygon m_bed_printable_area;
|
||||
std::vector<Polygon> m_extruder_printable_areas;
|
||||
|
||||
std::string m_gcode_label_objects_start;
|
||||
std::string m_gcode_label_objects_end;
|
||||
@@ -189,6 +208,10 @@ public:
|
||||
|
||||
std::string _travel_to_z(double z, const std::string &comment);
|
||||
std::string _spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment);
|
||||
// Orca: printable area of the active extruder (per-extruder when configured, otherwise the bed). Null when unknown.
|
||||
const Polygon *active_printable_area() const;
|
||||
// Orca: true if a full spiral-lift circle (center in bed coordinates, mm) fits inside the active printable area.
|
||||
bool spiral_lift_fits_printable_area(const Vec2d ¢er, double radius) const;
|
||||
std::string _retract(double length, double restart_extra, const std::string &comment);
|
||||
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ ExPolygons Layer::merged(float offset_scaled) const
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Layer::is_perimeter_compatible(const PrintRegion& a, const PrintRegion& b)
|
||||
bool Layer::is_perimeter_compatible(const Print& print, const PrintRegion& a, const PrintRegion& b)
|
||||
{
|
||||
const PrintRegionConfig& config = a.config();
|
||||
const PrintRegionConfig& other_config = b.config();
|
||||
@@ -146,10 +146,11 @@ bool Layer::is_perimeter_compatible(const PrintRegion& a, const PrintRegion& b)
|
||||
&& config.wall_loops == other_config.wall_loops
|
||||
&& config.wall_sequence == other_config.wall_sequence
|
||||
&& config.is_infill_first == other_config.is_infill_first
|
||||
&& config.inner_wall_speed == other_config.inner_wall_speed
|
||||
&& config.outer_wall_speed == other_config.outer_wall_speed
|
||||
&& config.small_perimeter_speed == other_config.small_perimeter_speed
|
||||
&& config.gap_infill_speed.value == other_config.gap_infill_speed.value
|
||||
&& config.inner_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.inner_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.outer_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.outer_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.small_perimeter_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.small_perimeter_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.small_support_perimeter_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.small_support_perimeter_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.gap_infill_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.gap_infill_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.filter_out_gap_fill.value == other_config.filter_out_gap_fill.value
|
||||
&& config.detect_overhang_wall == other_config.detect_overhang_wall
|
||||
&& config.overhang_reverse == other_config.overhang_reverse
|
||||
@@ -209,7 +210,7 @@ void Layer::make_perimeters()
|
||||
if (! (*it)->slices.empty()) {
|
||||
LayerRegion* other_layerm = *it;
|
||||
const PrintRegion &other_region = other_layerm->region();
|
||||
if (is_perimeter_compatible(this_region, other_region))
|
||||
if (is_perimeter_compatible(*m_object->print(), this_region, other_region))
|
||||
{
|
||||
other_layerm->perimeters.clear();
|
||||
other_layerm->fills.clear();
|
||||
|
||||
@@ -17,6 +17,7 @@ class LayerRegion;
|
||||
using LayerRegionPtrs = std::vector<LayerRegion*>;
|
||||
class PrintRegion;
|
||||
class PrintObject;
|
||||
class Print;
|
||||
|
||||
namespace FillAdaptive {
|
||||
struct Octree;
|
||||
@@ -156,6 +157,10 @@ public:
|
||||
ExPolygons lslices;
|
||||
ExPolygons lslices_extrudable; // BBS: the extrudable part of lslices used for tree support
|
||||
std::vector<BoundingBox> lslices_bboxes;
|
||||
// Orca: for separated infills / per-model centering. Aligned with lslices: for each island, the
|
||||
// full bounding box of the 3D connected body (across all layers) it belongs to. Populated by
|
||||
// PrintObject::infill() only when the feature is used; empty otherwise.
|
||||
std::vector<BoundingBox> lslices_separated_component_bboxes;
|
||||
|
||||
// BBS
|
||||
ExPolygons loverhangs;
|
||||
@@ -186,7 +191,7 @@ public:
|
||||
}
|
||||
|
||||
// Whether two regions can be printed in a continues perimeter
|
||||
static bool is_perimeter_compatible(const PrintRegion& a, const PrintRegion& b);
|
||||
static bool is_perimeter_compatible(const Print& print, const PrintRegion& a, const PrintRegion& b);
|
||||
void make_perimeters();
|
||||
// Phony version of make_fills() without parameters for Perl integration only.
|
||||
void make_fills() { this->make_fills(nullptr, nullptr); }
|
||||
|
||||
+21
-18
@@ -190,8 +190,8 @@ Model Model::read_from_step(const std::string&
|
||||
ImportStepProgressFn stepFn,
|
||||
StepIsUtf8Fn stepIsUtf8Fn,
|
||||
std::function<int(Slic3r::Step&, double&, double&, bool&)> step_mesh_fn,
|
||||
double linear_defletion,
|
||||
double angle_defletion,
|
||||
double linear_deflection,
|
||||
double angle_deflection,
|
||||
bool is_split_compound)
|
||||
{
|
||||
Model model;
|
||||
@@ -204,13 +204,13 @@ Model Model::read_from_step(const std::string&
|
||||
goto _finished;
|
||||
}
|
||||
if (step_mesh_fn) {
|
||||
if (step_mesh_fn(step_file, linear_defletion, angle_defletion, is_split_compound) == -1) {
|
||||
if (step_mesh_fn(step_file, linear_deflection, angle_deflection, is_split_compound) == -1) {
|
||||
status = Step::Step_Status::CANCEL;
|
||||
goto _finished;
|
||||
}
|
||||
}
|
||||
|
||||
status = step_file.mesh(&model, is_cb_cancel, is_split_compound, linear_defletion, angle_defletion);
|
||||
status = step_file.mesh(&model, is_cb_cancel, is_split_compound, linear_deflection, angle_deflection);
|
||||
|
||||
_finished:
|
||||
|
||||
@@ -2975,31 +2975,31 @@ void Model::setPrintSpeedTable(const DynamicPrintConfig& config, const PrintConf
|
||||
//Slic3r::DynamicPrintConfig config = wxGetApp().preset_bundle->full_config();
|
||||
printSpeedMap.maxSpeed = 0;
|
||||
if (config.has("inner_wall_speed")) {
|
||||
printSpeedMap.perimeterSpeed = config.opt_float("inner_wall_speed");
|
||||
printSpeedMap.perimeterSpeed = config.opt_float_nullable("inner_wall_speed", 0);
|
||||
if (printSpeedMap.perimeterSpeed > printSpeedMap.maxSpeed)
|
||||
printSpeedMap.maxSpeed = printSpeedMap.perimeterSpeed;
|
||||
}
|
||||
if (config.has("outer_wall_speed")) {
|
||||
printSpeedMap.externalPerimeterSpeed = config.opt_float("outer_wall_speed");
|
||||
printSpeedMap.externalPerimeterSpeed = config.opt_float_nullable("outer_wall_speed", 0);
|
||||
printSpeedMap.maxSpeed = std::max(printSpeedMap.maxSpeed, printSpeedMap.externalPerimeterSpeed);
|
||||
}
|
||||
if (config.has("sparse_infill_speed")) {
|
||||
printSpeedMap.infillSpeed = config.opt_float("sparse_infill_speed");
|
||||
printSpeedMap.infillSpeed = config.opt_float_nullable("sparse_infill_speed", 0);
|
||||
if (printSpeedMap.infillSpeed > printSpeedMap.maxSpeed)
|
||||
printSpeedMap.maxSpeed = printSpeedMap.infillSpeed;
|
||||
}
|
||||
if (config.has("internal_solid_infill_speed")) {
|
||||
printSpeedMap.solidInfillSpeed = config.opt_float("internal_solid_infill_speed");
|
||||
printSpeedMap.solidInfillSpeed = config.opt_float_nullable("internal_solid_infill_speed", 0);
|
||||
if (printSpeedMap.solidInfillSpeed > printSpeedMap.maxSpeed)
|
||||
printSpeedMap.maxSpeed = printSpeedMap.solidInfillSpeed;
|
||||
}
|
||||
if (config.has("top_surface_speed")) {
|
||||
printSpeedMap.topSolidInfillSpeed = config.opt_float("top_surface_speed");
|
||||
printSpeedMap.topSolidInfillSpeed = config.opt_float_nullable("top_surface_speed", 0);
|
||||
if (printSpeedMap.topSolidInfillSpeed > printSpeedMap.maxSpeed)
|
||||
printSpeedMap.maxSpeed = printSpeedMap.topSolidInfillSpeed;
|
||||
}
|
||||
if (config.has("support_speed")) {
|
||||
printSpeedMap.supportSpeed = config.opt_float("support_speed");
|
||||
printSpeedMap.supportSpeed = config.opt_float_nullable("support_speed", 0);
|
||||
|
||||
if (printSpeedMap.supportSpeed > printSpeedMap.maxSpeed)
|
||||
printSpeedMap.maxSpeed = printSpeedMap.supportSpeed;
|
||||
@@ -3229,25 +3229,28 @@ double Model::findMaxSpeed(const ModelObject* object) {
|
||||
double topSolidInfillSpeedObj = Model::printSpeedMap.topSolidInfillSpeed;
|
||||
double supportSpeedObj = Model::printSpeedMap.supportSpeed;
|
||||
double smallPerimeterSpeedObj = Model::printSpeedMap.smallPerimeterSpeed;
|
||||
double smallSupportPerimeterSpeedObj = Model::printSpeedMap.smallSupportPerimeterSpeed;
|
||||
for (std::string objectKey : objectKeys) {
|
||||
if (objectKey == "inner_wall_speed"){
|
||||
perimeterSpeedObj = object->config.opt_float(objectKey);
|
||||
perimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
|
||||
externalPerimeterSpeedObj = Model::printSpeedMap.externalPerimeterSpeed / Model::printSpeedMap.perimeterSpeed * perimeterSpeedObj;
|
||||
}
|
||||
if (objectKey == "sparse_infill_speed")
|
||||
infillSpeedObj = object->config.opt_float(objectKey);
|
||||
infillSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
|
||||
if (objectKey == "internal_solid_infill_speed")
|
||||
solidInfillSpeedObj = object->config.opt_float(objectKey);
|
||||
solidInfillSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
|
||||
if (objectKey == "top_surface_speed")
|
||||
topSolidInfillSpeedObj = object->config.opt_float(objectKey);
|
||||
topSolidInfillSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
|
||||
if (objectKey == "support_speed")
|
||||
supportSpeedObj = object->config.opt_float(objectKey);
|
||||
supportSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
|
||||
if (objectKey == "outer_wall_speed")
|
||||
externalPerimeterSpeedObj = object->config.opt_float(objectKey);
|
||||
externalPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
|
||||
if (objectKey == "small_perimeter_speed")
|
||||
smallPerimeterSpeedObj = object->config.opt_float(objectKey);
|
||||
smallPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
|
||||
if (objectKey == "small_support_perimeter_speed")
|
||||
smallSupportPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
|
||||
}
|
||||
objMaxSpeed = std::max(perimeterSpeedObj, std::max(externalPerimeterSpeedObj, std::max(infillSpeedObj, std::max(solidInfillSpeedObj, std::max(topSolidInfillSpeedObj, std::max(supportSpeedObj, std::max(smallPerimeterSpeedObj, objMaxSpeed)))))));
|
||||
objMaxSpeed = std::max(perimeterSpeedObj, std::max(externalPerimeterSpeedObj, std::max(infillSpeedObj, std::max(solidInfillSpeedObj, std::max(topSolidInfillSpeedObj, std::max(supportSpeedObj, std::max(smallPerimeterSpeedObj, std::max(smallSupportPerimeterSpeedObj, objMaxSpeed))))))));
|
||||
if (objMaxSpeed <= 0) objMaxSpeed = 250.;
|
||||
return objMaxSpeed;
|
||||
}
|
||||
|
||||
+20
-12
@@ -920,6 +920,13 @@ public:
|
||||
// Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes).
|
||||
int extruder_id() const;
|
||||
|
||||
//Orca: cache clearing procedure to ensure that the shape is positioned accurately when manipulating it
|
||||
void clear_cache() {
|
||||
m_cached_trans_matrix = Transform3d::Identity().inverse(); // get unvelivable matrix
|
||||
m_convex_hull_2d.clear();
|
||||
m_cached_2d_polygon.clear();
|
||||
};
|
||||
|
||||
bool is_splittable() const;
|
||||
|
||||
// BBS
|
||||
@@ -966,34 +973,34 @@ public:
|
||||
static std::string type_to_string(const ModelVolumeType t);
|
||||
|
||||
const Geometry::Transformation& get_transformation() const { return m_transformation; }
|
||||
void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; }
|
||||
void set_transformation(const Transform3d& trafo) { m_transformation.set_matrix(trafo); }
|
||||
void set_transformation(const Geometry::Transformation& transformation) { clear_cache(); m_transformation = transformation; }
|
||||
void set_transformation(const Transform3d& trafo) { clear_cache(); m_transformation.set_matrix(trafo); }
|
||||
|
||||
Vec3d get_offset() const { return m_transformation.get_offset(); }
|
||||
|
||||
double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
|
||||
|
||||
void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); }
|
||||
void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); }
|
||||
void set_offset(const Vec3d& offset) { clear_cache(); m_transformation.set_offset(offset); }
|
||||
void set_offset(Axis axis, double offset) { clear_cache(); m_transformation.set_offset(axis, offset); }
|
||||
|
||||
Vec3d get_rotation() const { return m_transformation.get_rotation(); }
|
||||
double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
|
||||
|
||||
void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); }
|
||||
void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); }
|
||||
void set_rotation(const Vec3d& rotation) { clear_cache(); m_transformation.set_rotation(rotation); }
|
||||
void set_rotation(Axis axis, double rotation) { clear_cache(); m_transformation.set_rotation(axis, rotation); }
|
||||
|
||||
Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
|
||||
double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); }
|
||||
|
||||
void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); }
|
||||
void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); }
|
||||
void set_scaling_factor(const Vec3d& scaling_factor) { clear_cache(); m_transformation.set_scaling_factor(scaling_factor); }
|
||||
void set_scaling_factor(Axis axis, double scaling_factor) {clear_cache(); m_transformation.set_scaling_factor(axis, scaling_factor); }
|
||||
|
||||
Vec3d get_mirror() const { return m_transformation.get_mirror(); }
|
||||
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
|
||||
bool is_left_handed() const { return m_transformation.is_left_handed(); }
|
||||
|
||||
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
|
||||
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
|
||||
void set_mirror(const Vec3d& mirror) { clear_cache(); m_transformation.set_mirror(mirror); }
|
||||
void set_mirror(Axis axis, double mirror) { clear_cache(); m_transformation.set_mirror(axis, mirror); }
|
||||
void convert_from_imperial_units();
|
||||
void convert_from_meters();
|
||||
|
||||
@@ -1467,6 +1474,7 @@ struct GlobalSpeedMap
|
||||
double topSolidInfillSpeed;
|
||||
double supportSpeed;
|
||||
double smallPerimeterSpeed;
|
||||
double smallSupportPerimeterSpeed;
|
||||
double maxSpeed;
|
||||
Polygon bed_poly;
|
||||
};
|
||||
@@ -1592,8 +1600,8 @@ public:
|
||||
ImportStepProgressFn stepFn,
|
||||
StepIsUtf8Fn stepIsUtf8Fn,
|
||||
std::function<int(Slic3r::Step&, double&, double&, bool&)> step_mesh_fn,
|
||||
double linear_defletion,
|
||||
double angle_defletion,
|
||||
double linear_deflection,
|
||||
double angle_deflection,
|
||||
bool is_split_compound);
|
||||
|
||||
//BBS: add part plate related logic
|
||||
|
||||
@@ -1177,6 +1177,18 @@ static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d
|
||||
|
||||
//#define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||
|
||||
double resolve_outer_wall_line_width(const PrintRegionConfig ®ion_config, const PrintObjectConfig &object_config, const PrintConfig &print_config)
|
||||
{
|
||||
// A filament id of 0 underflows, and get_at() then falls back to the first nozzle.
|
||||
const double nozzle_diameter = print_config.nozzle_diameter.get_at(region_config.outer_wall_filament_id - 1);
|
||||
ConfigOptionFloatOrPercent width = region_config.outer_wall_line_width;
|
||||
if (width.value == 0)
|
||||
width = object_config.line_width;
|
||||
if (!width.percent && width.value <= 0.)
|
||||
return Flow::auto_extrusion_width(frExternalPerimeter, float(nozzle_diameter));
|
||||
return width.get_abs_value(nozzle_diameter);
|
||||
}
|
||||
|
||||
// Returns segmentation of top and bottom layers based on painting in segmentation gizmos.
|
||||
static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_layers(const PrintObject &print_object,
|
||||
const std::vector<ExPolygons> &input_expolygons,
|
||||
@@ -1347,12 +1359,11 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
||||
// As this region may split existing regions, we collect statistics over all regions for color_idx == 0.
|
||||
color_idx == 0 || config.outer_wall_filament_id == int(color_idx)) {
|
||||
//BBS: the extrusion line width is outer wall rather than inner wall
|
||||
const double nozzle_diameter = print_object.print()->config().nozzle_diameter.get_at(0);
|
||||
double outer_wall_line_width = config.get_abs_value("outer_wall_line_width", nozzle_diameter);
|
||||
double outer_wall_line_width = resolve_outer_wall_line_width(config, print_object.config(), print_object.print()->config());
|
||||
out.extrusion_width = std::max<float>(out.extrusion_width, outer_wall_line_width);
|
||||
out.top_shell_layers = std::max<int>(out.top_shell_layers, config.top_shell_layers);
|
||||
out.bottom_shell_layers = std::max<int>(out.bottom_shell_layers, config.bottom_shell_layers);
|
||||
out.small_region_threshold = config.gap_infill_speed.value > 0 ?
|
||||
out.small_region_threshold = config.gap_infill_speed.get_at(print_object.print()->get_extruder_id(config.outer_wall_filament_id - 1)) > 0 ?
|
||||
// Gap fill enabled. Enable a single line of 1/2 extrusion width.
|
||||
0.5f * outer_wall_line_width :
|
||||
// Gap fill disabled. Enable two lines slightly overlapping.
|
||||
|
||||
@@ -9,6 +9,9 @@ namespace Slic3r {
|
||||
class ExPolygon;
|
||||
class ModelVolume;
|
||||
class PrintObject;
|
||||
class PrintConfig;
|
||||
class PrintObjectConfig;
|
||||
class PrintRegionConfig;
|
||||
class FacetsAnnotation;
|
||||
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
@@ -52,6 +55,9 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
|
||||
// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo
|
||||
std::vector<std::vector<ExPolygons>> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
|
||||
|
||||
// Effective outer-wall line width for a region, resolved against its own nozzle with PrintRegion::flow's fallback.
|
||||
double resolve_outer_wall_line_width(const PrintRegionConfig ®ion_config, const PrintObjectConfig &object_config, const PrintConfig &print_config);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace boost::polygon {
|
||||
|
||||
@@ -0,0 +1,970 @@
|
||||
#include "MultiNozzleUtils.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "ProjectTask.hpp" // Slic3r::FilamentInfo (StaticNozzleGroupResult / load_nozzle_infos_with_compatibility)
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
// Multi-nozzle support.
|
||||
|
||||
namespace Slic3r { namespace MultiNozzleUtils {
|
||||
// ==================== tool function implementations ====================
|
||||
std::vector<NozzleInfo> build_nozzle_list(std::vector<NozzleGroupInfo> nozzle_groups)
|
||||
{
|
||||
std::vector<NozzleInfo> ret;
|
||||
std::sort(nozzle_groups.begin(), nozzle_groups.end());
|
||||
int nozzle_id = 0;
|
||||
for (auto& group : nozzle_groups) {
|
||||
for (int i = 0; i < group.nozzle_count; ++i) {
|
||||
NozzleInfo tmp;
|
||||
tmp.diameter = group.diameter;
|
||||
tmp.extruder_id = group.extruder_id;
|
||||
tmp.volume_type = group.volume_type;
|
||||
tmp.group_id = nozzle_id++;
|
||||
ret.emplace_back(std::move(tmp));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> build_nozzle_list(double diameter, const std::vector<int>& filament_nozzle_map, const std::vector<int>& filament_volume_map, const std::vector<int>& filament_map)
|
||||
{
|
||||
std::string diameter_str = format_diameter_to_str(diameter);
|
||||
std::map<int, std::vector<int>> nozzle_to_filaments;
|
||||
for(size_t idx = 0; idx < filament_nozzle_map.size(); ++idx){
|
||||
int nozzle_id = filament_nozzle_map[idx];
|
||||
nozzle_to_filaments[nozzle_id].emplace_back(static_cast<int>(idx));
|
||||
}
|
||||
std::vector<NozzleInfo> ret;
|
||||
for(auto& elem : nozzle_to_filaments){
|
||||
int nozzle_id = elem.first;
|
||||
auto& filaments = elem.second;
|
||||
NozzleInfo info;
|
||||
info.diameter = diameter_str;
|
||||
info.group_id = nozzle_id;
|
||||
info.extruder_id = filament_map[filaments.front()];
|
||||
info.volume_type = NozzleVolumeType(filament_volume_map[filaments.front()]);
|
||||
ret.emplace_back(std::move(info));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void normalize_nozzle_map_per_layer(std::vector<std::vector<int>> &layer_filament_nozzle_maps,
|
||||
const std::vector<std::vector<unsigned int>> &layer_filaments)
|
||||
{
|
||||
if (layer_filament_nozzle_maps.empty())
|
||||
return;
|
||||
|
||||
const int total_layers = static_cast<int>(layer_filament_nozzle_maps.size());
|
||||
int filament_count = 0;
|
||||
for (const auto &layer_map : layer_filament_nozzle_maps)
|
||||
filament_count = std::max(filament_count, static_cast<int>(layer_map.size()));
|
||||
|
||||
auto layer_uses_filament = [](const std::vector<unsigned int> &filaments, int filament_id) {
|
||||
return std::find(filaments.begin(), filaments.end(), static_cast<unsigned int>(filament_id)) != filaments.end();
|
||||
};
|
||||
|
||||
std::vector<int> last_used_nozzle(filament_count, -1);
|
||||
std::unordered_map<int, int> first_used_nozzle;
|
||||
std::unordered_map<int, int> first_used_layer;
|
||||
|
||||
// Forward pass: layers that extrude a filament define its nozzle; layers that don't inherit
|
||||
// the nozzle it last used (carry-forward), remembering the first-ever nozzle for the back-fill.
|
||||
for (int layer_id = 0; layer_id < total_layers; ++layer_id) {
|
||||
auto &layer_map = layer_filament_nozzle_maps[layer_id];
|
||||
const auto &used = layer_id < static_cast<int>(layer_filaments.size()) ? layer_filaments[layer_id] : std::vector<unsigned int>();
|
||||
|
||||
for (int filament_id = 0; filament_id < static_cast<int>(layer_map.size()); ++filament_id) {
|
||||
if (layer_uses_filament(used, filament_id)) {
|
||||
last_used_nozzle[filament_id] = layer_map[filament_id];
|
||||
if (first_used_nozzle.count(filament_id) == 0) {
|
||||
first_used_nozzle[filament_id] = layer_map[filament_id];
|
||||
first_used_layer[filament_id] = layer_id;
|
||||
}
|
||||
} else if (last_used_nozzle[filament_id] >= 0) {
|
||||
layer_map[filament_id] = last_used_nozzle[filament_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Back-fill pass: layers before a filament's first use inherit the first nozzle it ever uses.
|
||||
for (int layer_id = 0; layer_id < total_layers; ++layer_id) {
|
||||
auto &layer_map = layer_filament_nozzle_maps[layer_id];
|
||||
for (int filament_id = 0; filament_id < static_cast<int>(layer_map.size()); ++filament_id) {
|
||||
if (first_used_layer.count(filament_id) != 0 && layer_id < first_used_layer[filament_id])
|
||||
layer_map[filament_id] = first_used_nozzle[filament_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== LayeredNozzleGroupResult ====================
|
||||
static bool has_filament_mapped_to_multiple_nozzles(const std::vector<std::vector<int>> &layer_filament_nozzle_maps,
|
||||
const std::vector<unsigned int> &used_filaments)
|
||||
{
|
||||
if (layer_filament_nozzle_maps.empty() || used_filaments.empty())
|
||||
return false;
|
||||
|
||||
for (auto filament_id_u : used_filaments) {
|
||||
int filament_id = static_cast<int>(filament_id_u);
|
||||
std::set<int> nozzle_ids;
|
||||
|
||||
for (size_t layer_id = 0; layer_id < layer_filament_nozzle_maps.size(); ++layer_id) {
|
||||
const auto &map = layer_filament_nozzle_maps[layer_id];
|
||||
if (filament_id < 0 || filament_id >= static_cast<int>(map.size()))
|
||||
continue;
|
||||
|
||||
int nozzle_id = map[filament_id];
|
||||
if (nozzle_id < 0)
|
||||
continue;
|
||||
|
||||
nozzle_ids.insert(nozzle_id);
|
||||
if (nozzle_ids.size() > 1)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<LayeredNozzleGroupResult> LayeredNozzleGroupResult::create(
|
||||
const std::vector<int>& filament_nozzle_map,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<unsigned int>& used_filaments)
|
||||
{
|
||||
if (filament_nozzle_map.empty() || nozzle_list.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
LayeredNozzleGroupResult result(false);
|
||||
result._default_filament_nozzle_map = filament_nozzle_map;
|
||||
result._nozzle_list = nozzle_list;
|
||||
result._used_filaments = used_filaments;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<LayeredNozzleGroupResult> LayeredNozzleGroupResult::create(
|
||||
const std::vector<std::vector<int>>& layer_filament_nozzle_maps,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filament_sequences)
|
||||
{
|
||||
if (layer_filament_nozzle_maps.empty() || nozzle_list.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool support_dynamic_nozzle_map = has_filament_mapped_to_multiple_nozzles(layer_filament_nozzle_maps, used_filaments);
|
||||
LayeredNozzleGroupResult result(support_dynamic_nozzle_map);
|
||||
result._layer_filament_nozzle_maps = layer_filament_nozzle_maps;
|
||||
result._layer_filament_sequences = layer_filament_sequences;
|
||||
result._nozzle_list = nozzle_list;
|
||||
result._used_filaments = used_filaments;
|
||||
|
||||
if (!layer_filament_nozzle_maps.empty()) {
|
||||
result._default_filament_nozzle_map = layer_filament_nozzle_maps[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<LayeredNozzleGroupResult> LayeredNozzleGroupResult::create(
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<int>& filament_map,
|
||||
const std::vector<int>& filament_volume_map,
|
||||
const std::vector<int>& filament_nozzle_map,
|
||||
const std::vector<std::map<NozzleVolumeType, int>> &nozzle_count,
|
||||
float diameter)
|
||||
{
|
||||
std::vector<NozzleGroupInfo> nozzle_groups;
|
||||
for (size_t extruder_id = 0; extruder_id < nozzle_count.size(); ++extruder_id) {
|
||||
for (auto elem : nozzle_count[extruder_id]) {
|
||||
NozzleGroupInfo group_info;
|
||||
group_info.diameter = format_diameter_to_str(diameter);
|
||||
group_info.volume_type = elem.first;
|
||||
group_info.nozzle_count = elem.second;
|
||||
group_info.extruder_id = static_cast<int>(extruder_id);
|
||||
nozzle_groups.emplace_back(group_info);
|
||||
}
|
||||
}
|
||||
|
||||
auto nozzle_list = build_nozzle_list(nozzle_groups);
|
||||
std::vector<bool> used_nozzle(nozzle_list.size(), false);
|
||||
std::map<int, int> input_nozzle_id_to_output;
|
||||
std::vector<int> output_nozzle_map(filament_nozzle_map.size(), 0);
|
||||
|
||||
for (auto filament_idx : used_filaments) {
|
||||
NozzleVolumeType req_type = NozzleVolumeType(filament_volume_map[filament_idx]);
|
||||
int req_extruder = filament_map[filament_idx];
|
||||
int input_nozzle_idx = filament_nozzle_map[filament_idx];
|
||||
|
||||
if (input_nozzle_id_to_output.find(input_nozzle_idx) != input_nozzle_id_to_output.end()) {
|
||||
output_nozzle_map[filament_idx] = input_nozzle_id_to_output[input_nozzle_idx];
|
||||
continue;
|
||||
}
|
||||
|
||||
int output_nozzle_idx = -1;
|
||||
for (size_t nozzle_idx = 0; nozzle_idx < nozzle_list.size(); ++nozzle_idx) {
|
||||
if (used_nozzle[nozzle_idx]) continue;
|
||||
|
||||
auto &nozzle_info = nozzle_list[nozzle_idx];
|
||||
if (!(nozzle_info.extruder_id == req_extruder && nozzle_info.volume_type == req_type)) continue;
|
||||
|
||||
output_nozzle_idx = static_cast<int>(nozzle_idx);
|
||||
input_nozzle_id_to_output[input_nozzle_idx] = output_nozzle_idx;
|
||||
used_nozzle[nozzle_idx] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (output_nozzle_idx == -1) { return std::nullopt; }
|
||||
output_nozzle_map[filament_idx] = output_nozzle_idx;
|
||||
}
|
||||
|
||||
return create(output_nozzle_map, nozzle_list, used_filaments);
|
||||
}
|
||||
|
||||
bool LayeredNozzleGroupResult::are_filaments_same_extruder(int filament_id1, int filament_id2, int layer_id) const
|
||||
{
|
||||
std::optional<NozzleInfo> nozzle_info1 = get_nozzle_for_filament(filament_id1, layer_id);
|
||||
std::optional<NozzleInfo> nozzle_info2 = get_nozzle_for_filament(filament_id2, layer_id);
|
||||
|
||||
if (!nozzle_info1 || !nozzle_info2) return false;
|
||||
|
||||
return nozzle_info1->extruder_id == nozzle_info2->extruder_id;
|
||||
}
|
||||
|
||||
bool LayeredNozzleGroupResult::are_filaments_same_nozzle(int filament_id1, int filament_id2, int layer_id) const
|
||||
{
|
||||
std::optional<NozzleInfo> nozzle_info1 = get_nozzle_for_filament(filament_id1, layer_id);
|
||||
std::optional<NozzleInfo> nozzle_info2 = get_nozzle_for_filament(filament_id2, layer_id);
|
||||
if (!nozzle_info1 || !nozzle_info2) return false;
|
||||
|
||||
return nozzle_info1->group_id == nozzle_info2->group_id;
|
||||
}
|
||||
|
||||
int LayeredNozzleGroupResult::get_extruder_count() const
|
||||
{
|
||||
std::set<int> extruder_ids;
|
||||
for (const auto &nozzle : _nozzle_list) { extruder_ids.insert(nozzle.extruder_id); }
|
||||
return static_cast<int>(extruder_ids.size());
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> LayeredNozzleGroupResult::get_used_nozzles_in_extruder(int target_extruder_id) const
|
||||
{
|
||||
return get_used_nozzles_in_extruder(target_extruder_id, -1);
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> LayeredNozzleGroupResult::get_used_nozzles_in_extruder(int target_extruder_id, int layer_id) const
|
||||
{
|
||||
std::set<int> nozzle_ids;
|
||||
std::vector<NozzleInfo> result;
|
||||
|
||||
std::vector<unsigned int> target_filaments = get_used_filaments(layer_id);
|
||||
|
||||
for (unsigned int filament_id : target_filaments) {
|
||||
if (layer_id != -1) {
|
||||
auto nozzle_opt = get_nozzle_for_filament(static_cast<int>(filament_id), layer_id);
|
||||
if (nozzle_opt) {
|
||||
if (target_extruder_id == -1 || nozzle_opt->extruder_id == target_extruder_id) { nozzle_ids.insert(nozzle_opt->group_id); }
|
||||
}
|
||||
} else {
|
||||
auto nozzles = get_nozzles_for_filament(static_cast<int>(filament_id));
|
||||
for (const auto &nozzle : nozzles) {
|
||||
if (target_extruder_id == -1 || nozzle.extruder_id == target_extruder_id) { nozzle_ids.insert(nozzle.group_id); }
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int nozzle_id : nozzle_ids) {
|
||||
if (nozzle_id >= 0 && nozzle_id < static_cast<int>(_nozzle_list.size())) { result.push_back(_nozzle_list[nozzle_id]); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int> LayeredNozzleGroupResult::get_used_extruders() const
|
||||
{
|
||||
return get_used_extruders(-1);
|
||||
}
|
||||
|
||||
std::vector<int> LayeredNozzleGroupResult::get_used_extruders(int layer_id) const
|
||||
{
|
||||
std::set<int> used_extruders;
|
||||
// used filaments on the given layer (or globally)
|
||||
std::vector<unsigned int> target_filaments = get_used_filaments(layer_id);
|
||||
for (auto filament_id : target_filaments) {
|
||||
if (layer_id != -1) {
|
||||
// single-layer: nozzle used by this filament on this layer
|
||||
auto nozzle_opt = get_nozzle_for_filament(static_cast<int>(filament_id), layer_id);
|
||||
if (nozzle_opt) { used_extruders.insert(nozzle_opt->extruder_id); }
|
||||
} else {
|
||||
// global: every nozzle this filament uses across all layers
|
||||
auto nozzles = get_nozzles_for_filament(static_cast<int>(filament_id));
|
||||
for (const auto &nozzle : nozzles) { used_extruders.insert(nozzle.extruder_id); }
|
||||
}
|
||||
}
|
||||
return std::vector<int>(used_extruders.begin(), used_extruders.end());
|
||||
}
|
||||
|
||||
std::vector<int> LayeredNozzleGroupResult::get_extruder_map(bool zero_based, int layer_id) const
|
||||
{
|
||||
const std::vector<int> &filament_nozzle_map = get_layer_filament_nozzle_map(layer_id);
|
||||
std::vector<int> extruder_map(filament_nozzle_map.size());
|
||||
for (size_t idx = 0; idx < filament_nozzle_map.size(); ++idx) {
|
||||
int nozzle_id = filament_nozzle_map[idx];
|
||||
if (nozzle_id >= 0 && nozzle_id < static_cast<int>(_nozzle_list.size())) {
|
||||
extruder_map[idx] = _nozzle_list[nozzle_id].extruder_id;
|
||||
} else {
|
||||
extruder_map[idx] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (zero_based) return extruder_map;
|
||||
|
||||
auto new_filament_map = extruder_map;
|
||||
std::transform(new_filament_map.begin(), new_filament_map.end(), new_filament_map.begin(), [](int val) { return val + 1; });
|
||||
return new_filament_map;
|
||||
}
|
||||
|
||||
std::vector<int> LayeredNozzleGroupResult::get_nozzle_map(int layer_id) const
|
||||
{
|
||||
const std::vector<int> &filament_nozzle_map = get_layer_filament_nozzle_map(layer_id);
|
||||
std::vector<int> nozzle_map(filament_nozzle_map.size());
|
||||
for (size_t idx = 0; idx < filament_nozzle_map.size(); ++idx) {
|
||||
int nozzle_id = filament_nozzle_map[idx];
|
||||
if (nozzle_id >= 0 && nozzle_id < static_cast<int>(_nozzle_list.size())) {
|
||||
nozzle_map[idx] = _nozzle_list[nozzle_id].group_id;
|
||||
} else {
|
||||
nozzle_map[idx] = -1;
|
||||
}
|
||||
}
|
||||
return nozzle_map;
|
||||
}
|
||||
|
||||
std::vector<int> LayeredNozzleGroupResult::get_volume_map(int layer_id) const
|
||||
{
|
||||
const std::vector<int> &filament_nozzle_map = get_layer_filament_nozzle_map(layer_id);
|
||||
std::vector<int> volume_map(filament_nozzle_map.size());
|
||||
for (size_t idx = 0; idx < filament_nozzle_map.size(); ++idx) {
|
||||
int nozzle_id = filament_nozzle_map[idx];
|
||||
if (nozzle_id >= 0 && nozzle_id < static_cast<int>(_nozzle_list.size())) {
|
||||
volume_map[idx] = _nozzle_list[nozzle_id].volume_type;
|
||||
} else {
|
||||
volume_map[idx] = -1;
|
||||
}
|
||||
}
|
||||
return volume_map;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> LayeredNozzleGroupResult::get_used_filaments(int layer_id) const
|
||||
{
|
||||
if (layer_id < 0) { return _used_filaments; }
|
||||
if (layer_id >= static_cast<int>(_layer_filament_nozzle_maps.size())) { return _used_filaments; }
|
||||
|
||||
if (!_layer_filament_sequences.empty() && layer_id < static_cast<int>(_layer_filament_sequences.size())) {
|
||||
return _layer_filament_sequences[layer_id];
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<NozzleInfo> LayeredNozzleGroupResult::get_nozzle_for_filament(int filament_id, int layer_id) const
|
||||
{
|
||||
const std::vector<int> &filament_nozzle_map = get_layer_filament_nozzle_map(layer_id);
|
||||
|
||||
if (filament_id < 0 || filament_id >= static_cast<int>(filament_nozzle_map.size())) { return std::nullopt; }
|
||||
|
||||
int nozzle_id = filament_nozzle_map[filament_id];
|
||||
return get_nozzle_from_id(nozzle_id);
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> LayeredNozzleGroupResult::get_nozzles_for_filament(int filament_id) const
|
||||
{
|
||||
std::set<int> nozzle_ids;
|
||||
|
||||
if (!support_dynamic_nozzle_map) {
|
||||
if (filament_id >= 0 && filament_id < static_cast<int>(_default_filament_nozzle_map.size())) {
|
||||
nozzle_ids.insert(_default_filament_nozzle_map[filament_id]);
|
||||
}
|
||||
} else {
|
||||
int start_layer = 0;
|
||||
int end_layer = static_cast<int>(_layer_filament_nozzle_maps.size());
|
||||
|
||||
for (int i = start_layer; i < end_layer; ++i) {
|
||||
const auto &map = _layer_filament_nozzle_maps[i];
|
||||
if (filament_id >= 0 && filament_id < static_cast<int>(map.size())) {
|
||||
nozzle_ids.insert(map[filament_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> result;
|
||||
for (int id : nozzle_ids) {
|
||||
if (id >= 0 && id < static_cast<int>(_nozzle_list.size())) { result.push_back(_nozzle_list[id]); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<NozzleInfo> LayeredNozzleGroupResult::get_first_nozzle_for_filament(int filament_id) const
|
||||
{
|
||||
if (filament_id < 0) return std::nullopt;
|
||||
|
||||
if (!support_dynamic_nozzle_map) {
|
||||
if (filament_id >= static_cast<int>(_default_filament_nozzle_map.size())) return std::nullopt;
|
||||
return get_nozzle_from_id(_default_filament_nozzle_map[filament_id]);
|
||||
}
|
||||
|
||||
for (size_t layer = 0; layer < _layer_filament_nozzle_maps.size(); ++layer) {
|
||||
auto layer_used_filaments = get_used_filaments(layer);
|
||||
if (std::find(layer_used_filaments.begin(), layer_used_filaments.end(), static_cast<unsigned int>(filament_id)) == layer_used_filaments.end()){
|
||||
continue;
|
||||
}
|
||||
const auto &map = _layer_filament_nozzle_maps[layer];
|
||||
if (filament_id >= 0 && filament_id < static_cast<int>(map.size())) {
|
||||
int nozzle_id = map[filament_id];
|
||||
auto nozzle = get_nozzle_from_id(nozzle_id);
|
||||
if (nozzle) return nozzle;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<NozzleInfo> LayeredNozzleGroupResult::get_nozzle_from_id(int nozzle_id) const
|
||||
{
|
||||
if (nozzle_id < 0 || nozzle_id >= static_cast<int>(_nozzle_list.size())) { return std::nullopt; }
|
||||
return _nozzle_list[nozzle_id];
|
||||
}
|
||||
|
||||
int LayeredNozzleGroupResult::get_extruder_id(int filament_id, int layer_id) const
|
||||
{
|
||||
auto nozzle_info = get_nozzle_for_filament(filament_id, layer_id);
|
||||
return nozzle_info ? nozzle_info->extruder_id : -1;
|
||||
}
|
||||
|
||||
int LayeredNozzleGroupResult::get_nozzle_id(int filament_id, int layer_id) const
|
||||
{
|
||||
auto nozzle_info = get_nozzle_for_filament(filament_id, layer_id);
|
||||
return nozzle_info ? nozzle_info->group_id : -1;
|
||||
}
|
||||
|
||||
const std::vector<int> &LayeredNozzleGroupResult::get_layer_filament_nozzle_map(int layer_id) const
|
||||
{
|
||||
if (layer_id >= 0 && layer_id < static_cast<int>(_layer_filament_nozzle_maps.size())) { return _layer_filament_nozzle_maps[layer_id]; }
|
||||
return _default_filament_nozzle_map;
|
||||
}
|
||||
|
||||
// ==================== filament-change-time model ====================
|
||||
FilamentChangeSimResult simulate_filament_change_time(
|
||||
const std::vector<int>& logical_filaments,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<int>& filament_change_seq,
|
||||
const std::vector<int>& nozzle_change_seq,
|
||||
const std::vector<int>& group_of_filament,
|
||||
const FilamentChangeTimeParams& time_params,
|
||||
const std::vector<bool>& ams_preload_enabled,
|
||||
bool calc_sliced_time)
|
||||
{
|
||||
FilamentChangeSimResult result;
|
||||
if (logical_filaments.empty() || nozzle_list.empty() || filament_change_seq.empty() || nozzle_change_seq.empty())
|
||||
return result;
|
||||
|
||||
// Re-map the parameter semantics:
|
||||
// standard = AMS -> selector -> extruder (full path), selector = selector -> extruder (short path)
|
||||
// so AMS -> selector = standard - selector
|
||||
const float load_ams_to_selector = time_params.standard_load_time - time_params.selector_load_time;
|
||||
const float unload_ams_to_selector = time_params.standard_unload_time - time_params.selector_unload_time;
|
||||
const float load_selector_to_ext = time_params.selector_load_time;
|
||||
const float unload_ext_to_selector = time_params.selector_unload_time;
|
||||
|
||||
// nozzle_id -> extruder_id
|
||||
std::unordered_map<int, int> nozzle_to_extruder;
|
||||
nozzle_to_extruder.reserve(nozzle_list.size());
|
||||
for (const auto& nozzle : nozzle_list)
|
||||
nozzle_to_extruder[nozzle.group_id] = nozzle.extruder_id;
|
||||
|
||||
// filament_id -> AMS group
|
||||
std::unordered_map<int, int> filament_to_group;
|
||||
filament_to_group.reserve(logical_filaments.size());
|
||||
for (size_t i = 0; i < logical_filaments.size(); ++i)
|
||||
filament_to_group[logical_filaments[i]] = group_of_filament[i];
|
||||
|
||||
const auto get_group = [&](int filament_id) -> int {
|
||||
auto it = filament_to_group.find(filament_id);
|
||||
return it != filament_to_group.end() ? it->second : -1;
|
||||
};
|
||||
|
||||
const auto is_preload_enabled = [&](int group_id) -> bool {
|
||||
if (group_id < 0 || group_id >= static_cast<int>(ams_preload_enabled.size()))
|
||||
return false;
|
||||
return ams_preload_enabled[group_id];
|
||||
};
|
||||
|
||||
// Filament location states
|
||||
enum class Location { IN_AMS, IN_SELECTOR, IN_EXTRUDER };
|
||||
std::unordered_map<int, Location> filament_location; // filament_id -> current location
|
||||
std::unordered_map<int, int> filament_extruder; // filament_id -> extruder it sits in (only valid when IN_EXTRUDER)
|
||||
std::unordered_map<int, int> extruder_filament; // extruder_id -> currently loaded filament
|
||||
// group_id -> filaments currently occupying that AMS channel (IN_SELECTOR or IN_EXTRUDER)
|
||||
std::unordered_map<int, std::unordered_set<int>> ams_group_occupied;
|
||||
|
||||
filament_location.reserve(logical_filaments.size());
|
||||
filament_extruder.reserve(logical_filaments.size());
|
||||
|
||||
// Initial state: every filament is in the AMS, every extruder is empty
|
||||
for (int f : logical_filaments)
|
||||
filament_location[f] = Location::IN_AMS;
|
||||
|
||||
// Slicer-estimate simulator: use NozzleStatusRecorder to track what each nozzle/extruder holds during slicing
|
||||
NozzleStatusRecorder sliced_recorder;
|
||||
|
||||
const size_t seq_len = std::min(filament_change_seq.size(), nozzle_change_seq.size());
|
||||
double actual_time = 0.0;
|
||||
double sliced_time = 0.0;
|
||||
|
||||
for (size_t i = 0; i < seq_len; ++i) {
|
||||
int B = filament_change_seq[i];
|
||||
int nozzle_id = nozzle_change_seq[i];
|
||||
|
||||
auto nozzle_iter = nozzle_to_extruder.find(nozzle_id);
|
||||
if (nozzle_iter == nozzle_to_extruder.end()) continue;
|
||||
|
||||
int E = nozzle_iter->second; // target extruder
|
||||
|
||||
// Step 0: compute the slicer-estimated time
|
||||
// Slicer estimate: simulate the slicer's view (no selector awareness);
|
||||
// count a load/unload when nozzle_in_extruder_change || filament_in_nozzle_change
|
||||
if (calc_sliced_time) {
|
||||
int old_nozzle_in_E = sliced_recorder.get_nozzle_in_extruder(E);
|
||||
int old_filament_in_nozzle = sliced_recorder.get_filament_in_nozzle(nozzle_id);
|
||||
int old_filament_in_ext = sliced_recorder.get_filament_in_nozzle(old_nozzle_in_E);
|
||||
|
||||
bool nozzle_change = (old_nozzle_in_E != nozzle_id);
|
||||
bool filament_change = (old_filament_in_nozzle != B);
|
||||
|
||||
if (nozzle_change || filament_change) {
|
||||
if (old_filament_in_ext != -1)
|
||||
sliced_time += time_params.standard_unload_time;
|
||||
sliced_time += time_params.standard_load_time;
|
||||
}
|
||||
sliced_recorder.set_nozzle_status(nozzle_id, B, E);
|
||||
}
|
||||
|
||||
// Step 1: find the filament A currently loaded in the target extruder E
|
||||
int A = -1;
|
||||
{
|
||||
auto it = extruder_filament.find(E);
|
||||
if (it != extruder_filament.end())
|
||||
A = it->second;
|
||||
}
|
||||
|
||||
int group_B = get_group(B);
|
||||
int group_A = (A != -1) ? get_group(A) : -1;
|
||||
|
||||
// Step 2: clear B's AMS-channel occupancy
|
||||
auto group_it = ams_group_occupied.find(group_B);
|
||||
if (group_it != ams_group_occupied.end()) {
|
||||
for (int X : group_it->second) {
|
||||
if (X == B) continue;
|
||||
// X shares B's AMS channel, retreat it to the AMS to make way
|
||||
Location loc_X = filament_location[X];
|
||||
if (loc_X == Location::IN_EXTRUDER) {
|
||||
actual_time += unload_ext_to_selector + unload_ams_to_selector;
|
||||
int E2 = filament_extruder[X];
|
||||
extruder_filament.erase(E2);
|
||||
filament_extruder.erase(X);
|
||||
} else if (loc_X == Location::IN_SELECTOR) {
|
||||
actual_time += unload_ams_to_selector;
|
||||
}
|
||||
filament_location[X] = Location::IN_AMS;
|
||||
}
|
||||
group_it->second.clear();
|
||||
}
|
||||
|
||||
// Step 3: A exits E (while A is still in the extruder)
|
||||
// Step 3.5: pre-load B (in parallel with Step 3)
|
||||
// actual time = max(Step 3, Step 3.5)
|
||||
bool step3_executed = false;
|
||||
float step3_time = 0.0f;
|
||||
if (A != -1 && A != B && filament_location[A] == Location::IN_EXTRUDER) {
|
||||
if (is_preload_enabled(group_A) && group_A != group_B) {
|
||||
step3_time = unload_ext_to_selector;
|
||||
filament_location[A] = Location::IN_SELECTOR;
|
||||
} else {
|
||||
step3_time = unload_ext_to_selector + unload_ams_to_selector;
|
||||
filament_location[A] = Location::IN_AMS;
|
||||
ams_group_occupied[group_A].erase(A);
|
||||
}
|
||||
extruder_filament.erase(E);
|
||||
filament_extruder.erase(A);
|
||||
step3_executed = true;
|
||||
}
|
||||
|
||||
float step3_5_time = 0.0f;
|
||||
if (step3_executed &&
|
||||
filament_location[B] == Location::IN_AMS &&
|
||||
group_A != group_B &&
|
||||
is_preload_enabled(group_B)) {
|
||||
step3_5_time = load_ams_to_selector;
|
||||
filament_location[B] = Location::IN_SELECTOR;
|
||||
ams_group_occupied[group_B].insert(B);
|
||||
}
|
||||
|
||||
actual_time += std::max(step3_time, step3_5_time);
|
||||
|
||||
// Step 4: push B into E
|
||||
// Step 6: pre-load the next filament C (in parallel with Step 4)
|
||||
// actual time = max(Step 4, Step 6)
|
||||
float step4_time = 0.0f;
|
||||
Location loc_B = filament_location[B];
|
||||
if (loc_B == Location::IN_AMS) {
|
||||
step4_time = load_ams_to_selector + load_selector_to_ext;
|
||||
} else if (loc_B == Location::IN_SELECTOR) {
|
||||
step4_time = load_selector_to_ext;
|
||||
}
|
||||
|
||||
// Step 5: update state
|
||||
extruder_filament[E] = B;
|
||||
filament_location[B] = Location::IN_EXTRUDER;
|
||||
filament_extruder[B] = E;
|
||||
ams_group_occupied[group_B].insert(B);
|
||||
|
||||
float step6_time = 0.0f;
|
||||
if (i + 1 < seq_len) {
|
||||
int C = filament_change_seq[i + 1];
|
||||
int group_C = get_group(C);
|
||||
if (filament_location[C] == Location::IN_AMS &&
|
||||
group_C != group_B &&
|
||||
is_preload_enabled(group_C) &&
|
||||
ams_group_occupied[group_C].empty()) {
|
||||
step6_time = load_ams_to_selector;
|
||||
filament_location[C] = Location::IN_SELECTOR;
|
||||
ams_group_occupied[group_C].insert(C);
|
||||
}
|
||||
}
|
||||
|
||||
actual_time += std::max(step4_time, step6_time);
|
||||
}
|
||||
|
||||
result.actual_time = actual_time;
|
||||
result.sliced_time = sliced_time;
|
||||
return result;
|
||||
}
|
||||
|
||||
// ==================== NozzleStatusRecorder implementation ====================
|
||||
|
||||
bool NozzleStatusRecorder::is_nozzle_empty(int nozzle_id) const
|
||||
{
|
||||
auto iter = nozzle_filament_status.find(nozzle_id);
|
||||
if (iter == nozzle_filament_status.end()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int NozzleStatusRecorder::get_filament_in_nozzle(int nozzle_id) const
|
||||
{
|
||||
auto iter = nozzle_filament_status.find(nozzle_id);
|
||||
if (iter == nozzle_filament_status.end()) return -1;
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
int NozzleStatusRecorder::get_nozzle_in_extruder(int extruder_id) const
|
||||
{
|
||||
auto iter = extruder_nozzle_status.find(extruder_id);
|
||||
if (iter == extruder_nozzle_status.end()) return -1;
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void NozzleStatusRecorder::set_nozzle_status(int nozzle_id, int filament_id, int extruder_id)
|
||||
{
|
||||
nozzle_filament_status[nozzle_id] = filament_id;
|
||||
if (extruder_id != -1) {
|
||||
extruder_nozzle_status[extruder_id] = nozzle_id;
|
||||
}
|
||||
}
|
||||
|
||||
void NozzleStatusRecorder::clear_nozzle_status(int nozzle_id)
|
||||
{
|
||||
auto iter = nozzle_filament_status.find(nozzle_id);
|
||||
if (iter == nozzle_filament_status.end()) return;
|
||||
nozzle_filament_status.erase(iter);
|
||||
}
|
||||
|
||||
int LayeredNozzleGroupResult::estimate_seq_flush_weight(const std::vector<std::vector<std::vector<float>>>& flush_matrix, const std::vector<int>& filament_change_seq) const
|
||||
{
|
||||
auto get_weight_from_volume = [](float volume){
|
||||
return static_cast<int>(volume * 1.26 * 0.01);
|
||||
};
|
||||
|
||||
float total_flush_volume = 0;
|
||||
NozzleStatusRecorder recorder;
|
||||
for(auto filament: filament_change_seq){
|
||||
auto nozzle = get_nozzle_for_filament(filament, -1);
|
||||
if(!nozzle)
|
||||
continue;
|
||||
|
||||
int extruder_id = nozzle->extruder_id;
|
||||
int nozzle_id = nozzle->group_id;
|
||||
int last_filament = recorder.get_filament_in_nozzle(nozzle_id);
|
||||
|
||||
if(last_filament!= -1 && last_filament != filament){
|
||||
// bounds check to avoid out-of-range access
|
||||
if (extruder_id >= 0 && extruder_id < static_cast<int>(flush_matrix.size()) &&
|
||||
last_filament >= 0 && last_filament < static_cast<int>(flush_matrix[extruder_id].size()) &&
|
||||
filament >= 0 && filament < static_cast<int>(flush_matrix[extruder_id][last_filament].size())) {
|
||||
float flush_volume = flush_matrix[extruder_id][last_filament][filament];
|
||||
total_flush_volume += flush_volume;
|
||||
}
|
||||
}
|
||||
recorder.set_nozzle_status(nozzle_id, filament);
|
||||
}
|
||||
|
||||
return get_weight_from_volume(total_flush_volume);
|
||||
}
|
||||
|
||||
// ==================== StaticNozzleGroupResult ====================
|
||||
|
||||
std::optional<StaticNozzleGroupResult> StaticNozzleGroupResult::create(
|
||||
const std::vector<FilamentInfo>& filaments_info,
|
||||
const std::vector<NozzleInfo>& nozzles_info,
|
||||
const std::vector<int>& filament_change_seq,
|
||||
const std::vector<int>& nozzle_change_seq,
|
||||
bool support_dynamic_nozzle_map)
|
||||
{
|
||||
if (filaments_info.empty() || nozzles_info.empty()) return std::nullopt;
|
||||
|
||||
std::map<int, NozzleInfo> nozzle_list_map;
|
||||
std::map<int, std::set<int>> filament_to_nozzles;
|
||||
|
||||
for (auto nozzle_info : nozzles_info)
|
||||
nozzle_list_map[nozzle_info.group_id] = nozzle_info;
|
||||
|
||||
for (auto filament_info : filaments_info) {
|
||||
auto fil_id = filament_info.id;
|
||||
auto nozzles_id = filament_info.group_id;
|
||||
std::set<int> nozzles_set(nozzles_id.begin(), nozzles_id.end());
|
||||
// Backward compat with older (single-nozzle) gcode.3mf: filament has no group_id, avoid an empty map.
|
||||
if (nozzles_set.empty()) {
|
||||
for (const auto& nozzle_entry : nozzle_list_map)
|
||||
nozzles_set.insert(nozzle_entry.first);
|
||||
}
|
||||
filament_to_nozzles[fil_id] = nozzles_set;
|
||||
}
|
||||
|
||||
StaticNozzleGroupResult result(support_dynamic_nozzle_map);
|
||||
result._filament_to_nozzles = filament_to_nozzles;
|
||||
result._nozzle_list_map = nozzle_list_map;
|
||||
result._filament_change_seq = filament_change_seq;
|
||||
result._nozzle_change_seq = nozzle_change_seq;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<NozzleInfo> StaticNozzleGroupResult::get_nozzle_from_id(int nozzle_id) const
|
||||
{
|
||||
auto iter = _nozzle_list_map.find(nozzle_id);
|
||||
if (iter == _nozzle_list_map.end()) { return std::nullopt; }
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
int StaticNozzleGroupResult::get_extruder_count() const
|
||||
{
|
||||
std::set<int> extruder_ids;
|
||||
for (const auto &elem : _nozzle_list_map) { extruder_ids.insert(elem.second.extruder_id); }
|
||||
return static_cast<int>(extruder_ids.size());
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> StaticNozzleGroupResult::get_used_nozzles_in_extruder(int target_extruder_id) const
|
||||
{
|
||||
std::vector<NozzleInfo> result;
|
||||
for (const auto &elem : _nozzle_list_map) {
|
||||
const auto &nozzle = elem.second;
|
||||
if (target_extruder_id == -1 || nozzle.extruder_id == target_extruder_id) {
|
||||
result.push_back(nozzle);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int> StaticNozzleGroupResult::get_used_extruders() const
|
||||
{
|
||||
std::set<int> used_extruders;
|
||||
for (const auto &elem : _nozzle_list_map) { used_extruders.insert(elem.second.extruder_id); }
|
||||
return std::vector<int>(used_extruders.begin(), used_extruders.end());
|
||||
}
|
||||
|
||||
std::vector<unsigned int> StaticNozzleGroupResult::get_used_filaments() const
|
||||
{
|
||||
std::vector<unsigned int> used_filaments;
|
||||
used_filaments.reserve(_filament_to_nozzles.size());
|
||||
for (const auto &elem : _filament_to_nozzles) {
|
||||
if (elem.first >= 0) {
|
||||
used_filaments.push_back(static_cast<unsigned int>(elem.first));
|
||||
}
|
||||
}
|
||||
return used_filaments;
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> StaticNozzleGroupResult::get_nozzles_for_filament(int filament_id) const
|
||||
{
|
||||
auto iter = _filament_to_nozzles.find(filament_id);
|
||||
if (iter == _filament_to_nozzles.end()) { return std::vector<NozzleInfo>(); }
|
||||
|
||||
std::vector<NozzleInfo> result;
|
||||
for (int nozzle_id : iter->second) {
|
||||
auto nozzle_iter = _nozzle_list_map.find(nozzle_id);
|
||||
if (nozzle_iter != _nozzle_list_map.end()) {
|
||||
result.push_back(nozzle_iter->second);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<NozzleInfo> StaticNozzleGroupResult::get_first_nozzle_for_filament(int filament_id) const
|
||||
{
|
||||
if (filament_id < 0) return std::nullopt;
|
||||
|
||||
if (!_filament_change_seq.empty() && _filament_change_seq.size() == _nozzle_change_seq.size()) {
|
||||
for (size_t idx = 0; idx < _filament_change_seq.size(); ++idx) {
|
||||
if (_filament_change_seq[idx] == filament_id) {
|
||||
int nozzle_id = _nozzle_change_seq[idx];
|
||||
auto nozzle = get_nozzle_from_id(nozzle_id);
|
||||
if (nozzle) return nozzle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto iter = _filament_to_nozzles.find(filament_id);
|
||||
if (iter == _filament_to_nozzles.end()) return std::nullopt;
|
||||
|
||||
for (int nozzle_id : iter->second) {
|
||||
auto nozzle = get_nozzle_from_id(nozzle_id);
|
||||
if (nozzle) return nozzle;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// ==================== serialization ====================
|
||||
|
||||
std::string NozzleInfo::serialize() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "id=\"" << group_id << "\" "
|
||||
<< "extruder_id=\"" << extruder_id + 1 << "\" "
|
||||
<< "nozzle_diameter=\"" << diameter << "\" "
|
||||
<< "volume_type=\"" << get_nozzle_volume_type_string(volume_type) << "\"";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string NozzleGroupInfo::serialize() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << extruder_id << "-"
|
||||
<< std::setprecision(2) << diameter << "-"
|
||||
<< get_nozzle_volume_type_string(volume_type) << "-"
|
||||
<< nozzle_count;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::optional<NozzleGroupInfo> NozzleGroupInfo::deserialize(const std::string &str)
|
||||
{
|
||||
std::istringstream iss(str);
|
||||
std::string token;
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
while (std::getline(iss, token, '-')) { tokens.push_back(token); }
|
||||
|
||||
if (tokens.size() != 4) { return std::nullopt; }
|
||||
|
||||
try {
|
||||
int extruder_id = std::stoi(tokens[0]);
|
||||
std::string diameter = tokens[1];
|
||||
NozzleVolumeType volume_type = NozzleVolumeType(ConfigOptionEnum<NozzleVolumeType>::get_enum_values().at(tokens[2]));
|
||||
int nozzle_count = std::stoi(tokens[3]);
|
||||
|
||||
return NozzleGroupInfo(diameter, volume_type, extruder_id, nozzle_count);
|
||||
} catch (const std::exception &) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> load_nozzle_infos_with_compatibility(
|
||||
const std::vector<NozzleInfo>& nozzle_infos,
|
||||
const std::vector<FilamentInfo>& filament_infos,
|
||||
const std::vector<int>& filament_map,
|
||||
const std::vector<NozzleVolumeType>& extruder_volume_types,
|
||||
const std::vector<double>& nozzle_diameter
|
||||
)
|
||||
{
|
||||
bool has_nozzle_info = !nozzle_infos.empty();
|
||||
bool has_valid_filament_info = !filament_infos.empty() && std::all_of(filament_infos.begin(), filament_infos.end(), [](const FilamentInfo& info){
|
||||
return info.group_id.size() == 1;
|
||||
});
|
||||
|
||||
if(!has_nozzle_info && !has_valid_filament_info){
|
||||
BOOST_LOG_TRIVIAL(warning)<<__FUNCTION__ << ": building nozzle list from filament map and volume types";
|
||||
|
||||
// Backward compatibility for older gcode.3mf:
|
||||
// - nozzle_diameter is always present and its size defines extruder count.
|
||||
// - filament_map may be missing; treat it as [0, 0, ...] for each extruder.
|
||||
// - extruder_volume_types may be missing; treat it as all Standard.
|
||||
const size_t extruder_count = nozzle_diameter.size();
|
||||
|
||||
std::vector<NozzleVolumeType> volume_types_fixed = extruder_volume_types;
|
||||
volume_types_fixed.resize(extruder_count, NozzleVolumeType::nvtStandard);
|
||||
|
||||
std::vector<NozzleInfo> result;
|
||||
result.reserve(extruder_count);
|
||||
for (size_t extruder_id = 0; extruder_id < extruder_count; ++extruder_id) {
|
||||
NozzleInfo info;
|
||||
info.diameter = format_diameter_to_str(nozzle_diameter[extruder_id]);
|
||||
info.group_id = static_cast<int>(extruder_id);
|
||||
info.extruder_id = static_cast<int>(extruder_id);
|
||||
info.volume_type = volume_types_fixed[extruder_id];
|
||||
result.emplace_back(std::move(info));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if(!has_nozzle_info){
|
||||
BOOST_LOG_TRIVIAL(info)<<__FUNCTION__ << ": building nozzle list from filament info";
|
||||
std::map<int, NozzleInfo> nozzle_map; // group_id -> NozzleInfo
|
||||
for(auto& filament : filament_infos){
|
||||
int group_id = filament.group_id.front();
|
||||
if(group_id < 0 || nozzle_map.find(group_id) != nozzle_map.end()){
|
||||
continue;
|
||||
}
|
||||
|
||||
auto volume_type_str_to_enum = ConfigOptionEnum<NozzleVolumeType>::get_enum_values();
|
||||
|
||||
NozzleInfo info;
|
||||
info.diameter = format_diameter_to_str(filament.nozzle_diameter);
|
||||
info.group_id = group_id;
|
||||
// Orca: bounds-check filament_map[filament.id] so a malformed 3mf (filament id
|
||||
// beyond the map) degrades to extruder 0 instead of dereferencing out of range.
|
||||
info.extruder_id = (filament.id >= 0 && filament.id < static_cast<int>(filament_map.size()))
|
||||
? filament_map[filament.id] - 1
|
||||
: 0; // to 0-based
|
||||
|
||||
if (volume_type_str_to_enum.count(filament.nozzle_volume_type))
|
||||
info.volume_type = NozzleVolumeType(volume_type_str_to_enum.at(filament.nozzle_volume_type));
|
||||
else {
|
||||
info.volume_type = NozzleVolumeType::nvtStandard;
|
||||
}
|
||||
|
||||
nozzle_map[group_id] = std::move(info);
|
||||
}
|
||||
|
||||
std::vector<NozzleInfo> ret;
|
||||
for(auto& elem : nozzle_map){
|
||||
ret.emplace_back(elem.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto result = nozzle_infos;
|
||||
std::sort(result.begin(), result.end());
|
||||
BOOST_LOG_TRIVIAL(info)<<__FUNCTION__ << ": using new 3mf format with " << result.size() << " nozzle infos.";
|
||||
return result;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::MultiNozzleUtils
|
||||
@@ -0,0 +1,296 @@
|
||||
#ifndef MULTI_NOZZLE_UTILS_HPP
|
||||
#define MULTI_NOZZLE_UTILS_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
// Multi-nozzle support types.
|
||||
// Declares the filament-grouping result types the slicing pipeline needs, plus the analytic
|
||||
// filament-change-time model (FilamentChangeTimeParams, NozzleStatusRecorder,
|
||||
// FilamentChangeSimResult, simulate_filament_change_time) — self-contained analytic code that
|
||||
// never touches the time estimator; its first consumer is the filament_group golden harness.
|
||||
// The gcode.3mf serialization surface lives here too: NozzleInfo/NozzleGroupInfo
|
||||
// serialize+deserialize, the device-side StaticNozzleGroupResult,
|
||||
// load_nozzle_infos_with_compatibility (the backward-compat 3mf reader) and
|
||||
// LayeredNozzleGroupResult::estimate_seq_flush_weight. The change-time-tuning helpers
|
||||
// calc_filament_change_gap_for_assignment / find_optimal_physical_assignment (used only by the
|
||||
// AMS pre-load optimizer, a later feature) are not implemented here.
|
||||
|
||||
namespace Slic3r {
|
||||
struct FilamentInfo; // Slic3r::FilamentInfo (ProjectTask.hpp) — consumed by StaticNozzleGroupResult / the 3mf reader
|
||||
namespace MultiNozzleUtils {
|
||||
|
||||
// Information about a single logical nozzle.
|
||||
struct NozzleInfo
|
||||
{
|
||||
std::string diameter;
|
||||
NozzleVolumeType volume_type;
|
||||
int extruder_id{-1}; // logical extruder id
|
||||
int group_id{-1}; // logical nozzle id
|
||||
|
||||
std::string serialize() const;
|
||||
|
||||
bool operator<(const NozzleInfo& other) const {
|
||||
if(group_id != other.group_id) return group_id < other.group_id;
|
||||
if(extruder_id != other.extruder_id) return extruder_id < other.extruder_id;
|
||||
if(volume_type != other.volume_type) return volume_type < other.volume_type;
|
||||
return diameter < other.diameter;
|
||||
}
|
||||
};
|
||||
|
||||
// A group of identical nozzles on one extruder (diameter + volume type + count).
|
||||
struct NozzleGroupInfo
|
||||
{
|
||||
std::string diameter;
|
||||
NozzleVolumeType volume_type;
|
||||
int extruder_id;
|
||||
int nozzle_count;
|
||||
|
||||
NozzleGroupInfo() = default;
|
||||
|
||||
NozzleGroupInfo(const std::string& nozzle_diameter_, const NozzleVolumeType volume_type_, const int extruder_id_, const int nozzle_count_)
|
||||
: diameter(nozzle_diameter_), volume_type(volume_type_), extruder_id(extruder_id_), nozzle_count(nozzle_count_)
|
||||
{}
|
||||
|
||||
inline bool operator<(const NozzleGroupInfo &rhs) const
|
||||
{
|
||||
if (extruder_id != rhs.extruder_id) return extruder_id < rhs.extruder_id;
|
||||
if (diameter != rhs.diameter) return diameter < rhs.diameter;
|
||||
if (volume_type != rhs.volume_type) return volume_type < rhs.volume_type;
|
||||
return nozzle_count < rhs.nozzle_count;
|
||||
}
|
||||
|
||||
bool is_same_type(const NozzleGroupInfo &rhs) const
|
||||
{
|
||||
return diameter == rhs.diameter && volume_type == rhs.volume_type && extruder_id == rhs.extruder_id;
|
||||
}
|
||||
|
||||
inline bool operator==(const NozzleGroupInfo &rhs) const
|
||||
{
|
||||
return diameter == rhs.diameter && volume_type == rhs.volume_type && extruder_id == rhs.extruder_id && nozzle_count == rhs.nozzle_count;
|
||||
}
|
||||
|
||||
std::string serialize() const;
|
||||
static std::optional<NozzleGroupInfo> deserialize(const std::string& str);
|
||||
};
|
||||
|
||||
// Load/unload time constants used by the filament-change-time model.
|
||||
// Consumed by simulate_filament_change_time() below and carried by the grouping-context
|
||||
// substrate (FilamentGroupContext::SpeedInfo).
|
||||
struct FilamentChangeTimeParams
|
||||
{
|
||||
float selector_load_time{0.0f};
|
||||
float selector_unload_time{0.0f};
|
||||
float standard_load_time{0.0f};
|
||||
float standard_unload_time{0.0f};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Abstract base for a nozzle-grouping result.
|
||||
*/
|
||||
class NozzleGroupResultBase
|
||||
{
|
||||
protected:
|
||||
bool support_dynamic_nozzle_map{false}; // whether dynamic (selector) mapping is used
|
||||
|
||||
public:
|
||||
NozzleGroupResultBase(bool support_dynamic_map = false) : support_dynamic_nozzle_map(support_dynamic_map) {}
|
||||
virtual ~NozzleGroupResultBase() = default;
|
||||
|
||||
virtual std::optional<NozzleInfo> get_nozzle_from_id(int nozzle_id) const = 0;
|
||||
virtual std::optional<NozzleInfo> get_first_nozzle_for_filament(int filament_id) const = 0; // logical nozzle a filament first uses
|
||||
|
||||
virtual std::vector<NozzleInfo> get_nozzles_for_filament(int filament_id) const = 0; // every nozzle a filament may use (across all layers)
|
||||
|
||||
bool is_support_dynamic_nozzle_map() const { return support_dynamic_nozzle_map; }
|
||||
|
||||
virtual int get_extruder_count() const = 0;
|
||||
|
||||
virtual std::vector<NozzleInfo> get_used_nozzles_in_extruder(int extruder_id =-1) const = 0;
|
||||
virtual std::vector<int> get_used_extruders() const = 0;
|
||||
virtual std::vector<unsigned int> get_used_filaments() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Layer-aware nozzle-grouping result.
|
||||
* Used by the back-end slicing code; supports per-layer nozzle mapping.
|
||||
*/
|
||||
class LayeredNozzleGroupResult : public NozzleGroupResultBase
|
||||
{
|
||||
private:
|
||||
std::vector<std::vector<int>> _layer_filament_nozzle_maps; // per-layer filament -> nozzle map
|
||||
std::vector<std::vector<unsigned int>> _layer_filament_sequences; // per-layer filament print order
|
||||
std::vector<int> _default_filament_nozzle_map; // global filament -> nozzle map
|
||||
std::vector<unsigned int> _used_filaments; // all used filament indices
|
||||
std::vector<NozzleInfo> _nozzle_list; // global nozzle list
|
||||
|
||||
public:
|
||||
LayeredNozzleGroupResult(bool support_dynamic_map = false) : NozzleGroupResultBase(support_dynamic_map) {}
|
||||
|
||||
// No selector: one global filament->nozzle map.
|
||||
static std::optional<LayeredNozzleGroupResult> create(
|
||||
const std::vector<int>& filament_nozzle_map,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<unsigned int>& used_filaments);
|
||||
|
||||
// Selector: built from per-layer maps (each layer may differ).
|
||||
static std::optional<LayeredNozzleGroupResult> create(
|
||||
const std::vector<std::vector<int>>& layer_filament_nozzle_maps,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filament_sequences);
|
||||
|
||||
// Multi-nozzle without selector: resolve each requested logical nozzle to a physical nozzle.
|
||||
static std::optional<LayeredNozzleGroupResult> create(
|
||||
const std::vector<unsigned int>& used_filaments,
|
||||
const std::vector<int>& filament_map,
|
||||
const std::vector<int>& filament_volume_map,
|
||||
const std::vector<int>& filament_nozzle_map,
|
||||
const std::vector<std::map<NozzleVolumeType, int>>& nozzle_count,
|
||||
float diameter);
|
||||
|
||||
bool are_filaments_same_extruder(int filament_id1, int filament_id2, int layer_id = -1) const;
|
||||
bool are_filaments_same_nozzle(int filament_id1, int filament_id2, int layer_id = -1) const;
|
||||
int get_extruder_count() const override;
|
||||
|
||||
std::vector<NozzleInfo> get_used_nozzles_in_extruder(int target_extruder_id = -1) const override;
|
||||
std::vector<NozzleInfo> get_used_nozzles_in_extruder(int target_extruder_id, int layer_id) const; // layer_id=-1 uses default map
|
||||
std::vector<int> get_used_extruders() const override;
|
||||
std::vector<int> get_used_extruders(int layer_id) const; // layer_id=-1 returns global extruders
|
||||
|
||||
std::vector<int> get_extruder_map(bool zero_based = true, int layer_id = -1) const;
|
||||
std::vector<int> get_nozzle_map(int layer_id = -1) const;
|
||||
std::vector<int> get_volume_map(int layer_id = -1) const;
|
||||
|
||||
std::vector<unsigned int> get_used_filaments() const override { return _used_filaments; }
|
||||
std::vector<unsigned int> get_used_filaments(int layer_id) const;
|
||||
|
||||
std::optional<NozzleInfo> get_nozzle_for_filament(int filament_id, int layer_id = -1) const;
|
||||
std::vector<NozzleInfo> get_nozzles_for_filament(int filament_id) const override;
|
||||
|
||||
std::optional<NozzleInfo> get_nozzle_from_id(int nozzle_id) const override;
|
||||
std::optional<NozzleInfo> get_first_nozzle_for_filament(int filament_id) const override;
|
||||
int get_extruder_id(int filament_id, int layer_id = -1) const;
|
||||
int get_nozzle_id(int filament_id, int layer_id = -1) const;
|
||||
|
||||
size_t get_layer_count() const { return _layer_filament_nozzle_maps.size(); }
|
||||
const std::vector<int>& get_layer_filament_nozzle_map(int layer_id) const;
|
||||
const std::vector<std::vector<int>> &get_layer_filament_nozzle_maps() const { return _layer_filament_nozzle_maps; }
|
||||
const std::vector<std::vector<unsigned int>>& get_layer_filament_sequences() const { return _layer_filament_sequences; }
|
||||
|
||||
// Estimate the flush weight of a filament-change sequence given the per-extruder flush matrix
|
||||
// (extruder -> from-filament -> to-filament).
|
||||
int estimate_seq_flush_weight(const std::vector<std::vector<std::vector<float>>>& flush_matrix, const std::vector<int>& filament_change_seq) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Layer-less nozzle-grouping result for the device side (static nozzle mapping only).
|
||||
* Reconstructed from a loaded gcode.3mf together with the filament/nozzle change sequences.
|
||||
*/
|
||||
class StaticNozzleGroupResult : public NozzleGroupResultBase
|
||||
{
|
||||
private:
|
||||
std::map<int, std::set<int>> _filament_to_nozzles; // every nozzle a filament may map to
|
||||
std::map<int, NozzleInfo> _nozzle_list_map; // used nozzles, keyed by logical nozzle id
|
||||
std::vector<int> _filament_change_seq; // filament sequence used to resolve first-use
|
||||
std::vector<int> _nozzle_change_seq; // logical-nozzle sequence paired with the filament sequence
|
||||
|
||||
public:
|
||||
StaticNozzleGroupResult(bool support_dynamic_map) : NozzleGroupResultBase(support_dynamic_map) {}
|
||||
// Build from a loaded 3mf, with the filament/nozzle change sequences.
|
||||
static std::optional<StaticNozzleGroupResult> create(
|
||||
const std::vector<FilamentInfo>& filaments_info,
|
||||
const std::vector<NozzleInfo>& nozzles_info,
|
||||
const std::vector<int>& filament_change_seq,
|
||||
const std::vector<int>& nozzle_change_seq,
|
||||
bool support_dynamic_map);
|
||||
|
||||
int get_extruder_count() const override;
|
||||
std::vector<NozzleInfo> get_used_nozzles_in_extruder(int extruder_id = -1) const override;
|
||||
std::vector<int> get_used_extruders() const override;
|
||||
std::vector<unsigned int> get_used_filaments() const override;
|
||||
|
||||
std::optional<NozzleInfo> get_nozzle_from_id(int nozzle_id) const override;
|
||||
|
||||
std::vector<NozzleInfo> get_nozzles_for_filament(int filament_id) const override;
|
||||
std::optional<NozzleInfo> get_first_nozzle_for_filament(int filament_id) const override;
|
||||
};
|
||||
|
||||
// Tracks, during the filament-change simulation, which filament sits in each physical nozzle
|
||||
// and which nozzle each extruder currently carries.
|
||||
class NozzleStatusRecorder
|
||||
{
|
||||
private:
|
||||
std::unordered_map<int, int> nozzle_filament_status; // Track filament in each nozzle
|
||||
std::unordered_map<int, int> extruder_nozzle_status; // Track nozzle for each extruder
|
||||
int current_extruder_id_ = -1; // Track current extruder id
|
||||
|
||||
public:
|
||||
NozzleStatusRecorder() = default;
|
||||
bool is_nozzle_empty(int nozzle_id) const;
|
||||
int get_filament_in_nozzle(int nozzle_id) const;
|
||||
int get_nozzle_in_extruder(int extruder_id) const;
|
||||
int get_current_extruder_id() const { return current_extruder_id_; }
|
||||
|
||||
void clear_nozzle_status(int nozzle_id);
|
||||
void set_current_extruder_id(int extruder_id) { current_extruder_id_ = extruder_id; }
|
||||
|
||||
// Update the status of a nozzle with new filament and extruder information
|
||||
void set_nozzle_status(int nozzle_id, int filament_id, int extruder_id = -1);
|
||||
|
||||
// key: nozzle id, value: filament id (-1 = the nozzle carries no filament)
|
||||
const std::unordered_map<int, int>& get_nozzle_filament_map() const { return nozzle_filament_status; }
|
||||
// key: extruder id, value: nozzle id (-1 = the extruder carries no nozzle)
|
||||
const std::unordered_map<int, int>& get_extruder_nozzle_map() const { return extruder_nozzle_status; }
|
||||
};
|
||||
|
||||
struct FilamentChangeSimResult {
|
||||
double actual_time = 0.0;
|
||||
double sliced_time = 0.0;
|
||||
};
|
||||
|
||||
// Analytic filament-change-time model. Given the used filaments, the nozzle
|
||||
// list, the filament/nozzle change sequences, each filament's AMS group and the load/unload time
|
||||
// constants, it simulates AMS->selector->extruder transport (with optional AMS pre-load overlap)
|
||||
// and returns the actual print time plus the slicer-estimated time. Self-contained: it never
|
||||
// touches the g-code time estimator.
|
||||
FilamentChangeSimResult simulate_filament_change_time(
|
||||
const std::vector<int>& logical_filaments,
|
||||
const std::vector<NozzleInfo>& nozzle_list,
|
||||
const std::vector<int>& filament_change_seq,
|
||||
const std::vector<int>& nozzle_change_seq,
|
||||
const std::vector<int>& group_of_filament,
|
||||
const FilamentChangeTimeParams& time_params,
|
||||
const std::vector<bool>& ams_preload_enabled = {},
|
||||
bool calc_sliced_time = false);
|
||||
|
||||
// ==================== tool functions ====================
|
||||
// Make each filament's per-layer nozzle assignment gap-free: layers where a filament is not
|
||||
// extruded inherit the nozzle it last used (forward carry); layers before its first use inherit
|
||||
// the first nozzle it ever uses (back-fill). Entries on layers where the filament is actually
|
||||
// used stay untouched. Needed for stitched sequential maps, where consumers indexing with an
|
||||
// object-local layer id must resolve the same nozzle as global-id consumers except across a
|
||||
// genuine mid-print reassignment.
|
||||
void normalize_nozzle_map_per_layer(std::vector<std::vector<int>>& layer_filament_nozzle_maps,
|
||||
const std::vector<std::vector<unsigned int>>& layer_filaments);
|
||||
std::vector<NozzleInfo> build_nozzle_list(std::vector<NozzleGroupInfo> info);
|
||||
std::vector<NozzleInfo> build_nozzle_list(double diameter, const std::vector<int>& filament_nozzle_map,
|
||||
const std::vector<int>& filament_volume_map, const std::vector<int>& filament_map);
|
||||
// Load nozzle infos from a gcode.3mf, handling backward compatibility with older 3mf that did not
|
||||
// record standalone <nozzle> tags: falls back to the per-filament group_id/diameter/volume_type, and
|
||||
// (for the oldest single-nozzle 3mf) to the filament_map + extruder volume types + nozzle diameters.
|
||||
std::vector<NozzleInfo> load_nozzle_infos_with_compatibility(
|
||||
const std::vector<NozzleInfo>& nozzle_infos,
|
||||
const std::vector<FilamentInfo>& filament_infos,
|
||||
const std::vector<int>& filament_map,
|
||||
const std::vector<NozzleVolumeType>& extruder_volume_types,
|
||||
const std::vector<double>& nozzle_diameter
|
||||
);
|
||||
} // namespace MultiNozzleUtils
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // MULTI_NOZZLE_UTILS_HPP
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <cereal/access.hpp>
|
||||
#include <cereal/types/base_class.hpp>
|
||||
#include <cstddef>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -41,6 +42,18 @@ private:
|
||||
template<class Archive> void serialize(Archive &ar) { ar(id); }
|
||||
};
|
||||
|
||||
struct ObjectInstanceID {
|
||||
ObjectID object_id;
|
||||
size_t instance_id { size_t(-1) };
|
||||
|
||||
bool operator==(const ObjectInstanceID& rhs) const { return object_id == rhs.object_id && instance_id == rhs.instance_id; }
|
||||
bool operator!=(const ObjectInstanceID& rhs) const { return !(*this == rhs); }
|
||||
bool operator<(const ObjectInstanceID& rhs) const
|
||||
{
|
||||
return object_id < rhs.object_id || (object_id == rhs.object_id && instance_id < rhs.instance_id);
|
||||
}
|
||||
};
|
||||
|
||||
// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID
|
||||
// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject).
|
||||
// Also base for Print, PrintObject, SLAPrint, SLAPrintObject to provide a unique ID for matching Model / ModelObject
|
||||
|
||||
+192
-15
@@ -27,19 +27,20 @@ namespace Slic3r {
|
||||
namespace orientation {
|
||||
|
||||
struct CostItems {
|
||||
float overhang;
|
||||
float bottom;
|
||||
float bottom_hull;
|
||||
float contour;
|
||||
float area_laf; // area_of_low_angle_faces
|
||||
float area_projected; // area of projected 2D profile
|
||||
float volume;
|
||||
float area_total; // total area of all faces
|
||||
float radius; // radius of bounding box
|
||||
float height_to_bottom_hull_ratio; // affects stability, the lower the better
|
||||
float unprintability;
|
||||
float overhang = 0;
|
||||
float bottom = 0;
|
||||
float bottom_hull = 0;
|
||||
float contour = 0;
|
||||
float area_laf = 0; // area_of_low_angle_faces
|
||||
float area_projected = 0; // area of projected 2D profile
|
||||
float volume = 0;
|
||||
float area_total = 0; // total area of all faces
|
||||
float radius = 0; // radius of bounding box
|
||||
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() { memset(this, 0, sizeof(*this)); }
|
||||
CostItems() = default;
|
||||
static std::string field_names() {
|
||||
return " overhang, bottom, bothull, contour, A_laf, A_prj, unprintability";
|
||||
}
|
||||
@@ -68,10 +69,11 @@ public:
|
||||
Eigen::VectorXf z_max, z_max_hull; // max of projected z
|
||||
Eigen::VectorXf z_median; // median of projected z
|
||||
Eigen::VectorXf z_mean; // mean of projected z
|
||||
Eigen::VectorXf areas_cooling; // weighted areas for cool direction
|
||||
std::vector<Vec3f> face_normals;
|
||||
std::vector<Vec3f> face_normals_hull;
|
||||
OrientParams params;
|
||||
|
||||
bool has_cooling_fan = false;
|
||||
|
||||
std::vector< Vec3f> orientations; // Vec3f == stl_normal
|
||||
std::function<void(unsigned)> progressind = { }; // default empty indicator function
|
||||
@@ -85,6 +87,7 @@ public:
|
||||
orient_mesh = orient_mesh_;
|
||||
mesh = &orient_mesh->mesh;
|
||||
params = params_;
|
||||
has_cooling_fan = orient_mesh->has_cooling_fan;
|
||||
progressind = progressind_;
|
||||
params.ASCENT = cos(PI - orient_mesh->overhang_angle * PI / 180); // use per-object overhang angle
|
||||
|
||||
@@ -158,12 +161,14 @@ public:
|
||||
//To avoid flipping, we need to verify if there are orientations with same unprintability.
|
||||
Vec3f n1 = {0, 0, 1};
|
||||
auto best_orientation = results_vector[0].first;
|
||||
size_t best_index = 0;
|
||||
|
||||
for (int i = 1; i< results_vector.size()-1; i++) {
|
||||
if (abs(results_vector[i].second.unprintability - results_vector[0].second.unprintability) < EPSILON && abs(results_vector[0].first.dot(n1)-1) > EPSILON) {
|
||||
if (abs(results_vector[i].first.dot(n1)-1) < EPSILON*EPSILON) {
|
||||
if (abs(results_vector[i].first.dot(n1)-1) < EPSILON*EPSILON) {
|
||||
best_orientation = n1;
|
||||
break;
|
||||
best_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -172,6 +177,9 @@ public:
|
||||
|
||||
}
|
||||
|
||||
// cooling weights are per-orientation, so take them from the orientation actually chosen
|
||||
areas_cooling = results_vector[best_index].second.areas_cooling;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << std::fixed << std::setprecision(6) << "best:" << best_orientation.transpose() << ", costs:" << results_vector[0].second.field_values();
|
||||
std::cout << std::fixed << std::setprecision(6) << "best:" << best_orientation.transpose() << ", costs:" << results_vector[0].second.field_values() << std::endl;
|
||||
|
||||
@@ -441,6 +449,19 @@ public:
|
||||
Eigen::MatrixXf laf_areas = ((normal_projection_abs.array() < params.LAF_MAX) * (normal_projection_abs.array() > params.LAF_MIN) * (z_max.array() > total_min_z + params.FIRST_LAY_H)).select(areas, 0);
|
||||
costs.area_laf = laf_areas.sum();
|
||||
|
||||
if (has_cooling_fan)
|
||||
{
|
||||
// Angle range of overhang faces requiring cooling
|
||||
float angle_thres_high = -0.6427f;
|
||||
float angle_thres_low = -0.97f;
|
||||
// compute the weighted overhang faces area
|
||||
Eigen::VectorXf ones_f = Eigen::VectorXf::Ones(mesh->facets_count());
|
||||
auto overhang_area_condition = (normal_projection.array() < angle_thres_high && normal_projection.array() > angle_thres_low).eval();
|
||||
Eigen::VectorXf areas_ = (overhang_area_condition * !bottom_condition_2nd).select(areas, 0);
|
||||
Eigen::VectorXf weighted_areas = areas_.cwiseProduct(ones_f - normal_projection);
|
||||
costs.areas_cooling = weighted_areas;
|
||||
}
|
||||
|
||||
// height to bottom_hull_area ratio
|
||||
//float total_max_z = z_projected.maxCoeff();
|
||||
//costs.height_to_bottom_hull_ratio = SQ(total_max_z) / (costs.bottom_hull + 1e-7);
|
||||
@@ -468,6 +489,67 @@ public:
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
Vec3d find_cooling_direction2(Vec3d euler_angles, const Eigen::VectorXf& areas_in, TriangleMesh& mesh)
|
||||
{
|
||||
Vec3f machine_cool_dir = this->orient_mesh->cooling_direction.cast<float>();
|
||||
const size_t num_faces = areas.rows();
|
||||
Vec3f best_direction = { 0, 0, 0 };
|
||||
|
||||
// 1. Make a copy of input mesh, rotate and translate to the best orientation
|
||||
TriangleMesh mesh_copy = TriangleMesh(mesh.its);
|
||||
mesh_copy.rotate_x(euler_angles(0, 0));
|
||||
mesh_copy.rotate_y(euler_angles(1, 0));
|
||||
mesh_copy.rotate_z(euler_angles(2, 0));
|
||||
auto bounding_box = mesh_copy.bounding_box();
|
||||
Eigen::VectorXf translate_distance = bounding_box.min.array().cast<float>();
|
||||
Vec3d mesh_center = mesh_copy.center();
|
||||
mesh_copy.translate(-mesh_center(0), -mesh_center(1), -translate_distance(2));
|
||||
|
||||
// 2. sample cooling direction
|
||||
const size_t sample_nums = 180;
|
||||
std::vector<Vec3f> cool_dirs;
|
||||
for (size_t i = 0; i < sample_nums; i++)
|
||||
{
|
||||
float angle_deg = i * (360.0 / sample_nums);
|
||||
float angle_rad = angle_deg * (PI / 180.0);
|
||||
cool_dirs.push_back(Vec3f{ std::cos(angle_rad), std::sin(angle_rad), 0});
|
||||
}
|
||||
|
||||
// 3. accumulate the weighted projected overhang area, find the max weighted project area direction
|
||||
std::vector<Vec3f> face_normals_copy = its_face_normals(mesh_copy.its);
|
||||
float overhang_projected_max = 0.f;
|
||||
float overhang_projected_origin = 0.f;
|
||||
for (auto cool_dir : cool_dirs)
|
||||
{
|
||||
float overhang_projected_tmp = 0.f;
|
||||
for (size_t i = 0; i < num_faces; i++)
|
||||
{
|
||||
float cool_dir_projection = face_normals_copy[i].dot(cool_dir);
|
||||
if (areas_in[i] > 0 && cool_dir_projection > 0)
|
||||
{
|
||||
overhang_projected_tmp += areas_in[i] * cool_dir_projection;
|
||||
}
|
||||
}
|
||||
if (overhang_projected_tmp > overhang_projected_max)
|
||||
{
|
||||
overhang_projected_max = overhang_projected_tmp;
|
||||
best_direction = cool_dir;
|
||||
}
|
||||
if (cool_dir.dot(machine_cool_dir) > 0.999)
|
||||
{
|
||||
overhang_projected_origin = overhang_projected_tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// The symmetric model has similar overhang projection at all angles, so Z-axis rotation is unnecessary.
|
||||
if (std::abs(overhang_projected_origin - overhang_projected_max) < 1.0f)
|
||||
{
|
||||
best_direction = machine_cool_dir;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "best cooling dir = " << best_direction.transpose() << "\n";
|
||||
return best_direction.cast<double>();
|
||||
}
|
||||
};
|
||||
|
||||
void _orient(OrientMeshs& meshs_,
|
||||
@@ -497,6 +579,13 @@ void _orient(OrientMeshs& meshs_,
|
||||
mesh_.orientation = orienter.process();
|
||||
Geometry::rotation_from_two_vectors(mesh_.orientation, { 0,0,1 }, mesh_.axis, mesh_.angle, &mesh_.rotation_matrix);
|
||||
mesh_.euler_angles = Geometry::extract_euler_angles(mesh_.rotation_matrix);
|
||||
// find cool direction
|
||||
if (mesh_.has_cooling_fan)
|
||||
{
|
||||
mesh_.orientation_vertical = orienter.find_cooling_direction2(mesh_.euler_angles, orienter.areas_cooling, mesh_.mesh);
|
||||
BOOST_LOG_TRIVIAL(info) << "cooling direction: " << mesh_.orientation_vertical.transpose() << "\n";
|
||||
Geometry::rotation_from_two_vectors(mesh_.orientation_vertical, mesh_.cooling_direction, mesh_.axis_vertical, mesh_.angle_vertical, &mesh_.rotation_matrix_vertical);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << "rotation_from_two_vectors: " << mesh_.orientation << "; " << mesh_.axis << "; " << mesh_.angle << "; euler: " << mesh_.euler_angles.transpose();
|
||||
}});
|
||||
}
|
||||
@@ -539,6 +628,94 @@ void orient(ModelInstance* instance)
|
||||
instance->rotate(rotation_matrix);
|
||||
}
|
||||
|
||||
void orient_for_cooling(TriangleMesh& mesh, const FanDirection& fan_dir)
|
||||
{
|
||||
Vec3f best_direction{ 0, 0, 0 };
|
||||
Vec3f machine_cool_dir{ 0, 0, 0 };
|
||||
|
||||
if (fan_dir == FanDirection::fdUndefine)
|
||||
{
|
||||
// no cooling fan, do not rotate along z axis
|
||||
return;
|
||||
}
|
||||
else if (fan_dir == FanDirection::fdRight)
|
||||
{
|
||||
machine_cool_dir = { 1, 0, 0 }; // the cooling fan is on the right side.
|
||||
}
|
||||
else
|
||||
{
|
||||
// the cooling fan is on the left side or both side has cooling fans
|
||||
machine_cool_dir = { -1, 0, 0 };
|
||||
}
|
||||
|
||||
// 1. filter the overhang_areas
|
||||
int nfaces = mesh.facets_count();
|
||||
auto face_normals = its_face_normals(mesh.its);
|
||||
|
||||
Eigen::VectorXf normal_projection(nfaces, 1);
|
||||
for (auto i = 0; i < nfaces; i++)
|
||||
{
|
||||
normal_projection(i) = face_normals[i].dot(Vec3f(0, 0, 1));
|
||||
}
|
||||
float angle_thres_high = -0.6427f;
|
||||
float angle_thres_low = -0.97f;
|
||||
// 2. compute the weighted overhang faces area
|
||||
Eigen::VectorXf weighted_areas = Eigen::VectorXf::Zero(nfaces);
|
||||
for (int i = 0; i < nfaces; i++)
|
||||
{
|
||||
if (normal_projection(i) < angle_thres_high && normal_projection(i) > angle_thres_low)
|
||||
{
|
||||
weighted_areas(i) = mesh.its.facet_area(i) * (1.0f - normal_projection(i));
|
||||
}
|
||||
}
|
||||
|
||||
const size_t sample_nums = 180;
|
||||
std::vector<Vec3f> cool_dirs;
|
||||
for (size_t i = 0; i < sample_nums; i++)
|
||||
{
|
||||
float angle_deg = i * (360.0 / sample_nums);
|
||||
float angle_rad = angle_deg * (PI / 180.0);
|
||||
cool_dirs.push_back(Vec3f{ std::cos(angle_rad), std::sin(angle_rad), 0 });
|
||||
}
|
||||
|
||||
// 3. accumulate the weighted projected overhang area, find the max weighted project area direction
|
||||
float overhang_projected_max = 0.f;
|
||||
float overhang_projected_origin = 0.f;
|
||||
for (auto cool_dir : cool_dirs)
|
||||
{
|
||||
float overhang_projected_tmp = 0.f;
|
||||
for (size_t i = 0; i < nfaces; i++)
|
||||
{
|
||||
float cool_dir_projection = face_normals[i].dot(cool_dir);
|
||||
if (weighted_areas[i] > 0 && cool_dir_projection > 0)
|
||||
{
|
||||
overhang_projected_tmp += weighted_areas[i] * cool_dir_projection;
|
||||
}
|
||||
}
|
||||
if (overhang_projected_tmp > overhang_projected_max)
|
||||
{
|
||||
overhang_projected_max = overhang_projected_tmp;
|
||||
best_direction = cool_dir;
|
||||
}
|
||||
if (cool_dir.dot(machine_cool_dir) > 0.999)
|
||||
{
|
||||
overhang_projected_origin = overhang_projected_tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// The symmetric model has similar overhang projection at all angles, so Z-axis rotation is unnecessary.
|
||||
if (std::abs(overhang_projected_origin - overhang_projected_max) < 1.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// rotate the mesh
|
||||
Vec3d axis;
|
||||
double angle;
|
||||
Matrix3d rotation_matrix;
|
||||
Geometry::rotation_from_two_vectors(best_direction.cast<double>(), machine_cool_dir.cast<double>(), axis, angle, &rotation_matrix);
|
||||
mesh.rotate(angle, axis);
|
||||
}
|
||||
|
||||
} // namespace arr
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -26,10 +26,18 @@ struct OrientMesh {
|
||||
TriangleMesh mesh; /// The real mesh data
|
||||
double overhang_angle = 30;
|
||||
double angle{ 0 };
|
||||
double angle_vertical{ 0 };
|
||||
Vec3d axis{ 0,0,1 };
|
||||
Vec3d axis_vertical{ 0,0,1 };
|
||||
Vec3d orientation{ 0,0,1 };
|
||||
Matrix3d rotation_matrix;
|
||||
Vec3d euler_angles;
|
||||
Vec3d orientation_vertical{ -1,0,0 };
|
||||
Matrix3d rotation_matrix = Matrix3d::Identity();
|
||||
Matrix3d rotation_matrix_vertical = Matrix3d::Identity();
|
||||
Vec3d euler_angles = {0, 0, 0};
|
||||
Vec3d euler_angles_vertical = {0, 0, 0};
|
||||
Vec3d cooling_direction = {0, 0, 0};
|
||||
bool has_cooling_fan{false};
|
||||
|
||||
std::string name;
|
||||
|
||||
/// Optional setter function which can store arbitrary data in its closure
|
||||
@@ -154,6 +162,9 @@ void orient(ModelObject* obj);
|
||||
|
||||
void orient(ModelInstance* instance);
|
||||
|
||||
// rotate z axis for cooling
|
||||
void orient_for_cooling(TriangleMesh& mesh, const FanDirection& fan_dir);
|
||||
|
||||
}} // namespace Slic3r::orientment
|
||||
|
||||
#endif // MODELORIENT_HPP
|
||||
|
||||
@@ -571,6 +571,19 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||
return extrusion_coll;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top detects the top as "slice − upper", so a feature rising from the middle of a
|
||||
// top surface becomes an enclosed hole that gets ringed with extra inner walls. Fill those holes back
|
||||
// into the top. Only holes that are both covered by the upper layer (excludes bridges) and backed by
|
||||
// solid material (excludes voids) are filled.
|
||||
static ExPolygons fill_enclosed_top_feature_holes(const ExPolygons &top, const Polygons &covered_by_upper, const ExPolygons &solid)
|
||||
{
|
||||
ExPolygons filled = top;
|
||||
for (ExPolygon &ex : filled)
|
||||
ex.holes.clear();
|
||||
const ExPolygons feature_holes = intersection_ex(intersection_ex(diff_ex(filled, top), covered_by_upper), solid);
|
||||
return feature_holes.empty() ? top : union_ex(top, feature_holes);
|
||||
}
|
||||
|
||||
void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExPolygons &top_fills,
|
||||
ExPolygons &non_top_polygons, ExPolygons &fill_clip) const {
|
||||
// other perimeters
|
||||
@@ -581,7 +594,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP
|
||||
coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
|
||||
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
|
||||
|
||||
bool has_gap_fill = this->config->gap_infill_speed.value > 0;
|
||||
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->outer_wall_filament_id - 1)) > 0;
|
||||
|
||||
// split the polygons with top/not_top
|
||||
// get the offset from solid surface anchor
|
||||
@@ -636,6 +649,8 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP
|
||||
ExPolygons delete_bridge = diff_ex(orig_polygons, bridge_checker, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons top_polygons = diff_ex(delete_bridge, upper_polygons_series_clipped, ApplySafetyOffset::Yes);
|
||||
top_polygons = fill_enclosed_top_feature_holes(top_polygons, upper_polygons_series_clipped, orig_polygons);
|
||||
|
||||
// get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the
|
||||
// min_width_top_surface we removed a bit before)
|
||||
ExPolygons temp_gap = diff_ex(top_polygons, fill_clip);
|
||||
@@ -1189,7 +1204,7 @@ void PerimeterGenerator::process_classic()
|
||||
// internal flow which is unrelated.
|
||||
coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
|
||||
coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
|
||||
bool has_gap_fill = this->config->gap_infill_speed.value > 0;
|
||||
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->outer_wall_filament_id - 1)) > 0;
|
||||
|
||||
// BBS: this flow is for smaller external perimeter for small area
|
||||
coord_t ext_min_spacing_smaller = coord_t(ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE));
|
||||
@@ -2194,6 +2209,7 @@ void PerimeterGenerator::process_arachne()
|
||||
upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, infill_contour_bbox);
|
||||
|
||||
top_expolygons = diff_ex(infill_contour, upper_slices_clipped);
|
||||
top_expolygons = fill_enclosed_top_feature_holes(top_expolygons, upper_slices_clipped, infill_contour);
|
||||
|
||||
if (!top_expolygons.empty()) {
|
||||
if (lower_slices != nullptr) {
|
||||
|
||||
@@ -712,6 +712,26 @@ namespace client
|
||||
static void regex_matches (expr &lhs, IteratorRange &rhs) { return regex_op(lhs, rhs, '=', lhs); }
|
||||
static void regex_doesnt_match(expr &lhs, IteratorRange &rhs) { return regex_op(lhs, rhs, '!', lhs); }
|
||||
|
||||
// Replace every match of the regular expression 'pattern' in the string 'subject' with 'replacement'.
|
||||
// The replacement may reference capture groups ($1, $2, ...). Store the result into subject.
|
||||
static void regex_replace(expr &subject, IteratorRange &pattern, expr &replacement)
|
||||
{
|
||||
if (subject.type() == TYPE_EMPTY)
|
||||
// Inside an if / else block to be skipped
|
||||
return;
|
||||
if (subject.type() != TYPE_STRING)
|
||||
subject.throw_exception("regex_replace() first parameter must be a string.");
|
||||
try {
|
||||
std::string re(++ pattern.begin(), -- pattern.end());
|
||||
std::string result = SLIC3R_REGEX_NAMESPACE::regex_replace(subject.s(), SLIC3R_REGEX_NAMESPACE::regex(re), replacement.to_string());
|
||||
subject.set_s(std::move(result));
|
||||
} catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) {
|
||||
// Syntax error in the regular expression
|
||||
boost::throw_exception(qi::expectation_failure<Iterator>(
|
||||
pattern.begin(), pattern.end(), spirit::info(std::string("*Regular expression compilation failed: ") + ex.what())));
|
||||
}
|
||||
}
|
||||
|
||||
static void one_of_test_init(expr &out) {
|
||||
out.set_b(false);
|
||||
}
|
||||
@@ -836,9 +856,11 @@ namespace client
|
||||
static std::map<std::string, std::string> tag_to_error_message;
|
||||
|
||||
size_t get_extruder_id() const {
|
||||
const ConfigOptionInts * filament_map_opt = external_config->option<ConfigOptionInts>("filament_map");
|
||||
if (filament_map_opt && current_extruder_id < filament_map_opt->values.size()) {
|
||||
return filament_map_opt->values[current_extruder_id];
|
||||
if (external_config != nullptr) {
|
||||
const ConfigOptionInts * filament_map_opt = external_config->option<ConfigOptionInts>("filament_map");
|
||||
if (filament_map_opt && current_extruder_id < filament_map_opt->values.size()) {
|
||||
return filament_map_opt->values[current_extruder_id];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1096,27 +1118,109 @@ namespace client
|
||||
const ConfigOptionVectorBase* vec = static_cast<const ConfigOptionVectorBase*>(opt.opt);
|
||||
if (vec->empty())
|
||||
ctx->throw_exception("Indexing an empty vector variable", opt.it_range);
|
||||
|
||||
// Helper to resolve a FloatOrPercent value (handles ratio_over chain for percent values).
|
||||
// elem_index: the element index used to access this vector element, so that
|
||||
// parent vectors (via ratio_over) use the same index rather than the current extruder.
|
||||
auto resolve_float_or_percent = [ctx, &opt, &output](const FloatOrPercent &fop, size_t elem_index) {
|
||||
std::string opt_key(opt.it_range.begin(), opt.it_range.end());
|
||||
if (boost::ends_with(opt_key, "line_width")) {
|
||||
// Line width supports defaults and a complex graph of dependencies.
|
||||
output.set_d(Flow::extrusion_width(opt_key, *ctx, static_cast<unsigned int>(ctx->current_extruder_id)));
|
||||
} else if (! fop.percent) {
|
||||
// Not a percent, just return the value.
|
||||
output.set_d(fop.value);
|
||||
} else {
|
||||
// Resolve dependencies using the "ratio_over" link to a parent value.
|
||||
const ConfigOptionDef *opt_def = print_config_def.get(opt_key);
|
||||
assert(opt_def != nullptr);
|
||||
double v = fop.value * 0.01; // percent to ratio
|
||||
for (;;) {
|
||||
const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over);
|
||||
if (opt_parent == nullptr)
|
||||
ctx->throw_exception("FloatOrPercent variable failed to resolve the \"ratio_over\" dependencies", opt.it_range);
|
||||
if (boost::ends_with(opt_def->ratio_over, "line_width")) {
|
||||
// Line width supports defaults and a complex graph of dependencies.
|
||||
assert(opt_parent->type() == coFloatOrPercent);
|
||||
v *= Flow::extrusion_width(opt_def->ratio_over, static_cast<const ConfigOptionFloatOrPercent*>(opt_parent), *ctx, static_cast<unsigned int>(ctx->current_extruder_id));
|
||||
break;
|
||||
}
|
||||
if (opt_parent->type() == coFloat || opt_parent->type() == coFloatOrPercent) {
|
||||
v *= opt_parent->getFloat();
|
||||
if (opt_parent->type() == coFloat || ! static_cast<const ConfigOptionFloatOrPercent*>(opt_parent)->percent)
|
||||
break;
|
||||
v *= 0.01; // percent to ratio
|
||||
} else if (opt_parent->type() == coFloats) {
|
||||
// Vector parent: extract the value for the current extruder.
|
||||
const ConfigOptionFloatsNullable *parent_nullable = dynamic_cast<const ConfigOptionFloatsNullable *>(opt_parent);
|
||||
if (parent_nullable) {
|
||||
v *= (parent_nullable->size() == 1) ? parent_nullable->get_at(0) : parent_nullable->get_at(elem_index);
|
||||
} else {
|
||||
const ConfigOptionFloats *parent_vec = static_cast<const ConfigOptionFloats *>(opt_parent);
|
||||
v *= (parent_vec->size() == 1) ? parent_vec->get_at(0) : parent_vec->get_at(elem_index);
|
||||
}
|
||||
break;
|
||||
} else if (opt_parent->type() == coFloatsOrPercents) {
|
||||
// Vector parent with percent support: extract the FloatOrPercent for the current extruder.
|
||||
const ConfigOptionFloatsOrPercentsNullable *parent_nullable = dynamic_cast<const ConfigOptionFloatsOrPercentsNullable *>(opt_parent);
|
||||
if (parent_nullable) {
|
||||
const FloatOrPercent &parent_fop = (parent_nullable->size() == 1) ? parent_nullable->get_at(0) : parent_nullable->get_at(elem_index);
|
||||
if (! parent_fop.percent) {
|
||||
v *= parent_fop.value;
|
||||
break;
|
||||
}
|
||||
v *= parent_fop.value * 0.01; // percent to ratio
|
||||
} else {
|
||||
const ConfigOptionFloatsOrPercents *parent_vec = static_cast<const ConfigOptionFloatsOrPercents *>(opt_parent);
|
||||
const FloatOrPercent &parent_fop = (parent_vec->size() == 1) ? parent_vec->get_at(0) : parent_vec->get_at(elem_index);
|
||||
if (! parent_fop.percent) {
|
||||
v *= parent_fop.value;
|
||||
break;
|
||||
}
|
||||
v *= parent_fop.value * 0.01; // percent to ratio
|
||||
}
|
||||
}
|
||||
// Continue one level up in the "ratio_over" hierarchy.
|
||||
opt_def = print_config_def.get(opt_def->ratio_over);
|
||||
assert(opt_def != nullptr);
|
||||
}
|
||||
output.set_d(v);
|
||||
}
|
||||
};
|
||||
|
||||
if (!opt.has_index()) {
|
||||
// Allow omitting extruder id when referencing vectors
|
||||
switch (opt.opt->type()) {
|
||||
case coFloats: {
|
||||
const ConfigOptionFloatsNullable* opt_floatsnullable = static_cast<const ConfigOptionFloatsNullable *>(opt.opt);
|
||||
const ConfigOptionFloatsNullable* opt_floatsnullable = dynamic_cast<const ConfigOptionFloatsNullable *>(opt.opt);
|
||||
if (opt_floatsnullable) {
|
||||
if (opt_floatsnullable->size() == 1) { // old version
|
||||
output.set_d(static_cast<const ConfigOptionFloatsNullable*>(opt.opt)->get_at(0));
|
||||
output.set_d(opt_floatsnullable->get_at(0));
|
||||
} else {
|
||||
output.set_d(static_cast<const ConfigOptionFloatsNullable*>(opt.opt)->get_at(ctx->get_extruder_id()));
|
||||
output.set_d(opt_floatsnullable->get_at(ctx->get_extruder_id()));
|
||||
}
|
||||
} else {
|
||||
const ConfigOptionFloats* opt_floats = static_cast<const ConfigOptionFloats*>(opt.opt);
|
||||
if (opt_floats->size() == 1) { // old version
|
||||
output.set_d(static_cast<const ConfigOptionFloats*>(opt.opt)->get_at(0));
|
||||
output.set_d(opt_floats->get_at(0));
|
||||
} else {
|
||||
output.set_d(static_cast<const ConfigOptionFloats*>(opt.opt)->get_at(ctx->get_extruder_id()));
|
||||
output.set_d(opt_floats->get_at(ctx->get_extruder_id()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case coFloatsOrPercents: {
|
||||
const ConfigOptionFloatsOrPercentsNullable *opt_vec_nullable = dynamic_cast<const ConfigOptionFloatsOrPercentsNullable *>(opt.opt);
|
||||
if (opt_vec_nullable) {
|
||||
size_t elem_index = (opt_vec_nullable->size() == 1) ? 0 : ctx->get_extruder_id();
|
||||
resolve_float_or_percent(opt_vec_nullable->get_at(elem_index), elem_index);
|
||||
} else {
|
||||
const ConfigOptionFloatsOrPercents *opt_vec = static_cast<const ConfigOptionFloatsOrPercents *>(opt.opt);
|
||||
size_t elem_index = (opt_vec->size() == 1) ? 0 : ctx->get_extruder_id();
|
||||
resolve_float_or_percent(opt_vec->get_at(elem_index), elem_index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range);
|
||||
}
|
||||
} else {
|
||||
@@ -1131,6 +1235,15 @@ namespace client
|
||||
case coPoints: output.set_s(to_string(static_cast<const ConfigOptionPoints*>(opt.opt)->values[idx])); break;
|
||||
case coBools: output.set_b(static_cast<const ConfigOptionBools*>(opt.opt)->values[idx] != 0); break;
|
||||
case coEnums: output.set_i(static_cast<const ConfigOptionInts *>(opt.opt)->values[idx]); break;
|
||||
case coFloatsOrPercents: {
|
||||
const ConfigOptionFloatsOrPercentsNullable *opt_vec_nullable = dynamic_cast<const ConfigOptionFloatsOrPercentsNullable *>(opt.opt);
|
||||
if (opt_vec_nullable) {
|
||||
resolve_float_or_percent(opt_vec_nullable->values[idx], idx);
|
||||
} else {
|
||||
resolve_float_or_percent(static_cast<const ConfigOptionFloatsOrPercents *>(opt.opt)->values[idx], idx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ctx->throw_exception("Unsupported vector variable type", opt.it_range);
|
||||
}
|
||||
@@ -2230,6 +2343,8 @@ namespace client
|
||||
[ px::bind(&expr::digits<false>, _val, _2, _3) ]
|
||||
| (kw["zdigits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1))
|
||||
[ px::bind(&expr::digits<true>, _val, _2, _3) ]
|
||||
| (kw["regex_replace"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > regular_expression > ',' > conditional_expression(_r1) > ')')
|
||||
[ px::bind(&expr::regex_replace, _val, _2, _3) ]
|
||||
| (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ]
|
||||
| (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ]
|
||||
| (kw["ceil"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::ceil, _1, _val) ]
|
||||
@@ -2311,6 +2426,7 @@ namespace client
|
||||
("min")
|
||||
("max")
|
||||
("random")
|
||||
("regex_replace")
|
||||
("filament_change")
|
||||
("repeat")
|
||||
("round")
|
||||
|
||||
@@ -105,6 +105,32 @@ PlatformFlavor platform_flavor()
|
||||
return s_platform_flavor;
|
||||
}
|
||||
|
||||
std::string platform_os_type()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return "win";
|
||||
#elif defined(__APPLE__)
|
||||
return "macos";
|
||||
#elif defined(__linux__) || defined(__LINUX__)
|
||||
return "linux";
|
||||
#else
|
||||
return "unknown";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string platform_architecture()
|
||||
{
|
||||
#if defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64)
|
||||
return "arm64";
|
||||
#elif defined(__x86_64__) || defined(__x86_64) || defined(__amd64__) || defined(__amd64) || defined(_M_X64) || defined(_M_AMD64)
|
||||
return "x86_64";
|
||||
#elif defined(__i386__) || defined(__i386) || defined(i386) || defined(_M_IX86)
|
||||
return "i386";
|
||||
#else
|
||||
return "unknown";
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string platform_to_string(Platform platform)
|
||||
|
||||
@@ -36,6 +36,8 @@ void detect_platform();
|
||||
Platform platform();
|
||||
PlatformFlavor platform_flavor();
|
||||
|
||||
std::string platform_os_type();
|
||||
std::string platform_architecture();
|
||||
std::string platform_to_string(Platform platform);
|
||||
std::string platform_flavor_to_string(PlatformFlavor pf);
|
||||
|
||||
|
||||
+120
-21
@@ -252,7 +252,7 @@ void extend_default_config_length(DynamicPrintConfig& config, const bool set_nil
|
||||
auto replace_nil_and_resize = [&](const std::string & key, int length){
|
||||
ConfigOption* raw_ptr = config.option(key);
|
||||
ConfigOptionVectorBase* opt_vec = static_cast<ConfigOptionVectorBase *>(raw_ptr);
|
||||
if(set_nil_to_default && raw_ptr->is_nil() && defaults.has(key) && std::find(filament_extruder_override_keys.begin(), filament_extruder_override_keys.end(), key) == filament_extruder_override_keys.end()){
|
||||
if(set_nil_to_default && raw_ptr->is_nil() && defaults.has(key) && !is_filament_extruder_override_key(key)){
|
||||
opt_vec->clear();
|
||||
opt_vec->resize(length, defaults.option(key));
|
||||
}
|
||||
@@ -470,6 +470,8 @@ void Preset::normalize(DynamicPrintConfig &config)
|
||||
continue;
|
||||
if (filament_options_with_variant.find(key) != filament_options_with_variant.end())
|
||||
continue;
|
||||
if (filament_dev_options.find(key) != filament_dev_options.end())
|
||||
continue;
|
||||
auto *opt = config.option(key, false);
|
||||
/*assert(opt != nullptr);
|
||||
assert(opt->is_vector());*/
|
||||
@@ -890,12 +892,11 @@ std::string Preset::get_printer_type(PresetBundle *preset_bundle)
|
||||
{
|
||||
if (preset_bundle) {
|
||||
auto config = &preset_bundle->printers.get_edited_preset().config;
|
||||
std::string vendor_name;
|
||||
for (auto vendor_profile : preset_bundle->vendors) {
|
||||
for (auto vendor_model : vendor_profile.second.models)
|
||||
if (vendor_model.name == config->opt_string("printer_model"))
|
||||
const auto& printer_model = config->opt_string("printer_model");
|
||||
for (const auto& vendor_profile : preset_bundle->vendors) {
|
||||
for (const auto& vendor_model : vendor_profile.second.models)
|
||||
if (vendor_model.name == printer_model)
|
||||
{
|
||||
vendor_name = vendor_profile.first;
|
||||
return vendor_model.model_id;
|
||||
}
|
||||
}
|
||||
@@ -907,11 +908,10 @@ std::string Preset::get_current_printer_type(PresetBundle *preset_bundle)
|
||||
{
|
||||
if (preset_bundle) {
|
||||
auto config = &(this->config);
|
||||
std::string vendor_name;
|
||||
for (auto vendor_profile : preset_bundle->vendors) {
|
||||
for (auto vendor_model : vendor_profile.second.models)
|
||||
if (vendor_model.name == config->opt_string("printer_model")) {
|
||||
vendor_name = vendor_profile.first;
|
||||
const auto& printer_model = config->opt_string("printer_model");
|
||||
for (const auto& vendor_profile : preset_bundle->vendors) {
|
||||
for (const auto& vendor_model : vendor_profile.second.models)
|
||||
if (vendor_model.name == printer_model) {
|
||||
return vendor_model.model_id;
|
||||
}
|
||||
}
|
||||
@@ -1044,9 +1044,16 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"lightning_prune_angle",
|
||||
"lightning_straightening_angle",
|
||||
"top_surface_pattern",
|
||||
"top_surface_expansion",
|
||||
"top_surface_expansion_margin",
|
||||
"top_surface_expansion_direction",
|
||||
"bottom_surface_pattern",
|
||||
"top_surface_fill_order",
|
||||
"bottom_surface_fill_order",
|
||||
"infill_direction",
|
||||
"solid_infill_direction",
|
||||
"top_layer_direction",
|
||||
"bottom_layer_direction",
|
||||
"counterbore_hole_bridging",
|
||||
"infill_shift_step",
|
||||
"sparse_infill_rotate_template",
|
||||
@@ -1058,6 +1065,8 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"skin_infill_density",
|
||||
"align_infill_direction_to_model",
|
||||
"extra_solid_infills",
|
||||
"center_of_surface_pattern",
|
||||
"separated_infills",
|
||||
"minimum_sparse_infill_area",
|
||||
"reduce_infill_retraction",
|
||||
"internal_solid_infill_pattern",
|
||||
@@ -1192,12 +1201,17 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"min_feature_size",
|
||||
"min_bead_width",
|
||||
"post_process",
|
||||
"slicing_pipeline_plugin",
|
||||
"plugins",
|
||||
"plugin_config_overrides",
|
||||
"process_change_extrusion_role_gcode",
|
||||
"min_length_factor",
|
||||
"wall_maximum_resolution",
|
||||
"wall_maximum_deviation",
|
||||
"small_perimeter_speed",
|
||||
"small_perimeter_threshold",
|
||||
"small_support_perimeter_speed",
|
||||
"small_support_perimeter_threshold",
|
||||
"bridge_angle",
|
||||
"internal_bridge_angle",
|
||||
"relative_bridge_angle",
|
||||
@@ -1268,6 +1282,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"wipe_tower_bridging",
|
||||
"wipe_tower_extra_flow",
|
||||
"single_extruder_multi_material_priming",
|
||||
"toolchange_ordering",
|
||||
"wipe_tower_rotation_angle",
|
||||
"tree_support_branch_distance_organic",
|
||||
"tree_support_branch_diameter_organic",
|
||||
@@ -1275,6 +1290,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"hole_to_polyhole",
|
||||
"hole_to_polyhole_threshold",
|
||||
"hole_to_polyhole_twisted",
|
||||
"hole_to_polyhole_max_edges",
|
||||
"mmu_segmented_region_max_width",
|
||||
"mmu_segmented_region_interlocking_depth",
|
||||
"small_area_infill_flow_compensation",
|
||||
@@ -1307,7 +1323,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
};
|
||||
|
||||
static std::vector<std::string> s_Preset_filament_options {/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "pellet_flow_coefficient", "volumetric_speed_coefficients", "filament_type",
|
||||
"filament_soluble", "filament_is_support", "filament_printable",
|
||||
"filament_soluble", "filament_is_support", "filament_printable", "filament_extruder_compatibility",
|
||||
"filament_max_volumetric_speed", "filament_adaptive_volumetric_speed",
|
||||
"filament_flow_ratio", "filament_density", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower",
|
||||
"filament_tower_interface_pre_extrusion_dist", "filament_tower_interface_pre_extrusion_length", "filament_tower_ironing_area", "filament_tower_interface_purge_volume",
|
||||
@@ -1318,13 +1334,25 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
|
||||
// "bed_type",
|
||||
//BBS:temperature_vitrification
|
||||
"temperature_vitrification", "reduce_fan_stop_start_freq","dont_slow_down_outer_wall", "slow_down_for_layer_cooling", "fan_min_speed",
|
||||
"fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "close_additional_fan_first_x_layers", "first_x_layer_fan_speed", "full_fan_speed_layer", "additional_fan_full_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed",
|
||||
"fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "close_additional_fan_first_x_layers", "first_x_layer_fan_speed", "full_fan_speed_layer", "initial_layer_fan_speed", "additional_fan_full_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed",
|
||||
"filament_start_gcode", "filament_end_gcode", "filament_change_extrusion_role_gcode",
|
||||
//exhaust fan control
|
||||
"activate_air_filtration","activate_air_filtration_during_print","activate_air_filtration_on_completion","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed",
|
||||
// Retract overrides
|
||||
"filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_lift_enforce", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_restart_extra", "filament_retraction_minimum_travel",
|
||||
"filament_retract_when_changing_layer", "filament_wipe", "filament_retract_before_wipe",
|
||||
"filament_deretraction_speed",
|
||||
"filament_retract_after_wipe", // Orca
|
||||
"filament_retract_before_wipe",
|
||||
"filament_retract_lift_above",
|
||||
"filament_retract_lift_below",
|
||||
"filament_retract_lift_enforce",
|
||||
"filament_retract_restart_extra",
|
||||
"filament_retract_when_changing_layer",
|
||||
"filament_retraction_length",
|
||||
"filament_retraction_minimum_travel",
|
||||
"filament_retraction_speed",
|
||||
"filament_wipe",
|
||||
"filament_z_hop",
|
||||
"filament_z_hop_types",
|
||||
// Profile compatibility
|
||||
"filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits",
|
||||
//BBS
|
||||
@@ -1342,8 +1370,19 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
|
||||
"filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "activate_chamber_temp_control", "chamber_minimal_temperature",
|
||||
"filament_long_retractions_when_cut","filament_retraction_distances_when_cut", "idle_temperature",
|
||||
//BBS filament change length while the extruder color
|
||||
"filament_change_length","filament_flush_volumetric_speed","filament_flush_temp", "filament_cooling_before_tower",
|
||||
"long_retractions_when_ec", "retraction_distances_when_ec"
|
||||
"filament_change_length","filament_flush_volumetric_speed","filament_flush_temp","filament_flush_temp_fast", "filament_cooling_before_tower",
|
||||
// Multi-nozzle pre-cooling / ramming / nozzle-change (nc) filament overrides
|
||||
"filament_ramming_volumetric_speed", "filament_ramming_volumetric_speed_nc",
|
||||
"filament_ramming_travel_time", "filament_ramming_travel_time_nc",
|
||||
"filament_pre_cooling_temperature", "filament_pre_cooling_temperature_nc",
|
||||
"filament_preheat_temperature_delta", "filament_retract_length_nc",
|
||||
"filament_change_length_nc", "filament_prime_volume", "filament_prime_volume_nc",
|
||||
"long_retractions_when_ec", "retraction_distances_when_ec",
|
||||
"plugin_config_overrides",
|
||||
//ams chamber
|
||||
"filament_dev_ams_drying_ams_limitations", "filament_dev_ams_drying_temperature", "filament_dev_ams_drying_time", "filament_dev_ams_drying_heat_distortion_temperature",
|
||||
"filament_dev_chamber_drying_bed_temperature", "filament_dev_chamber_drying_time",
|
||||
"filament_dev_drying_softening_temperature", "filament_dev_drying_cooling_temperature"
|
||||
};
|
||||
|
||||
static std::vector<std::string> s_Preset_machine_limits_options {
|
||||
@@ -1353,6 +1392,8 @@ static std::vector<std::string> s_Preset_machine_limits_options {
|
||||
"machine_min_extruding_rate", "machine_min_travel_rate",
|
||||
"machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e",
|
||||
"machine_max_junction_deviation",
|
||||
// Bedslinger mass/force limits
|
||||
"machine_max_force_Y", "machine_bed_mass_Y", "machine_max_printed_mass",
|
||||
//resonance avoidance ported from qidi slicer
|
||||
"resonance_avoidance", "min_resonance_avoidance_speed", "max_resonance_avoidance_speed",
|
||||
// Orca: input shaping
|
||||
@@ -1370,7 +1411,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"default_print_profile", "inherits",
|
||||
"silent_mode",
|
||||
"scan_first_layer", "enable_power_loss_recovery", "wrapping_detection_layers", "wrapping_exclude_area", "machine_load_filament_time", "machine_unload_filament_time", "machine_tool_change_time", "time_cost", "machine_pause_gcode", "template_custom_gcode",
|
||||
"nozzle_type", "nozzle_hrc","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types", "travel_slope", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","printer_structure",
|
||||
"nozzle_type", "nozzle_hrc","auxiliary_fan", "fan_direction", "nozzle_volume","upward_compatible_machine", "z_hop_types", "travel_slope", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","support_cooling_filter","cooling_filter_enabled","printer_structure","farthest_point_timelapse",
|
||||
"best_object_pos", "head_wrap_detect_zone",
|
||||
"host_type", "print_host", "printhost_apikey", "flashforge_serial_number", "bbl_use_printhost", "printer_agent",
|
||||
"print_host_webui",
|
||||
@@ -1382,7 +1423,14 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower",
|
||||
"z_offset",
|
||||
"disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "use_3mf", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut",
|
||||
"bed_temperature_formula", "nozzle_flush_dataset"
|
||||
"bed_temperature_formula", "nozzle_flush_dataset",
|
||||
// Multi-nozzle count + pre-heat model printer options
|
||||
"extruder_max_nozzle_count", "group_algo_with_time", "enable_pre_heating", "hotend_heating_rate", "hotend_cooling_rate",
|
||||
"machine_hotend_change_time", "machine_prepare_compensation_time",
|
||||
// Fast-purge printer flag + device/firmware-facing per-variant extruder-change
|
||||
// deretraction speed (unconsumed by the slicer; carried by H2D/A2L/X2D/P2S machine profiles).
|
||||
"support_fast_purge_mode", "deretract_speed_extruder_change",
|
||||
"plugin_config_overrides"
|
||||
};
|
||||
|
||||
static std::vector<std::string> s_Preset_sla_print_options {
|
||||
@@ -1857,6 +1905,26 @@ int PresetCollection::get_differed_values_to_update(Preset& preset, std::map<std
|
||||
if (opt_src)
|
||||
key_values[option] = opt_src->serialize();
|
||||
}
|
||||
|
||||
// Orca: force-emit nullable filament override keys whenever they hold a nil ("off")
|
||||
// value, even when the diff dropped them because the parent is nil too. Otherwise the
|
||||
// key is absent from the synced profile and the cloud re-materializes it against the
|
||||
// option's non-nil default (e.g. filament_retract_before_wipe -> 100%), silently
|
||||
// resurrecting an override the user turned off. See GitHub issue on Retract Before Wipe.
|
||||
if (m_type == Preset::TYPE_FILAMENT) {
|
||||
for (const std::string& opt_key : filament_extruder_override_keys) {
|
||||
if (key_values.count(opt_key))
|
||||
continue; // already carried by the diff
|
||||
const auto* opt_vec = dynamic_cast<const ConfigOptionVectorBase*>(preset.config.option(opt_key));
|
||||
if (opt_vec == nullptr)
|
||||
continue;
|
||||
bool has_nil = false;
|
||||
for (size_t i = 0; i < opt_vec->size(); ++i)
|
||||
if (opt_vec->is_nil(i)) { has_nil = true; break; }
|
||||
if (has_nil)
|
||||
key_values[opt_key] = opt_vec->serialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (auto iter = preset.config.cbegin(); iter != preset.config.cend(); ++iter)
|
||||
@@ -3390,7 +3458,18 @@ void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys&
|
||||
|
||||
for (int i = 0; i < int(opt_cur->values.size()); i++)
|
||||
{
|
||||
int init_id = i <= opt_init_max_id ? i : 0;
|
||||
const bool is_new_index = i > opt_init_max_id;
|
||||
int init_id = is_new_index ? 0 : i;
|
||||
if (is_new_index) {
|
||||
// Orca: intentional divergence from upstream. Any new vector index (at or
|
||||
// beyond the reference vector's length) is flagged dirty unconditionally --
|
||||
// independent of its value and nil-state -- so preset dirty-detection notices
|
||||
// per-extruder/filament entries added by growth (e.g. extruder count). This
|
||||
// applies to every vector option type routed through deep_diff().
|
||||
// Covered by tests/libslic3r/test_preset_diff.cpp.
|
||||
vec.emplace_back(opt_key + "#" + std::to_string(i));
|
||||
continue;
|
||||
}
|
||||
if (opt_cur->values[i] != opt_init->values[init_id]) {
|
||||
if (opt_cur->nullable()) {
|
||||
if (opt_cur->is_nil(i)) {
|
||||
@@ -3425,7 +3504,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
|
||||
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
|
||||
{
|
||||
//BBS: add bed_exclude_area
|
||||
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area") {
|
||||
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area" || opt_key == "slicing_pipeline_plugin") {
|
||||
// Scalar variable, or a vector variable, which is independent from number of extruders,
|
||||
// thus the vector is presented to the user as a single input.
|
||||
diff.emplace_back(opt_key);
|
||||
@@ -3756,6 +3835,26 @@ void PresetCollection::set_custom_preset_alias(Preset &preset)
|
||||
set_printer_hold_alias(preset.alias, preset);
|
||||
}
|
||||
|
||||
std::string PresetCollection::get_preset_alias(Preset &preset, bool force)
|
||||
{
|
||||
if (!preset.alias.empty())
|
||||
return preset.alias;
|
||||
else
|
||||
set_custom_preset_alias(preset);
|
||||
|
||||
if (!preset.alias.empty() || !force)
|
||||
return preset.alias;
|
||||
|
||||
std::string alias_name;
|
||||
std::string preset_name = preset.name;
|
||||
size_t end_pos = preset_name.find_first_of("@");
|
||||
if (end_pos != std::string::npos) {
|
||||
alias_name = preset_name.substr(0, end_pos);
|
||||
boost::trim_right(alias_name);
|
||||
}
|
||||
return alias_name;
|
||||
}
|
||||
|
||||
void PresetCollection::set_printer_hold_alias(const std::string &alias, Preset &preset, bool remove)
|
||||
{
|
||||
auto compatible_printers = dynamic_cast<ConfigOptionStrings *>(preset.config.option("compatible_printers"));
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
#define BBL_JSON_KEY_BOTTOM_TEXTURE_END_NAME "bottom_texture_end_name"
|
||||
#define BBL_JSON_KEY_USE_DOUBLE_EXTRUDER_DEFAULT_TEXTURE "use_double_extruder_default_texture"
|
||||
#define BBL_JSON_KEY_BOTTOM_TEXTURE_RECT "bottom_texture_rect"
|
||||
#define BBL_JSON_KEY_BOTTOM_TEXTURE_RECT_LONGER "bottom_texture_rect_longer"
|
||||
#define BBL_JSON_KEY_MIDDLE_TEXTURE_RECT "middle_texture_rect"
|
||||
|
||||
#define BBL_JSON_KEY_HOTEND_MODEL "hotend_model"
|
||||
@@ -150,6 +151,7 @@ public:
|
||||
std::string bottom_texture_end_name;
|
||||
std::string use_double_extruder_default_texture;
|
||||
std::string bottom_texture_rect;
|
||||
std::string bottom_texture_rect_longer;
|
||||
std::string middle_texture_rect;
|
||||
std::string hotend_model;
|
||||
PrinterVariant* variant(const std::string &name) {
|
||||
@@ -803,6 +805,9 @@ public:
|
||||
std::string path_from_name(const std::string &new_name, bool detach = false) const;
|
||||
std::string path_for_preset(const Preset & preset) const;
|
||||
|
||||
// Get the alias of a preset, setting it if it's empty
|
||||
std::string get_preset_alias(Preset &preset, bool force = false);
|
||||
|
||||
size_t num_default_presets() { return m_num_default_presets; }
|
||||
|
||||
protected:
|
||||
|
||||
+312
-26
@@ -52,9 +52,23 @@ static std::vector<std::string> s_project_options {
|
||||
"wipe_tower_rotation_angle",
|
||||
"curr_bed_type",
|
||||
"flush_multiplier",
|
||||
// Fast-purge mode: project-level purge control, inert at Default.
|
||||
"flush_multiplier_fast",
|
||||
"prime_volume_mode",
|
||||
"nozzle_volume_type",
|
||||
"filament_map_mode",
|
||||
"filament_map"
|
||||
"filament_map",
|
||||
// Per-filament nozzle-volume choice; project-level like filament_map so the per-filament
|
||||
// slot resolution survives preset switches.
|
||||
"filament_volume_map",
|
||||
// Per-filament physical-nozzle choice the grouping engine writes back; project-level so a
|
||||
// saved project round-trips the assignment alongside filament_map/filament_volume_map.
|
||||
"filament_nozzle_map",
|
||||
// Filament Track Switch device state: whether the switch is installed and ready, and
|
||||
// whether dynamic per-nozzle filament mapping is active. Persisted with the project and
|
||||
// restored from a saved 3mf; reset to false on load and set true only by live device sync.
|
||||
"has_filament_switcher",
|
||||
"enable_filament_dynamic_map"
|
||||
};
|
||||
|
||||
//Orca: add custom as default
|
||||
@@ -71,7 +85,8 @@ DynamicPrintConfig PresetBundle::construct_full_config(
|
||||
const DynamicPrintConfig& project_config,
|
||||
std::vector<Preset>& in_filament_presets,
|
||||
bool apply_extruder,
|
||||
std::optional<std::vector<int>> filament_maps_new)
|
||||
std::optional<std::vector<int>> filament_maps_new,
|
||||
std::optional<std::vector<int>> filament_volume_maps_new)
|
||||
{
|
||||
DynamicPrintConfig &printer_config = in_printer_preset.config;
|
||||
DynamicPrintConfig &print_config = in_print_preset.config;
|
||||
@@ -86,12 +101,23 @@ DynamicPrintConfig PresetBundle::construct_full_config(
|
||||
size_t num_filaments = in_filament_presets.size();
|
||||
|
||||
std::vector<int> filament_maps = out.option<ConfigOptionInts>("filament_map")->values;
|
||||
std::vector<int> filament_volume_maps(num_filaments, (int)nvtStandard);
|
||||
|
||||
ConfigOptionInts* filament_volume_map_opt = out.option<ConfigOptionInts>("filament_volume_map");
|
||||
if (filament_maps_new.has_value())
|
||||
filament_maps = *filament_maps_new;
|
||||
if (filament_volume_maps_new.has_value())
|
||||
filament_volume_maps = *filament_volume_maps_new;
|
||||
else if (filament_volume_map_opt && filament_volume_map_opt->values.size() == num_filaments)
|
||||
filament_volume_maps = filament_volume_map_opt->values;
|
||||
|
||||
// in some middle state, they may be different
|
||||
if (filament_maps.size() != num_filaments) {
|
||||
filament_maps.resize(num_filaments, 1);
|
||||
}
|
||||
if (filament_volume_maps.size() != num_filaments) {
|
||||
filament_volume_maps.resize(num_filaments, nvtStandard);
|
||||
}
|
||||
|
||||
auto *extruder_diameter = dynamic_cast<const ConfigOptionFloats *>(out.option("nozzle_diameter"));
|
||||
// Collect the "compatible_printers_condition" and "inherits" values over all presets (print, filaments, printers) into a single vector.
|
||||
@@ -112,17 +138,34 @@ DynamicPrintConfig PresetBundle::construct_full_config(
|
||||
inherits.emplace_back(print_inherits);
|
||||
|
||||
// BBS: update printer config related with variants
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 1, extruder_volume_type_count = 1;
|
||||
bool different_extruder = false;
|
||||
if (apply_extruder) {
|
||||
out.update_values_to_printer_extruders(out, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
out.update_values_to_printer_extruders(out, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
// update print config related with variants
|
||||
out.update_values_to_printer_extruders(out, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
different_extruder = out.support_different_extruders(extruder_count);
|
||||
extruder_volume_type_count = out.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
if ((extruder_count > 1) || different_extruder) {
|
||||
// Orca: keep processing variant_1 before variant_2 here; variant_2 slots are resolved
|
||||
// against the printer id/variant lists as rewritten by the variant_1 pass, and the
|
||||
// composed values depend on that order. Note the order is load-bearing, not correct
|
||||
// in general: the variant_2 pass reads the original full-width arrays through indices
|
||||
// resolved on the shrunk lists, which mis-reads presets whose variant_2 columns differ
|
||||
// per variant (e.g. X2D machine_max_speed_e/machine_max_acceleration_e). The slicing
|
||||
// path composes variant_2 first and is unaffected; changing the order here would alter
|
||||
// long-standing composed values, so any fix must re-baseline them.
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
// update print config related with variants
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
}
|
||||
}
|
||||
|
||||
if (num_filaments <= 1) {
|
||||
// BBS: update filament config related with variants
|
||||
DynamicPrintConfig filament_config = in_filament_presets[0].config;
|
||||
if (apply_extruder) filament_config.update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]);
|
||||
if (apply_extruder && ((extruder_count > 1) || different_extruder))
|
||||
filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0], (NozzleVolumeType)filament_volume_maps[0]);
|
||||
out.apply(filament_config);
|
||||
compatible_printers_condition.emplace_back(in_filament_presets[0].compatible_printers_condition());
|
||||
compatible_prints_condition.emplace_back(in_filament_presets[0].compatible_prints_condition());
|
||||
@@ -145,8 +188,8 @@ DynamicPrintConfig PresetBundle::construct_full_config(
|
||||
filament_temp_configs.resize(num_filaments);
|
||||
for (size_t i = 0; i < num_filaments; ++i) {
|
||||
filament_temp_configs[i] = *(filament_configs[i]);
|
||||
if (apply_extruder)
|
||||
filament_temp_configs[i].update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]);
|
||||
if (apply_extruder && ((extruder_count > 1) || different_extruder))
|
||||
filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i], (NozzleVolumeType)filament_volume_maps[i]);
|
||||
}
|
||||
|
||||
// loop through options and apply them to the resulting config.
|
||||
@@ -221,6 +264,7 @@ DynamicPrintConfig PresetBundle::construct_full_config(
|
||||
out.option<ConfigOptionString>("printer_settings_id", true)->value = in_printer_preset.name;
|
||||
out.option<ConfigOptionStrings>("filament_ids", true)->values = filament_ids;
|
||||
out.option<ConfigOptionInts>("filament_map", true)->values = filament_maps;
|
||||
out.option<ConfigOptionInts>("filament_volume_map", true)->values = filament_volume_maps;
|
||||
|
||||
auto add_if_some_non_empty = [&out](std::vector<std::string> &&values, const std::string &key) {
|
||||
bool nonempty = false;
|
||||
@@ -348,7 +392,7 @@ PresetBundle::PresetBundle()
|
||||
auto& default_config = this->filaments.default_preset().config;
|
||||
for(const std::string& opt_key : default_config.keys()){
|
||||
ConfigOption* opt = default_config.optptr(opt_key, false);
|
||||
bool is_override_key = std::find(filament_extruder_override_keys.begin(),filament_extruder_override_keys.end(), opt_key) != filament_extruder_override_keys.end();
|
||||
bool is_override_key = is_filament_extruder_override_key(opt_key);
|
||||
if(!is_override_key || !opt->nullable())
|
||||
continue;
|
||||
opt->deserialize("nil",ForwardCompatibilitySubstitutionRule::Disable);
|
||||
@@ -532,6 +576,12 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
|
||||
load_user_presets(dir_user_presets, substitution_rule);
|
||||
}
|
||||
|
||||
// Rewrite renamed compatible_printers / compatible_prints references before selection. Skipped
|
||||
// in validation mode so the profile validator (has_errors -> check_preset_references) sees the
|
||||
// raw vendor-JSON references instead of the silently-repaired ones.
|
||||
if (!validation_mode)
|
||||
this->normalize_compatible_presets();
|
||||
|
||||
this->update_multi_material_filament_presets();
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
|
||||
@@ -715,6 +765,8 @@ std::optional<FilamentBaseInfo> PresetBundle::get_filament_by_filament_id(const
|
||||
auto iter = std::find(compatible_printers.begin(), compatible_printers.end(), printer_name);
|
||||
if (iter != compatible_printers.end() && config.has("filament_printable")) {
|
||||
info.filament_printable = config.option<ConfigOptionInts>("filament_printable")->values[0];
|
||||
if (config.has("filament_extruder_compatibility"))
|
||||
info.set_filament_extruder_compatibility(config.option<ConfigOptionInts>("filament_extruder_compatibility")->values[0]);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -752,6 +804,8 @@ PresetsConfigSubstitutions PresetBundle::load_project_embedded_presets(std::vect
|
||||
|
||||
//this->update_multi_material_filament_presets();
|
||||
//this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
// Rewrite renamed compatible references before the caller (Plater) selects the project presets.
|
||||
this->normalize_compatible_presets();
|
||||
if (! errors_cummulative.empty())
|
||||
throw Slic3r::RuntimeError(errors_cummulative);
|
||||
|
||||
@@ -1122,6 +1176,9 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig &
|
||||
this->printers.update_after_user_presets_loaded();
|
||||
}*/
|
||||
|
||||
// Rewrite renamed compatible references in synced user presets before compatibility is evaluated.
|
||||
this->normalize_compatible_presets();
|
||||
|
||||
this->update_multi_material_filament_presets();
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
//this->load_selections(config, PresetPreferences());
|
||||
@@ -1807,6 +1864,9 @@ PresetsConfigSubstitutions PresetBundle::update_subscribed_presets(
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to save bundle metadata to: " << metadata_save_path.string();
|
||||
}
|
||||
|
||||
// Rewrite renamed compatible references in synced user presets before compatibility is evaluated.
|
||||
this->normalize_compatible_presets();
|
||||
|
||||
this->update_multi_material_filament_presets();
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
|
||||
@@ -2660,6 +2720,12 @@ void PresetBundle::update_selections(AppConfig &config)
|
||||
std::vector<int> filament_maps(filament_colors.size(), 1);
|
||||
project_config.option<ConfigOptionInts>("filament_map")->values = filament_maps;
|
||||
|
||||
std::vector<int> filament_nozzle_maps(filament_colors.size(), 0);
|
||||
project_config.option<ConfigOptionInts>("filament_nozzle_map")->values = filament_nozzle_maps;
|
||||
|
||||
std::vector<int> filament_volume_maps(filament_colors.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
project_config.option<ConfigOptionInts>("filament_volume_map")->values = filament_volume_maps;
|
||||
|
||||
std::vector<std::string> extruder_ams_count_str;
|
||||
if (config.has_printer_setting(initial_printer_profile_name, "extruder_ams_count")) {
|
||||
boost::algorithm::split(extruder_ams_count_str, config.get_printer_setting(initial_printer_profile_name, "extruder_ams_count"), boost::algorithm::is_any_of(","));
|
||||
@@ -2804,6 +2870,12 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
std::vector<int> filament_maps(filament_colors.size(), 1);
|
||||
project_config.option<ConfigOptionInts>("filament_map")->values = filament_maps;
|
||||
|
||||
std::vector<int> filament_nozzle_maps(filament_colors.size(), 0);
|
||||
project_config.option<ConfigOptionInts>("filament_nozzle_map")->values = filament_nozzle_maps;
|
||||
|
||||
std::vector<int> filament_volume_maps(filament_colors.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
project_config.option<ConfigOptionInts>("filament_volume_map")->values = filament_volume_maps;
|
||||
|
||||
std::vector<std::string> extruder_ams_count_str;
|
||||
if (config.has_printer_setting(initial_printer_profile_name, "extruder_ams_count")) {
|
||||
boost::algorithm::split(extruder_ams_count_str, config.get_printer_setting(initial_printer_profile_name, "extruder_ams_count"), boost::algorithm::is_any_of(","));
|
||||
@@ -2980,7 +3052,8 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> ne
|
||||
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
|
||||
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
|
||||
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
|
||||
|
||||
ConfigOptionInts* filament_nozzle_map = project_config.option<ConfigOptionInts>("filament_nozzle_map");
|
||||
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
|
||||
|
||||
filament_color->resize(n);
|
||||
// Sync filament multi colour
|
||||
@@ -2990,6 +3063,8 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> ne
|
||||
}
|
||||
filament_color_type->resize(n);
|
||||
filament_map->values.resize(n, 1);
|
||||
filament_nozzle_map->values.resize(n, 0);
|
||||
filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
ams_multi_color_filment.resize(n);
|
||||
|
||||
// BBS set new filament color to new_color
|
||||
@@ -3017,7 +3092,8 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
|
||||
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
|
||||
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
|
||||
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
|
||||
|
||||
ConfigOptionInts* filament_nozzle_map = project_config.option<ConfigOptionInts>("filament_nozzle_map");
|
||||
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
|
||||
|
||||
filament_color->resize(n);
|
||||
// Sync filament multi colour
|
||||
@@ -3027,6 +3103,8 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
|
||||
}
|
||||
filament_color_type->resize(n);
|
||||
filament_map->values.resize(n, 1);
|
||||
filament_nozzle_map->values.resize(n, 0);
|
||||
filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
ams_multi_color_filment.resize(n);
|
||||
|
||||
//BBS set new filament color to new_color
|
||||
@@ -3067,15 +3145,25 @@ void PresetBundle::update_num_filaments(unsigned int to_del_flament_id)
|
||||
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
|
||||
ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
|
||||
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
|
||||
ConfigOptionInts* filament_nozzle_map = project_config.option<ConfigOptionInts>("filament_nozzle_map");
|
||||
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
|
||||
if (filament_color->values.size() > to_del_flament_id) {
|
||||
filament_color->values.erase(filament_color->values.begin() + to_del_flament_id);
|
||||
if (filament_map->values.size() > to_del_flament_id) {
|
||||
filament_map->values.erase(filament_map->values.begin() + to_del_flament_id);
|
||||
}
|
||||
if (filament_nozzle_map->values.size() > to_del_flament_id) {
|
||||
filament_nozzle_map->values.erase(filament_nozzle_map->values.begin() + to_del_flament_id);
|
||||
}
|
||||
if (filament_volume_map->values.size() > to_del_flament_id) {
|
||||
filament_volume_map->values.erase(filament_volume_map->values.begin() + to_del_flament_id);
|
||||
}
|
||||
}
|
||||
else {
|
||||
filament_color->values.resize(to_del_flament_id);
|
||||
filament_map->values.resize(to_del_flament_id, 1);
|
||||
filament_nozzle_map->values.resize(to_del_flament_id, 0);
|
||||
filament_volume_map->values.resize(to_del_flament_id, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
}
|
||||
|
||||
// lambda function to erase or resize the container
|
||||
@@ -3298,6 +3386,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
ConfigOptionStrings *filament_color = project_config.option<ConfigOptionStrings>("filament_colour");
|
||||
ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
|
||||
ConfigOptionInts * filament_map = project_config.option<ConfigOptionInts>("filament_map");
|
||||
ConfigOptionInts * filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
|
||||
if (color_only) {
|
||||
auto get_map_index = [&ams_infos](const std::vector<AMSMapInfo> &infos, const AMSMapInfo &temp) {
|
||||
for (int i = 0; i < infos.size(); i++) {
|
||||
@@ -3479,6 +3568,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
ams_multi_color_filment = exist_multi_color_filment;
|
||||
this->filament_presets = exist_filament_presets;
|
||||
filament_map->values.resize(exist_filament_presets.size(), 1);
|
||||
filament_volume_map->values.resize(exist_filament_presets.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
}
|
||||
else {//overwrite;
|
||||
bool has_placeholders = std::any_of(ams_infos.begin(), ams_infos.end(),
|
||||
@@ -3533,12 +3623,14 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
this->filament_presets = result_presets;
|
||||
ams_multi_color_filment = result_multi_colors;
|
||||
filament_map->values.resize(total, 1);
|
||||
filament_volume_map->values.resize(total, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
} else {
|
||||
// BBL: existing wholesale replace
|
||||
filament_color->values = ams_filament_colors;
|
||||
filament_color_type->values = ams_filament_color_types;
|
||||
this->filament_presets = ams_filament_presets;
|
||||
filament_map->values.resize(ams_filament_colors.size(), 1);
|
||||
filament_volume_map->values.resize(ams_filament_colors.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
}
|
||||
|
||||
auto& print_config = this->prints.get_edited_preset().config;
|
||||
@@ -3832,19 +3924,35 @@ void PresetBundle::update_filament_count()
|
||||
: filament_presets.back());
|
||||
}
|
||||
|
||||
bool PresetBundle::support_different_extruders()
|
||||
bool PresetBundle::support_different_extruders() const
|
||||
{
|
||||
Preset& printer_preset = this->printers.get_edited_preset();
|
||||
const Preset& printer_preset = this->printers.get_edited_preset();
|
||||
int extruder_count;
|
||||
bool supported = printer_preset.config.support_different_extruders(extruder_count);
|
||||
|
||||
return supported;
|
||||
}
|
||||
|
||||
DynamicPrintConfig PresetBundle::full_config(bool apply_extruder, std::optional<std::vector<int>>filament_maps) const
|
||||
std::vector<int> PresetBundle::get_default_nozzle_volume_types_for_filaments(std::vector<int>& f_maps)
|
||||
{
|
||||
std::vector<int> result;
|
||||
int filament_count = f_maps.size();
|
||||
result.resize(filament_count, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
|
||||
auto opt_nozzle_volume_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(this->project_config.option("nozzle_volume_type"));
|
||||
for (int index = 0; index < filament_count; index++)
|
||||
{
|
||||
if (opt_nozzle_volume_type && opt_nozzle_volume_type->values.size() > (f_maps[index] - 1))
|
||||
result[index] = opt_nozzle_volume_type->values[f_maps[index] - 1];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DynamicPrintConfig PresetBundle::full_config(bool apply_extruder, std::optional<std::vector<int>>filament_maps, std::optional<std::vector<int>> filament_volume_maps) const
|
||||
{
|
||||
return (this->printers.get_edited_preset().printer_technology() == ptFFF) ?
|
||||
this->full_fff_config(apply_extruder, filament_maps) :
|
||||
this->full_fff_config(apply_extruder, filament_maps, filament_volume_maps) :
|
||||
this->full_sla_config();
|
||||
}
|
||||
|
||||
@@ -3858,16 +3966,52 @@ DynamicPrintConfig PresetBundle::full_config_secure(std::optional<std::vector<in
|
||||
config.erase("printhost_cafile");
|
||||
config.erase("printhost_user");
|
||||
config.erase("printhost_password");
|
||||
config.erase("printhost_port");
|
||||
config.erase("printhost_port");
|
||||
return config;
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::vector<float>>> PresetBundle::get_full_flush_matrix(bool with_multiplier) const
|
||||
{
|
||||
auto full_config = this->full_config();
|
||||
int extruder_nums = full_config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
|
||||
std::vector<double> flush_volume_value = full_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values;
|
||||
int filament_nums = full_config.option<ConfigOptionStrings>("filament_type")->values.size();
|
||||
|
||||
std::vector<std::vector<std::vector<float>>> matrix;
|
||||
for (size_t extruder_id = 0; extruder_id < extruder_nums; ++extruder_id) {
|
||||
std::vector<float> flush_matrix(cast<float>(get_flush_volumes_matrix(flush_volume_value, extruder_id, extruder_nums)));
|
||||
std::vector<std::vector<float>> wipe_volumes;
|
||||
for (unsigned int i = 0; i < filament_nums; ++i)
|
||||
wipe_volumes.push_back(std::vector<float>(flush_matrix.begin() + i * filament_nums, flush_matrix.begin() + (i + 1) * filament_nums));
|
||||
|
||||
matrix.emplace_back(wipe_volumes);
|
||||
}
|
||||
|
||||
if (with_multiplier) {
|
||||
// Fast purge mode uses flush_multiplier_fast; the default prime_volume_mode==Default
|
||||
// (or the key absent) reads flush_multiplier, so this is inert.
|
||||
auto* mode_opt = project_config.option<ConfigOptionEnum<PrimeVolumeMode>>("prime_volume_mode");
|
||||
const bool use_fast = mode_opt && mode_opt->value == PrimeVolumeMode::pvmFast;
|
||||
auto* mult_opt = project_config.option<ConfigOptionFloats>(use_fast ? "flush_multiplier_fast" : "flush_multiplier");
|
||||
auto flush_multiplies = mult_opt ? mult_opt->values : project_config.option<ConfigOptionFloats>("flush_multiplier")->values;
|
||||
flush_multiplies.resize(extruder_nums, 1);
|
||||
for (size_t extruder_id = 0; extruder_id < extruder_nums; ++extruder_id) {
|
||||
for (auto& vec : matrix[extruder_id]) {
|
||||
for (auto& v : vec)
|
||||
v *= flush_multiplies[extruder_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
const std::set<std::string> ignore_settings_list ={
|
||||
"inherits",
|
||||
"print_settings_id", "filament_settings_id", "printer_settings_id"
|
||||
};
|
||||
|
||||
DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps_new) const
|
||||
DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps_new, std::optional<std::vector<int>> filament_volume_maps_new) const
|
||||
{
|
||||
DynamicPrintConfig out;
|
||||
out.apply(FullPrintConfig::defaults());
|
||||
@@ -3881,8 +4025,17 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
|
||||
size_t num_filaments = this->filament_presets.size();
|
||||
|
||||
std::vector<int> filament_maps = out.option<ConfigOptionInts>("filament_map")->values;
|
||||
std::vector<int> filament_volume_maps(num_filaments, (int)nvtStandard);
|
||||
|
||||
ConfigOptionInts* filament_volume_map_opt = out.option<ConfigOptionInts>("filament_volume_map");
|
||||
if (filament_maps_new.has_value())
|
||||
filament_maps = *filament_maps_new;
|
||||
if (filament_volume_maps_new.has_value()) {
|
||||
filament_volume_maps = *filament_volume_maps_new;
|
||||
out.option<ConfigOptionInts>("filament_volume_map", true)->values = filament_volume_maps;
|
||||
}
|
||||
else if (filament_volume_map_opt && filament_volume_map_opt->values.size() == num_filaments)
|
||||
filament_volume_maps = filament_volume_map_opt->values;
|
||||
//in some middle state, they may be different
|
||||
if (filament_maps.size() != num_filaments) {
|
||||
filament_maps.resize(num_filaments, 1);
|
||||
@@ -3890,6 +4043,9 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
|
||||
else {
|
||||
assert(filament_maps.size() == num_filaments);
|
||||
}
|
||||
if (filament_volume_maps.size() != num_filaments) {
|
||||
filament_volume_maps.resize(num_filaments, nvtStandard);
|
||||
}
|
||||
|
||||
auto* extruder_diameter = dynamic_cast<const ConfigOptionFloats*>(out.option("nozzle_diameter"));
|
||||
// Collect the "compatible_printers_condition" and "inherits" values over all presets (print, filaments, printers) into a single vector.
|
||||
@@ -3919,18 +4075,34 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
|
||||
different_settings.emplace_back(different_print_settings);
|
||||
|
||||
//BBS: update printer config related with variants
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 1, extruder_volume_type_count = 1;
|
||||
bool different_extruder = false;
|
||||
if (apply_extruder) {
|
||||
out.update_values_to_printer_extruders(out, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
out.update_values_to_printer_extruders(out, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
//update print config related with variants
|
||||
out.update_values_to_printer_extruders(out, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
different_extruder = out.support_different_extruders(extruder_count);
|
||||
extruder_volume_type_count = out.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
if ((extruder_count > 1) || different_extruder) {
|
||||
// Orca: keep processing variant_1 before variant_2 here; variant_2 slots are resolved
|
||||
// against the printer id/variant lists as rewritten by the variant_1 pass, and the
|
||||
// composed values depend on that order. Note the order is load-bearing, not correct
|
||||
// in general: the variant_2 pass reads the original full-width arrays through indices
|
||||
// resolved on the shrunk lists, which mis-reads presets whose variant_2 columns differ
|
||||
// per variant (e.g. X2D machine_max_speed_e/machine_max_acceleration_e). The slicing
|
||||
// path composes variant_2 first and is unaffected; changing the order here would alter
|
||||
// long-standing composed values, so any fix must re-baseline them.
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
//update print config related with variants
|
||||
out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
}
|
||||
}
|
||||
|
||||
if (num_filaments <= 1) {
|
||||
//BBS: update filament config related with variants
|
||||
DynamicPrintConfig filament_config = this->filaments.get_edited_preset().config;
|
||||
if (apply_extruder)
|
||||
filament_config.update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]);
|
||||
if (apply_extruder && ((extruder_count > 1) || different_extruder))
|
||||
filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0], (NozzleVolumeType)filament_volume_maps[0]);
|
||||
out.apply(filament_config);
|
||||
compatible_printers_condition.emplace_back(this->filaments.get_edited_preset().compatible_printers_condition());
|
||||
compatible_prints_condition .emplace_back(this->filaments.get_edited_preset().compatible_prints_condition());
|
||||
@@ -4023,8 +4195,8 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
|
||||
filament_temp_configs.resize(num_filaments);
|
||||
for (size_t i = 0; i < num_filaments; ++i) {
|
||||
filament_temp_configs[i] = *(filament_configs[i]);
|
||||
if (apply_extruder)
|
||||
filament_temp_configs[i].update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]);
|
||||
if (apply_extruder && ((extruder_count > 1) || different_extruder))
|
||||
filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i], (NozzleVolumeType)filament_volume_maps[i]);
|
||||
}
|
||||
|
||||
// loop through options and apply them to the resulting config.
|
||||
@@ -4268,6 +4440,10 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
};
|
||||
clear_compatible_printers(config);
|
||||
|
||||
// Dynamic per-nozzle filament mapping reflects live device state, not a stored setting;
|
||||
// drop it from any imported config so it only comes from the connected printer.
|
||||
config.erase("enable_filament_dynamic_map");
|
||||
|
||||
#if 0
|
||||
size_t num_extruders = (printer_technology == ptFFF) ?
|
||||
std::min(config.option<ConfigOptionFloats>("nozzle_diameter" )->values.size(),
|
||||
@@ -4756,6 +4932,8 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
model.use_double_extruder_default_texture = it.value();
|
||||
} else if (boost::iequals(it.key(), BBL_JSON_KEY_BOTTOM_TEXTURE_RECT)) {
|
||||
model.bottom_texture_rect = it.value();
|
||||
} else if (boost::iequals(it.key(), BBL_JSON_KEY_BOTTOM_TEXTURE_RECT_LONGER)) {
|
||||
model.bottom_texture_rect_longer = it.value();
|
||||
} else if (boost::iequals(it.key(), BBL_JSON_KEY_MIDDLE_TEXTURE_RECT)) {
|
||||
model.middle_texture_rect = it.value();
|
||||
}
|
||||
@@ -5240,6 +5418,49 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite a preset-name-list field (compatible_printers / compatible_prints) so references to a
|
||||
// renamed system preset point at the current name. Sibling-collection analog of
|
||||
// Preset::normalize_inherits: target.find_preset(name, false) resolves "renamed_from" recursively
|
||||
// and returns nullptr for unknown names, so we rewrite only on a positive, changed match and leave
|
||||
// user/deleted names untouched.
|
||||
static void normalize_compatible_field(Preset &preset, const char *field_key, PresetCollection &target)
|
||||
{
|
||||
auto *opt = preset.config.option<ConfigOptionStrings>(field_key);
|
||||
if (opt == nullptr)
|
||||
return;
|
||||
for (std::string &name : opt->values) {
|
||||
if (name.empty())
|
||||
continue;
|
||||
if (const Preset *resolved = target.find_preset(name, false); resolved != nullptr && resolved->name != name)
|
||||
name = resolved->name;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve compatible_printers / compatible_prints references that point at a renamed system preset
|
||||
// to the current name, mirroring Preset::normalize_inherits for the "inherits" field. Because these
|
||||
// fields reference presets in sibling collections (printers / prints / sla_prints), the resolution
|
||||
// cannot happen inside a single collection's load_presets() and runs here, after update_system_maps()
|
||||
// has built every collection's rename map. Must be called before selection so the rewritten stored
|
||||
// preset is copied into the edited preset by select_preset (no spurious "modified" flag).
|
||||
void PresetBundle::normalize_compatible_presets()
|
||||
{
|
||||
// compatible_printers references a printer preset; compatible_prints (filaments / SLA materials)
|
||||
// references a process preset. System presets are normalized too: a vendor profile can itself
|
||||
// reference a sibling preset by a name that was later renamed, and the rewrite is in-memory only
|
||||
// (system presets are never persisted back to vendor JSON). (begin()/end() skip defaults.)
|
||||
auto normalize = [this](PresetCollection &holders, PresetCollection *processes) {
|
||||
for (Preset &p : holders) {
|
||||
normalize_compatible_field(p, "compatible_printers", this->printers);
|
||||
if (processes != nullptr)
|
||||
normalize_compatible_field(p, "compatible_prints", *processes);
|
||||
}
|
||||
};
|
||||
normalize(this->prints, nullptr);
|
||||
normalize(this->filaments, &this->prints);
|
||||
normalize(this->sla_prints, nullptr);
|
||||
normalize(this->sla_materials, &this->sla_prints);
|
||||
}
|
||||
|
||||
void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible)
|
||||
{
|
||||
const Preset &printer_preset = this->printers.get_edited_preset();
|
||||
@@ -5494,6 +5715,9 @@ bool PresetBundle::has_errors(bool check_duplicate_filament_subtypes) const
|
||||
if (check_duplicate_filament_subtypes && this->check_duplicate_filament_subtypes())
|
||||
has_errors = true;
|
||||
|
||||
if (this->check_preset_references())
|
||||
has_errors = true;
|
||||
|
||||
return has_errors;
|
||||
}
|
||||
|
||||
@@ -5524,6 +5748,68 @@ static std::string preset_file_uri(const std::string &file)
|
||||
return uri;
|
||||
}
|
||||
|
||||
// Orca: validator-only. Flag any system preset whose inherits / compatible_printers /
|
||||
// compatible_prints references a name that no longer resolves. Uses find_preset (exact match, then
|
||||
// the renamed_from map - no fuzzy find_preset2, no alias resolution), so:
|
||||
// nullptr -> the referenced preset was deleted/renamed away (name is dangling),
|
||||
// resolved != name -> the reference uses an old name that renamed_from maps to a current one.
|
||||
// Both should be fixed at the source rather than relying on load-time normalization - which is why
|
||||
// normalize_compatible_presets() is skipped in validation mode (see load_presets), so this sees the
|
||||
// raw vendor-JSON references. Safe under a single-vendor run (-v) too: inherits / compatible_printers
|
||||
// / compatible_prints only name same-vendor or OrcaFilamentLibrary presets (both loaded), so a
|
||||
// reference that does not resolve is genuinely dangling rather than an unloaded cross-vendor preset.
|
||||
bool PresetBundle::check_preset_references() const
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
// Resolve one reference (an inherits parent or a compatible_* entry) against its target
|
||||
// collection and log if it is dangling (unknown) or uses a renamed preset's old name.
|
||||
auto report_ref = [&](const Preset &p, const std::string &name, const PresetCollection &target,
|
||||
const char *verb, const char *noun) {
|
||||
const Preset *resolved = target.find_preset(name, false);
|
||||
if (resolved == nullptr) {
|
||||
found = true;
|
||||
BOOST_LOG_TRIVIAL(error) << "Preset \"" << p.name << "\" " << verb << " unknown " << noun << " \"" << name << "\":\n"
|
||||
<< preset_file_uri(p.file);
|
||||
} else if (resolved->name != name) {
|
||||
found = true;
|
||||
BOOST_LOG_TRIVIAL(error) << "Preset \"" << p.name << "\" " << verb << " renamed " << noun << " \"" << name
|
||||
<< "\" (now \"" << resolved->name << "\"):\n" << preset_file_uri(p.file);
|
||||
}
|
||||
};
|
||||
|
||||
auto check_list = [&](const Preset &p, const char *key, const PresetCollection &target) {
|
||||
const auto *opt = p.config.option<ConfigOptionStrings>(key);
|
||||
if (opt == nullptr)
|
||||
return;
|
||||
for (const std::string &name : opt->values)
|
||||
if (!name.empty())
|
||||
report_ref(p, name, target, "references", key);
|
||||
};
|
||||
|
||||
auto check_collection = [&](const PresetCollection &holders, const PresetCollection *processes) {
|
||||
for (const Preset &p : holders) {
|
||||
if (!p.is_system)
|
||||
continue;
|
||||
if (const std::string &inh = p.inherits(); !inh.empty())
|
||||
report_ref(p, inh, holders, "inherits", "parent");
|
||||
check_list(p, "compatible_printers", this->printers);
|
||||
if (processes != nullptr)
|
||||
check_list(p, "compatible_prints", *processes);
|
||||
}
|
||||
};
|
||||
|
||||
// Printers carry no compatible_printers/compatible_prints (those name a printer, so a printer
|
||||
// holding them makes no sense); check_list is a no-op for them, so only their inherits is checked.
|
||||
check_collection(this->printers, nullptr);
|
||||
check_collection(this->prints, nullptr);
|
||||
check_collection(this->filaments, &this->prints);
|
||||
check_collection(this->sla_prints, nullptr);
|
||||
check_collection(this->sla_materials, &this->sla_prints);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
// Orca: a filament is matched from the AMS by (filament_id + printer compatibility).
|
||||
// For any one printer, at most one instantiated filament preset with a given
|
||||
// filament_id may be compatible - otherwise the AMS match is ambiguous and the
|
||||
|
||||
@@ -72,6 +72,25 @@ struct FilamentBaseInfo
|
||||
bool is_support{ false };
|
||||
bool is_system{ true };
|
||||
int filament_printable = 3;
|
||||
|
||||
// filament_extruder_compatibility packs one compatibility level per extruder into a single
|
||||
// 32-bit int, 3 bits per extruder (up to 10 extruders). Levels: 0 = printable, 1 = error,
|
||||
// 2 = critical warning, 3 = warning (4-7 reserved). extruder_id is 0-based.
|
||||
int get_extruder_compatibility(int extruder_id) const {
|
||||
constexpr int bits_per_extruder = 3;
|
||||
constexpr int extruder_mask = (1 << bits_per_extruder) - 1; // 0x7
|
||||
constexpr int max_extruder_count = 32 / bits_per_extruder; // 10
|
||||
|
||||
if (extruder_id < 0 || extruder_id >= max_extruder_count)
|
||||
return 0;
|
||||
return (m_filament_extruder_compatibility >> (bits_per_extruder * extruder_id)) & extruder_mask;
|
||||
}
|
||||
|
||||
void set_filament_extruder_compatibility(int value) { m_filament_extruder_compatibility = value; }
|
||||
int get_filament_extruder_compatibility() const { return m_filament_extruder_compatibility; }
|
||||
|
||||
private:
|
||||
int m_filament_extruder_compatibility = 0;
|
||||
};
|
||||
|
||||
enum BundleType{
|
||||
@@ -156,7 +175,8 @@ public:
|
||||
const DynamicPrintConfig &project_config,
|
||||
std::vector<Preset> &in_filament_presets,
|
||||
bool apply_extruder,
|
||||
std::optional<std::vector<int>> filament_maps_new);
|
||||
std::optional<std::vector<int>> filament_maps_new,
|
||||
std::optional<std::vector<int>> filament_volume_maps_new = std::nullopt);
|
||||
|
||||
// ORCA: utility function to find the vendor for a given preset name
|
||||
static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type);
|
||||
@@ -364,13 +384,22 @@ public:
|
||||
bool has_defauls_only() const
|
||||
{ return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); }
|
||||
|
||||
DynamicPrintConfig full_config(bool apply_extruder = true, std::optional<std::vector<int>>filament_maps = std::nullopt) const;
|
||||
DynamicPrintConfig full_config(bool apply_extruder = true, std::optional<std::vector<int>>filament_maps = std::nullopt, std::optional<std::vector<int>> filament_volume_maps = std::nullopt) const;
|
||||
// full_config() with the some "useless" config removed.
|
||||
DynamicPrintConfig full_config_secure(std::optional<std::vector<int>>filament_maps = std::nullopt) const;
|
||||
|
||||
// Default per-filament nozzle-volume types: each filament inherits the volume type of the
|
||||
// extruder it maps to (1-based f_maps), Standard when unknown.
|
||||
std::vector<int> get_default_nozzle_volume_types_for_filaments(std::vector<int>& f_maps);
|
||||
|
||||
// Per-extruder flush matrix [extruder_id][from_filament][to_filament] in mm^3, optionally scaled
|
||||
// by the per-extruder flush_multiplier (or flush_multiplier_fast when prime_volume_mode==Fast).
|
||||
// Used by the print-dispatch nozzle-mapping flush-weight estimate.
|
||||
std::vector<std::vector<std::vector<float>>> get_full_flush_matrix(bool with_multiplier = true) const;
|
||||
|
||||
//BBS: add some functions for multiple extruders
|
||||
int get_printer_extruder_count() const;
|
||||
bool support_different_extruders();
|
||||
bool support_different_extruders() const;
|
||||
|
||||
// Orca: Ensure filament_presets has at least one slot per nozzle on FFF printers.
|
||||
// Called from (load|update)_selections before the parallel project_config arrays
|
||||
@@ -448,6 +477,11 @@ public:
|
||||
void update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible);
|
||||
void update_compatible(PresetSelectCompatibleType select_other_if_incompatible) { this->update_compatible(select_other_if_incompatible, select_other_if_incompatible); }
|
||||
|
||||
// Rewrite compatible_printers / compatible_prints references that point at a renamed system
|
||||
// preset to the current name, mirroring Preset::normalize_inherits for the "inherits" field.
|
||||
// Call after loading presets and before selection; requires update_system_maps() to have run.
|
||||
void normalize_compatible_presets();
|
||||
|
||||
// Set the is_visible flag for printer vendors, printer models and printer variants
|
||||
// based on the user configuration.
|
||||
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
|
||||
@@ -480,9 +514,13 @@ public:
|
||||
return { Preset::TYPE_PRINTER, Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL };
|
||||
}
|
||||
|
||||
// Orca: for validation only. The duplicate filament subtype check is opt-in for now
|
||||
// Orca: for validation only.
|
||||
bool has_errors(bool check_duplicate_filament_subtypes = false) const;
|
||||
|
||||
// Orca: for validation only. Flag any system preset whose inherits / compatible_printers /
|
||||
// compatible_prints references a deleted (unknown) or renamed (old) preset name.
|
||||
bool check_preset_references() const;
|
||||
|
||||
private:
|
||||
// Orca: validation only - flag any printer with two or more compatible
|
||||
// filament presets sharing one filament_id (ambiguous AMS subtype match).
|
||||
@@ -510,7 +548,7 @@ private:
|
||||
/*ConfigSubstitutions load_config_file_config_bundle(
|
||||
const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);*/
|
||||
|
||||
DynamicPrintConfig full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps=std::nullopt) const;
|
||||
DynamicPrintConfig full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps=std::nullopt, std::optional<std::vector<int>> filament_volume_maps=std::nullopt) const;
|
||||
DynamicPrintConfig full_sla_config() const;
|
||||
|
||||
// Orca: used for validation only
|
||||
|
||||
+918
-262
File diff suppressed because it is too large
Load Diff
+169
-14
@@ -17,12 +17,14 @@
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#include "GCode/GCodeProcessor.hpp"
|
||||
#include "MultiMaterialSegmentation.hpp"
|
||||
#include "ObjectID.hpp"
|
||||
#include "libslic3r.h"
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "calib.hpp"
|
||||
|
||||
@@ -38,6 +40,7 @@ class SupportLayer;
|
||||
class TreeSupportData;
|
||||
class TreeSupport;
|
||||
class ExtrusionLayers;
|
||||
namespace MultiNozzleUtils { class NozzleGroupResultBase; class LayeredNozzleGroupResult; }
|
||||
|
||||
#define MAX_OUTER_NOZZLE_DIAMETER 4
|
||||
// BBS: move from PrintObjectSlice.cpp
|
||||
@@ -99,6 +102,14 @@ enum PrintObjectStep {
|
||||
posCount,
|
||||
};
|
||||
|
||||
enum class SlicingPipelineStepPlugin {
|
||||
posSlice, posPerimeters, posEstimateCurledExtrusions, posPrepareInfill, posInfill, posIroning, posContouring,
|
||||
posSupportMaterial, posDetectOverhangsForLift, posSimplifyPath, psWipeTower, psSkirtBrim,
|
||||
// Fires from the GUI G-code export/post-process seam (PostProcessor.cpp), NOT from Print::process().
|
||||
// At this step the plugin edits the exported G-code file in place; see SlicingPipelinePluginCapability for the full contract.
|
||||
psGCodePostProcess
|
||||
};
|
||||
|
||||
// A PrintRegion object represents a group of volumes to print
|
||||
// sharing the same config (including the same assigned extruder(s))
|
||||
class PrintRegion
|
||||
@@ -418,7 +429,7 @@ public:
|
||||
// (layer height, first layer height, raft settings, print nozzle diameter etc).
|
||||
const SlicingParameters& slicing_parameters() const { return m_slicing_params; }
|
||||
// Orca: XYZ shrinkage compensation has introduced the const Vec3d &object_shrinkage_compensation parameter to the function below
|
||||
static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z, const Vec3d &object_shrinkage_compensation);
|
||||
static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z, const Vec3d &object_shrinkage_compensation, std::vector<int> variant_index = std::vector<int>());
|
||||
|
||||
size_t num_printing_regions() const throw() { return m_shared_regions->all_regions.size(); }
|
||||
const PrintRegion& printing_region(size_t idx) const throw() { return *m_shared_regions->all_regions[idx].get(); }
|
||||
@@ -489,7 +500,7 @@ public:
|
||||
// If ! m_slicing_params.valid, recalculate.
|
||||
void update_slicing_parameters();
|
||||
|
||||
static PrintObjectConfig object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders);
|
||||
static PrintObjectConfig object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders, std::vector<int>& variant_index);
|
||||
|
||||
private:
|
||||
void make_perimeters();
|
||||
@@ -772,6 +783,7 @@ struct WipeTowerData
|
||||
number_of_toolchanges = -1;
|
||||
depth = 0.f;
|
||||
brim_width = 0.f;
|
||||
height = 0.f;
|
||||
rib_offset = Vec2f::Zero();
|
||||
wipe_tower_mesh_data = std::nullopt;
|
||||
}
|
||||
@@ -891,6 +903,11 @@ private: // Prevents erroneous use by other classes.
|
||||
typedef std::pair<PrintObject *, bool> PrintObjectInfo;
|
||||
|
||||
public:
|
||||
using SlicingPipelineHookFn = std::function<void(Print&, const PrintObject*, SlicingPipelineStepPlugin)>;
|
||||
// Cross-layer injection (mirrors ConfigBase::set_resolve_capability_fn): the GUI/plugin
|
||||
// layer registers a dispatcher; libslic3r stays free of any plugin/Python dependency.
|
||||
static void set_slicing_pipeline_hook_fn(SlicingPipelineHookFn fn) { s_slicing_pipeline_hook_fn = std::move(fn); }
|
||||
|
||||
Print() = default;
|
||||
virtual ~Print() { this->clear(); }
|
||||
|
||||
@@ -958,7 +975,7 @@ public:
|
||||
[object_id](const PrintObject *obj) { return obj->id() == object_id; });
|
||||
return (it == m_objects.end()) ? nullptr : *it;
|
||||
}
|
||||
//BBS: Function to get m_brimMap;
|
||||
// Orca: Old callers still expect object-keyed brim paths.
|
||||
std::map<ObjectID, ExtrusionEntityCollection>&
|
||||
get_brimMap() { return m_brimMap; }
|
||||
|
||||
@@ -973,11 +990,11 @@ public:
|
||||
struct SkirtBrimGroup {
|
||||
struct Brim {
|
||||
ExtrusionEntityCollection brim;
|
||||
std::vector<ObjectID> object_ids;
|
||||
std::vector<ObjectInstanceID> instances;
|
||||
};
|
||||
|
||||
ExtrusionEntityCollection skirt;
|
||||
std::vector<ObjectID> object_ids;
|
||||
std::vector<ObjectInstanceID> instances;
|
||||
// Brims stay separate unless Combine brims merges colliding brims inside this group.
|
||||
std::vector<Brim> brims;
|
||||
};
|
||||
@@ -1003,15 +1020,50 @@ public:
|
||||
const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const;
|
||||
const ToolOrdering& tool_ordering() const { return m_tool_ordering; }
|
||||
|
||||
void update_filament_maps_to_config(std::vector<int> f_maps);
|
||||
void update_filament_maps_to_config(std::vector<int> f_maps, std::vector<int> f_volume_maps = std::vector<int>{}, std::vector<int> f_nozzle_maps = std::vector<int>{});
|
||||
// Write-back for a selector (per-layer planned) grouping result. When a filament actually
|
||||
// migrates between nozzle variants, rebuilds the per-slot filament arrays so it holds one
|
||||
// slot per variant and recomputes the extruder retract overrides against the expanded
|
||||
// slots — update_filament_maps_to_config's single-slot rebuild cannot represent a
|
||||
// migration. A result without migration reduces to a single grouping and takes the
|
||||
// three-map write-back like the static paths.
|
||||
void update_to_config_by_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result);
|
||||
void apply_config_for_render(const DynamicConfig &config);
|
||||
|
||||
// 1 based group ids
|
||||
std::vector<int> get_filament_maps() const;
|
||||
FilamentMapMode get_filament_map_mode() const;
|
||||
std::vector<int> get_filament_volume_maps() const;
|
||||
std::vector<int> get_filament_nozzle_maps() const;
|
||||
// get the group label of filament
|
||||
size_t get_extruder_id(unsigned int filament_id) const;
|
||||
|
||||
// The region every extruder can reach,
|
||||
// i.e. the intersection of all per-extruder printable areas. Falls back to the full printable_area
|
||||
// for single-nozzle printers and whenever extruder_printable_area is not populated (all current
|
||||
// single/dual profiles), so the wipe-tower-center clamp is byte-identical to full-bed clamping there.
|
||||
Polygons get_extruder_shared_printable_polygon() const;
|
||||
|
||||
// Logical (extruder, nozzle) grouping result produced by ToolOrdering during reorder.
|
||||
// Consumed by GCode via get_layered_nozzle_group_result()->get_nozzle_id(filament, layer) etc.
|
||||
void set_nozzle_group_result(std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> result) { m_nozzle_group_result = result; }
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> get_nozzle_group_result() const { return m_nozzle_group_result; }
|
||||
std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> get_layered_nozzle_group_result() const;
|
||||
|
||||
// True only when the project opts into the per-layer filament selector
|
||||
// (enable_filament_dynamic_map) in auto-for-flush mode on a multi-extruder machine. Gates the
|
||||
// dynamic (per-layer) regroup branch in ToolOrdering::reorder_extruders_for_minimum_flush_volume,
|
||||
// the sequential (by-object) plan stitching in Print::process, and GCode's use of the cached
|
||||
// sequential plans. No profile sets the flag, so the static grouping path (byte-identical
|
||||
// output) is the only one taken unless the user enables the selector.
|
||||
bool is_dynamic_group_reorder() const;
|
||||
|
||||
// Per-object tool orderings planned by the sequential (by-object) selector regroup with
|
||||
// cross-object nozzle-status threading. GCode export must consume these exact plans: a fresh
|
||||
// per-object construction would re-plan from a different seed and diverge from the published
|
||||
// stitched result. Empty on the static path.
|
||||
const std::map<const PrintObject*, ToolOrdering>& sequential_dynamic_orderings() const { return m_sequential_dynamic_orderings; }
|
||||
|
||||
const std::vector<std::vector<DynamicPrintConfig>>& get_extruder_filament_info() const { return m_extruder_filament_info; }
|
||||
void set_extruder_filament_info(const std::vector<std::vector<DynamicPrintConfig>>& filament_info) { m_extruder_filament_info = filament_info; }
|
||||
|
||||
@@ -1037,6 +1089,18 @@ public:
|
||||
*/
|
||||
std::vector<std::set<int>> get_physical_unprintable_filaments(const std::vector<unsigned int>& used_filaments) const;
|
||||
|
||||
/**
|
||||
* @brief Determines the forbidden nozzle volume types for each used filament
|
||||
*
|
||||
* A filament may declare the extruder variants it supports. Every volume type offered by the
|
||||
* printer's extruders that the filament does not support is forbidden for that filament.
|
||||
* Hybrid volumes are ignored on both sides, and filaments declaring no variants are unrestricted.
|
||||
*
|
||||
* @param used_filaments Totally used filaments when slicing
|
||||
* @return A map from used filament index to the set of nozzle volume types it cannot print on
|
||||
*/
|
||||
std::map<int, std::set<NozzleVolumeType>> get_filament_unprintable_flow(const std::vector<unsigned int> &used_filaments) const;
|
||||
|
||||
std::vector<double> get_extruder_printable_height() const;
|
||||
std::vector<Polygons> get_extruder_printable_polygons() const;
|
||||
std::vector<Polygons> get_extruder_unprintable_polygons() const;
|
||||
@@ -1120,7 +1184,12 @@ public:
|
||||
bool is_all_objects_are_short() const {
|
||||
return std::all_of(this->objects().begin(), this->objects().end(), [&](PrintObject* obj) { return obj->height() < scale_(this->config().nozzle_height.value); });
|
||||
}
|
||||
|
||||
|
||||
// Post-slicing config-slot resolvers: map a (filament, layer) pair to the index of its
|
||||
// per-(extruder x volume type) column in the expanded variant arrays, cached by grouping context.
|
||||
int get_filament_config_indx(int filament_id, int layer_id);
|
||||
int get_nozzle_config_index(int filament_id, int layer_id);
|
||||
|
||||
// Orca: Implement prusa's filament shrink compensation approach
|
||||
// Returns if all used filaments have same shrinkage compensations.
|
||||
bool has_same_shrinkage_compensations() const;
|
||||
@@ -1130,6 +1199,57 @@ public:
|
||||
std::tuple<float, float> object_skirt_offset(double margin_height = 0) const;
|
||||
|
||||
protected:
|
||||
struct FilamentIndexKey
|
||||
{
|
||||
int filament_id;
|
||||
ExtruderType extruder;
|
||||
NozzleVolumeType nozzle_volume_type;
|
||||
|
||||
bool operator==(const FilamentIndexKey &other) const
|
||||
{
|
||||
return filament_id == other.filament_id && extruder == other.extruder && nozzle_volume_type == other.nozzle_volume_type;
|
||||
}
|
||||
};
|
||||
|
||||
struct PrintIndexKey
|
||||
{
|
||||
int filament_id;
|
||||
int extruder_id;
|
||||
ExtruderType extruder;
|
||||
NozzleVolumeType nozzle_volume_type;
|
||||
|
||||
bool operator==(const PrintIndexKey &other) const
|
||||
{
|
||||
return filament_id == other.filament_id && extruder_id == other.extruder_id && extruder == other.extruder && nozzle_volume_type == other.nozzle_volume_type;
|
||||
}
|
||||
};
|
||||
|
||||
struct FilamentIndexKeyHash
|
||||
{
|
||||
std::size_t operator()(const FilamentIndexKey &k) const
|
||||
{
|
||||
size_t h1 = std::hash<int>{}(k.filament_id);
|
||||
size_t h2 = std::hash<int>{}(static_cast<int>(k.extruder));
|
||||
size_t h3 = std::hash<int>{}(static_cast<int>(k.nozzle_volume_type));
|
||||
return h1 ^ (h2 << 8) ^ (h3 << 12);
|
||||
}
|
||||
};
|
||||
struct PrintIndexKeyHash
|
||||
{
|
||||
std::size_t operator()(const PrintIndexKey &k) const
|
||||
{
|
||||
size_t h1 = std::hash<int>{}(k.filament_id);
|
||||
size_t h2 = std::hash<int>{}(k.extruder_id);
|
||||
size_t h3 = std::hash<int>{}(static_cast<int>(k.extruder));
|
||||
size_t h4 = std::hash<int>{}(static_cast<int>(k.nozzle_volume_type));
|
||||
return h1 ^ (h2 << 8) ^ (h3 << 12) ^ (h4 << 16);
|
||||
}
|
||||
};
|
||||
using FilamentIndexMap = std::unordered_map<FilamentIndexKey, int, FilamentIndexKeyHash>;
|
||||
using PrintIndexMap = std::unordered_map<PrintIndexKey, int, PrintIndexKeyHash>;
|
||||
int get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, FilamentIndexMap &index_map);
|
||||
int get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, PrintIndexMap &index_map);
|
||||
|
||||
// Invalidates the step, and its depending steps in Print.
|
||||
bool invalidate_step(PrintStep step);
|
||||
|
||||
@@ -1143,10 +1263,27 @@ private:
|
||||
void _make_skirt();
|
||||
void _make_wipe_tower();
|
||||
void finalize_first_layer_convex_hull();
|
||||
void update_filament_self_index_cache();
|
||||
// Deduplicates, per filament, the (extruder type x volume type) variants the grouping
|
||||
// result routes it through; filaments the plan never routes get their default-map
|
||||
// assignment so the slot resolution never depends on the (mutable) filament_map. config
|
||||
// must carry extruder_type; returns false when it does not. Both the slice-time write-back
|
||||
// and the apply-time reproduction call this with m_ori_full_print_config so the two
|
||||
// expansions resolve identical slots.
|
||||
bool collect_filament_variant_uses(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result,
|
||||
const DynamicPrintConfig& config,
|
||||
std::unordered_map<int, std::vector<FilamentVariantUse>>& uses) const;
|
||||
|
||||
// Islands of objects and their supports extruded at the 1st layer.
|
||||
Polygons first_layer_islands() const;
|
||||
|
||||
static SlicingPipelineHookFn s_slicing_pipeline_hook_fn;
|
||||
bool m_pipeline_plugin_active { false };
|
||||
void run_pipeline_hook(SlicingPipelineStepPlugin step, const PrintObject* object) {
|
||||
if (m_pipeline_plugin_active && s_slicing_pipeline_hook_fn)
|
||||
s_slicing_pipeline_hook_fn(*this, object, step);
|
||||
}
|
||||
|
||||
PrintConfig m_config;
|
||||
PrintObjectConfig m_default_object_config;
|
||||
PrintRegionConfig m_default_region_config;
|
||||
@@ -1154,18 +1291,18 @@ private:
|
||||
PrintRegionPtrs m_print_regions;
|
||||
|
||||
//SoftFever
|
||||
bool m_isBBLPrinter;
|
||||
bool m_isBBLPrinter = false;
|
||||
|
||||
// Ordered collections of extrusion paths to build skirt loops and brim.
|
||||
ExtrusionEntityCollection m_skirt;
|
||||
std::vector<SkirtBrimGroup> m_skirt_brim_groups;
|
||||
bool m_has_shared_per_object_skirt { false };
|
||||
// BBS: collecting extrusion paths to build brim by objs
|
||||
// Orca: Object-keyed brim paths kept for existing code.
|
||||
std::map<ObjectID, ExtrusionEntityCollection> m_brimMap;
|
||||
std::map<ObjectID, ExtrusionEntityCollection> m_supportBrimMap;
|
||||
// Orca: cached occupied brim footprints used when grouping per-object skirts.
|
||||
std::map<ObjectID, ExPolygons> m_objectBrimAreas;
|
||||
std::map<ObjectID, ExPolygons> m_supportBrimAreas;
|
||||
// Orca: Actual brim paths keyed by object instance.
|
||||
std::map<ObjectInstanceID, ExtrusionEntityCollection> m_brimMapByInstance;
|
||||
// Orca: Translated brim areas keyed by instance, used to find touching brims.
|
||||
std::map<ObjectInstanceID, ExPolygons> m_objectBrimAreasByInstance;
|
||||
// Convex hull of the 1st layer extrusions.
|
||||
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
|
||||
// It does NOT encompass user extrusions generated by custom G-code,
|
||||
@@ -1176,6 +1313,24 @@ private:
|
||||
|
||||
std::vector<std::vector<DynamicPrintConfig>> m_extruder_filament_info;
|
||||
|
||||
// Logical (extruder, nozzle) grouping result, set by ToolOrdering during reorder.
|
||||
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> m_nozzle_group_result;
|
||||
|
||||
// Sequential (by-object) selector plans, keyed by object; see sequential_dynamic_orderings().
|
||||
// Rebuilt (or cleared) on every process().
|
||||
std::map<const PrintObject*, ToolOrdering> m_sequential_dynamic_orderings;
|
||||
|
||||
// Used to cache filament parameter information
|
||||
FilamentIndexMap m_filament_index_map;
|
||||
// Used to cache printer and process parameter information
|
||||
PrintIndexMap m_nozzle_index_map;
|
||||
// Orca: filament ids already reported as missing a nozzle-group entry this slice. get_config_index()
|
||||
// falls back per-filament/per-layer in the g-code hot path, so this dedupes its log to once per
|
||||
// filament instead of flooding thousands of identical error lines. Cleared with the caches each slice.
|
||||
std::set<int> m_missing_nozzle_group_logged;
|
||||
// save the config value of "filament_self_index"
|
||||
std::vector<int> m_filament_self_index;
|
||||
|
||||
// Following section will be consumed by the GCodeGenerator.
|
||||
ToolOrdering m_tool_ordering;
|
||||
WipeTowerData m_wipe_tower_data {m_tool_ordering};
|
||||
@@ -1189,7 +1344,7 @@ private:
|
||||
std::vector<unsigned int> m_slice_used_filaments_first_layer;
|
||||
|
||||
//BBS: plate's origin
|
||||
Vec3d m_origin;
|
||||
Vec3d m_origin {0, 0, 0};
|
||||
//BBS: modified_count
|
||||
int m_modified_count {0};
|
||||
//BBS
|
||||
|
||||
+165
-31
@@ -224,7 +224,11 @@ static t_config_option_keys print_config_diffs(
|
||||
const DynamicPrintConfig &new_full_config,
|
||||
DynamicPrintConfig &filament_overrides,
|
||||
int plate_index,
|
||||
std::vector<int>& filament_maps)
|
||||
std::vector<int>& filament_maps,
|
||||
// Per-slot machine indices when the filament arrays hold the per-variant expansion of a
|
||||
// selector result (one slot per variant a filament migrates through); the per-filament
|
||||
// map cannot index the expanded override arrays. Null on the single-slot path.
|
||||
const std::vector<int>* dynamic_override_indices = nullptr)
|
||||
{
|
||||
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
|
||||
const std::string filament_prefix = "filament_";
|
||||
@@ -240,7 +244,15 @@ static t_config_option_keys print_config_diffs(
|
||||
const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr;
|
||||
|
||||
if (opt_new_filament != nullptr) {
|
||||
compute_filament_override_value(opt_key, opt_old, opt_new, opt_new_filament, new_full_config, print_diff, filament_overrides, filament_maps);
|
||||
std::vector<int> filament_map_indices;
|
||||
if (dynamic_override_indices)
|
||||
filament_map_indices = *dynamic_override_indices;
|
||||
else {
|
||||
filament_map_indices.assign(filament_maps.size(), 0);
|
||||
for (int i = 0; i < filament_maps.size(); i++)
|
||||
filament_map_indices[i] = filament_maps[i] - 1;
|
||||
}
|
||||
compute_filament_override_value(opt_key, opt_old, opt_new, opt_new_filament, new_full_config, print_diff, filament_overrides, filament_map_indices);
|
||||
} else if (*opt_new != *opt_old) {
|
||||
//BBS: add plate_index logic for wipe_tower_x/wipe_tower_y
|
||||
if (!opt_key.compare("wipe_tower_x") || !opt_key.compare("wipe_tower_y")) {
|
||||
@@ -724,7 +736,7 @@ PrintObjectRegions::BoundingBox find_modifier_volume_extents(const PrintObjectRe
|
||||
return out;
|
||||
}
|
||||
|
||||
PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders);
|
||||
PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders, std::vector<int>& variant_index);
|
||||
|
||||
void print_region_ref_inc(PrintRegion &r) { ++ r.m_ref_cnt; }
|
||||
void print_region_ref_reset(PrintRegion &r) { r.m_ref_cnt = 0; }
|
||||
@@ -738,7 +750,8 @@ bool verify_update_print_object_regions(
|
||||
const PrintRegionConfig &default_region_config,
|
||||
size_t num_extruders,
|
||||
PrintObjectRegions &print_object_regions,
|
||||
const std::function<void(const PrintRegionConfig&, const PrintRegionConfig&, const t_config_option_keys&)> &callback_invalidate)
|
||||
const std::function<void(const PrintRegionConfig&, const PrintRegionConfig&, const t_config_option_keys&)> &callback_invalidate,
|
||||
std::vector<int>& variant_index)
|
||||
{
|
||||
// Sort by ModelVolume ID.
|
||||
model_volumes_sort_by_id(model_volumes);
|
||||
@@ -783,7 +796,7 @@ bool verify_update_print_object_regions(
|
||||
} else if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox))
|
||||
// Such parent region does not exist. If it is needed, then we need to reslice.
|
||||
// Only create new region for a modifier, which actually modifies config of it's parent.
|
||||
if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders);
|
||||
if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders, variant_index);
|
||||
config != parent_region.region->config())
|
||||
// This modifier newly overrides a region, which it did not before. We need to reslice.
|
||||
return false;
|
||||
@@ -791,8 +804,8 @@ bool verify_update_print_object_regions(
|
||||
}
|
||||
}
|
||||
PrintRegionConfig cfg = region.parent == -1 ?
|
||||
region_config_from_model_volume(default_region_config, layer_range.config, **it_model_volume, num_extruders) :
|
||||
region_config_from_model_volume(layer_range.volume_regions[region.parent].region->config(), nullptr, **it_model_volume, num_extruders);
|
||||
region_config_from_model_volume(default_region_config, layer_range.config, **it_model_volume, num_extruders, variant_index) :
|
||||
region_config_from_model_volume(layer_range.volume_regions[region.parent].region->config(), nullptr, **it_model_volume, num_extruders, variant_index);
|
||||
if (cfg != region.region->config()) {
|
||||
// Region configuration changed.
|
||||
if (print_region_ref_cnt(*region.region) == 0) {
|
||||
@@ -964,6 +977,7 @@ static PrintObjectRegions* generate_print_object_regions(
|
||||
size_t num_extruders,
|
||||
const float xy_contour_compensation,
|
||||
const std::vector<unsigned int> &painting_extruders,
|
||||
std::vector<int> &variant_index,
|
||||
const bool has_painted_fuzzy_skin)
|
||||
{
|
||||
// Reuse the old object or generate a new one.
|
||||
@@ -1022,7 +1036,7 @@ static PrintObjectRegions* generate_print_object_regions(
|
||||
// Add a model volume, assign an existing region or generate a new one.
|
||||
layer_range.volume_regions.push_back({
|
||||
&volume, -1,
|
||||
get_create_region(region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders)),
|
||||
get_create_region(region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders, variant_index)),
|
||||
bbox
|
||||
});
|
||||
} else if (volume.is_negative_volume()) {
|
||||
@@ -1039,7 +1053,7 @@ static PrintObjectRegions* generate_print_object_regions(
|
||||
if (parent_volume.is_model_part() || parent_volume.is_modifier())
|
||||
if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) {
|
||||
// Only create new region for a modifier, which actually modifies config of it's parent.
|
||||
if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders);
|
||||
if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders, variant_index);
|
||||
config != parent_region.region->config()) {
|
||||
added = true;
|
||||
layer_range.volume_regions.push_back({ &volume, parent_region_id, get_create_region(std::move(config)), bbox });
|
||||
@@ -1162,24 +1176,58 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
}
|
||||
|
||||
//apply extruder related values
|
||||
std::vector<int> print_variant_index;
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 1, extruder_volume_type_count = 1;
|
||||
bool different_extruder = false;
|
||||
// Filled only when the filament arrays are rebuilt from a persisted selector result below;
|
||||
// print_config_diffs then keys the retract overrides per expanded slot.
|
||||
std::vector<int> dynamic_slot_indices;
|
||||
|
||||
different_extruder = new_full_config.support_different_extruders(extruder_count);
|
||||
extruder_volume_type_count = new_full_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
if (!extruder_applied) {
|
||||
new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
//update print config related with variants
|
||||
new_full_config.update_values_to_printer_extruders(new_full_config, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
if ((extruder_count > 1) || different_extruder) {
|
||||
// variant_2 must be processed first, because variant_1 will make `printer_extruder_id` and `printer_extruder_variant` half of the size that makes `get_index_for_extruder` no longer work properly
|
||||
new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
//update print config related with variants
|
||||
print_variant_index = new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
}
|
||||
else
|
||||
print_variant_index.resize(1, 0);
|
||||
|
||||
m_ori_full_print_config = new_full_config;
|
||||
new_full_config.update_values_to_printer_extruders_for_multiple_filaments(new_full_config, filament_options_with_variant, "filament_self_index", "filament_extruder_variant");
|
||||
|
||||
std::set<std::string> filament_keys = filament_options_with_variant;
|
||||
filament_keys.insert("filament_self_index");
|
||||
// A persisted selector result with an actual migration means the last slice rebuilt the
|
||||
// per-slot filament arrays from it (one slot per variant a filament prints through).
|
||||
// Reproduce that exact expansion here so an unchanged config diffs empty — the expanded
|
||||
// keys invalidate the wipe tower / g-code export, and the placeholder parser aliases
|
||||
// the full config — instead of trimming back to one slot per filament.
|
||||
auto group_result = std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(this->get_nozzle_group_result());
|
||||
std::unordered_map<int, std::vector<FilamentVariantUse>> filament_variant_uses;
|
||||
if (group_result && group_result->is_support_dynamic_nozzle_map()
|
||||
&& collect_filament_variant_uses(*group_result, m_ori_full_print_config, filament_variant_uses))
|
||||
new_full_config.update_filament_config_values_for_multiple_extruders(m_ori_full_print_config, filament_variant_uses,
|
||||
extruder_count, extruder_volume_type_count, filament_keys,
|
||||
"filament_self_index", "filament_extruder_variant",
|
||||
&dynamic_slot_indices);
|
||||
else if ((extruder_count > 1) || different_extruder)
|
||||
new_full_config.update_values_to_printer_extruders_for_multiple_filaments(m_ori_full_print_config, extruder_count, extruder_volume_type_count, filament_keys,
|
||||
"filament_self_index", "filament_extruder_variant");
|
||||
}
|
||||
else {
|
||||
//should not come here, we can not get the result of print_variant, for the values have been updated
|
||||
//we just use the default values here
|
||||
auto variant_opt = dynamic_cast<const ConfigOptionStrings *>(new_full_config.option("printer_extruder_variant"));
|
||||
print_variant_index.resize(variant_opt->values.size());
|
||||
for (int e_index = 0; e_index < variant_opt->values.size(); e_index++)
|
||||
{
|
||||
print_variant_index[e_index] = e_index;
|
||||
}
|
||||
}
|
||||
// else {
|
||||
// int extruder_count;
|
||||
// bool different_extruder = new_full_config.support_different_extruders(extruder_count);
|
||||
// print_variant_index.resize(extruder_count);
|
||||
// for (int e_index = 0; e_index < extruder_count; e_index++)
|
||||
// {
|
||||
// print_variant_index[e_index] = e_index;
|
||||
// }
|
||||
// }
|
||||
|
||||
auto opt_filament_map = new_full_config.option<ConfigOptionInts>("filament_map");
|
||||
std::vector<int> filament_maps = opt_filament_map ? opt_filament_map->values : std::vector<int>();
|
||||
@@ -1187,7 +1235,15 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
// Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
|
||||
DynamicPrintConfig filament_overrides;
|
||||
//BBS: add plate index
|
||||
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, filament_maps);
|
||||
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, filament_maps,
|
||||
dynamic_slot_indices.empty() ? nullptr : &dynamic_slot_indices);
|
||||
// Orca: filament_map_2 is engine-derived state, never a user input: the rebuild below
|
||||
// recomputes it from filament_map/filament_volume_map/the variant slots on every apply
|
||||
// (all of which are diffed and invalidation-listed on their own), and the grouping
|
||||
// write-back overwrites it during process(). The incoming full config only ever carries
|
||||
// the ConfigDef default, so diffing it would invalidate every print step on each apply
|
||||
// for any multi-extruder printer and permanently invalidate fresh slice results.
|
||||
print_diff.erase(std::remove(print_diff.begin(), print_diff.end(), "filament_map_2"), print_diff.end());
|
||||
t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config, this->m_plate_index);
|
||||
// Collect changes to object and region configs.
|
||||
t_config_option_keys object_diff = m_default_object_config.diff(new_full_config);
|
||||
@@ -1195,10 +1251,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
|
||||
//BBS: process the filament_map related logic
|
||||
std::unordered_set<std::string> print_diff_set(print_diff.begin(), print_diff.end());
|
||||
if (print_diff_set.find("filament_map_mode") == print_diff_set.end())
|
||||
if (!print_diff_set.empty() && print_diff_set.find("filament_map_mode") == print_diff_set.end())
|
||||
{
|
||||
FilamentMapMode map_mode = new_full_config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode", true)->value;
|
||||
if (map_mode < fmmManual) {
|
||||
if (is_auto_filament_map_mode(map_mode)) {
|
||||
if (print_diff_set.find("filament_map") != print_diff_set.end()) {
|
||||
print_diff_set.erase("filament_map");
|
||||
//full_config_diff.erase("filament_map");
|
||||
@@ -1207,9 +1263,29 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
old_opt->set(new_opt);
|
||||
m_config.filament_map = *new_opt;
|
||||
}
|
||||
if (print_diff_set.find("filament_volume_map") != print_diff_set.end()) {
|
||||
print_diff_set.erase("filament_volume_map");
|
||||
//full_config_diff.erase("filament_volume_map");
|
||||
ConfigOptionInts* old_opt = m_full_print_config.option<ConfigOptionInts>("filament_volume_map", true);
|
||||
ConfigOptionInts* new_opt = new_full_config.option<ConfigOptionInts>("filament_volume_map", true);
|
||||
old_opt->set(new_opt);
|
||||
m_config.filament_volume_map = *new_opt;
|
||||
}
|
||||
if (print_diff_set.find("filament_nozzle_map") != print_diff_set.end()) {
|
||||
print_diff_set.erase("filament_nozzle_map");
|
||||
//full_config_diff.erase("filament_nozzle_map");
|
||||
ConfigOptionInts* old_opt = m_full_print_config.option<ConfigOptionInts>("filament_nozzle_map", true);
|
||||
ConfigOptionInts* new_opt = new_full_config.option<ConfigOptionInts>("filament_nozzle_map", true);
|
||||
old_opt->set(new_opt);
|
||||
m_config.filament_nozzle_map = *new_opt;
|
||||
}
|
||||
}
|
||||
else {
|
||||
print_diff_set.erase("extruder_ams_count");
|
||||
if (map_mode == fmmManual) {
|
||||
// filament_nozzle_map is an engine output, not a GUI input, in manual mode
|
||||
print_diff_set.erase("filament_nozzle_map");
|
||||
}
|
||||
std::vector<int> old_filament_map = m_config.filament_map.values;
|
||||
std::vector<int> new_filament_map = new_full_config.option<ConfigOptionInts>("filament_map", true)->values;
|
||||
|
||||
@@ -1226,14 +1302,62 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (same_map)
|
||||
if (same_map) {
|
||||
print_diff_set.erase("filament_map");
|
||||
|
||||
// The extruder retract overrides are keyed by the (unchanged) filament map;
|
||||
// recompute them and drop diffs whose recomputed value matches the current
|
||||
// config, so a cosmetic reordering of unused filaments does not invalidate.
|
||||
const auto& retract_keys = print_config_def.extruder_retract_keys();
|
||||
const std::string filament_prefix = "filament_";
|
||||
std::vector<int> old_f_map_indices(old_filament_map.size(), 0);
|
||||
for (size_t i = 0; i < old_filament_map.size(); i++)
|
||||
old_f_map_indices[i] = old_filament_map[i] - 1;
|
||||
|
||||
for (const auto& rk : retract_keys) {
|
||||
if (print_diff_set.find(rk) == print_diff_set.end())
|
||||
continue;
|
||||
const ConfigOption* opt_old = m_config.option(rk);
|
||||
const ConfigOption* opt_new_m = new_full_config.option(rk);
|
||||
const ConfigOption* opt_new_f = new_full_config.option(filament_prefix + rk);
|
||||
if (opt_old && opt_new_m && opt_new_f) {
|
||||
std::unique_ptr<ConfigOption> opt_recomputed(opt_new_m->clone());
|
||||
opt_recomputed->apply_override(opt_new_f, old_f_map_indices);
|
||||
if (*opt_old == *opt_recomputed)
|
||||
print_diff_set.erase(rk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (print_diff_set.size() != print_diff.size())
|
||||
print_diff.assign(print_diff_set.begin(), print_diff_set.end());
|
||||
}
|
||||
|
||||
//filament_map_2
|
||||
// Orca: seed with 0-based extruder indices so the copy stays a valid slot map even when the
|
||||
// variant options are absent below and the rebuild loop is skipped (unit tests, degenerate
|
||||
// presets); the loop overwrites every entry when it runs.
|
||||
m_config.filament_map_2.values = filament_maps;
|
||||
for (auto& v : m_config.filament_map_2.values)
|
||||
--v;
|
||||
auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(new_full_config.option("extruder_type"));
|
||||
auto opt_filament_volume_maps = dynamic_cast<const ConfigOptionInts*>(new_full_config.option("filament_volume_map"));
|
||||
auto opt_nozzle_volume_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(new_full_config.option("nozzle_volume_type"));
|
||||
for (int index = 0; opt_extruder_type && opt_nozzle_volume_type && index < filament_maps.size(); index++)
|
||||
{
|
||||
ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(filament_maps[index] - 1));
|
||||
NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(filament_maps[index] - 1));
|
||||
// Orca: honour the per-filament volume map only when a producer sized it to the filament
|
||||
// count; mis-sized maps (stale project values, CLI runs until the per-filament synthesis
|
||||
// lands there) must not be indexed per filament (see
|
||||
// update_values_to_printer_extruders_for_multiple_filaments for the same guard).
|
||||
if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps
|
||||
&& opt_filament_volume_maps->values.size() == filament_maps.size())
|
||||
nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]);
|
||||
m_config.filament_map_2.values[index] = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
}
|
||||
|
||||
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
||||
unsigned int apply_status = APPLY_STATUS_UNCHANGED;
|
||||
auto update_apply_status = [&apply_status](bool invalidated)
|
||||
@@ -1281,6 +1405,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
m_default_region_config.apply_only(new_full_config, region_diff, true);
|
||||
//m_full_print_config = std::move(new_full_config);
|
||||
m_full_print_config = new_full_config;
|
||||
update_filament_self_index_cache();
|
||||
if (num_extruders != m_config.filament_diameter.size()) {
|
||||
num_extruders = m_config.filament_diameter.size();
|
||||
num_extruders_changed = true;
|
||||
@@ -1474,7 +1599,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
if (object_config_changed)
|
||||
model_object.config.assign_config(model_object_new.config);
|
||||
if (! object_diff.empty() || object_config_changed || num_extruders_changed ) {
|
||||
PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders );
|
||||
PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders, print_variant_index);
|
||||
for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(model_object)) {
|
||||
t_config_option_keys diff = print_object_status.print_object->config().diff(new_config);
|
||||
if (! diff.empty()) {
|
||||
@@ -1540,10 +1665,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
// Generate a list of trafos and XY offsets for instances of a ModelObject
|
||||
// Producing the config for PrintObject on demand, caching it at print_object_last.
|
||||
const PrintObject *print_object_last = nullptr;
|
||||
auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders ](PrintObject *print_object) {
|
||||
auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders, &print_variant_index](PrintObject *print_object) {
|
||||
print_object->config_apply(print_object_last ?
|
||||
print_object_last->config() :
|
||||
PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders ));
|
||||
PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders, print_variant_index));
|
||||
print_object_last = print_object;
|
||||
};
|
||||
if (old.empty()) {
|
||||
@@ -1653,7 +1778,14 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
m_default_object_config.apply_only(new_full_config, new_changed_keys, true);
|
||||
// Handle changes to regions config defaults
|
||||
m_default_region_config.apply_only(new_full_config, new_changed_keys, true);
|
||||
// Orca: keep the pre-expansion snapshot in sync with this late normalization pass.
|
||||
// The engine map write-back rebuilds m_full_print_config from m_ori_full_print_config
|
||||
// after slicing; a stale snapshot would resurrect the un-normalized values (e.g.
|
||||
// enable_prime_tower on a single-filament print) in the dumped config and spuriously
|
||||
// re-invalidate the g-code on the next apply.
|
||||
m_ori_full_print_config.apply_only(new_full_config, new_changed_keys, true);
|
||||
m_full_print_config = std::move(new_full_config);
|
||||
update_filament_self_index_cache();
|
||||
}
|
||||
|
||||
// All regions now have distinct settings.
|
||||
@@ -1714,7 +1846,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
for (auto it = it_print_object; it != it_print_object_end; ++it)
|
||||
if ((*it)->m_shared_regions != nullptr)
|
||||
update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys));
|
||||
})) {
|
||||
},
|
||||
print_variant_index)) {
|
||||
// Regions are valid, just keep them.
|
||||
} else {
|
||||
// Regions were reshuffled.
|
||||
@@ -1736,6 +1869,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
num_extruders ,
|
||||
print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_contour_compensation.value),
|
||||
painting_extruders,
|
||||
print_variant_index,
|
||||
print_object.is_fuzzy_skin_painted());
|
||||
}
|
||||
for (auto it = it_print_object; it != it_print_object_end; ++it)
|
||||
|
||||
@@ -21,11 +21,12 @@ void PrintTryCancel::operator()()
|
||||
|
||||
size_t PrintStateBase::g_last_timestamp = 0;
|
||||
|
||||
// Update "scale", "input_filename", "input_filename_base" placeholders from the current m_objects.
|
||||
// Update "scale", "input_filename", "input_filename_base", "first_object_name" placeholders from the current m_objects.
|
||||
void PrintBase::update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const
|
||||
{
|
||||
// get the first input file name
|
||||
std::string input_file;
|
||||
std::string first_object_name;
|
||||
std::vector<std::string> v_scale;
|
||||
int num_objects = 0;
|
||||
int num_instances = 0;
|
||||
@@ -38,6 +39,8 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str
|
||||
}
|
||||
if (printable) {
|
||||
++ num_objects;
|
||||
if (num_objects == 1)
|
||||
first_object_name = model_object->name;
|
||||
// CHECK_ME -> Is the following correct ?
|
||||
v_scale.push_back("x:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(X) * 100) +
|
||||
"% y:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(Y) * 100) +
|
||||
@@ -51,6 +54,7 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str
|
||||
config.set_key_value("num_instances", new ConfigOptionInt(num_instances));
|
||||
|
||||
config.set_key_value("scale", new ConfigOptionStrings(v_scale));
|
||||
config.set_key_value("first_object_name", new ConfigOptionString(first_object_name));
|
||||
if (! input_file.empty()) {
|
||||
// get basename with and without suffix
|
||||
const std::string input_filename = boost::filesystem::path(input_file).filename().string();
|
||||
|
||||
+1658
-498
File diff suppressed because it is too large
Load Diff
+282
-48
@@ -46,6 +46,14 @@ enum GCodeFlavor : unsigned char {
|
||||
gcfNoExtrusion
|
||||
};
|
||||
|
||||
// How a filament is used across the model. Part of the multi-nozzle grouping data; not yet
|
||||
// read by the shipping slicer — the nozzle-centric FilamentGroup engine consumes it.
|
||||
enum FilamentUsageType {
|
||||
SupportOnly,
|
||||
ModelOnly,
|
||||
Hybrid
|
||||
};
|
||||
|
||||
|
||||
enum class FuzzySkinType {
|
||||
None,
|
||||
@@ -62,6 +70,19 @@ enum class FuzzySkinMode {
|
||||
Combined,
|
||||
};
|
||||
|
||||
// ORCA: direction in which top_surface_expansion grows the top surfaces.
|
||||
enum class TopSurfaceExpansionDirection {
|
||||
InwardAndOutward,
|
||||
Inward,
|
||||
Outward,
|
||||
};
|
||||
|
||||
enum class CenterOfSurfacePattern {
|
||||
Each_Surface,
|
||||
Each_Model,
|
||||
Each_Assembly,
|
||||
};
|
||||
|
||||
enum class NoiseType {
|
||||
Classic,
|
||||
Perlin,
|
||||
@@ -97,6 +118,34 @@ enum InfillPattern : int {
|
||||
ipCount,
|
||||
};
|
||||
|
||||
// Orca: Infill patterns whose alignment origin follows the fill bounding box, so the
|
||||
// "separated_infills" option can re-center them per connected body. Patterns evaluated in
|
||||
// absolute/global coordinates (Gyroid, TPMS, Honeycomb, CrossHatch, ...) or that are shape-relative
|
||||
// (Concentric) ignore that bounding box and are therefore excluded.
|
||||
inline bool is_separable_infill_pattern(InfillPattern pattern)
|
||||
{
|
||||
switch (pattern) {
|
||||
case ipRectilinear:
|
||||
case ipAlignedRectilinear:
|
||||
case ipZigZag:
|
||||
case ipCrossZag:
|
||||
case ipLockedZag:
|
||||
case ipGrid:
|
||||
case ipTriangles:
|
||||
case ipStars: // tri-hexagon
|
||||
case ipCubic:
|
||||
case ipQuarterCubic:
|
||||
case ipLateralHoneycomb:
|
||||
case ipLateralLattice:
|
||||
case ipHilbertCurve:
|
||||
case ipArchimedeanChords:
|
||||
case ipOctagramSpiral:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum class IroningType {
|
||||
NoIroning,
|
||||
TopSurfaces,
|
||||
@@ -144,6 +193,15 @@ enum class WallDirection
|
||||
Count,
|
||||
};
|
||||
|
||||
// Orca: print order of surface fill loops/fragments for center-based fill patterns
|
||||
// (Concentric, Archimedean Chords, Octagram Spiral).
|
||||
enum class SurfaceFillOrder {
|
||||
Default,
|
||||
Outward,
|
||||
Inward,
|
||||
Count,
|
||||
};
|
||||
|
||||
//BBS
|
||||
enum class PrintSequence {
|
||||
ByLayer,
|
||||
@@ -300,6 +358,12 @@ enum class PerimeterGeneratorType
|
||||
Arachne
|
||||
};
|
||||
|
||||
enum class ToolChangeOrderingType
|
||||
{
|
||||
Default,
|
||||
Cyclic,
|
||||
};
|
||||
|
||||
// BBS
|
||||
enum OverhangFanThreshold {
|
||||
Overhang_threshold_none = 0,
|
||||
@@ -335,6 +399,13 @@ enum LayerSeq {
|
||||
flsCustomize
|
||||
};
|
||||
|
||||
enum FanDirection {
|
||||
fdUndefine = 0,
|
||||
fdLeft,
|
||||
fdRight,
|
||||
fdBoth
|
||||
};
|
||||
|
||||
static std::unordered_map<NozzleType, std::string>NozzleTypeEumnToStr = {
|
||||
{NozzleType::ntUndefine, "undefine"},
|
||||
{NozzleType::ntHardenedSteel, "hardened_steel"},
|
||||
@@ -418,18 +489,54 @@ enum ExtruderType {
|
||||
enum NozzleVolumeType {
|
||||
nvtStandard = 0,
|
||||
nvtHighFlow,
|
||||
nvtMaxNozzleVolumeType = nvtHighFlow
|
||||
nvtHybrid, // extruder holds a mix of Standard and High Flow sub-nozzles; selectable only for extruders
|
||||
// with more than one sub-nozzle (extruder_max_nozzle_count > 1); matched as Standard for
|
||||
// preset lookup and never emitted in profile variant strings
|
||||
nvtTPUHighFlow, // physical variant, used on H2D/H2DP 0.4 nozzles only
|
||||
// Integer values are serialized as raw ints in 3mf plate metadata and device MQTT, so they MUST stay stable.
|
||||
nvtMaxNozzleVolumeType = nvtTPUHighFlow
|
||||
};
|
||||
|
||||
enum FilamentMapMode {
|
||||
fmmAutoForFlush,
|
||||
fmmAutoForMatch,
|
||||
fmmManual,
|
||||
fmmNozzleManual, // Fully-manual filament->physical-nozzle mapping (filament_nozzle_map). Kept ordered right after fmmManual so every `< fmmManual` "is-auto" check stays correct.
|
||||
fmmDefault
|
||||
};
|
||||
|
||||
// All auto modes are ordered before fmmManual (see the enum ordering note above).
|
||||
inline bool is_auto_filament_map_mode(FilamentMapMode mode) {
|
||||
return mode < fmmManual;
|
||||
}
|
||||
|
||||
// Dual-extruder purge control. Default reproduces the current
|
||||
// per-extruder flush_multiplier + filament_prime_volume behaviour, so absent/default is inert.
|
||||
// Saving -> reduce prime volume to 15 mm3; Fast -> use flush_multiplier_fast + filament_flush_temp_fast.
|
||||
enum PrimeVolumeMode {
|
||||
pvmDefault = 0,
|
||||
pvmSaving,
|
||||
pvmFast
|
||||
};
|
||||
|
||||
extern std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type);
|
||||
|
||||
// Base slot lookup: scans a variant list (paired with its 1-based extruder/filament ids) for the
|
||||
// entry matching the given extruder/volume type and id. Returns 0 when no entry matches.
|
||||
extern int get_config_index_base(NozzleVolumeType volume_type, ExtruderType extruder_type, int variant_id_1based, const std::vector<std::string>& variant_list, const std::vector<int>& variant_ids_1based);
|
||||
|
||||
static std::set<NozzleVolumeType> get_valid_nozzle_volume_type() {
|
||||
std::set<NozzleVolumeType> type;
|
||||
for (int i = 0; i <= nvtMaxNozzleVolumeType; ++i) {
|
||||
auto t = static_cast<NozzleVolumeType>(i);
|
||||
// Hybrid is not a physical nozzle variant: presets never define it, so it must not
|
||||
// produce a variant string.
|
||||
if (t == nvtHybrid) continue;
|
||||
type.insert(t);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type);
|
||||
|
||||
static std::string bed_type_to_gcode_string(const BedType type)
|
||||
@@ -510,11 +617,20 @@ static std::string get_bed_temp_1st_layer_key(const BedType type)
|
||||
}
|
||||
|
||||
extern const std::vector<std::string> filament_extruder_override_keys;
|
||||
// Full override-key check incl. filament_retract_length_nc (defined outside the generator list).
|
||||
extern bool is_filament_extruder_override_key(const std::string &opt_key);
|
||||
|
||||
// for parse extruder_ams_count
|
||||
extern std::vector<std::map<int, int>> get_extruder_ams_count(const std::vector<std::string> &strs);
|
||||
extern std::vector<std::string> save_extruder_ams_count_to_string(const std::vector<std::map<int, int>> &extruder_ams_count);
|
||||
|
||||
// maps a full extruder variant string (e.g. "Direct Drive High Flow") to its NozzleVolumeType; nvtHybrid if unparsable
|
||||
extern NozzleVolumeType convert_to_nvt_type(const std::string& variant_str);
|
||||
|
||||
// for parse extruder_nozzle_stats (per-extruder physical nozzle inventory by volume type)
|
||||
extern std::vector<std::map<NozzleVolumeType, int>> get_extruder_nozzle_stats(const std::vector<std::string> &strs);
|
||||
extern std::vector<std::string> save_extruder_nozzle_stats_to_string(const std::vector<std::map<NozzleVolumeType, int>> &extruder_nozzle_stats);
|
||||
|
||||
#define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \
|
||||
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names(); \
|
||||
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values();
|
||||
@@ -523,6 +639,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinMode)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TopSurfaceExpansionDirection)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(WipeTowerType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NoiseType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(InfillPattern)
|
||||
@@ -550,7 +667,9 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrintHostType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(AuthorizationType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(WipeTowerWallType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ToolChangeOrderingType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PowerLossRecoveryMode)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SurfaceFillOrder)
|
||||
|
||||
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
|
||||
|
||||
@@ -602,6 +721,23 @@ class StaticPrintConfig;
|
||||
// Minimum object distance for arrangement, based on printer technology.
|
||||
double min_object_distance(const ConfigBase &cfg);
|
||||
|
||||
// One (extruder type x nozzle volume type) parameter variant a filament prints through, plus a
|
||||
// representative physical extruder observed using it. Ordering (and set-dedup identity) covers
|
||||
// the variant pair only, so the same variant reached through two extruders keeps one config slot.
|
||||
struct FilamentVariantUse
|
||||
{
|
||||
ExtruderType extruder_type{etDirectDrive};
|
||||
NozzleVolumeType nozzle_volume_type{nvtStandard};
|
||||
int extruder_id{0}; // 0-based, first extruder seen using this variant
|
||||
|
||||
bool operator<(const FilamentVariantUse &other) const
|
||||
{
|
||||
if (extruder_type != other.extruder_type)
|
||||
return extruder_type < other.extruder_type;
|
||||
return nozzle_volume_type < other.nozzle_volume_type;
|
||||
}
|
||||
};
|
||||
|
||||
// Slic3r dynamic configuration, used to override the configuration
|
||||
// per object, per modification volume or per printing material.
|
||||
// The dynamic configuration is also used to store user modifications of the print global parameters,
|
||||
@@ -658,10 +794,27 @@ public:
|
||||
|
||||
//BBS
|
||||
bool is_using_different_extruders();
|
||||
bool support_different_extruders(int& extruder_count);
|
||||
bool support_different_extruders(int& extruder_count) const;
|
||||
// Counts the config slots of a printer: one per (extruder x nozzle volume type) as described by
|
||||
// extruder_nozzle_stats, or simply one per extruder when the stats are absent/mismatched.
|
||||
// Fills nozzle_volume_types with each extruder's volume types in ascending enum order.
|
||||
int get_extruder_nozzle_volume_count(int extruder_count, std::vector<std::vector<NozzleVolumeType>>& nozzle_volume_types) const;
|
||||
int get_index_for_extruder(int extruder_or_filament_id, std::string id_name, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type, std::string variant_name, unsigned int stride = 1) const;
|
||||
void update_values_to_printer_extruders(DynamicPrintConfig& printer_config, std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0);
|
||||
void update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, std::set<std::string>& key_set, std::string id_name, std::string variant_name);
|
||||
std::vector<int> update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector<std::vector<NozzleVolumeType>>& nv_types,
|
||||
std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0, NozzleVolumeType filament_nvt = nvtStandard);
|
||||
void update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::set<std::string>& key_set, std::string id_name, std::string variant_name);
|
||||
// Rebuilds the per-slot filament arrays from a per-layer grouping outcome: a filament that
|
||||
// prints through several (extruder x nozzle volume type) variants keeps one slot per variant
|
||||
// (unlike the single-slot rebuild above), so layer-aware consumers can resolve the slot the
|
||||
// current layer actually prints with. Filaments absent from filament_variant_uses keep a
|
||||
// single slot resolved from filament_map / filament_volume_map. When slot_machine_indices is
|
||||
// non-null it receives one machine-variant slot index per output slot (the nil-value fallback
|
||||
// keying for the extruder retract overrides; a per-filament map cannot index expanded arrays).
|
||||
void update_filament_config_values_for_multiple_extruders(DynamicPrintConfig& printer_config,
|
||||
const std::unordered_map<int, std::vector<FilamentVariantUse>>& filament_variant_uses,
|
||||
int extruder_count, int extruder_nozzle_volume_count,
|
||||
std::set<std::string>& key_set, std::string id_name, std::string variant_name,
|
||||
std::vector<int>* slot_machine_indices = nullptr);
|
||||
|
||||
void update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set<std::string>& different_keys, std::string extruder_id_name, std::string extruder_variant_name,
|
||||
std::set<std::string>& key_set1, std::set<std::string>& key_set2);
|
||||
@@ -687,8 +840,11 @@ extern std::set<std::string> printer_options_with_variant_1;
|
||||
extern std::set<std::string> printer_options_with_variant_2;
|
||||
extern std::set<std::string> empty_options;
|
||||
|
||||
extern std::set<std::string> filament_dev_options;
|
||||
|
||||
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
|
||||
extern void compute_filament_override_value(const std::string& opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig& new_full_config,
|
||||
t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector<int>& f_maps);
|
||||
t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector<int>& f_map_indices);
|
||||
|
||||
void handle_legacy_sla(DynamicPrintConfig &config);
|
||||
|
||||
@@ -965,13 +1121,13 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionInt, support_interface_bottom_layers))
|
||||
// Spacing between interface lines (the hatching distance). Set zero to get a solid interface.
|
||||
((ConfigOptionFloat, support_interface_spacing))
|
||||
((ConfigOptionFloat, support_interface_speed))
|
||||
((ConfigOptionFloatsNullable, support_interface_speed))
|
||||
((ConfigOptionEnum<SupportMaterialPattern>, support_base_pattern))
|
||||
((ConfigOptionEnum<SupportMaterialInterfacePattern>, support_interface_pattern))
|
||||
// Spacing between support material lines (the hatching distance).
|
||||
((ConfigOptionFloat, support_base_pattern_spacing))
|
||||
((ConfigOptionFloat, support_expansion))
|
||||
((ConfigOptionFloat, support_speed))
|
||||
((ConfigOptionFloatsNullable, support_speed))
|
||||
((ConfigOptionEnum<SupportMaterialStyle>, support_style))
|
||||
|
||||
// Orca: a flag enabling the ability to override flow ratios
|
||||
@@ -1039,25 +1195,25 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, min_length_factor))
|
||||
|
||||
// Move all acceleration and jerk settings to object
|
||||
((ConfigOptionFloat, default_acceleration))
|
||||
((ConfigOptionFloat, outer_wall_acceleration))
|
||||
((ConfigOptionFloat, inner_wall_acceleration))
|
||||
((ConfigOptionFloat, top_surface_acceleration))
|
||||
((ConfigOptionFloat, initial_layer_acceleration))
|
||||
((ConfigOptionFloatOrPercent, bridge_acceleration))
|
||||
((ConfigOptionFloat, travel_acceleration))
|
||||
((ConfigOptionFloatOrPercent, sparse_infill_acceleration))
|
||||
((ConfigOptionFloatOrPercent, internal_solid_infill_acceleration))
|
||||
((ConfigOptionFloatsNullable, default_acceleration))
|
||||
((ConfigOptionFloatsNullable, outer_wall_acceleration))
|
||||
((ConfigOptionFloatsNullable, inner_wall_acceleration))
|
||||
((ConfigOptionFloatsNullable, top_surface_acceleration))
|
||||
((ConfigOptionFloatsNullable, initial_layer_acceleration))
|
||||
((ConfigOptionFloatsOrPercentsNullable, bridge_acceleration))
|
||||
((ConfigOptionFloatsNullable, travel_acceleration))
|
||||
((ConfigOptionFloatsOrPercentsNullable, sparse_infill_acceleration))
|
||||
((ConfigOptionFloatsOrPercentsNullable, internal_solid_infill_acceleration))
|
||||
|
||||
((ConfigOptionFloat, default_jerk))
|
||||
((ConfigOptionFloat, outer_wall_jerk))
|
||||
((ConfigOptionFloat, inner_wall_jerk))
|
||||
((ConfigOptionFloat, infill_jerk))
|
||||
((ConfigOptionFloat, top_surface_jerk))
|
||||
((ConfigOptionFloat, initial_layer_jerk))
|
||||
((ConfigOptionFloat, travel_jerk))
|
||||
((ConfigOptionFloatsNullable, default_jerk))
|
||||
((ConfigOptionFloatsNullable, outer_wall_jerk))
|
||||
((ConfigOptionFloatsNullable, inner_wall_jerk))
|
||||
((ConfigOptionFloatsNullable, infill_jerk))
|
||||
((ConfigOptionFloatsNullable, top_surface_jerk))
|
||||
((ConfigOptionFloatsNullable, initial_layer_jerk))
|
||||
((ConfigOptionFloatsNullable, travel_jerk))
|
||||
((ConfigOptionBool, precise_z_height))
|
||||
((ConfigOptionFloat, default_junction_deviation))
|
||||
((ConfigOptionFloatsNullable, default_junction_deviation))
|
||||
|
||||
((ConfigOptionBool, interlocking_beam))
|
||||
((ConfigOptionFloat,interlocking_beam_width))
|
||||
@@ -1084,18 +1240,22 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, bridge_flow))
|
||||
((ConfigOptionFloatOrPercent, bridge_line_width))
|
||||
((ConfigOptionFloat, internal_bridge_flow))
|
||||
((ConfigOptionFloat, bridge_speed))
|
||||
((ConfigOptionFloatOrPercent, internal_bridge_speed))
|
||||
((ConfigOptionFloatsNullable, bridge_speed))
|
||||
((ConfigOptionFloatsOrPercentsNullable, internal_bridge_speed))
|
||||
((ConfigOptionEnum<EnsureVerticalShellThickness>, ensure_vertical_shell_thickness))
|
||||
((ConfigOptionPercent, top_surface_density))
|
||||
((ConfigOptionPercent, bottom_surface_density))
|
||||
((ConfigOptionEnum<InfillPattern>, top_surface_pattern))
|
||||
((ConfigOptionEnum<InfillPattern>, bottom_surface_pattern))
|
||||
((ConfigOptionEnum<SurfaceFillOrder>, top_surface_fill_order))
|
||||
((ConfigOptionEnum<SurfaceFillOrder>, bottom_surface_fill_order))
|
||||
((ConfigOptionEnum<InfillPattern>, internal_solid_infill_pattern))
|
||||
((ConfigOptionFloatOrPercent, outer_wall_line_width))
|
||||
((ConfigOptionFloat, outer_wall_speed))
|
||||
((ConfigOptionFloatsNullable, outer_wall_speed))
|
||||
((ConfigOptionFloat, infill_direction))
|
||||
((ConfigOptionFloat, solid_infill_direction))
|
||||
((ConfigOptionFloat, top_layer_direction))
|
||||
((ConfigOptionFloat, bottom_layer_direction))
|
||||
((ConfigOptionString, solid_infill_rotate_template))
|
||||
((ConfigOptionBool, symmetric_infill_y_axis))
|
||||
((ConfigOptionFloat, infill_shift_step))
|
||||
@@ -1109,6 +1269,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, lightning_prune_angle))
|
||||
((ConfigOptionFloat, lightning_straightening_angle))
|
||||
((ConfigOptionBool, align_infill_direction_to_model))
|
||||
((ConfigOptionEnum<CenterOfSurfacePattern>, center_of_surface_pattern))
|
||||
((ConfigOptionBool, separated_infills))
|
||||
((ConfigOptionString, extra_solid_infills))
|
||||
((ConfigOptionEnum<FuzzySkinType>, fuzzy_skin))
|
||||
((ConfigOptionFloat, fuzzy_skin_thickness))
|
||||
@@ -1122,12 +1284,12 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionInt, fuzzy_skin_ripples_per_layer))
|
||||
((ConfigOptionPercent, fuzzy_skin_ripple_offset))
|
||||
((ConfigOptionInt, fuzzy_skin_layers_between_ripple_offset))
|
||||
((ConfigOptionFloat, gap_infill_speed))
|
||||
((ConfigOptionFloatsNullable, gap_infill_speed))
|
||||
((ConfigOptionInt, sparse_infill_filament_id))
|
||||
((ConfigOptionFloatOrPercent, sparse_infill_line_width))
|
||||
((ConfigOptionPercent, infill_wall_overlap))
|
||||
((ConfigOptionPercent, top_bottom_infill_wall_overlap))
|
||||
((ConfigOptionFloat, sparse_infill_speed))
|
||||
((ConfigOptionFloatsNullable, sparse_infill_speed))
|
||||
((ConfigOptionPercent, skeleton_infill_density))
|
||||
((ConfigOptionPercent, skin_infill_density))
|
||||
((ConfigOptionFloat, infill_lock_depth))
|
||||
@@ -1159,7 +1321,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionInt, outer_wall_filament_id))
|
||||
((ConfigOptionInt, inner_wall_filament_id))
|
||||
((ConfigOptionFloatOrPercent, inner_wall_line_width))
|
||||
((ConfigOptionFloat, inner_wall_speed))
|
||||
((ConfigOptionFloatsNullable, inner_wall_speed))
|
||||
// Total number of perimeters.
|
||||
((ConfigOptionInt, wall_loops))
|
||||
((ConfigOptionBool, alternate_extra_wall))
|
||||
@@ -1168,19 +1330,22 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionInt, top_surface_filament_id))
|
||||
((ConfigOptionInt, bottom_surface_filament_id))
|
||||
((ConfigOptionFloatOrPercent, internal_solid_infill_line_width))
|
||||
((ConfigOptionFloat, internal_solid_infill_speed))
|
||||
((ConfigOptionFloatsNullable, internal_solid_infill_speed))
|
||||
// Detect thin walls.
|
||||
((ConfigOptionBool, detect_thin_wall))
|
||||
((ConfigOptionFloatOrPercent, top_surface_line_width))
|
||||
((ConfigOptionInt, top_shell_layers))
|
||||
((ConfigOptionFloat, top_shell_thickness))
|
||||
((ConfigOptionFloat, top_surface_speed))
|
||||
((ConfigOptionFloat, top_surface_expansion))
|
||||
((ConfigOptionFloat, top_surface_expansion_margin))
|
||||
((ConfigOptionEnum<TopSurfaceExpansionDirection>, top_surface_expansion_direction))
|
||||
((ConfigOptionFloatsNullable, top_surface_speed))
|
||||
//BBS
|
||||
((ConfigOptionBool, enable_overhang_speed))
|
||||
((ConfigOptionFloatOrPercent, overhang_1_4_speed))
|
||||
((ConfigOptionFloatOrPercent, overhang_2_4_speed))
|
||||
((ConfigOptionFloatOrPercent, overhang_3_4_speed))
|
||||
((ConfigOptionFloatOrPercent, overhang_4_4_speed))
|
||||
((ConfigOptionBoolsNullable, enable_overhang_speed))
|
||||
((ConfigOptionFloatsOrPercentsNullable, overhang_1_4_speed))
|
||||
((ConfigOptionFloatsOrPercentsNullable, overhang_2_4_speed))
|
||||
((ConfigOptionFloatsOrPercentsNullable, overhang_3_4_speed))
|
||||
((ConfigOptionFloatsOrPercentsNullable, overhang_4_4_speed))
|
||||
((ConfigOptionBool, only_one_wall_top))
|
||||
|
||||
//SoftFever
|
||||
@@ -1196,8 +1361,10 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, precise_outer_wall))
|
||||
((ConfigOptionPercent, bridge_density))
|
||||
((ConfigOptionFloat, filter_out_gap_fill))
|
||||
((ConfigOptionFloatOrPercent, small_perimeter_speed))
|
||||
((ConfigOptionFloat, small_perimeter_threshold))
|
||||
((ConfigOptionFloatsOrPercentsNullable, small_perimeter_speed))
|
||||
((ConfigOptionFloatsNullable, small_perimeter_threshold))
|
||||
((ConfigOptionFloatsOrPercentsNullable, small_support_perimeter_speed))
|
||||
((ConfigOptionFloatsNullable, small_support_perimeter_threshold))
|
||||
((ConfigOptionFloat, top_solid_infill_flow_ratio))
|
||||
((ConfigOptionFloat, bottom_solid_infill_flow_ratio))
|
||||
((ConfigOptionFloatOrPercent, infill_anchor))
|
||||
@@ -1206,10 +1373,12 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
// Orca
|
||||
((ConfigOptionBool, make_overhang_printable))
|
||||
((ConfigOptionBool, extra_perimeters_on_overhangs))
|
||||
((ConfigOptionBool, slowdown_for_curled_perimeters))
|
||||
((ConfigOptionBoolsNullable, slowdown_for_curled_perimeters))
|
||||
((ConfigOptionBool, hole_to_polyhole))
|
||||
((ConfigOptionFloatOrPercent, hole_to_polyhole_threshold))
|
||||
((ConfigOptionBool, hole_to_polyhole_twisted))
|
||||
((ConfigOptionInt, hole_to_polyhole_max_edges))
|
||||
|
||||
((ConfigOptionBool, overhang_reverse))
|
||||
((ConfigOptionBool, overhang_reverse_internal_only))
|
||||
((ConfigOptionFloatOrPercent, overhang_reverse_threshold))
|
||||
@@ -1280,6 +1449,12 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloats, machine_min_travel_rate))
|
||||
// M205 S... [mm/sec]
|
||||
((ConfigOptionFloats, machine_min_extruding_rate))
|
||||
// Bedslinger mass/force model: drive the per-layer Y-axis
|
||||
// acceleration limit (curr_y_acceleration_limit) and the printed-mass check.
|
||||
// Default 0 => inactive for every existing printer (mass model reads them as disabled).
|
||||
((ConfigOptionFloat, machine_max_force_Y))
|
||||
((ConfigOptionFloat, machine_bed_mass_Y))
|
||||
((ConfigOptionFloat, machine_max_printed_mass))
|
||||
|
||||
//resonance avoidance ported from qidi slicer
|
||||
((ConfigOptionBool, resonance_avoidance))
|
||||
@@ -1334,6 +1509,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionStrings, filament_vendor))
|
||||
((ConfigOptionBools, filament_is_support))
|
||||
((ConfigOptionInts, filament_printable))
|
||||
((ConfigOptionInts, filament_extruder_compatibility))
|
||||
((ConfigOptionFloats, filament_change_length))
|
||||
((ConfigOptionFloats, filament_cost))
|
||||
((ConfigOptionStrings, default_filament_colour))
|
||||
@@ -1342,14 +1518,20 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionInts, required_nozzle_HRC))
|
||||
((ConfigOptionEnum<FilamentMapMode>, filament_map_mode))
|
||||
((ConfigOptionInts, filament_map))
|
||||
((ConfigOptionInts, filament_volume_map))
|
||||
((ConfigOptionInts, filament_nozzle_map))
|
||||
((ConfigOptionInts, filament_map_2)) //used for multi nozzle, map filament to the index identified by extruder+nozzle_volume_type
|
||||
//((ConfigOptionInts, filament_extruder_id))
|
||||
((ConfigOptionStrings, filament_extruder_variant))
|
||||
((ConfigOptionInts, filament_self_index))
|
||||
((ConfigOptionBool, support_object_skip_flush))
|
||||
((ConfigOptionEnum<BedTempFormula>, bed_temperature_formula))
|
||||
((ConfigOptionInts, physical_extruder_map))
|
||||
((ConfigOptionIntsNullable, nozzle_flush_dataset))
|
||||
((ConfigOptionFloatsNullable, filament_flush_volumetric_speed))
|
||||
((ConfigOptionIntsNullable, filament_flush_temp))
|
||||
// Fast-purge flush temperature; consumed only when prime_volume_mode==pvmFast.
|
||||
((ConfigOptionIntsNullable, filament_flush_temp_fast))
|
||||
// BBS
|
||||
((ConfigOptionBool, scan_first_layer))
|
||||
((ConfigOptionEnum<PowerLossRecoveryMode>, enable_power_loss_recovery))
|
||||
@@ -1369,10 +1551,13 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
|
||||
((ConfigOptionFloat, max_volumetric_extrusion_rate_slope))
|
||||
((ConfigOptionFloat, max_volumetric_extrusion_rate_slope_segment_length))
|
||||
((ConfigOptionBool, extrusion_rate_smoothing_external_perimeter_only))
|
||||
((ConfigOptionBool, extrusion_rate_smoothing_external_perimeter_only))
|
||||
|
||||
|
||||
((ConfigOptionPercents, retract_before_wipe))
|
||||
// Orca
|
||||
((ConfigOptionPercents, retract_after_wipe))
|
||||
|
||||
((ConfigOptionFloats, retraction_length))
|
||||
((ConfigOptionFloats, retract_length_toolchange))
|
||||
((ConfigOptionInt, enable_long_retraction_when_cut))
|
||||
@@ -1396,13 +1581,14 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, single_extruder_multi_material))
|
||||
((ConfigOptionBool, manual_filament_change))
|
||||
((ConfigOptionBool, single_extruder_multi_material_priming))
|
||||
((ConfigOptionEnum<ToolChangeOrderingType>, toolchange_ordering))
|
||||
((ConfigOptionBool, wipe_tower_no_sparse_layers))
|
||||
((ConfigOptionString, change_filament_gcode))
|
||||
((ConfigOptionString, change_extrusion_role_gcode))
|
||||
((ConfigOptionString, process_change_extrusion_role_gcode))
|
||||
((ConfigOptionStrings, filament_change_extrusion_role_gcode))
|
||||
((ConfigOptionFloat, travel_speed))
|
||||
((ConfigOptionFloat, travel_speed_z))
|
||||
((ConfigOptionFloatsNullable, travel_speed))
|
||||
((ConfigOptionFloatsNullable, travel_speed_z))
|
||||
((ConfigOptionBool, silent_mode))
|
||||
((ConfigOptionString, machine_pause_gcode))
|
||||
((ConfigOptionString, template_custom_gcode))
|
||||
@@ -1410,12 +1596,16 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionEnumsGenericNullable,nozzle_type))
|
||||
((ConfigOptionInt, nozzle_hrc))
|
||||
((ConfigOptionBool, auxiliary_fan))
|
||||
((ConfigOptionEnum<FanDirection>, fan_direction))
|
||||
((ConfigOptionBool, support_air_filtration))
|
||||
((ConfigOptionBool, support_cooling_filter))
|
||||
((ConfigOptionBool, cooling_filter_enabled))
|
||||
((ConfigOptionEnum<PrinterStructure>,printer_structure))
|
||||
((ConfigOptionBool, support_chamber_temp_control))
|
||||
((ConfigOptionEnumsGeneric, extruder_type))
|
||||
((ConfigOptionEnumsGeneric, nozzle_volume_type))
|
||||
((ConfigOptionStrings, extruder_ams_count))
|
||||
((ConfigOptionStrings, extruder_nozzle_stats))
|
||||
((ConfigOptionInts, printer_extruder_id))
|
||||
((ConfigOptionInt, master_extruder_id))
|
||||
((ConfigOptionStrings, printer_extruder_variant))
|
||||
@@ -1426,9 +1616,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, use_relative_e_distances))
|
||||
((ConfigOptionBool, accel_to_decel_enable))
|
||||
((ConfigOptionPercent, accel_to_decel_factor))
|
||||
((ConfigOptionFloatOrPercent, initial_layer_travel_speed))
|
||||
((ConfigOptionFloatOrPercent, initial_layer_travel_acceleration))
|
||||
((ConfigOptionFloatOrPercent, initial_layer_travel_jerk))
|
||||
((ConfigOptionFloatsOrPercentsNullable, initial_layer_travel_speed))
|
||||
((ConfigOptionFloatsOrPercentsNullable, initial_layer_travel_acceleration))
|
||||
((ConfigOptionFloatsOrPercentsNullable, initial_layer_travel_jerk))
|
||||
((ConfigOptionBool, bbl_calib_mark_logo))
|
||||
((ConfigOptionBool, disable_m73))
|
||||
|
||||
@@ -1473,6 +1663,36 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionStrings, small_area_infill_flow_compensation_model))
|
||||
|
||||
((ConfigOptionBool, has_scarf_joint_seam))
|
||||
|
||||
// Multi-nozzle + pre-heating + nozzle-change (nc) keys. Defaults are no-ops for existing
|
||||
// single-nozzle printers; new slicing paths gate on extruder_max_nozzle_count > 1.
|
||||
((ConfigOptionFloat, machine_hotend_change_time))
|
||||
((ConfigOptionFloat, machine_prepare_compensation_time))
|
||||
((ConfigOptionBool, enable_pre_heating))
|
||||
((ConfigOptionFloatsNullable, hotend_cooling_rate))
|
||||
((ConfigOptionFloatsNullable, hotend_heating_rate))
|
||||
((ConfigOptionFloats, filament_change_length_nc))
|
||||
((ConfigOptionFloatsNullable, filament_ramming_travel_time))
|
||||
((ConfigOptionIntsNullable, filament_pre_cooling_temperature))
|
||||
((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed))
|
||||
((ConfigOptionFloatsNullable, filament_ramming_travel_time_nc))
|
||||
((ConfigOptionIntsNullable, filament_pre_cooling_temperature_nc))
|
||||
((ConfigOptionFloatsNullable, filament_ramming_volumetric_speed_nc))
|
||||
((ConfigOptionFloatsNullable, filament_retract_length_nc))
|
||||
((ConfigOptionIntsNullable, extruder_max_nozzle_count))
|
||||
// Printer flag: whether the printer offers the fast-purge mode selector.
|
||||
// Default false; no shipping profile sets it, so the fast-purge UI stays hidden.
|
||||
((ConfigOptionBool, support_fast_purge_mode))
|
||||
|
||||
//ams chamber
|
||||
((ConfigOptionStrings, filament_dev_ams_drying_ams_limitations))
|
||||
((ConfigOptionFloats, filament_dev_ams_drying_temperature))
|
||||
((ConfigOptionFloats, filament_dev_ams_drying_time))
|
||||
((ConfigOptionFloats, filament_dev_ams_drying_heat_distortion_temperature))
|
||||
((ConfigOptionFloats, filament_dev_chamber_drying_bed_temperature))
|
||||
((ConfigOptionFloats, filament_dev_chamber_drying_time))
|
||||
((ConfigOptionFloats, filament_dev_drying_softening_temperature))
|
||||
((ConfigOptionFloats, filament_dev_drying_cooling_temperature))
|
||||
)
|
||||
|
||||
// This object is mapped to Perl as Slic3r::Config::Print.
|
||||
@@ -1538,12 +1758,14 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionInts, complete_print_exhaust_fan_speed))
|
||||
((ConfigOptionFloatOrPercent, initial_layer_line_width))
|
||||
((ConfigOptionFloat, initial_layer_print_height))
|
||||
((ConfigOptionFloat, initial_layer_speed))
|
||||
((ConfigOptionFloatsNullable, initial_layer_speed))
|
||||
|
||||
//BBS
|
||||
((ConfigOptionFloat, initial_layer_infill_speed))
|
||||
((ConfigOptionFloatsNullable, initial_layer_infill_speed))
|
||||
((ConfigOptionInts, nozzle_temperature_initial_layer))
|
||||
((ConfigOptionInts, full_fan_speed_layer))
|
||||
// ORCA: explicit override for the part cooling fan speed on the first printed layer.
|
||||
((ConfigOptionInts, initial_layer_fan_speed))
|
||||
((ConfigOptionFloats, fan_max_speed))
|
||||
((ConfigOptionFloats, max_layer_height))
|
||||
((ConfigOptionFloats, fan_min_speed))
|
||||
@@ -1557,6 +1779,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionBool, ooze_prevention))
|
||||
((ConfigOptionString, filename_format))
|
||||
((ConfigOptionStrings, post_process))
|
||||
((ConfigOptionStrings, slicing_pipeline_plugin))
|
||||
((ConfigOptionString, printer_model))
|
||||
((ConfigOptionFloat, resolution))
|
||||
((ConfigOptionFloats, retraction_minimum_travel))
|
||||
@@ -1617,7 +1840,16 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
|
||||
// BBS: wipe tower is only used for priming
|
||||
((ConfigOptionFloat, prime_volume))
|
||||
// Nozzle-change (nc) prime volume + pre-heat delta
|
||||
((ConfigOptionFloats, filament_prime_volume))
|
||||
((ConfigOptionFloats, filament_prime_volume_nc))
|
||||
((ConfigOptionFloatsNullable, filament_preheat_temperature_delta))
|
||||
((ConfigOptionFloats, flush_multiplier))
|
||||
// Fast-purge mode. Kept out of the g-code config block (banned_keys in
|
||||
// GCode::append_full_config) so registering them leaves the shipping fleet's g-code byte-identical;
|
||||
// consumed only on the prime_volume_mode==pvmFast / pvmSaving branch (default pvmDefault = inert).
|
||||
((ConfigOptionEnum<PrimeVolumeMode>, prime_volume_mode))
|
||||
((ConfigOptionFloats, flush_multiplier_fast))
|
||||
((ConfigOptionFloat, z_offset))
|
||||
// BBS: project filaments
|
||||
((ConfigOptionFloats, filament_colour_new))
|
||||
@@ -1625,6 +1857,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionFloatsNullable, nozzle_volume))
|
||||
((ConfigOptionPoints, start_end_points))
|
||||
((ConfigOptionEnum<TimelapseType>, timelapse_type))
|
||||
// Corexy farthest-point timelapse (default false → inert for existing printers)
|
||||
((ConfigOptionBool, farthest_point_timelapse))
|
||||
((ConfigOptionString, thumbnails))
|
||||
// BBS: move from PrintObjectConfig
|
||||
((ConfigOptionBool, independent_support_layer_height))
|
||||
|
||||
+193
-36
@@ -4,6 +4,7 @@
|
||||
#include "Print.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Clipper2Utils.hpp"
|
||||
#include "ElephantFootCompensation.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "I18N.hpp"
|
||||
@@ -97,7 +98,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Transfor
|
||||
// snug height and an approximate bounding box in XY.
|
||||
BoundingBoxf3 bbox = model_object->raw_bounding_box();
|
||||
Vec3d bbox_center = bbox.center();
|
||||
|
||||
|
||||
// We may need to rotate the bbox / bbox_center from the original instance to the current instance.
|
||||
double z_diff = Geometry::rotation_diff_z(model_object->instances.front()->get_rotation(), instances.front().model_instance->get_rotation());
|
||||
if (std::abs(z_diff) > EPSILON) {
|
||||
@@ -157,10 +158,10 @@ std::vector<std::reference_wrapper<const PrintRegion>> PrintObject::all_regions(
|
||||
return out;
|
||||
}
|
||||
|
||||
Polygons create_polyholes(const Point center, const coord_t radius, const coord_t nozzle_diameter, bool multiple)
|
||||
Polygons create_polyholes(const Point center, const coord_t radius, const coord_t nozzle_diameter, bool multiple, int max_edges)
|
||||
{
|
||||
// n = max(round(2 * d), 3); // for 0.4mm nozzle
|
||||
size_t nb_edges = (int)std::max(3, (int)std::round(4.0 * unscaled(radius) * 0.4 / unscaled(nozzle_diameter)));
|
||||
size_t nb_edges = (int)std::min(max_edges, std::max(3, (int)std::round(4.0 * unscaled(radius) * 0.4 / unscaled(nozzle_diameter))));
|
||||
// cylinder(h = h, r = d / cos (180 / n), $fn = n);
|
||||
//create x polyholes by rotation if multiple
|
||||
int nb_polyhole = 1;
|
||||
@@ -190,8 +191,8 @@ void PrintObject::_transform_hole_to_polyholes()
|
||||
{
|
||||
// get all circular holes for each layer
|
||||
// the id is center-diameter-extruderid
|
||||
//the tuple is Point center; float diameter_max; int extruder_id; coord_t max_variation; bool twist;
|
||||
std::vector<std::vector<std::pair<std::tuple<Point, float, int, coord_t, bool>, Polygon*>>> layerid2center;
|
||||
//the tuple is Point center; float diameter_max; int extruder_id; coord_t max_variation; bool twist; int max_edges;
|
||||
std::vector<std::vector<std::pair<std::tuple<Point, float, int, coord_t, bool, int>, Polygon*>>> layerid2center;
|
||||
for (size_t i = 0; i < this->m_layers.size(); i++) layerid2center.emplace_back();
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
@@ -230,9 +231,10 @@ void PrintObject::_transform_hole_to_polyholes()
|
||||
// SCALED_EPSILON was a bit too harsh. Now using a config, as some may want some harsh setting and some don't.
|
||||
coord_t max_variation = std::max(SCALED_EPSILON, scale_(this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_threshold.get_abs_value(unscaled(diameter_sum / hole.points.size()))));
|
||||
bool twist = this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_twisted.value;
|
||||
int max_edges = this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_max_edges.value;
|
||||
if (diameter_max - diameter_min < max_variation * 2 && diameter_line_max - diameter_line_min < max_variation * 2) {
|
||||
layerid2center[layer_idx].emplace_back(
|
||||
std::tuple<Point, float, int, coord_t, bool>{center, diameter_max, layer->m_regions[region_idx]->region().config().outer_wall_filament_id.value, max_variation, twist}, & hole);
|
||||
std::tuple<Point, float, int, coord_t, bool, int>{center, diameter_max, layer->m_regions[region_idx]->region().config().outer_wall_filament_id.value, max_variation, twist, max_edges}, & hole);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,14 +245,14 @@ void PrintObject::_transform_hole_to_polyholes()
|
||||
}
|
||||
});
|
||||
//sort holes per center-diameter
|
||||
std::map<std::tuple<Point, float, int, coord_t, bool>, std::vector<std::pair<Polygon*, int>>> id2layerz2hole;
|
||||
std::map<std::tuple<Point, float, int, coord_t, bool, int>, std::vector<std::pair<Polygon*, int>>> id2layerz2hole;
|
||||
|
||||
//search & find hole that span at least X layers
|
||||
const size_t min_nb_layers = 2;
|
||||
for (size_t layer_idx = 0; layer_idx < this->m_layers.size(); ++layer_idx) {
|
||||
for (size_t hole_idx = 0; hole_idx < layerid2center[layer_idx].size(); ++hole_idx) {
|
||||
//get all other same polygons
|
||||
std::tuple<Point, float, int, coord_t, bool>& id = layerid2center[layer_idx][hole_idx].first;
|
||||
std::tuple<Point, float, int, coord_t, bool, int>& id = layerid2center[layer_idx][hole_idx].first;
|
||||
float max_z = layers()[layer_idx]->print_z;
|
||||
std::vector<std::pair<Polygon*, int>> holes;
|
||||
holes.emplace_back(layerid2center[layer_idx][hole_idx].second, layer_idx);
|
||||
@@ -258,7 +260,7 @@ void PrintObject::_transform_hole_to_polyholes()
|
||||
if (layers()[search_layer_idx]->print_z - layers()[search_layer_idx]->height - max_z > EPSILON) break;
|
||||
//search an other polygon with same id
|
||||
for (size_t search_hole_idx = 0; search_hole_idx < layerid2center[search_layer_idx].size(); ++search_hole_idx) {
|
||||
std::tuple<Point, float, int, coord_t, bool>& search_id = layerid2center[search_layer_idx][search_hole_idx].first;
|
||||
std::tuple<Point, float, int, coord_t, bool, int>& search_id = layerid2center[search_layer_idx][search_hole_idx].first;
|
||||
if (std::get<2>(id) == std::get<2>(search_id)
|
||||
&& std::get<0>(id).distance_to(std::get<0>(search_id)) < std::get<3>(id)
|
||||
&& std::abs(std::get<1>(id) - std::get<1>(search_id)) < std::get<3>(id)
|
||||
@@ -279,7 +281,7 @@ void PrintObject::_transform_hole_to_polyholes()
|
||||
}
|
||||
//create a polyhole per id and replace holes points by it.
|
||||
for (auto entry : id2layerz2hole) {
|
||||
Polygons polyholes = create_polyholes(std::get<0>(entry.first), std::get<1>(entry.first), scale_(print()->config().nozzle_diameter.get_at(std::get<2>(entry.first) - 1)), std::get<4>(entry.first));
|
||||
Polygons polyholes = create_polyholes(std::get<0>(entry.first), std::get<1>(entry.first), scale_(print()->config().nozzle_diameter.get_at(std::get<2>(entry.first) - 1)), std::get<4>(entry.first), std::get<5>(entry.first));
|
||||
for (auto& poly_to_replace : entry.second) {
|
||||
Polygon polyhole = polyholes[poly_to_replace.second % polyholes.size()];
|
||||
//search the clone in layers->slices
|
||||
@@ -703,6 +705,72 @@ void PrintObject::infill()
|
||||
|
||||
if (this->set_started(posInfill)) {
|
||||
m_print->set_status(35, L("Generating infill toolpath"));
|
||||
|
||||
// Orca: precompute the object's 3D connected bodies for separated infills / per-model
|
||||
// centering. Two islands belong to the same body when their slices overlap on adjacent
|
||||
// layers; islands that only overlap in top-down projection but never touch (e.g. interleaved
|
||||
// chain links) stay separate, matching "split to objects". Each layer island then records
|
||||
// the full bounding box of its body, so its infill is centered on that body as if it were
|
||||
// sliced alone. Done once here, before the parallel fill, and only when a region needs it.
|
||||
bool needs_separated_components = false;
|
||||
for (size_t i = 0; i < this->num_printing_regions(); ++ i) {
|
||||
const PrintRegionConfig &rc = this->printing_region(i).config();
|
||||
if (rc.separated_infills || rc.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model) {
|
||||
needs_separated_components = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Fast path: the feature only changes anything when the object is made of more than one
|
||||
// connected body. Detect that cheaply the same way as "Split to objects" — more than one
|
||||
// model part, or a single part whose mesh is splittable (is_splittable() is cached). A single
|
||||
// body already shares the object center, i.e. the default, so skip the connectivity pass.
|
||||
if (needs_separated_components) {
|
||||
int parts = 0;
|
||||
const ModelVolume *first_part = nullptr;
|
||||
for (const ModelVolume *v : this->model_object()->volumes)
|
||||
if (v->is_model_part()) { ++ parts; first_part = v; }
|
||||
if (parts <= 1 && ! (first_part != nullptr && first_part->is_splittable()))
|
||||
needs_separated_components = false;
|
||||
}
|
||||
for (Layer *layer : m_layers)
|
||||
layer->lslices_separated_component_bboxes.clear();
|
||||
if (needs_separated_components) {
|
||||
const size_t nl = m_layers.size();
|
||||
std::vector<size_t> offset(nl + 1, 0); // flat index of the first island of each layer
|
||||
for (size_t i = 0; i < nl; ++ i)
|
||||
offset[i + 1] = offset[i] + m_layers[i]->lslices.size();
|
||||
const size_t nreg = offset[nl];
|
||||
// Union-find over every (layer, island).
|
||||
std::vector<size_t> parent(nreg);
|
||||
for (size_t i = 0; i < nreg; ++ i) parent[i] = i;
|
||||
auto find = [&parent](size_t x) {
|
||||
while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
|
||||
return x;
|
||||
};
|
||||
auto unite = [&](size_t a, size_t b) { a = find(a); b = find(b); if (a != b) parent[a] = b; };
|
||||
// Join islands that overlap between two consecutive layers.
|
||||
for (size_t i = 0; i + 1 < nl; ++ i) {
|
||||
const Layer *la = m_layers[i], *lb = m_layers[i + 1];
|
||||
for (size_t a = 0; a < la->lslices.size(); ++ a)
|
||||
for (size_t b = 0; b < lb->lslices.size(); ++ b)
|
||||
if (la->lslices_bboxes[a].overlap(lb->lslices_bboxes[b]) &&
|
||||
! intersection_ex(la->lslices[a], lb->lslices[b]).empty())
|
||||
unite(offset[i] + a, offset[i + 1] + b);
|
||||
}
|
||||
// Full bounding box of each body, indexed by its union-find root.
|
||||
std::vector<BoundingBox> body_bbox(nreg);
|
||||
for (size_t i = 0; i < nl; ++ i)
|
||||
for (size_t a = 0; a < m_layers[i]->lslices.size(); ++ a)
|
||||
body_bbox[find(offset[i] + a)].merge(m_layers[i]->lslices_bboxes[a]);
|
||||
// Store the body bbox for every island.
|
||||
for (size_t i = 0; i < nl; ++ i) {
|
||||
Layer *layer = m_layers[i];
|
||||
layer->lslices_separated_component_bboxes.resize(layer->lslices.size());
|
||||
for (size_t a = 0; a < layer->lslices.size(); ++ a)
|
||||
layer->lslices_separated_component_bboxes[a] = body_bbox[find(offset[i] + a)];
|
||||
}
|
||||
}
|
||||
|
||||
const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first;
|
||||
const auto& support_fill_octree = this->m_adaptive_fill_octrees.second;
|
||||
|
||||
@@ -897,13 +965,15 @@ void PrintObject::generate_support_material()
|
||||
void PrintObject::estimate_curled_extrusions()
|
||||
{
|
||||
if (this->set_started(posEstimateCurledExtrusions)) {
|
||||
if ( std::any_of(this->print()->m_print_regions.begin(), this->print()->m_print_regions.end(),
|
||||
[](const PrintRegion *region) { return region->config().enable_overhang_speed.getBool(); })) {
|
||||
if ( std::any_of(this->print()->m_print_regions.begin(), this->print()->m_print_regions.end(), [](const PrintRegion* region) {
|
||||
const auto& cfg = region->config().enable_overhang_speed.values;
|
||||
return std::any_of(cfg.begin(), cfg.end(), [](const unsigned char v) { return (bool) v; });
|
||||
})) {
|
||||
|
||||
// Estimate curling of support material and add it to the malformaition lines of each layer
|
||||
float support_flow_width = support_material_flow(this, this->config().layer_height).width();
|
||||
SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values,
|
||||
float(this->print()->default_object_config().inner_wall_acceleration.getFloat()),
|
||||
/*float(this->print()->default_object_config().inner_wall_acceleration.getFloat()),*/
|
||||
this->config().raft_layers.getInt(), this->config().brim_type.value,
|
||||
float(this->config().brim_width.getFloat())};
|
||||
SupportSpotsGenerator::estimate_malformations(this->layers(), params);
|
||||
@@ -1109,6 +1179,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "outer_wall_speed"
|
||||
|| opt_key == "small_perimeter_speed"
|
||||
|| opt_key == "small_perimeter_threshold"
|
||||
|| opt_key == "small_support_perimeter_speed"
|
||||
|| opt_key == "small_support_perimeter_threshold"
|
||||
|| opt_key == "sparse_infill_speed"
|
||||
|| opt_key == "inner_wall_speed"
|
||||
|| opt_key == "support_speed"
|
||||
@@ -1150,11 +1222,11 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
// todo multi_extruders: Parameter migration between single and double extruder printers
|
||||
auto is_gap_fill_changed_state_due_to_speed = [&opt_key, &old_config, &new_config]() -> bool {
|
||||
if (opt_key == "gap_infill_speed") {
|
||||
const auto *old_gap_fill_speed = old_config.option<ConfigOptionFloat>(opt_key);
|
||||
const auto *new_gap_fill_speed = new_config.option<ConfigOptionFloat>(opt_key);
|
||||
const auto *old_gap_fill_speed = old_config.option<ConfigOptionFloatsNullable>(opt_key);
|
||||
const auto *new_gap_fill_speed = new_config.option<ConfigOptionFloatsNullable>(opt_key);
|
||||
assert(old_gap_fill_speed && new_gap_fill_speed);
|
||||
return (old_gap_fill_speed->value > 0.f && new_gap_fill_speed->value == 0.f) ||
|
||||
(old_gap_fill_speed->value == 0.f && new_gap_fill_speed->value > 0.f);
|
||||
return (old_gap_fill_speed->values.size() != new_gap_fill_speed->values.size())
|
||||
|| (old_gap_fill_speed->values != new_gap_fill_speed->values);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -1201,6 +1273,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "hole_to_polyhole"
|
||||
|| opt_key == "hole_to_polyhole_threshold"
|
||||
|| opt_key == "hole_to_polyhole_twisted"
|
||||
|| opt_key == "hole_to_polyhole_max_edges"
|
||||
) {
|
||||
steps.emplace_back(posSlice);
|
||||
} else if (opt_key == "enable_support") {
|
||||
@@ -1291,6 +1364,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_combination_max_layer_height"
|
||||
|| opt_key == "bottom_shell_thickness"
|
||||
|| opt_key == "top_shell_thickness"
|
||||
|| opt_key == "top_surface_expansion"
|
||||
|| opt_key == "top_surface_expansion_margin"
|
||||
|| opt_key == "top_surface_expansion_direction"
|
||||
|| opt_key == "minimum_sparse_infill_area"
|
||||
|| opt_key == "sparse_infill_filament_id"
|
||||
|| opt_key == "internal_solid_filament_id"
|
||||
@@ -1301,6 +1377,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "skeleton_infill_line_width"
|
||||
|| opt_key == "infill_direction"
|
||||
|| opt_key == "solid_infill_direction"
|
||||
|| opt_key == "top_layer_direction"
|
||||
|| opt_key == "bottom_layer_direction"
|
||||
|| opt_key == "align_infill_direction_to_model"
|
||||
|| opt_key == "extra_solid_infills"
|
||||
|| opt_key == "ensure_vertical_shell_thickness"
|
||||
@@ -1315,6 +1393,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
} else if (
|
||||
opt_key == "top_surface_pattern"
|
||||
|| opt_key == "bottom_surface_pattern"
|
||||
|| opt_key == "top_surface_fill_order"
|
||||
|| opt_key == "bottom_surface_fill_order"
|
||||
|| opt_key == "internal_solid_infill_pattern"
|
||||
|| opt_key == "external_fill_link_max_length"
|
||||
|| opt_key == "infill_anchor"
|
||||
@@ -1322,6 +1402,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "top_surface_line_width"
|
||||
|| opt_key == "top_surface_density"
|
||||
|| opt_key == "bottom_surface_density"
|
||||
|| opt_key == "center_of_surface_pattern"
|
||||
|| opt_key == "separated_infills"
|
||||
|| opt_key == "initial_layer_line_width"
|
||||
|| opt_key == "small_area_infill_flow_compensation"
|
||||
|| opt_key == "lateral_lattice_angle_1"
|
||||
@@ -1422,6 +1504,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "outer_wall_speed"
|
||||
|| opt_key == "small_perimeter_speed"
|
||||
|| opt_key == "small_perimeter_threshold"
|
||||
|| opt_key == "small_support_perimeter_speed"
|
||||
|| opt_key == "small_support_perimeter_threshold"
|
||||
|| opt_key == "sparse_infill_speed"
|
||||
|| opt_key == "inner_wall_speed"
|
||||
|| opt_key == "internal_solid_infill_speed"
|
||||
@@ -1676,6 +1760,69 @@ void PrintObject::detect_surfaces_type()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: Expand the top surfaces outward by top_surface_expansion in every direction. This
|
||||
// enlarges the top solid infill and, in particular, grows it over the covered material left
|
||||
// by features rising from the middle of a top surface (filling holes and joining tops so the
|
||||
// features rest on it). The expansion stays inside the section it belongs to: each connected
|
||||
// solid island has its own outer wall, so the top is grown within each island separately and
|
||||
// clipped to it - growing one island's top across the gap into another island (which may have
|
||||
// no top surface, leaving a partially filled layer) is never allowed. The top infill sits
|
||||
// inside the perimeters, so the margin is measured from the walls: the island is inset by the
|
||||
// band the walls consume (outer wall + inner walls) plus the configured margin, making that
|
||||
// value the real clearance between the expanded top and the walls (avoiding a hull line). The
|
||||
// original top is unioned back in, so where it already sits within that band it is kept as-is.
|
||||
// Never claims a bottom surface.
|
||||
const double top_expansion = layerm->region().config().top_surface_expansion.value;
|
||||
if (top_expansion > 0. && ! top.empty()) {
|
||||
const double d = scale_(top_expansion);
|
||||
const auto jt = Clipper2Lib::JoinType::Miter;
|
||||
const ExPolygons T = union_ex(to_expolygons(top));
|
||||
const int wall_loops = layerm->region().config().wall_loops.value;
|
||||
const double wall_band = wall_loops <= 0 ? 0. :
|
||||
double(layerm->flow(frExternalPerimeter).scaled_width()) +
|
||||
double(layerm->flow(frPerimeter).scaled_width()) * double(wall_loops - 1);
|
||||
const double margin = scale_(layerm->region().config().top_surface_expansion_margin.value);
|
||||
// minimum real top to act on: ignore anything thinner than ~2 top-infill lines
|
||||
const float min_top = float(layerm->flow(frTopSolidInfill).scaled_width());
|
||||
const auto direction = layerm->region().config().top_surface_expansion_direction.value;
|
||||
|
||||
ExPolygons grown;
|
||||
for (const ExPolygon &island : union_ex(layerm_slices_surfaces)) {
|
||||
// The top infill only exists inside the perimeters, so seed and measure from the infill
|
||||
// region (the island minus the wall band), not the raw slice. A section whose only
|
||||
// exposed top lies in the wall band - i.e. a layer where the top is just the walls
|
||||
// themselves - has no infill here and is skipped, instead of being flooded inward by
|
||||
// the expansion. Thin slivers inside the infill region are dropped by the opening too.
|
||||
const ExPolygons infill_region = wall_band > 0. ? offset_ex(island, -float(wall_band)) : ExPolygons{ island };
|
||||
const ExPolygons island_top = intersection_ex(T, infill_region);
|
||||
if (opening_ex(island_top, min_top).empty())
|
||||
continue; // no real top infill in this section - never expand into it
|
||||
|
||||
// grow by d, then keep only the part allowed by the configured direction: inward fills
|
||||
// the holes/gaps left by features (clip the growth back to the top's own filled outline,
|
||||
// which leaves the outer edge fixed), outward grows the outer edge toward the walls (drop
|
||||
// the growth that fell into the original holes), and inward+outward keeps both.
|
||||
ExPolygons expanded = offset_ex_2(island_top, d, jt);
|
||||
if (direction != TopSurfaceExpansionDirection::InwardAndOutward) {
|
||||
ExPolygons outline; // the top with its holes filled (same outer edge)
|
||||
outline.reserve(island_top.size());
|
||||
for (const ExPolygon &ex : island_top)
|
||||
outline.emplace_back(ex.contour);
|
||||
outline = union_ex(outline);
|
||||
expanded = direction == TopSurfaceExpansionDirection::Inward ?
|
||||
intersection_ex(expanded, outline) : // only growth into the holes
|
||||
diff_ex(expanded, diff_ex(outline, island_top)); // only growth past the outer edge
|
||||
}
|
||||
// hold the expansion clear of the walls by the configured margin
|
||||
const ExPolygons allowed = margin > 0. ? offset_ex(infill_region, -float(margin)) : infill_region;
|
||||
append(grown, intersection_ex(expanded, allowed));
|
||||
}
|
||||
|
||||
ExPolygons new_top = diff_ex(union_ex(T, grown), to_expolygons(bottom));
|
||||
top.clear();
|
||||
surfaces_append(top, std::move(new_top), stTop);
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
@@ -2172,7 +2319,7 @@ void PrintObject::discover_vertical_shells()
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
Flow solid_infill_flow = layerm->flow(frSolidInfill);
|
||||
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
|
||||
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
|
||||
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
|
||||
Polygons shell;
|
||||
Polygons holes;
|
||||
@@ -2214,7 +2361,7 @@ void PrintObject::discover_vertical_shells()
|
||||
shell = std::move(shells2);
|
||||
else if (! shells2.empty()) {
|
||||
polygons_append(shell, shells2);
|
||||
// Running the union_ using the Clipper library piece by piece is cheaper
|
||||
// Running the union_ using the Clipper library piece by piece is cheaper
|
||||
// than running the union_ all at once.
|
||||
shell = union_(shell);
|
||||
}
|
||||
@@ -2281,12 +2428,12 @@ void PrintObject::discover_vertical_shells()
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell));
|
||||
svg.draw(shell);
|
||||
svg.draw_outline(shell, "black", scale_(0.05));
|
||||
svg.Close();
|
||||
svg.Close();
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
#if 0
|
||||
// shell = union_(shell, true);
|
||||
shell = union_(shell, false);
|
||||
shell = union_(shell, false);
|
||||
#endif
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
shell_ex = union_safety_offset_ex(shell);
|
||||
@@ -2590,7 +2737,7 @@ void PrintObject::bridge_over_infill()
|
||||
}
|
||||
}
|
||||
|
||||
// LIGHTNING INFILL SECTION - If lightning infill is used somewhere, we check the areas that are going to be bridges, and those that rely on the
|
||||
// LIGHTNING INFILL SECTION - If lightning infill is used somewhere, we check the areas that are going to be bridges, and those that rely on the
|
||||
// lightning infill under them get expanded. This somewhat helps to ensure that most of the extrusions are anchored to the lightning infill at the ends.
|
||||
// It requires modifying this instance of print object in a specific way, so that we do not invalidate the pointers in our surfaces_by_layer structure.
|
||||
if (has_lightning_infill) {
|
||||
@@ -3565,13 +3712,13 @@ static void clamp_feature_filament_to_valid(ConfigOptionInt &opt, size_t num_ext
|
||||
opt.value = 1;
|
||||
}
|
||||
|
||||
PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders)
|
||||
PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders, std::vector<int>& variant_index)
|
||||
{
|
||||
PrintObjectConfig config = default_object_config;
|
||||
{
|
||||
DynamicPrintConfig src_normalized(object.config.get());
|
||||
src_normalized.normalize_fdm();
|
||||
config.apply(src_normalized, true);
|
||||
update_static_print_config_from_dynamic(config, src_normalized, variant_index, print_options_with_variant, 1);
|
||||
}
|
||||
// Clamp invalid extruders to the default extruder (with index 1).
|
||||
clamp_exturder_to_default(config.support_filament, num_extruders);
|
||||
@@ -3599,7 +3746,7 @@ struct FeatureFilamentOverrideMask
|
||||
bool inner_wall_filament_id = false;
|
||||
};
|
||||
|
||||
static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in, FeatureFilamentOverrideMask &feature_overrides)
|
||||
static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in, FeatureFilamentOverrideMask &feature_overrides, std::vector<int>& variant_index)
|
||||
{
|
||||
// 1) Explicit feature filament values take precedence over base extruder fallback.
|
||||
auto *opt_extruder = in.opt<ConfigOptionInt>(key_extruder);
|
||||
@@ -3640,8 +3787,18 @@ static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPr
|
||||
else if (it->first == "inner_wall_filament_id")
|
||||
feature_overrides.inner_wall_filament_id = false;
|
||||
}
|
||||
} else
|
||||
my_opt->set(it->second.get());
|
||||
} else {
|
||||
if (*my_opt != *(it->second)) {
|
||||
if (my_opt->is_scalar() || variant_index.empty() || (print_options_with_variant.find(it->first) == print_options_with_variant.end()))
|
||||
my_opt->set(it->second.get());
|
||||
//my_opt->set(it->second.get());
|
||||
else {
|
||||
ConfigOptionVectorBase* opt_vec_src = static_cast<ConfigOptionVectorBase*>(my_opt);
|
||||
const ConfigOptionVectorBase* opt_vec_dest = static_cast<const ConfigOptionVectorBase*>(it->second.get());
|
||||
opt_vec_src->set_to_index(opt_vec_dest, variant_index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Apply base extruder only to features that were not explicitly overridden.
|
||||
@@ -3661,7 +3818,7 @@ static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPr
|
||||
}
|
||||
}
|
||||
|
||||
PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders)
|
||||
PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders, std::vector<int>& variant_index)
|
||||
{
|
||||
PrintRegionConfig config = default_or_parent_region_config;
|
||||
FeatureFilamentOverrideMask feature_overrides;
|
||||
@@ -3679,17 +3836,17 @@ PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &defau
|
||||
if (volume.is_model_part()) {
|
||||
// default_or_parent_region_config contains the Print's PrintRegionConfig.
|
||||
// Override with ModelObject's PrintRegionConfig values.
|
||||
apply_to_print_region_config(config, volume.get_object()->config.get(), feature_overrides);
|
||||
apply_to_print_region_config(config, volume.get_object()->config.get(), feature_overrides, variant_index);
|
||||
} else {
|
||||
// default_or_parent_region_config contains parent PrintRegion config, which already contains ModelVolume's config.
|
||||
}
|
||||
apply_to_print_region_config(config, volume.config.get(), feature_overrides);
|
||||
apply_to_print_region_config(config, volume.config.get(), feature_overrides, variant_index);
|
||||
if (! volume.material_id().empty())
|
||||
apply_to_print_region_config(config, volume.material()->config.get(), feature_overrides);
|
||||
apply_to_print_region_config(config, volume.material()->config.get(), feature_overrides, variant_index);
|
||||
if (layer_range_config != nullptr) {
|
||||
// Not applicable to modifiers.
|
||||
assert(volume.is_model_part());
|
||||
apply_to_print_region_config(config, *layer_range_config, feature_overrides);
|
||||
apply_to_print_region_config(config, *layer_range_config, feature_overrides, variant_index);
|
||||
}
|
||||
// Resolve feature defaults and clamp invalid extruders to index 1.
|
||||
clamp_feature_filament_to_valid(config.sparse_infill_filament_id, num_extruders);
|
||||
@@ -3739,7 +3896,7 @@ void PrintObject::update_slicing_parameters()
|
||||
}
|
||||
|
||||
// Orca: XYZ shrinkage compensation has introduced the const Vec3d &object_shrinkage_compensation parameter to the function below
|
||||
SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z, const Vec3d &object_shrinkage_compensation)
|
||||
SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z, const Vec3d &object_shrinkage_compensation, std::vector<int> variant_index)
|
||||
{
|
||||
PrintConfig print_config;
|
||||
PrintObjectConfig object_config;
|
||||
@@ -3749,14 +3906,14 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
|
||||
default_region_config.apply(full_config, true);
|
||||
// BBS
|
||||
size_t filament_extruders = print_config.filament_diameter.size();
|
||||
object_config = object_config_from_model_object(object_config, model_object, filament_extruders);
|
||||
object_config = object_config_from_model_object(object_config, model_object, filament_extruders, variant_index);
|
||||
|
||||
std::vector<unsigned int> object_extruders;
|
||||
for (const ModelVolume* model_volume : model_object.volumes)
|
||||
if (model_volume->is_model_part()) {
|
||||
PrintRegion::collect_object_printing_extruders(
|
||||
print_config,
|
||||
region_config_from_model_volume(default_region_config, nullptr, *model_volume, filament_extruders),
|
||||
region_config_from_model_volume(default_region_config, nullptr, *model_volume, filament_extruders, variant_index),
|
||||
object_config.brim_type != btNoBrim && object_config.brim_width > 0.,
|
||||
object_extruders);
|
||||
for (const std::pair<const t_layer_height_range, ModelConfig> &range_and_config : model_object.layer_config_ranges)
|
||||
@@ -3768,7 +3925,7 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
|
||||
range_and_config.second.has("bottom_surface_filament_id"))
|
||||
PrintRegion::collect_object_printing_extruders(
|
||||
print_config,
|
||||
region_config_from_model_volume(default_region_config, &range_and_config.second.get(), *model_volume, filament_extruders),
|
||||
region_config_from_model_volume(default_region_config, &range_and_config.second.get(), *model_volume, filament_extruders, variant_index),
|
||||
object_config.brim_type != btNoBrim && object_config.brim_width > 0.,
|
||||
object_extruders);
|
||||
}
|
||||
|
||||
@@ -376,10 +376,7 @@ SupportGeneratorLayersPtr generate_raft_base(
|
||||
Polygons trimming;
|
||||
// BBS: if first layer of support is intersected with object island, it must have the same function as brim unless in nobrim mode.
|
||||
// brim_object_gap is changed to 0 by default, it's no longer appropriate to use it to determine the gap of first layer support.
|
||||
//if (object.has_brim())
|
||||
// trimming = offset(object.layers().front()->lslices, (float)scale_(object.config().brim_object_gap.value), SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
//else
|
||||
trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
trimming = offset(object.layers().front()->lslices, (float) scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
if (inflate_factor_1st_layer > SCALED_EPSILON) {
|
||||
// Inflate in multiple steps to avoid leaking of the support 1st layer through object walls.
|
||||
auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width())));
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace SupportSpotsGenerator {
|
||||
struct Params
|
||||
{
|
||||
Params(
|
||||
const std::vector<std::string> &filament_types, float max_acceleration, int raft_layers_count, BrimType brim_type, float brim_width)
|
||||
: max_acceleration(max_acceleration), raft_layers_count(raft_layers_count), brim_type(brim_type), brim_width(brim_width)
|
||||
const std::vector<std::string> &filament_types/*, float max_acceleration*/, int raft_layers_count, BrimType brim_type, float brim_width)
|
||||
: /*max_acceleration(max_acceleration), */raft_layers_count(raft_layers_count), brim_type(brim_type), brim_width(brim_width)
|
||||
{
|
||||
if (filament_types.size() > 1) {
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SupportSpotsGenerator does not currently handle different materials properly, only first will be used";
|
||||
}
|
||||
if (filament_types.empty() || filament_types[0].empty()) {
|
||||
@@ -36,8 +36,8 @@ struct Params
|
||||
|
||||
// the algorithm should use the following units for all computations: distance [mm], mass [g], time [s], force [g*mm/s^2]
|
||||
const float bridge_distance = 16.0f; // mm
|
||||
const float max_acceleration; // mm/s^2 ; max acceleration of object in XY -- should be applicable only to printers with bed slinger,
|
||||
// however we do not have such info yet. The force is usually small anyway, so not such a big deal to include it everytime
|
||||
// const float max_acceleration; // mm/s^2 ; max acceleration of object in XY -- should be applicable only to printers with bed slinger,
|
||||
// // however we do not have such info yet. The force is usually small anyway, so not such a big deal to include it everytime
|
||||
const int raft_layers_count;
|
||||
std::string filament_type;
|
||||
|
||||
|
||||
@@ -102,6 +102,9 @@ const std::string& var_dir();
|
||||
// Return a full resource path for a file_name.
|
||||
std::string var(const std::string &file_name);
|
||||
|
||||
// Snap a nozzle diameter to the closest supported value and format it as a string (e.g. 0.4 -> "0.4").
|
||||
std::string format_diameter_to_str(double diameter, int precision = 1);
|
||||
|
||||
// Set a path with various static definition data (for example the initial config bundles).
|
||||
void set_resources_dir(const std::string &path);
|
||||
// Return a full path to the resources directory.
|
||||
@@ -194,6 +197,7 @@ std::string debug_out_path(const char *name, ...);
|
||||
// smaller level means less log. level=5 means saving all logs.
|
||||
void set_log_path_and_level(const std::string& file, unsigned int level);
|
||||
void flush_logs();
|
||||
void shutdown_console_logging();
|
||||
boost::filesystem::path get_log_file_name();
|
||||
|
||||
// A special type for strings encoded in the local Windows 8-bit code page.
|
||||
@@ -299,6 +303,10 @@ std::string header_gcodeviewer_generated();
|
||||
|
||||
// getpid platform wrapper
|
||||
extern unsigned get_current_pid();
|
||||
// Per-user id for isolating temp dirs; empty on Windows (its temp dir is already per-user).
|
||||
std::string per_user_temp_id();
|
||||
// Per-user temp root under `base`; an empty `user_id` returns `base` unchanged.
|
||||
std::string per_user_temp_dir(const std::string &base, const std::string &user_id);
|
||||
// BBS: backup & restore
|
||||
std::string get_process_name(int pid);
|
||||
|
||||
|
||||
+58
-5
@@ -16,7 +16,7 @@ float CalibPressureAdvance::find_optimal_PA_speed(const DynamicPrintConfig &conf
|
||||
const float nozzle_diameter = config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(extruder_id);
|
||||
if (line_width <= 0.) line_width = Flow::auto_extrusion_width(frPerimeter, nozzle_diameter);
|
||||
Flow pattern_line = Flow(line_width, layer_height, nozzle_diameter);
|
||||
auto pa_speed = std::min(std::max(general_suggested_min_speed, config.option<ConfigOptionFloat>("outer_wall_speed")->value),
|
||||
auto pa_speed = std::min(std::max(general_suggested_min_speed, config.option<ConfigOptionFloatsNullable>("outer_wall_speed")->get_at(extruder_id)),
|
||||
filament_max_volumetric_speed / pattern_line.mm3_per_mm());
|
||||
|
||||
return std::floor(pa_speed);
|
||||
@@ -469,6 +469,31 @@ std::string CalibPressureAdvanceLine::generate_test(double start_pa /*= 0*/, dou
|
||||
return print_pa_lines(startx, starty, start_pa, step_pa, count);
|
||||
}
|
||||
|
||||
BoundingBoxf CalibPressureAdvanceLine::print_extents(const BoundingBoxf &bed_ext) const
|
||||
{
|
||||
BoundingBoxf adjusted_bed = bed_ext;
|
||||
if (is_delta()) {
|
||||
CalibPressureAdvanceLine::delta_scale_bed_ext(adjusted_bed);
|
||||
}
|
||||
|
||||
double bed_width = adjusted_bed.size().x();
|
||||
// m_length_long adjusts for narrow beds – exactly as in generate_test()
|
||||
double line_long = 40.0 + std::min(bed_width - 120.0, 0.0);
|
||||
double total_line_len = m_length_short * 2 + line_long;
|
||||
double start_x = adjusted_bed.min.x() + (bed_width - 2 * m_length_short - line_long - 20.0) / 2.0;
|
||||
double box_width = m_draw_numbers ? (number_spacing() * 8) : 0.0; // 3.0 * 8 = 24 mm
|
||||
|
||||
BoundingBoxf extent;
|
||||
extent.min.x() = start_x;
|
||||
extent.max.x() = start_x + total_line_len + m_line_width + box_width;
|
||||
|
||||
// Y bounds are the full bed (the caller will inset them by -25)
|
||||
extent.min.y() = adjusted_bed.min.y();
|
||||
extent.max.y() = adjusted_bed.max.y();
|
||||
|
||||
return extent;
|
||||
}
|
||||
|
||||
bool CalibPressureAdvanceLine::is_delta() const { return mp_gcodegen->config().printable_area.values.size() > 4; }
|
||||
|
||||
std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double start_y, double start_pa, double step_pa, int num)
|
||||
@@ -491,9 +516,10 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
|
||||
const double slow = CalibPressureAdvance::speed_adjust(m_slow_speed);
|
||||
std::stringstream gcode;
|
||||
gcode << mp_gcodegen->writer().travel_to_z(m_height_layer + z_offset);
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << (m_height_layer + z_offset) << "\n";
|
||||
double y_pos = start_y;
|
||||
|
||||
// prime line
|
||||
// Purge/first perimeter - acts as an anchor to the rest of the model
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
|
||||
gcode << writer.set_pressure_advance(0.0);
|
||||
auto prime_x = start_x;
|
||||
gcode << move_to(Vec2d(prime_x, y_pos + (num) * m_space_y), writer);
|
||||
@@ -503,10 +529,13 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
|
||||
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 << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
|
||||
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 << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
|
||||
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 << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
|
||||
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);
|
||||
@@ -530,9 +559,15 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
|
||||
|
||||
const auto box_start_x = start_x + m_length_short + m_length_long + m_length_short + m_line_width;
|
||||
DrawBoxOptArgs default_box_opt_args(2, m_height_layer, m_line_width, fast);
|
||||
//Draw box
|
||||
default_box_opt_args.is_filled = true;
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Bottom surface\n";
|
||||
gcode << draw_box(writer, box_start_x, start_y - m_space_y,
|
||||
number_spacing() * 8, (num + 1) * m_space_y, default_box_opt_args);
|
||||
//Ensure numbers are shown on the next layer in gcode processor, as in reality
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) << "\n";
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << (m_height_layer*2 + z_offset) << "\n";
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Top surface\n";
|
||||
gcode << writer.travel_to_z(m_height_layer*2 + z_offset);
|
||||
for (int i = 0; i < num; i += 2) {
|
||||
gcode << draw_number(box_start_x + 3 + m_line_width, y_pos + i * m_space_y + m_space_y / 2, start_pa + i * step_pa, m_draw_digit_mode,
|
||||
@@ -595,12 +630,16 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
|
||||
speed_adjust(speed_first_layer()));
|
||||
|
||||
// create anchoring frame
|
||||
//pattern uses outer wall speed/width
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
|
||||
gcode << draw_box(m_writer, 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();
|
||||
//draw box as bottom surface, so numbers are clearly visible on top
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Bottom surface\n";
|
||||
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y() + frame_size_y() + line_spacing_first_layer(),
|
||||
print_size_x(),
|
||||
max_numbering_height() + line_spacing_first_layer() + m_glyph_padding_vertical * 2, draw_box_opt_args);
|
||||
@@ -611,7 +650,9 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
|
||||
const double zhop_config_value = m_config.option<ConfigOptionFloats>("z_hop")->get_at(0);
|
||||
const auto accel = accel_perimeter();
|
||||
|
||||
// draw pressure advance pattern
|
||||
// Draw pressure advance pattern
|
||||
// pattern uses outer wall speed, label it as such
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
|
||||
for (int i = 0; i < m_num_layers; ++i) {
|
||||
const double layer_height = height_first_layer() + height_z_offset() + (i * height_layer());
|
||||
const double zhop_height = layer_height + zhop_config_value;
|
||||
@@ -643,6 +684,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
|
||||
m_config.option<ConfigOptionFloats>("filament_flow_ratio")->get_at(0));
|
||||
|
||||
// glyph on every other line
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
|
||||
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,
|
||||
@@ -692,7 +734,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
|
||||
for (int j = 0; j < num_patterns; ++j) {
|
||||
// increment pressure advance
|
||||
gcode << m_writer.set_pressure_advance(m_params.start + (j * m_params.step));
|
||||
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
|
||||
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;
|
||||
@@ -729,6 +771,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
|
||||
}
|
||||
}
|
||||
|
||||
gcode << m_writer.reset_e();
|
||||
gcode << m_writer.set_pressure_advance(m_params.start);
|
||||
gcode << "; end pressure advance pattern for layer\n";
|
||||
|
||||
@@ -756,6 +799,16 @@ Vec3d CalibPressureAdvancePattern::get_start_offset()
|
||||
return m_starting_point;
|
||||
}
|
||||
|
||||
double CalibPressureAdvancePattern::line_width_first_layer() const
|
||||
{
|
||||
// TODO: FIXME: find out current filament/extruder?
|
||||
const double nozzle_diameter = m_config.opt_float("nozzle_diameter", m_params.extruder_id);
|
||||
const double width = m_config.get_abs_value("initial_layer_line_width", nozzle_diameter);
|
||||
if (width <= 0.)
|
||||
return Flow::auto_extrusion_width(frExternalPerimeter, nozzle_diameter);
|
||||
return width;
|
||||
};
|
||||
|
||||
double CalibPressureAdvancePattern::line_width() const
|
||||
{
|
||||
// TODO: FIXME: find out current filament/extruder?
|
||||
|
||||
+22
-15
@@ -35,10 +35,10 @@ struct Calib_Params
|
||||
{
|
||||
Calib_Params() : mode(CalibMode::Calib_None){};
|
||||
int extruder_id = 0;
|
||||
double start, end, step;
|
||||
bool print_numbers;
|
||||
double freqStartX, freqEndX, freqStartY, freqEndY;
|
||||
int test_model;
|
||||
double start = 0.0, end = 1.0, step = 0.1;
|
||||
bool print_numbers = false;
|
||||
double freqStartX = 0.0, freqEndX = 1.0, freqStartY = 0.0, freqEndY = 1.0;
|
||||
int test_model = 0;
|
||||
std::string shaper_type;
|
||||
std::vector<double> accelerations;
|
||||
std::vector<double> speeds;
|
||||
@@ -83,6 +83,8 @@ public:
|
||||
NozzleVolumeType nozzle_volume_type;
|
||||
BedType bed_type;
|
||||
float nozzle_diameter;
|
||||
int nozzle_pos_id{-1};
|
||||
std::string nozzle_sn;
|
||||
std::string filament_id;
|
||||
std::string setting_id;
|
||||
std::string name;
|
||||
@@ -93,6 +95,8 @@ public:
|
||||
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;
|
||||
@@ -123,7 +127,9 @@ public:
|
||||
int ams_id = 0;
|
||||
int slot_id = 0;
|
||||
int cali_idx = -1;
|
||||
int nozzle_pos_id = -1; //-1 means no nozzle pos
|
||||
float nozzle_diameter;
|
||||
std::string nozzle_sn;
|
||||
std::string filament_id;
|
||||
std::string setting_id;
|
||||
std::string name;
|
||||
@@ -140,7 +146,9 @@ struct PACalibIndexInfo
|
||||
int ams_id = 0;
|
||||
int slot_id = 0;
|
||||
int cali_idx = -1; // -1 means default
|
||||
int nozzle_pos_id = -1; //-1 means no nozzle pos
|
||||
float nozzle_diameter;
|
||||
std::string nozzle_sn;
|
||||
std::string filament_id;
|
||||
};
|
||||
|
||||
@@ -148,7 +156,9 @@ struct PACalibExtruderInfo
|
||||
{
|
||||
int extruder_id = 0;
|
||||
NozzleVolumeType nozzle_volume_type;
|
||||
int nozzle_pos_id = -1; //-1 means no nozzle pos
|
||||
float nozzle_diameter;
|
||||
std::string nozzle_sn;
|
||||
std::string filament_id = "";
|
||||
bool use_extruder_id{true};
|
||||
bool use_nozzle_volume_type{true};
|
||||
@@ -244,6 +254,8 @@ class CalibPressureAdvanceLine : public CalibPressureAdvance
|
||||
public:
|
||||
CalibPressureAdvanceLine(GCode* gcodegen);
|
||||
~CalibPressureAdvanceLine(){};
|
||||
// Return the X‑bounds of the pattern on the given bed.
|
||||
BoundingBoxf print_extents(const BoundingBoxf &bed_ext) const;
|
||||
|
||||
std::string generate_test(double start_pa = 0, double step_pa = 0.002, int count = 50);
|
||||
|
||||
@@ -280,7 +292,7 @@ private:
|
||||
|
||||
struct SuggestedConfigCalibPAPattern
|
||||
{
|
||||
const std::vector<std::pair<std::string, double>> float_pairs{{"initial_layer_speed", 30}};
|
||||
const std::vector<std::pair<std::string, std::vector<double>>> floats_pairs{{"initial_layer_speed", {30}}};
|
||||
|
||||
const std::vector<std::pair<std::string, double>> nozzle_ratio_pairs{{"line_width", 112.5}, {"initial_layer_line_width", 140}};
|
||||
|
||||
@@ -312,15 +324,10 @@ public:
|
||||
|
||||
protected:
|
||||
// todo multi_extruders:
|
||||
double speed_first_layer() const { return m_config.option<ConfigOptionFloat>("initial_layer_speed")->value; };
|
||||
double speed_perimeter() const { return m_config.option<ConfigOptionFloat>("outer_wall_speed")->value; };
|
||||
double accel_perimeter() const { return m_config.option<ConfigOptionFloat>("outer_wall_acceleration")->value; }
|
||||
double line_width_first_layer() const
|
||||
{
|
||||
// TODO: FIXME: find out current filament/extruder?
|
||||
const double nozzle_diameter = m_config.opt_float("nozzle_diameter", 0);
|
||||
return m_config.get_abs_value("initial_layer_line_width", nozzle_diameter);
|
||||
};
|
||||
double speed_first_layer() const { return m_config.get_abs_value_at("initial_layer_speed", m_params.extruder_id); };
|
||||
double speed_perimeter() const { return m_config.get_abs_value_at("outer_wall_speed", m_params.extruder_id); };
|
||||
double accel_perimeter() const { return m_config.get_abs_value_at("outer_wall_acceleration", m_params.extruder_id); }
|
||||
double line_width_first_layer() const;
|
||||
double line_width() const;
|
||||
int wall_count() const { return m_config.option<ConfigOptionInt>("wall_loops")->value; };
|
||||
|
||||
@@ -374,4 +381,4 @@ private:
|
||||
const double m_glyph_padding_vertical{1};
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <exception>
|
||||
#include <cstdint>
|
||||
|
||||
#include "miniz_extension.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW64__)
|
||||
#include "boost/nowide/cstdio.hpp"
|
||||
@@ -15,6 +17,33 @@
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
std::string decode_zip_unicode_path_extra_field(const std::string& extra, const std::string& path)
|
||||
{
|
||||
size_t offset = 0;
|
||||
const mz_uint32 path_crc = mz_crc32(0, reinterpret_cast<const unsigned char*>(path.data()), path.size());
|
||||
|
||||
while (offset + 4 <= extra.size()) {
|
||||
const unsigned char* field = reinterpret_cast<const unsigned char*>(extra.data() + offset);
|
||||
const std::uint16_t len = field[2] | (static_cast<std::uint16_t>(field[3]) << 8);
|
||||
if (offset + 4 + len > extra.size())
|
||||
break;
|
||||
|
||||
if (field[0] == 0x75 && field[1] == 0x70 && len >= 5 && field[4] == 0x01) {
|
||||
const mz_uint32 stored_crc =
|
||||
static_cast<mz_uint32>(field[5]) |
|
||||
(static_cast<mz_uint32>(field[6]) << 8) |
|
||||
(static_cast<mz_uint32>(field[7]) << 16) |
|
||||
(static_cast<mz_uint32>(field[8]) << 24);
|
||||
if (stored_crc == path_crc)
|
||||
return std::string(extra.data() + offset + 9, extra.data() + offset + 4 + len);
|
||||
}
|
||||
|
||||
offset += 4 + len;
|
||||
}
|
||||
|
||||
return Slic3r::decode_path(path.c_str());
|
||||
}
|
||||
|
||||
bool open_zip(mz_zip_archive *zip, const char *fname, bool isread)
|
||||
{
|
||||
if (!zip) return false;
|
||||
@@ -76,6 +105,16 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname)
|
||||
bool close_zip_reader(mz_zip_archive *zip) { return close_zip(zip, true); }
|
||||
bool close_zip_writer(mz_zip_archive *zip) { return close_zip(zip, false); }
|
||||
|
||||
std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat)
|
||||
{
|
||||
if (stat.m_is_utf8)
|
||||
return stat.m_filename;
|
||||
|
||||
std::string extra(1024, 0);
|
||||
const size_t extra_size = mz_zip_reader_get_extra(zip, stat.m_file_index, extra.data(), extra.size());
|
||||
return decode_zip_unicode_path_extra_field(extra.substr(0, extra_size > 0 ? extra_size - 1 : 0), stat.m_filename);
|
||||
}
|
||||
|
||||
MZ_Archive::MZ_Archive()
|
||||
{
|
||||
mz_zip_zero_struct(&arch);
|
||||
|
||||
@@ -10,6 +10,7 @@ bool open_zip_reader(mz_zip_archive *zip, const std::string &fname_utf8);
|
||||
bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8);
|
||||
bool close_zip_reader(mz_zip_archive *zip);
|
||||
bool close_zip_writer(mz_zip_archive *zip);
|
||||
std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat);
|
||||
|
||||
class MZ_Archive {
|
||||
public:
|
||||
|
||||
@@ -5,8 +5,13 @@
|
||||
#include <locale>
|
||||
#include <ctime>
|
||||
#include <cstdarg>
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "format.hpp"
|
||||
#include "Platform.hpp"
|
||||
@@ -46,14 +51,19 @@
|
||||
#include <boost/log/core.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/log/expressions.hpp>
|
||||
#include <boost/log/sinks/async_frontend.hpp>
|
||||
#include <boost/log/sinks/text_file_backend.hpp>
|
||||
#include <boost/log/sinks/text_ostream_backend.hpp>
|
||||
#include <boost/log/utility/setup/file.hpp>
|
||||
#include <boost/log/utility/setup/common_attributes.hpp>
|
||||
#include <boost/log/sources/severity_logger.hpp>
|
||||
#include <boost/log/sources/record_ostream.hpp>
|
||||
#include <boost/log/support/date_time.hpp>
|
||||
|
||||
#include <boost/core/null_deleter.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
@@ -174,6 +184,7 @@ unsigned get_logging_level()
|
||||
}
|
||||
|
||||
boost::shared_ptr<boost::log::sinks::synchronous_sink<boost::log::sinks::text_file_backend>> g_log_sink;
|
||||
boost::shared_ptr<boost::log::sinks::asynchronous_sink<boost::log::sinks::text_ostream_backend>> g_console_log_sink;
|
||||
|
||||
// Force set_logging_level(<=error) after loading of the DLL.
|
||||
// This is currently only needed if libslic3r is loaded as a shared library into Perl interpreter
|
||||
@@ -346,6 +357,19 @@ namespace src = boost::log::sources;
|
||||
namespace expr = boost::log::expressions;
|
||||
namespace keywords = boost::log::keywords;
|
||||
namespace attrs = boost::log::attributes;
|
||||
namespace sinks = boost::log::sinks;
|
||||
|
||||
void shutdown_console_logging()
|
||||
{
|
||||
if (!g_console_log_sink)
|
||||
return;
|
||||
|
||||
auto console_sink = g_console_log_sink;
|
||||
boost::log::core::get()->remove_sink(console_sink);
|
||||
console_sink->stop();
|
||||
g_console_log_sink.reset();
|
||||
}
|
||||
|
||||
void set_log_path_and_level(const std::string& file, unsigned int level)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
@@ -377,6 +401,24 @@ void set_log_path_and_level(const std::string& file, unsigned int level)
|
||||
keywords::auto_flush = true
|
||||
);
|
||||
|
||||
shutdown_console_logging();
|
||||
|
||||
#ifdef SLIC3R_CONSOLE_LOG
|
||||
auto console_backend = boost::make_shared<sinks::text_ostream_backend>();
|
||||
console_backend->add_stream(boost::shared_ptr<std::ostream>(&std::cout, boost::null_deleter()));
|
||||
console_backend->auto_flush(true);
|
||||
|
||||
g_console_log_sink = boost::make_shared<sinks::asynchronous_sink<sinks::text_ostream_backend>>(console_backend);
|
||||
g_console_log_sink->set_formatter(
|
||||
expr::stream
|
||||
<< "[" << expr::attr< logging::trivial::severity_level >("Severity") << "]\t"
|
||||
<< expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S.%f") << " "
|
||||
<<"[Thread " << expr::attr<attrs::current_thread_id::value_type>("ThreadID") << "]"
|
||||
<< ": " << expr::smessage
|
||||
);
|
||||
boost::log::core::get()->add_sink(g_console_log_sink);
|
||||
#endif
|
||||
|
||||
logging::add_common_attributes();
|
||||
|
||||
set_logging_level(level);
|
||||
@@ -1247,6 +1289,24 @@ unsigned get_current_pid()
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string per_user_temp_id()
|
||||
{
|
||||
#ifdef WIN32
|
||||
return {};
|
||||
#else
|
||||
return std::to_string(static_cast<unsigned long>(::getuid()));
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string per_user_temp_dir(const std::string &base, const std::string &user_id)
|
||||
{
|
||||
if (user_id.empty())
|
||||
return base;
|
||||
// Keep the id at the top level so each user's dir sits directly in the world-writable temp
|
||||
// root; a shared parent dir would be owned by whichever user created it first.
|
||||
return base + "/orcaslicer_" + user_id;
|
||||
}
|
||||
|
||||
// BBS: backup & restore
|
||||
std::string get_process_name(int pid)
|
||||
{
|
||||
@@ -1441,6 +1501,15 @@ std::string format_memsize(size_t bytes, unsigned int decimals)
|
||||
}
|
||||
}
|
||||
|
||||
std::string format_diameter_to_str(double diameter, int precision)
|
||||
{
|
||||
double candidates[] = {0.2, 0.4, 0.6, 0.8};
|
||||
double best = *std::min_element(std::begin(candidates), std::end(candidates), [diameter](double a, double b) { return std::abs(a - diameter) < std::abs(b - diameter); });
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(precision) << best;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// Returns platform-specific string to be used as log output or parsed in SysInfoDialog.
|
||||
// The latter parses the string with (semi)colons as separators, it should look about as
|
||||
// "desc1: value1; desc2: value2" or similar (spaces should not matter).
|
||||
|
||||
@@ -91,6 +91,13 @@ public:
|
||||
//
|
||||
void toggle_top_layer_only_view_range();
|
||||
//
|
||||
// Dim previous layers (ORCA, ported from preFlight)
|
||||
// Whether the layers below the current top layer are rendered darkened while
|
||||
// scrubbing below the full print, so only the current layer is shown at full brightness.
|
||||
//
|
||||
bool is_dim_previous_layers() const;
|
||||
void set_dim_previous_layers(bool value);
|
||||
//
|
||||
// Returns true if the given option is visible.
|
||||
//
|
||||
bool is_option_visible(EOptionType type) const;
|
||||
|
||||
@@ -19,6 +19,9 @@ struct Settings
|
||||
EViewType view_type{ EViewType::FeatureType };
|
||||
ETimeMode time_mode{ ETimeMode::Normal };
|
||||
bool top_layer_only_view_range{ false };
|
||||
// ORCA: when enabled, all layers below the current top layer are rendered
|
||||
// darkened (keeping their color) while scrubbing below the full print (ported from preFlight)
|
||||
bool dim_previous_layers{ false };
|
||||
bool spiral_vase_mode{ false };
|
||||
//
|
||||
// Required update flags
|
||||
|
||||
@@ -72,6 +72,16 @@ void Viewer::toggle_top_layer_only_view_range()
|
||||
m_impl->toggle_top_layer_only_view_range();
|
||||
}
|
||||
|
||||
bool Viewer::is_dim_previous_layers() const
|
||||
{
|
||||
return m_impl->is_dim_previous_layers();
|
||||
}
|
||||
|
||||
void Viewer::set_dim_previous_layers(bool value)
|
||||
{
|
||||
m_impl->set_dim_previous_layers(value);
|
||||
}
|
||||
|
||||
bool Viewer::is_option_visible(EOptionType type) const
|
||||
{
|
||||
return m_impl->is_option_visible(type);
|
||||
|
||||
@@ -1223,6 +1223,20 @@ static float encode_color(const Color& color) {
|
||||
return static_cast<float>(i_color);
|
||||
}
|
||||
|
||||
// ORCA: how much the layers below the current top layer are darkened when
|
||||
// Settings::dim_previous_layers is enabled (ported from preFlight). 0.0 = no change, 1.0 = black.
|
||||
static constexpr float PREVIOUS_LAYER_DARKEN_FACTOR = 0.60f;
|
||||
|
||||
// ORCA: returns the encoded color scaled towards black by 'factor', preserving its hue
|
||||
static float encode_color_darkened(const Color& color, float factor) {
|
||||
const float keep = 1.0f - factor;
|
||||
const int r = static_cast<int>(color[0] * keep);
|
||||
const int g = static_cast<int>(color[1] * keep);
|
||||
const int b = static_cast<int>(color[2] * keep);
|
||||
const int i_color = r << 16 | g << 8 | b;
|
||||
return static_cast<float>(i_color);
|
||||
}
|
||||
|
||||
|
||||
void ViewerImpl::update_colors_texture()
|
||||
{
|
||||
@@ -1234,14 +1248,30 @@ void ViewerImpl::update_colors_texture()
|
||||
const size_t top_layer_id = m_settings.top_layer_only_view_range ? m_layers.get_view_range()[1] : 0;
|
||||
const bool color_top_layer_only = m_view_range.get_full()[1] != m_view_range.get_visible()[1];
|
||||
|
||||
// ORCA: when dim_previous_layers is enabled, darken every layer below the current top layer
|
||||
// (keeping its color) whenever we are not rendering the whole print, so that only the layer
|
||||
// being scrubbed to is shown at full brightness (ported from preFlight). This shares
|
||||
// top_layer_id with the greying path, so it only applies while in top-layer-only mode - that
|
||||
// way the moves slider still animates normally across all layers when that mode is disabled.
|
||||
const bool dim_previous_layers = m_settings.dim_previous_layers && !m_layers.empty();
|
||||
const bool full_render = (m_layers.get_view_range()[0] == 0) &&
|
||||
(m_layers.get_view_range()[1] >= static_cast<uint32_t>(m_layers.count()) - 1) &&
|
||||
(m_view_range.get_visible()[1] == m_view_range.get_full()[1]);
|
||||
|
||||
// Based on current settings and slider position, we might want to render some
|
||||
// vertices as dark grey. Use either that or the normal color (from the cache).
|
||||
// vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache).
|
||||
std::vector<float> colors(m_vertices_colors.size());
|
||||
assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size());
|
||||
for (size_t i=0; i<m_vertices.size(); ++i)
|
||||
colors[i] = (color_top_layer_only && m_vertices[i].layer_id < top_layer_id &&
|
||||
(!m_settings.spiral_vase_mode || i != m_view_range.get_enabled()[0])) ?
|
||||
encode_color(DUMMY_COLOR) : m_vertices_colors[i];
|
||||
for (size_t i=0; i<m_vertices.size(); ++i) {
|
||||
const PathVertex& v = m_vertices[i];
|
||||
const bool keep_spiral_seam = m_settings.spiral_vase_mode && i == m_view_range.get_enabled()[0];
|
||||
if (dim_previous_layers && !full_render && v.layer_id < top_layer_id && !keep_spiral_seam)
|
||||
colors[i] = encode_color_darkened(get_vertex_color(v), PREVIOUS_LAYER_DARKEN_FACTOR);
|
||||
else if (color_top_layer_only && v.layer_id < top_layer_id && !keep_spiral_seam)
|
||||
colors[i] = encode_color(DUMMY_COLOR);
|
||||
else
|
||||
colors[i] = m_vertices_colors[i];
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENGL_ES
|
||||
if (!colors.empty())
|
||||
@@ -1349,6 +1379,17 @@ void ViewerImpl::toggle_top_layer_only_view_range()
|
||||
update_colors_texture();
|
||||
}
|
||||
|
||||
// ORCA: enable/disable darkening of the layers below the current top layer (ported from preFlight)
|
||||
void ViewerImpl::set_dim_previous_layers(bool value)
|
||||
{
|
||||
if (m_settings.dim_previous_layers == value)
|
||||
return;
|
||||
m_settings.dim_previous_layers = value;
|
||||
// defer the actual color/texture rebuild to the next render(), when the GL context is current
|
||||
// (this may be toggled from the Preferences dialog, outside the canvas context)
|
||||
m_settings.update_colors = true;
|
||||
}
|
||||
|
||||
std::vector<ETimeMode> ViewerImpl::get_time_modes() const
|
||||
{
|
||||
std::vector<ETimeMode> ret;
|
||||
|
||||
@@ -85,6 +85,10 @@ public:
|
||||
bool is_top_layer_only_view_range() const { return m_settings.top_layer_only_view_range; }
|
||||
void toggle_top_layer_only_view_range();
|
||||
|
||||
// ORCA: darken layers below the current top layer while scrubbing (ported from preFlight)
|
||||
bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; }
|
||||
void set_dim_previous_layers(bool value);
|
||||
|
||||
bool is_spiral_vase_mode() const { return m_settings.spiral_vase_mode; }
|
||||
|
||||
std::vector<ETimeMode> get_time_modes() const;
|
||||
|
||||
+116
-1
@@ -23,11 +23,14 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/AboutDialog.cpp
|
||||
GUI/AboutDialog.hpp
|
||||
GUI/AmsMappingPopup.cpp
|
||||
GUI/AmsMappingPopupUpdate.cpp
|
||||
GUI/AmsMappingPopup.hpp
|
||||
GUI/AMSMaterialsSetting.cpp
|
||||
GUI/AMSMaterialsSetting.hpp
|
||||
GUI/AMSSetting.cpp
|
||||
GUI/AMSSetting.hpp
|
||||
GUI/AMSDryControl.cpp
|
||||
GUI/AMSDryControl.hpp
|
||||
GUI/AmsWidgets.cpp
|
||||
GUI/AmsWidgets.hpp
|
||||
GUI/Auxiliary.cpp
|
||||
@@ -38,6 +41,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Auxiliary.hpp
|
||||
GUI/BackgroundSlicingProcess.cpp
|
||||
GUI/BackgroundSlicingProcess.hpp
|
||||
GUI/PostProcessor.cpp
|
||||
GUI/PostProcessor.hpp
|
||||
GUI/BBLStatusBarBind.cpp
|
||||
GUI/BBLStatusBarBind.hpp
|
||||
GUI/BBLStatusBar.cpp
|
||||
@@ -107,6 +112,27 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Downloader.hpp
|
||||
GUI/DownloadProgressDialog.cpp
|
||||
GUI/DownloadProgressDialog.hpp
|
||||
GUI/PluginSource.hpp
|
||||
GUI/PluginSort.hpp
|
||||
GUI/PluginStatus.hpp
|
||||
GUI/PluginPickerDialog.cpp
|
||||
GUI/PluginPickerDialog.hpp
|
||||
GUI/PluginsDialog.cpp
|
||||
GUI/PluginsDialog.hpp
|
||||
GUI/SpeedDialDialog.cpp
|
||||
GUI/SpeedDialDialog.hpp
|
||||
GUI/ActionRegistry.cpp
|
||||
GUI/ActionRegistry.hpp
|
||||
GUI/PluginsConfigDialog.cpp
|
||||
GUI/PluginsConfigDialog.hpp
|
||||
GUI/ProcessRunner.cpp
|
||||
GUI/ProcessRunner.hpp
|
||||
GUI/TerminalDialog.cpp
|
||||
GUI/TerminalDialog.hpp
|
||||
GUI/PluginProgressDialog.cpp
|
||||
GUI/PluginProgressDialog.hpp
|
||||
GUI/PluginWebDialog.cpp
|
||||
GUI/PluginWebDialog.hpp
|
||||
GUI/DragCanvas.cpp
|
||||
GUI/DragCanvas.hpp
|
||||
GUI/EditGCodeDialog.cpp
|
||||
@@ -411,6 +437,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Project.hpp
|
||||
GUI/PublishDialog.cpp
|
||||
GUI/PublishDialog.hpp
|
||||
GUI/PurgeModeDialog.cpp
|
||||
GUI/PurgeModeDialog.hpp
|
||||
GUI/RammingChart.cpp
|
||||
GUI/RammingChart.hpp
|
||||
GUI/RecenterDialog.cpp
|
||||
@@ -494,6 +522,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Widgets/AMSControl.hpp
|
||||
GUI/Widgets/AMSItem.cpp
|
||||
GUI/Widgets/AMSItem.hpp
|
||||
GUI/Widgets/MultiNozzleSync.cpp
|
||||
GUI/Widgets/MultiNozzleSync.hpp
|
||||
GUI/Widgets/AxisCtrlButton.cpp
|
||||
GUI/Widgets/AxisCtrlButton.hpp
|
||||
GUI/SafetyOptionsDialog.hpp
|
||||
@@ -564,11 +594,58 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Widgets/TempInput.hpp
|
||||
GUI/Widgets/TextInput.cpp
|
||||
GUI/Widgets/TextInput.hpp
|
||||
GUI/Widgets/WebViewHostDialog.cpp
|
||||
GUI/Widgets/WebViewHostDialog.hpp
|
||||
GUI/Widgets/WebView.cpp
|
||||
GUI/Widgets/WebView.hpp
|
||||
GUI/WipeTowerDialog.cpp
|
||||
GUI/wxExtensions.cpp
|
||||
GUI/wxExtensions.hpp
|
||||
plugin/PythonInterpreter.cpp
|
||||
plugin/PythonInterpreter.hpp
|
||||
plugin/PythonPluginBridge.cpp
|
||||
plugin/PythonPluginBridge.hpp
|
||||
plugin/PythonPluginInterface.hpp
|
||||
plugin/PyPluginPackage.hpp
|
||||
plugin/PluginBindingUtils.hpp
|
||||
plugin/host/PluginHost.cpp
|
||||
plugin/host/PluginHost.hpp
|
||||
plugin/host/PluginHostBindings.hpp
|
||||
plugin/host/PluginHostApp.cpp
|
||||
plugin/host/PluginHostGeometry.cpp
|
||||
plugin/host/PluginHostMesh.cpp
|
||||
plugin/host/PluginHostMesh.hpp
|
||||
plugin/host/PluginHostModel.cpp
|
||||
plugin/host/PluginHostPresets.cpp
|
||||
plugin/host/PluginHostSlicing.cpp
|
||||
plugin/host/PluginHostUi.cpp
|
||||
plugin/host/PluginHostUi.hpp
|
||||
plugin/CloudPluginService.cpp
|
||||
plugin/CloudPluginService.hpp
|
||||
plugin/PluginFsUtils.cpp
|
||||
plugin/PluginFsUtils.hpp
|
||||
plugin/PluginConfig.cpp
|
||||
plugin/PluginConfig.hpp
|
||||
plugin/PluginLoader.cpp
|
||||
plugin/PluginLoader.hpp
|
||||
plugin/PluginDescriptor.hpp
|
||||
plugin/PluginHooks.cpp
|
||||
plugin/PluginHooks.hpp
|
||||
plugin/PluginManager.cpp
|
||||
plugin/PluginManager.hpp
|
||||
plugin/PluginAuditManager.cpp
|
||||
plugin/PluginAuditManager.hpp
|
||||
plugin/PluginResolver.cpp
|
||||
plugin/PluginResolver.hpp
|
||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp
|
||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp
|
||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp
|
||||
plugin/pluginTypes/script/ScriptPluginCapability.hpp
|
||||
plugin/pluginTypes/script/ScriptPluginCapability.cpp
|
||||
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp
|
||||
plugin/pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp
|
||||
plugin/pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.cpp
|
||||
plugin/pluginTypes/slicingPipeline/SlicingPipelinePluginCapabilityTrampoline.hpp
|
||||
pchheader.cpp
|
||||
pchheader.hpp
|
||||
Utils/ASCIIFolding.cpp
|
||||
@@ -785,8 +862,16 @@ else()
|
||||
set(_opengl_link_lib OpenGL::GL)
|
||||
endif()
|
||||
|
||||
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise)
|
||||
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise pybind11::embed)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
# Linux finds wxWidgets in module mode, whose include dirs and definitions
|
||||
# only apply at src/ directory scope; export them from the target so wx
|
||||
# headers reach GUI-header consumers outside src/ (tests). The CONFIG-mode
|
||||
# wx::* imported targets used on Windows/macOS already propagate these.
|
||||
target_include_directories(libslic3r_gui SYSTEM PUBLIC ${wxWidgets_INCLUDE_DIRS})
|
||||
target_compile_definitions(libslic3r_gui PUBLIC ${wxWidgets_DEFINITIONS} $<$<CONFIG:Debug>:${wxWidgets_DEFINITIONS_DEBUG}>)
|
||||
endif ()
|
||||
|
||||
if (MSVC)
|
||||
target_link_libraries(libslic3r_gui Setupapi.lib)
|
||||
@@ -864,3 +949,33 @@ endif ()
|
||||
|
||||
# Add a definition so that we can tell we are compiling slic3r.
|
||||
target_compile_definitions(libslic3r_gui PRIVATE SLIC3R_CURRENTLY_COMPILING_GUI_MODULE)
|
||||
|
||||
if(ORCA_BUNDLED_UV_EXECUTABLE_CONFIG)
|
||||
target_compile_definitions(libslic3r_gui PRIVATE "ORCA_BUNDLED_UV_EXECUTABLE=\"${ORCA_BUNDLED_UV_EXECUTABLE_CONFIG}\"")
|
||||
endif()
|
||||
|
||||
if (ORCA_BUILD_PYTHON_STUBGEN_MODULE)
|
||||
add_library(orca_stubgen MODULE
|
||||
plugin/PythonPluginBridge.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(orca_stubgen PRIVATE
|
||||
ORCA_PYTHON_STUBGEN_MODULE
|
||||
)
|
||||
|
||||
target_include_directories(orca_stubgen PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
Utils
|
||||
)
|
||||
|
||||
target_link_libraries(orca_stubgen PRIVATE
|
||||
libslic3r_gui
|
||||
pybind11::embed
|
||||
)
|
||||
|
||||
set_target_properties(orca_stubgen PROPERTIES
|
||||
OUTPUT_NAME orca
|
||||
PREFIX ""
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -495,7 +495,29 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
simple_render(shader, model_objects, colors);
|
||||
return;
|
||||
}
|
||||
|
||||
// 0th. render pass, render the model using stencil buffer
|
||||
glsafe(::glEnable(GL_STENCIL_TEST));
|
||||
glsafe(::glStencilMask(0xFF));
|
||||
glsafe(::glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE));
|
||||
glsafe(::glClearStencil(0));
|
||||
glsafe(::glClear(GL_STENCIL_BUFFER_BIT));
|
||||
glsafe(::glStencilFunc(GL_ALWAYS, 0xFF, 0xFF));
|
||||
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
||||
model.render(shader);
|
||||
else
|
||||
model.render(this->tverts_range, shader);
|
||||
glsafe(::glStencilFunc(GL_NOTEQUAL, 0xFF, 0xFF));
|
||||
glsafe(::glStencilMask(0x00));
|
||||
shader->set_uniform("is_outline", true);
|
||||
shader->set_uniform("screen_size", Vec2f{cnv_size.get_width(), cnv_size.get_height()});
|
||||
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
||||
model.render(shader);
|
||||
else
|
||||
model.render(this->tverts_range, shader);
|
||||
shader->set_uniform("is_outline", false);
|
||||
glsafe(::glStencilMask(0xFF));
|
||||
glsafe(::glDisable(GL_STENCIL_TEST));
|
||||
// render the outline using depth buffer and discard the pixels that are not on the outline
|
||||
// 1st. render pass, render the model into a separate render target that has only depth buffer
|
||||
GLuint depth_fbo = 0;
|
||||
GLuint depth_tex = 0;
|
||||
@@ -526,7 +548,7 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
|
||||
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
}
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
||||
@@ -1024,7 +1046,8 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
|
||||
const Transform3d& projection_matrix,
|
||||
const GUI::Size& cnv_size,
|
||||
std::function<bool(const GLVolume &)> filter_func,
|
||||
bool partly_inside_enable) const
|
||||
bool partly_inside_enable,
|
||||
std::vector<double> * printable_heights) const
|
||||
{
|
||||
GLVolumeWithIdAndZList to_render = volumes_to_render(volumes, type, view_matrix, filter_func);
|
||||
if (to_render.empty())
|
||||
@@ -1108,7 +1131,24 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
|
||||
//use -1 ad a invalid type
|
||||
shader->set_uniform("print_volume.type", -1);
|
||||
}
|
||||
|
||||
|
||||
// Per-extruder printable-height shading. The flag is set to
|
||||
// 2.0 only for multi-extruder printers (two per-extruder heights); otherwise it is forced to 0.0
|
||||
// on every render so no stale flag survives a multi->single-extruder plate switch, keeping the
|
||||
// shared gouraud shader pixel-identical for single-extruder printers. When active the height
|
||||
// branch reads print_volume.xy_data (the bed rect), so set it explicitly here.
|
||||
std::array<float, 3> extruder_printable_heights = {0.0f, 0.0f, 0.0f};
|
||||
if (printable_heights != nullptr && printable_heights->size() > 1) {
|
||||
extruder_printable_heights[0] = 2.0f;
|
||||
extruder_printable_heights[1] = static_cast<float>((*printable_heights)[0]);
|
||||
extruder_printable_heights[2] = static_cast<float>((*printable_heights)[1]);
|
||||
shader->set_uniform("extruder_printable_heights", extruder_printable_heights);
|
||||
shader->set_uniform("print_volume.xy_data", m_print_volume.data);
|
||||
}
|
||||
else {
|
||||
shader->set_uniform("extruder_printable_heights", extruder_printable_heights);
|
||||
}
|
||||
|
||||
shader->set_uniform("volume_world_matrix", volume.first->world_matrix());
|
||||
shader->set_uniform("slope.actived", m_slope.isGlobalActive && !volume.first->is_modifier && !volume.first->is_wipe_tower);
|
||||
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
|
||||
|
||||
@@ -497,7 +497,10 @@ public:
|
||||
const Transform3d& projection_matrix,
|
||||
const GUI::Size& cnv_size,
|
||||
std::function<bool(const GLVolume &)> filter_func = std::function<bool(const GLVolume &)>(),
|
||||
bool partly_inside_enable =true
|
||||
bool partly_inside_enable =true,
|
||||
// Per-extruder printable heights (extruder_printable_height); null / size<=1
|
||||
// leaves the shader's extruder_printable_heights flag at 0.0 (single-extruder = inert).
|
||||
std::vector<double> * printable_heights = nullptr
|
||||
) const;
|
||||
|
||||
// Clear the geometry
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user