mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-30 12:21:05 +00:00
Merge branch 'main' into feature/filament_id
Brings in 560 commits (merge base 2026-07-04 .. origin/main af9fd10d7a).
19 conflicts resolved; the other 138 touched files auto-merged.
Conflict resolutions
- Snapmaker/Polymaker (7): main normalised JSON key order in #15039 while this
branch inserted filament_id/filament_vendor. Took main's key order and kept
this branch's values, inserting a single filament_id key rather than letting
git's line merge leave duplicate "from"/"instantiation" keys.
- re3D rPETG @0.8/@1.75 nozzle (2): main renamed these from "re3D Greengate
rPETG @*" and re-vendored GreenGate3D -> re3D (#15169). Git's rename-aware
merge produced a file with two filament_vendor keys; took main's version
wholesale instead. The id is reconciled by the tooling, not by hand.
- re3D rPP (1): kept main's inherits change (fdm_filament_pet -> fdm_filament_pp,
local filament_type override dropped) and its widened compatible_printers.
The flattened type is still PP, so the product triple and id OFfHGM1D are
unchanged.
- re3D Greengate rPETG.json, Afinia {ABS+,ABS,PLA,TPU,Value ABS,Value PLA}.json
(7 modify/delete): accepted main's deletions. The Afinia ids are not orphaned
- the surviving @HS presets resolve the same triple and already carry the
same ids, so nothing is retired.
- .github/workflows/check_profiles.yml: kept both main's new "validate slice"
step and this branch's tree-wide filament-subtype check.
- tests/libslic3r/CMakeLists.txt: kept both test_filament_id_succession.cpp and
main's test_preset_diff.cpp.
Verified
- No conflict markers; all 12795 profile JSONs parse with no duplicate keys.
- All branch artifacts (tooling, snapshot, ledger, doc, tests) intact and
unmodified by the merge.
- The succession-ledger C++ call sites survived main's refactors; audited
Preset.{cpp,hpp}, PresetBundle.cpp, DeviceManager.cpp, PresetComboBoxes.cpp,
CaliHistoryDialog.cpp, MoonrakerPrinterAgent.cpp against base/ours/theirs.
- scripts/tests: 115/116 pass.
Known follow-up: assign_filament_ids.py --check reports 243 errors, all from
filament data main added in the last month (BBL/addnorth, Qidi X5/Plus 5,
Snapmaker U1, re3D). The single failing unit test is the live-tree conformance
test asserting that count is zero. Reconciliation lands separately.
This commit is contained in:
+74
-4
@@ -88,6 +88,10 @@ if (SLIC3R_GUI)
|
||||
list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX OpenGL)
|
||||
|
||||
# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc)
|
||||
|
||||
find_package(wxInspector REQUIRED)
|
||||
list(APPEND wxWidgets_LIBRARIES "wxInspector::wxInspector")
|
||||
|
||||
message(STATUS "wx libs: ${wxWidgets_LIBRARIES}")
|
||||
|
||||
add_subdirectory(slic3r)
|
||||
@@ -129,6 +133,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")
|
||||
@@ -197,6 +218,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)
|
||||
@@ -231,6 +272,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 ()
|
||||
@@ -240,33 +287,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.
|
||||
@@ -288,6 +357,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 ()
|
||||
|
||||
+125
-11
@@ -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;
|
||||
@@ -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;
|
||||
@@ -6955,7 +7054,7 @@ int CLI::run(int argc, char **argv)
|
||||
gcode_viewer.render_calibration_thumbnail(*calibration_data, cali_thumbnail_width, cali_thumbnail_height,
|
||||
calibration_params, partplate_list, opengl_mgr);
|
||||
//generate_calibration_thumbnail(*calibration_data, thumbnail_width, thumbnail_height, calibration_params);
|
||||
//*plate_bboxes[index] = p->generate_first_layer_bbox();
|
||||
// *plate_bboxes[index] = p->generate_first_layer_bbox();
|
||||
calibration_thumbnails.push_back(calibration_data);*/
|
||||
|
||||
PlateBBoxData* plate_bbox = new PlateBBoxData();
|
||||
@@ -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,8 +379,9 @@ 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.")
|
||||
("check_preset_references,r", po::bool_switch()->default_value(false), "Also flag presets whose inherits/compatible_printers/compatible_prints reference a deleted or renamed preset. 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
|
||||
|
||||
@@ -120,8 +405,9 @@ 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>();
|
||||
bool check_preset_references = vm["check_preset_references"].as<bool>();
|
||||
|
||||
// check if path is valid, and return error if not
|
||||
if (!fs::exists(path) || !fs::is_directory(path)) {
|
||||
@@ -134,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();
|
||||
@@ -141,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);
|
||||
@@ -168,7 +466,7 @@ int main(int argc, char* argv[])
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (preset_bundle->has_errors(check_filament_subtypes, check_preset_references)) {
|
||||
if (preset_bundle->has_errors(check_filament_subtypes)) {
|
||||
std::cout << "Validation failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -54,12 +54,12 @@ public:
|
||||
int & i,
|
||||
Eigen::Matrix<double, 1, 3> &closest)
|
||||
{
|
||||
size_t idx_unsigned = 0;
|
||||
Vec3d closest_vec3d(closest);
|
||||
double dist =
|
||||
size_t idx_unsigned { 0 };
|
||||
Vec3d closest_vec3d { Vec3d::Zero() };
|
||||
const double dist {
|
||||
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
its.vertices, its.indices, m_tree, point, idx_unsigned,
|
||||
closest_vec3d);
|
||||
closest_vec3d) };
|
||||
i = int(idx_unsigned);
|
||||
closest = closest_vec3d;
|
||||
return dist;
|
||||
@@ -311,10 +311,9 @@ AABBMesh::hit_result IndexedMesh::filter_hits(
|
||||
|
||||
|
||||
double AABBMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||
double sqdst = 0;
|
||||
Eigen::Matrix<double, 1, 3> pp = p;
|
||||
Eigen::Matrix<double, 1, 3> cc;
|
||||
sqdst = m_aabb->squared_distance(*m_tm, pp, i, cc);
|
||||
const Eigen::Matrix<double, 1, 3> pp { p };
|
||||
Eigen::Matrix<double, 1, 3> cc { Vec3d::Zero() };
|
||||
const double sqdst { m_aabb->squared_distance(*m_tm, pp, i, cc) };
|
||||
c = cc;
|
||||
return sqdst;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,9 @@ namespace AABBTreeLines {
|
||||
inline VectorType closest_point_to_origin(size_t primitive_index, ScalarType& squared_distance) const
|
||||
{
|
||||
Vec<LineType::Dim, typename LineType::Scalar> nearest_point;
|
||||
Vec<LineType::Dim, typename LineType::Scalar> cast_origin = origin.template cast<typename LineType::Scalar>();
|
||||
const LineType& line = lines[primitive_index];
|
||||
squared_distance = line_alg::distance_to_squared(line, origin.template cast<typename LineType::Scalar>(), &nearest_point);
|
||||
squared_distance = line_alg::distance_to_squared(line, cast_origin, &nearest_point);
|
||||
return nearest_point.template cast<ScalarType>();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -202,6 +202,25 @@ void AppConfig::set_defaults()
|
||||
if (get("seq_top_layer_only").empty())
|
||||
set("seq_top_layer_only", "1");
|
||||
|
||||
// ORCA: darken the layers the preview layer slider is not scrubbed to
|
||||
if (get("preview_dim_previous_layers").empty())
|
||||
set_bool("preview_dim_previous_layers", false);
|
||||
|
||||
// ORCA: brightness of those dimmed layers, in percent. 0 = black, capped at 99 because
|
||||
// 100 would render them unchanged, which is what disabling the option already does
|
||||
if (get("preview_dim_previous_layers_brightness").empty())
|
||||
set("preview_dim_previous_layers_brightness", "40");
|
||||
else {
|
||||
int brightness = 40;
|
||||
try {
|
||||
brightness = std::stoi(get("preview_dim_previous_layers_brightness"));
|
||||
}
|
||||
catch (...) {
|
||||
brightness = 40;
|
||||
}
|
||||
set("preview_dim_previous_layers_brightness", std::to_string(std::max(0, std::min(brightness, 99))));
|
||||
}
|
||||
|
||||
if (get("filaments_area_preferred_count").empty())
|
||||
set("filaments_area_preferred_count", "10");
|
||||
|
||||
@@ -304,6 +323,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);
|
||||
|
||||
@@ -603,6 +626,12 @@ void AppConfig::set_defaults()
|
||||
set_bool("window_buttons_on_left", false);
|
||||
#endif
|
||||
|
||||
if (get("use_printer_agents").empty())
|
||||
{
|
||||
// false = legacy behavior using print hosts
|
||||
set_bool("use_printer_agents", false);
|
||||
}
|
||||
|
||||
// Remove legacy window positions/sizes
|
||||
erase("app", "main_frame_maximized");
|
||||
erase("app", "main_frame_pos");
|
||||
@@ -800,6 +829,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);
|
||||
}
|
||||
}
|
||||
@@ -850,7 +883,7 @@ std::string AppConfig::load()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(std::exception err) {
|
||||
} catch(const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(info) << format("parse app config \"%1%\", error: %2%", AppConfig::loading_path(), err.what());
|
||||
|
||||
return err.what();
|
||||
@@ -957,6 +990,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
|
||||
|
||||
@@ -23,14 +23,14 @@ inline coord_t meshfix_maximum_extrusion_area_deviation() { return scaled<coo
|
||||
class WallToolPathsParams
|
||||
{
|
||||
public:
|
||||
float min_bead_width;
|
||||
float min_feature_size;
|
||||
float min_length_factor;
|
||||
float wall_transition_length;
|
||||
float wall_transition_angle;
|
||||
float wall_transition_filter_deviation;
|
||||
int wall_distribution_count;
|
||||
bool is_top_or_bottom_layer;
|
||||
float min_bead_width = 0.f;
|
||||
float min_feature_size = 0.f;
|
||||
float min_length_factor = 0.5f;
|
||||
float wall_transition_length = 0.f;
|
||||
float wall_transition_angle = 10.f;
|
||||
float wall_transition_filter_deviation = 0.f;
|
||||
int wall_distribution_count = 1;
|
||||
bool is_top_or_bottom_layer = false;
|
||||
|
||||
coord_t wall_maximum_resolution = meshfix_maximum_resolution();
|
||||
coord_t wall_maximum_deviation = meshfix_maximum_deviation();
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
|
||||
|
||||
template<class It, class = IteratorOnly<It>>
|
||||
BoundingBoxBase(It from, It to)
|
||||
BoundingBoxBase(It from, It to) : BoundingBoxBase()
|
||||
{ construct(*this, from, to); }
|
||||
|
||||
BoundingBoxBase(const PointsType &points)
|
||||
@@ -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>();
|
||||
|
||||
+80
-163
@@ -32,16 +32,14 @@ static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const P
|
||||
for (; dst_idx < dst.size(); ++dst_idx)
|
||||
dst[dst_idx].translate(instance_shift);
|
||||
}
|
||||
// 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) {
|
||||
// Orca: Translate the brim area into print coordinates and store it per instance.
|
||||
static void append_and_translate(const ExPolygons& src, 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));
|
||||
for (ExPolygon& expoly : srcShifted)
|
||||
expoly.translate(instance_shift);
|
||||
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 +59,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 +67,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 +92,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 +103,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.
|
||||
@@ -351,18 +349,19 @@ static ExPolygons make_brim_ears_auto(const ExPolygons& obj_expoly, coord_t size
|
||||
return mouse_ears_ex;
|
||||
}
|
||||
|
||||
static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWidth, float brim_offset, Flow &flow, bool is_outer_brim)
|
||||
static ExPolygons make_brim_ears(const PrintObject* object)
|
||||
{
|
||||
ExPolygons mouse_ears_ex;
|
||||
BrimPoints brim_ear_points = object->model_object()->brim_points;
|
||||
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;
|
||||
|
||||
@@ -374,12 +373,7 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi
|
||||
Vec3f world_pos = pt.transform(trsf.get_matrix());
|
||||
if ( world_pos.z() > 0) continue;
|
||||
Polygon point_round;
|
||||
float brim_width = floor(scale_(pt.head_front_radius) / flowWidth / 2) * flowWidth * 2;
|
||||
if (is_outer_brim) {
|
||||
double flowWidthScale = flowWidth / SCALING_FACTOR;
|
||||
brim_width = floor(brim_width / flowWidthScale / 2) * flowWidthScale * 2;
|
||||
}
|
||||
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
|
||||
const coord_t size_ear = scale_(pt.head_front_radius);
|
||||
for (size_t i = 0; i < POLY_SIDE_COUNT; i++) {
|
||||
double angle = (2.0 * PI * i) / POLY_SIDE_COUNT;
|
||||
point_round.points.emplace_back(size_ear * cos(angle), size_ear * sin(angle));
|
||||
@@ -390,17 +384,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 +413,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)
|
||||
{
|
||||
@@ -454,7 +447,8 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
bool has_brim_auto = object->config().brim_type == btAutoBrim;
|
||||
const bool use_auto_brim_ears = object->config().brim_type == btEar;
|
||||
const bool use_brim_ears = object->config().brim_type == btPainted;
|
||||
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_auto_brim_ears || use_brim_ears;
|
||||
const bool use_inner_brim_ears = (use_auto_brim_ears || use_brim_ears) && !object->config().brim_ears_outer_only.value;
|
||||
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_inner_brim_ears;
|
||||
const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || use_auto_brim_ears || use_brim_ears;
|
||||
coord_t ear_detection_length = scale_(object->config().brim_ears_detection_length.value);
|
||||
coordf_t brim_ears_max_angle = object->config().brim_ears_max_angle.value;
|
||||
@@ -469,7 +463,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;
|
||||
@@ -534,7 +527,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
auto innerExpoly = offset_ex(ex_poly.contour, brim_offset, jtRound, SCALED_RESOLUTION);
|
||||
ExPolygons outerExpoly;
|
||||
if (use_brim_ears) {
|
||||
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, true);
|
||||
outerExpoly = make_brim_ears(object);
|
||||
//outerExpoly = offset_ex(outerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION);
|
||||
} else if (use_auto_brim_ears) {
|
||||
coord_t size_ear = (brim_width_mod - brim_offset - flow.scaled_spacing());
|
||||
@@ -548,7 +541,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
ExPolygons outerExpoly;
|
||||
auto innerExpoly = offset_ex(ex_poly_holes_reversed, -brim_width - brim_offset);
|
||||
if (use_brim_ears) {
|
||||
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, false);
|
||||
outerExpoly = make_brim_ears(object);
|
||||
} else if (use_auto_brim_ears) {
|
||||
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
|
||||
outerExpoly = make_brim_ears_auto(offset_ex(ex_poly_holes_reversed, -brim_offset), size_ear, ear_detection_length, brim_ears_max_angle, false);
|
||||
@@ -570,16 +563,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_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 +586,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 +604,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 +646,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 +678,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 +859,23 @@ 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);
|
||||
|
||||
if (!print.config().combine_brims) {
|
||||
ExPolygons claimed_area;
|
||||
for (auto& [_, areas] : brimAreaMap) {
|
||||
areas = diff_ex(areas, claimed_area);
|
||||
expolygons_append(claimed_area, areas);
|
||||
}
|
||||
}
|
||||
|
||||
// BBS: Find boundingbox of the first layer
|
||||
for (const ObjectID printObjID : print.print_object_ids()) {
|
||||
@@ -923,14 +897,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 +913,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)
|
||||
|
||||
set(lisbslic3r_sources
|
||||
AABBMesh.cpp
|
||||
@@ -221,11 +222,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
|
||||
@@ -248,6 +248,8 @@ set(lisbslic3r_sources
|
||||
GCode/Thumbnails.hpp
|
||||
GCode/ToolOrdering.cpp
|
||||
GCode/ToolOrdering.hpp
|
||||
GCode/OrderingStrategies.cpp
|
||||
GCode/OrderingStrategies.hpp
|
||||
GCode/WipeTower2.cpp
|
||||
GCode/WipeTower2.hpp
|
||||
GCode/WipeTower.cpp
|
||||
@@ -470,6 +472,8 @@ set(lisbslic3r_sources
|
||||
FilamentGroup.cpp
|
||||
FilamentGroupUtils.hpp
|
||||
FilamentGroupUtils.cpp
|
||||
MultiNozzleUtils.hpp
|
||||
MultiNozzleUtils.cpp
|
||||
GCode/ToolOrderUtils.hpp
|
||||
GCode/ToolOrderUtils.cpp
|
||||
FlushVolPredictor.hpp
|
||||
@@ -496,8 +500,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
|
||||
@@ -534,7 +542,9 @@ endif ()
|
||||
encoding_check(libslic3r)
|
||||
|
||||
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
|
||||
target_compile_definitions(libslic3r PRIVATE $<$<CONFIG:RelWithDebInfo>:SLIC3R_CONSOLE_LOG>)
|
||||
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})
|
||||
|
||||
@@ -544,7 +554,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
|
||||
TKXDESTEP
|
||||
|
||||
@@ -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);
|
||||
|
||||
+104
-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;
|
||||
}
|
||||
@@ -1486,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
|
||||
{
|
||||
@@ -1522,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;
|
||||
@@ -1553,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)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define slic3r_Config_hpp_
|
||||
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <climits>
|
||||
#include <cfloat>
|
||||
@@ -9,6 +10,7 @@
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -363,6 +365,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;
|
||||
@@ -586,6 +589,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
|
||||
@@ -716,6 +745,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.");
|
||||
@@ -751,10 +781,14 @@ 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];
|
||||
// Orca: a negative slot (failed variant lookup) must not silently collapse the
|
||||
// whole array to the first slot's value — the int-vs-size_t comparison used to
|
||||
// promote -1 past the bounds check. Keep the slot's own value (get_at-style
|
||||
// clamp) when no valid index is available.
|
||||
if ((i < default_index.size()) && (default_index[i] >= 0) && (size_t(default_index[i]) < default_value.size()))
|
||||
this->values[i] = default_value[default_index[i]];
|
||||
else
|
||||
this->values[i] = default_value[0];
|
||||
this->values[i] = default_value[std::min(i, default_value.size() - 1)];
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
@@ -2077,6 +2111,11 @@ public:
|
||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
this->value = rhs->getInt();
|
||||
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
||||
// adopt the source's so a later serialize() can emit names.
|
||||
if (this->keys_map == nullptr)
|
||||
if (auto rhs_generic = dynamic_cast<const ConfigOptionEnumGeneric *>(rhs))
|
||||
this->keys_map = rhs_generic->keys_map;
|
||||
}
|
||||
|
||||
std::string serialize() const override
|
||||
@@ -2133,7 +2172,12 @@ public:
|
||||
if (rhs->type() != this->type())
|
||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumsGeneric
|
||||
this->values = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs)->values;
|
||||
auto rhs_enums = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs);
|
||||
this->values = rhs_enums->values;
|
||||
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
||||
// adopt the source's so a later serialize() emits names instead of empty tokens.
|
||||
if (this->keys_map == nullptr)
|
||||
this->keys_map = rhs_enums->keys_map;
|
||||
}
|
||||
|
||||
std::string serialize() const override
|
||||
@@ -2226,6 +2270,11 @@ 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,
|
||||
// PrinterAgentChoice
|
||||
printer_agent_select,
|
||||
};
|
||||
|
||||
// Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map.
|
||||
@@ -2442,6 +2491,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.
|
||||
@@ -2755,14 +2811,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.
|
||||
@@ -2999,6 +3070,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,27 +209,29 @@ 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
|
||||
{
|
||||
return m_config->retract_length_toolchange.get_at(extruder_id());
|
||||
return m_config->retract_length_toolchange.get_at(m_config_index);
|
||||
}
|
||||
|
||||
double Extruder::retract_restart_extra_toolchange() const
|
||||
{
|
||||
return m_config->retract_restart_extra_toolchange.get_at(extruder_id());
|
||||
return m_config->retract_restart_extra_toolchange.get_at(m_config_index);
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
+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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,15 @@ struct SurfaceFillParams
|
||||
// For Gyroid: when true, use the parameterized "optimized" wave.
|
||||
bool gyroid_optimized = false;
|
||||
|
||||
// Orca: corner smoothing factor in the range [0, 1].
|
||||
double smooth_factor { 0. };
|
||||
|
||||
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 +316,13 @@ 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(smooth_factor);
|
||||
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 +349,11 @@ 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->smooth_factor == rhs.smooth_factor &&
|
||||
this->fill_order == rhs.fill_order;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -868,6 +892,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 +948,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;
|
||||
|
||||
@@ -935,6 +969,11 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
|
||||
region_config.sparse_infill_rotate_template.value);
|
||||
params.fixed_angle = !region_config.sparse_infill_rotate_template.value.empty();
|
||||
|
||||
// Orca: special case; apply smoothing factor only for Hilbert Curve sparse infill.
|
||||
// FillHilbertCurve::generate clamps and validates the value itself.
|
||||
if (params.pattern == ipHilbertCurve)
|
||||
params.smooth_factor = 0.01 * region_config.sparse_infill_smooth_factor.value;
|
||||
} else {
|
||||
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.;
|
||||
@@ -1299,6 +1338,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2;
|
||||
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
|
||||
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
|
||||
params.smooth_factor = surface_fill.params.smooth_factor;
|
||||
|
||||
// BBS
|
||||
params.flow = surface_fill.params.flow;
|
||||
@@ -1308,6 +1348,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;
|
||||
@@ -1332,7 +1388,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);
|
||||
@@ -1495,6 +1580,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
|
||||
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
|
||||
params.multiline = surface_fill.params.multiline;
|
||||
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
|
||||
params.smooth_factor = surface_fill.params.smooth_factor;
|
||||
|
||||
for (ExPolygon &expoly : surface_fill.expolygons) {
|
||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -1853,12 +1857,12 @@ static inline void base_support_extend_infill_lines(Polylines &infill, BoundaryI
|
||||
const bool first = graph.first(cp);
|
||||
int extend_next_idx = -1;
|
||||
int extend_prev_idx = -1;
|
||||
coord_t dist_y_prev;
|
||||
coord_t dist_y_next;
|
||||
double arc_len_prev;
|
||||
double arc_len_next;
|
||||
coord_t dist_y_prev = 0;
|
||||
coord_t dist_y_next = 0;
|
||||
double arc_len_prev = 0;
|
||||
double arc_len_next = 0;
|
||||
|
||||
if (! graph.next_vertical(cp)){
|
||||
if (! graph.next_vertical(cp)) {
|
||||
size_t i = cp.point_idx;
|
||||
size_t j = next_idx_modulo(i, contour);
|
||||
while (j != cp.next_on_contour->point_idx) {
|
||||
|
||||
@@ -82,6 +82,9 @@ struct FillParams
|
||||
// For Gyroid: when true, use the parameterized "optimized" variant.
|
||||
bool gyroid_optimized { false };
|
||||
|
||||
// Orca: corner smoothing factor in the range [0, 1].
|
||||
double smooth_factor { 0. };
|
||||
|
||||
// For Lateral lattice
|
||||
coordf_t lateral_lattice_angle_1 { 0.f };
|
||||
coordf_t lateral_lattice_angle_2 { 0.f };
|
||||
@@ -100,12 +103,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() :
|
||||
@@ -110,12 +114,12 @@ void FillPlanePath::_fill_surface_single(
|
||||
// Filling in a bounding box over the whole object, clip generated polyline against the snug bounding box.
|
||||
snug_bounding_box.translate(-shift.x(), -shift.y());
|
||||
InfillPolylineClipper output(snug_bounding_box, distance_between_lines);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, params, output);
|
||||
polyline.points = std::move(output.result());
|
||||
} else {
|
||||
// Filling in a snug bounding box, no need to clip.
|
||||
InfillPolylineOutput output(distance_between_lines);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, params, output);
|
||||
polyline.points = std::move(output.result());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -266,6 +288,147 @@ static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x,
|
||||
}
|
||||
}
|
||||
|
||||
using QuinticBezier = std::array<Vec2d, 6>;
|
||||
|
||||
static bool is_bezier_flat(const QuinticBezier &curve, const double deviation)
|
||||
{
|
||||
// A Bezier curve stays inside the convex hull of its control points. Therefore, keeping every
|
||||
// control point within a deviation-wide strip around the endpoint chord conservatively bounds the
|
||||
// flattening error. The cross product is the perpendicular distance scaled by the chord length;
|
||||
// comparing squared values avoids a square root.
|
||||
const Vec2d chord = curve.back() - curve.front();
|
||||
const double chord_length_sq = chord.squaredNorm();
|
||||
const double max_cross_sq = deviation * deviation * chord_length_sq;
|
||||
|
||||
for (size_t i = 1; i + 1 < curve.size(); ++i) {
|
||||
const Vec2d offset = curve[i] - curve.front();
|
||||
const double cross = chord.x() * offset.y() - chord.y() * offset.x();
|
||||
if (cross * cross > max_cross_sq)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void subdivide_bezier(const QuinticBezier &curve, QuinticBezier &left, QuinticBezier &right)
|
||||
{
|
||||
// Split the curve at t = 0.5 using de Casteljau's algorithm. Each averaging level contributes one
|
||||
// control point to the left half and one to the right half; the latter is filled backwards to keep
|
||||
// both resulting control polygons in their original parameter direction.
|
||||
QuinticBezier subdivision = curve;
|
||||
left.front() = subdivision.front();
|
||||
right.back() = subdivision.back();
|
||||
for (size_t level = 1; level < curve.size(); ++level) {
|
||||
for (size_t i = 0; i + level < curve.size(); ++i)
|
||||
subdivision[i] = 0.5 * (subdivision[i] + subdivision[i + 1]);
|
||||
left[level] = subdivision.front();
|
||||
right[curve.size() - level - 1] = subdivision[curve.size() - level - 1];
|
||||
}
|
||||
}
|
||||
|
||||
static void flatten_bezier(const QuinticBezier &curve, const double deviation, std::vector<Vec2d> &output)
|
||||
{
|
||||
// Subdivide to at least depth 1 so a rounded corner cannot collapse to a single diagonal chord.
|
||||
// A uniform subdivision depth keeps samples at equal parameter intervals t = k / 2^depth,
|
||||
// avoiding abrupt segment-length jumps at adaptive-depth boundaries.
|
||||
static constexpr size_t max_depth = 16;
|
||||
|
||||
std::vector<QuinticBezier> subcurves(2);
|
||||
subdivide_bezier(curve, subcurves[0], subcurves[1]);
|
||||
|
||||
for (size_t depth = 1; depth < max_depth; ++depth) {
|
||||
bool all_flat = true;
|
||||
for (const QuinticBezier &c : subcurves)
|
||||
if (!is_bezier_flat(c, deviation)) {
|
||||
all_flat = false;
|
||||
break;
|
||||
}
|
||||
if (all_flat)
|
||||
break;
|
||||
std::vector<QuinticBezier> finer(subcurves.size() * 2);
|
||||
for (size_t i = 0; i < subcurves.size(); ++i)
|
||||
subdivide_bezier(subcurves[i], finer[i * 2], finer[i * 2 + 1]);
|
||||
subcurves = std::move(finer);
|
||||
}
|
||||
|
||||
// The curve start is deliberately omitted so consecutive curve pieces can share it without duplication.
|
||||
output.reserve(output.size() + subcurves.size());
|
||||
for (const QuinticBezier &c : subcurves)
|
||||
output.emplace_back(c.back());
|
||||
}
|
||||
|
||||
template<typename Output>
|
||||
static void generate_smooth_hilbert_curve(
|
||||
coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const double corner_distance, Output &output)
|
||||
{
|
||||
// A Hilbert curve is defined on a square grid whose side is a power of two. As in the unsmoothed
|
||||
// generator, expand the larger requested dimension to the next valid Hilbert grid size. The output
|
||||
// clipper or the later region intersection removes the padded part of the traversal.
|
||||
size_t sz = 2;
|
||||
const size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y);
|
||||
while (sz < sz0)
|
||||
sz <<= 1;
|
||||
|
||||
const size_t point_count = sz * sz;
|
||||
output.reserve(point_count);
|
||||
|
||||
// The caller normalizes resolution to the unit Hilbert grid; retain a finite positive tolerance
|
||||
// if this helper is invoked with an invalid resolution.
|
||||
const double deviation = resolution > 0. && std::isfinite(resolution) ? resolution : EPSILON;
|
||||
// Construct one canonical 90-degree corner from (-corner_distance, 0) to (0, corner_distance).
|
||||
// At each end, the first three control points are collinear and equally spaced: the tangent follows
|
||||
// the adjoining straight leg and the second derivative is zero. The endpoint curvature is therefore
|
||||
// zero, giving G2 joins to both legs. Every Hilbert turn is an oriented copy of this curve, so flatten
|
||||
// it only once to the requested chordal-deviation tolerance.
|
||||
const QuinticBezier corner_curve {{
|
||||
{-corner_distance, 0.}, {-0.7 * corner_distance, 0.}, {-0.4 * corner_distance, 0.},
|
||||
{0., 0.4 * corner_distance}, {0., 0.7 * corner_distance}, {0., corner_distance}
|
||||
}};
|
||||
std::vector<Vec2d> curve_coefficients;
|
||||
flatten_bezier(corner_curve, deviation, curve_coefficients);
|
||||
|
||||
auto translated_point = [min_x, min_y](size_t idx) {
|
||||
Point p = hilbert_n_to_xy(idx);
|
||||
return Point(p.x() + min_x, p.y() + min_y);
|
||||
};
|
||||
auto to_vec2d = [](const Point &p) { return Vec2d(double(p.x()), double(p.y())); };
|
||||
bool has_last_output = false;
|
||||
Vec2d last_output;
|
||||
// Fully smoothed adjacent corners may meet at the same segment midpoint. Suppress such duplicates
|
||||
// to avoid emitting zero-length extrusion segments.
|
||||
auto add_point = [&output, &has_last_output, &last_output](const Vec2d &point) {
|
||||
if (!has_last_output || point.x() != last_output.x() || point.y() != last_output.y()) {
|
||||
output.add_point(point);
|
||||
last_output = point;
|
||||
has_last_output = true;
|
||||
}
|
||||
};
|
||||
|
||||
Vec2d previous = to_vec2d(translated_point(0));
|
||||
Vec2d corner = to_vec2d(translated_point(1));
|
||||
add_point(previous);
|
||||
// Replace each non-collinear Hilbert vertex by the canonical curve expressed in the local basis of
|
||||
// its incoming and outgoing unit vectors. Collinear vertices remain part of the straight polyline.
|
||||
for (size_t i = 1; i + 1 < point_count; ++i) {
|
||||
const Vec2d next = to_vec2d(translated_point(i + 1));
|
||||
const Vec2d incoming = (corner - previous).normalized();
|
||||
const Vec2d outgoing = (next - corner).normalized();
|
||||
const double cross = incoming.x() * outgoing.y() - incoming.y() * outgoing.x();
|
||||
|
||||
if (std::abs(cross) < EPSILON) {
|
||||
add_point(corner);
|
||||
} else {
|
||||
add_point(corner - corner_distance * incoming);
|
||||
for (const Vec2d &coefficient : curve_coefficients)
|
||||
add_point(corner + coefficient.x() * incoming + coefficient.y() * outgoing);
|
||||
}
|
||||
|
||||
previous = corner;
|
||||
corner = next;
|
||||
}
|
||||
add_point(corner);
|
||||
}
|
||||
|
||||
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output)
|
||||
{
|
||||
if (output.clips())
|
||||
@@ -274,6 +437,24 @@ void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coo
|
||||
generate_hilbert_curve(min_x, min_y, max_x, max_y, output);
|
||||
}
|
||||
|
||||
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams ¶ms, InfillPolylineOutput &output)
|
||||
{
|
||||
const double smooth_factor = std::isfinite(params.smooth_factor) ?
|
||||
std::clamp(params.smooth_factor, 0., 1.) : 0.;
|
||||
if (smooth_factor == 0.) {
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
return;
|
||||
}
|
||||
|
||||
const double corner_distance = 0.5 * smooth_factor;
|
||||
if (output.clips())
|
||||
generate_smooth_hilbert_curve(
|
||||
min_x, min_y, max_x, max_y, resolution, corner_distance, static_cast<InfillPolylineClipper&>(output));
|
||||
else
|
||||
generate_smooth_hilbert_curve(min_x, min_y, max_x, max_y, resolution, corner_distance, output);
|
||||
}
|
||||
|
||||
template<typename Output>
|
||||
static void generate_octagram_spiral(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output)
|
||||
{
|
||||
|
||||
@@ -53,6 +53,11 @@ protected:
|
||||
friend class InfillPolylineClipper;
|
||||
|
||||
virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) = 0;
|
||||
virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams & /* params */, InfillPolylineOutput &output)
|
||||
{
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
}
|
||||
};
|
||||
|
||||
class FillArchimedeanChords : public FillPlanePath
|
||||
@@ -75,6 +80,8 @@ public:
|
||||
protected:
|
||||
bool centered() const override { return false; }
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams ¶ms, InfillPolylineOutput &output) override;
|
||||
};
|
||||
|
||||
class FillOctagramSpiral : public FillPlanePath
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -3553,7 +3576,7 @@ Polylines FillLateralHoneycomb::fill_surface(const Surface *surface, const FillP
|
||||
// |
|
||||
// |
|
||||
// 0 --+--
|
||||
// / \
|
||||
// ⟋ ⟍
|
||||
// why inverted?
|
||||
// it makes determining some of the properties easier
|
||||
// and the two angled legs provide additional horizontal stiffness
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -712,7 +712,7 @@ unsigned int Step::get_triangle_num(double linear_deflection, double angle_defle
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
} catch(const Exception &e) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
Standard_Boolean UserBreak() override { return should_stop.load(); }
|
||||
|
||||
void Show(const Message_ProgressScope&, const Standard_Boolean) override {
|
||||
std::cout << "Progress: " << GetPosition() << "%" << std::endl;
|
||||
std::cout << "Progress: " << std::fixed << std::setprecision(2) << 100.0 * GetPosition() << "%" << std::endl;
|
||||
}
|
||||
private:
|
||||
std::atomic<bool>& should_stop;
|
||||
|
||||
@@ -347,6 +347,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";
|
||||
@@ -699,6 +700,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;
|
||||
@@ -706,6 +736,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;
|
||||
@@ -1283,6 +1320,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();
|
||||
@@ -1618,8 +1658,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;
|
||||
@@ -2289,9 +2331,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;
|
||||
@@ -3469,6 +3513,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)
|
||||
@@ -3503,6 +3549,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)
|
||||
@@ -4460,6 +4508,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;
|
||||
@@ -4569,6 +4632,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)
|
||||
@@ -4622,6 +4690,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) {
|
||||
@@ -5949,7 +6052,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,
|
||||
@@ -7980,6 +8083,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()) {
|
||||
@@ -8119,7 +8234,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;
|
||||
}
|
||||
@@ -8205,6 +8325,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";
|
||||
@@ -8214,7 +8346,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";
|
||||
@@ -8329,7 +8469,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) << "\" "
|
||||
@@ -8349,12 +8492,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()) {
|
||||
@@ -8988,7 +9145,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;
|
||||
|
||||
+1715
-415
File diff suppressed because it is too large
Load Diff
+111
-8
@@ -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);
|
||||
};
|
||||
|
||||
@@ -125,8 +130,11 @@ public:
|
||||
private:
|
||||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const BoundingBox &printer_bbx) const;
|
||||
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const Polygons &bed_polygons) const;
|
||||
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
std::string travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const;
|
||||
Vec2f transform_wt2_pt(const Vec2f &pt) const;
|
||||
Polygons shared_printable_area(GCode &gcodegen) const;
|
||||
|
||||
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
||||
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
||||
@@ -176,7 +184,7 @@ struct LayerResult {
|
||||
// It is used for the pressure equalizer because it needs to buffer one layer back.
|
||||
bool nop_layer_result { false };
|
||||
|
||||
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
|
||||
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<size_t>::max(), false, false, true}; }
|
||||
};
|
||||
|
||||
class GCode {
|
||||
@@ -252,8 +260,9 @@ 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(); }
|
||||
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1);
|
||||
// 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 defer_temp_wait = false);
|
||||
bool is_BBL_Printer();
|
||||
WipeTowerType wipe_tower_type();
|
||||
|
||||
@@ -263,6 +272,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 +362,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(
|
||||
@@ -394,6 +411,7 @@ private:
|
||||
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; }
|
||||
@@ -402,6 +420,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,
|
||||
@@ -500,14 +523,59 @@ 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;
|
||||
|
||||
// One stop of the island-level tour: consecutive islands of a single instance. An instance
|
||||
// can have several visits per layer when its islands are toured non-consecutively.
|
||||
struct InstanceVisit
|
||||
{
|
||||
// Index into the per-filament InstanceToPrint vector.
|
||||
size_t instance_idx;
|
||||
// Islands to print, in order (indices into ObjectByExtruder::islands). Empty: print all
|
||||
// islands, ordered at extrusion time.
|
||||
std::vector<size_t> islands;
|
||||
// First visit of this instance this layer; skirt, brim and support are emitted here.
|
||||
bool first_visit;
|
||||
};
|
||||
|
||||
// One node of the island-level tour, also used as cache key: identity plus quantized position.
|
||||
struct IslandOrderNode
|
||||
{
|
||||
ObjectID object_id;
|
||||
size_t instance_id;
|
||||
// Index into ObjectByExtruder::islands, or size_t(-1) for an instance without chainable
|
||||
// islands (e.g. support only), which is toured as a single stop.
|
||||
size_t island_idx;
|
||||
// Island centroid in G-code coordinates, quantized to 1 mm for cache stability.
|
||||
Point pos;
|
||||
bool operator==(const IslandOrderNode &rhs) const {
|
||||
return object_id == rhs.object_id && instance_id == rhs.instance_id &&
|
||||
island_idx == rhs.island_idx && pos == rhs.pos;
|
||||
}
|
||||
};
|
||||
|
||||
// Cache the per-filament island tour to avoid recomputing while the layer's island layout is
|
||||
// unchanged. Key: filament_id. Value: {nodes the tour was computed from, resulting visits}.
|
||||
std::map<unsigned int, std::pair<std::vector<IslandOrderNode>, std::vector<InstanceVisit>>>
|
||||
m_ordering_cache;
|
||||
|
||||
ExtrusionQualityEstimator m_extrusion_quality_estimator;
|
||||
|
||||
|
||||
@@ -556,6 +624,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;
|
||||
@@ -595,6 +689,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)
|
||||
@@ -654,12 +751,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);
|
||||
|
||||
@@ -844,7 +844,10 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
ironing_fan_control = false; // ORCA: Add support for ironing fan speed control
|
||||
ironing_fan_speed = 0; // ORCA: Add support for ironing fan speed control
|
||||
}
|
||||
if (fan_speed_new != m_fan_speed) {
|
||||
// A tool change may keep the same configured base fan speed while the physical fan is
|
||||
// still running at the previous filament's overhang speed. Restore the base speed before
|
||||
// emitting G-code for the new tool in that case.
|
||||
if (fan_speed_new != m_fan_speed || (immediately_apply && m_current_fan_speed != fan_speed_new)) {
|
||||
m_fan_speed = fan_speed_new;
|
||||
m_current_fan_speed = fan_speed_new;
|
||||
if (immediately_apply)
|
||||
@@ -1040,8 +1043,10 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed, part_cooling_fan_min_pwm);
|
||||
fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] = false;
|
||||
}
|
||||
else
|
||||
else {
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed, part_cooling_fan_min_pwm);
|
||||
m_current_fan_speed = m_fan_speed;
|
||||
}
|
||||
need_set_fan = false;
|
||||
}
|
||||
pos = line_end;
|
||||
|
||||
@@ -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 };
|
||||
@@ -624,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.
|
||||
@@ -651,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
|
||||
{
|
||||
@@ -795,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
|
||||
@@ -825,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;
|
||||
@@ -846,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
|
||||
{
|
||||
@@ -875,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,
|
||||
@@ -886,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;
|
||||
|
||||
@@ -1084,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;
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
// Print-object ordering strategies: implementation.
|
||||
// Consolidates TSP post-processing, Snake, and Best-of-Strategies.
|
||||
|
||||
#include "OrderingStrategies.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/* ====================================================================
|
||||
* TSP post-processing utilities
|
||||
* ==================================================================== */
|
||||
|
||||
bool tsp_2opt_improve(std::vector<size_t>& path, const Points& centers, int max_passes)
|
||||
{
|
||||
size_t pn = path.size();
|
||||
if (pn <= 2) return false;
|
||||
|
||||
// Pre-compute edge lengths once per pass to avoid redundant norm() calls.
|
||||
auto recompute_edges = [&]() {
|
||||
std::vector<double> el(pn);
|
||||
for (size_t i = 0; i < pn; ++i) {
|
||||
size_t ni = (i + 1) % pn;
|
||||
el[i] = (centers[path[i]].cast<double>() - centers[path[ni]].cast<double>()).norm();
|
||||
}
|
||||
return el;
|
||||
};
|
||||
std::vector<double> el = recompute_edges();
|
||||
|
||||
// Pre-compute squared edge lengths for early rejection in the inner loop.
|
||||
auto recompute_edges_sq = [&]() {
|
||||
std::vector<double> elsq(pn);
|
||||
for (size_t i = 0; i < pn; ++i) {
|
||||
size_t ni = (i + 1) % pn;
|
||||
elsq[i] = (centers[path[i]].cast<double>() - centers[path[ni]].cast<double>()).squaredNorm();
|
||||
}
|
||||
return elsq;
|
||||
};
|
||||
std::vector<double> elsq = recompute_edges_sq();
|
||||
|
||||
bool improved = false;
|
||||
for (int pass = 0; max_passes <= 0 || pass < max_passes; ++pass) {
|
||||
size_t best_i = pn, best_j = pn;
|
||||
double best_gain = 0;
|
||||
|
||||
for (size_t i = 0; i < pn; ++i) {
|
||||
const Vec2d& pi = centers[path[i]].cast<double>();
|
||||
const Vec2d& p_in = centers[path[(i + 1) % pn]].cast<double>();
|
||||
double d_i = el[i];
|
||||
double d_i_sq = elsq[i];
|
||||
|
||||
for (size_t j = i + 2; j < pn; ++j) {
|
||||
size_t j_next = (j + 1) % pn;
|
||||
// Skip the swap that would reverse the entire cycle (removes both
|
||||
// edges (0,1) and (pn-1,0), equivalent to traversing the cycle backwards).
|
||||
if (i == 0 && j_next == 0) continue;
|
||||
|
||||
const Vec2d& pj = centers[path[j]].cast<double>();
|
||||
const Vec2d& p_jn = centers[path[j_next]].cast<double>();
|
||||
double d_j = el[j];
|
||||
|
||||
// Early rejection using squared distances (avoids 2 sqrt calls).
|
||||
double new_a_sq = (pj - pi).squaredNorm();
|
||||
double new_b_sq = (p_jn - p_in).squaredNorm();
|
||||
if (new_a_sq >= d_i_sq && new_b_sq >= elsq[j]) continue;
|
||||
|
||||
double new_a = std::sqrt(new_a_sq);
|
||||
double new_b = std::sqrt(new_b_sq);
|
||||
double gain = d_i + d_j - new_a - new_b;
|
||||
|
||||
if (gain > best_gain) {
|
||||
best_gain = gain;
|
||||
best_i = i; best_j = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best_i == pn) break;
|
||||
improved = true;
|
||||
// Reverse the best swap segment
|
||||
std::reverse(path.begin() + best_i + 1, path.begin() + best_j + 1);
|
||||
|
||||
// Recompute edge lengths after reversal
|
||||
el = recompute_edges();
|
||||
elsq = recompute_edges_sq();
|
||||
}
|
||||
return improved;
|
||||
}
|
||||
|
||||
// Fast bounding-box overlap test (rejects most non-intersecting pairs).
|
||||
static inline bool bboxes_overlap(const Point& a, const Point& b, const Point& c, const Point& d)
|
||||
{
|
||||
return !(std::max(a.x(), b.x()) < std::min(c.x(), d.x()) ||
|
||||
std::max(c.x(), d.x()) < std::min(a.x(), b.x()) ||
|
||||
std::max(a.y(), b.y()) < std::min(c.y(), d.y()) ||
|
||||
std::max(c.y(), d.y()) < std::min(a.y(), b.y()));
|
||||
}
|
||||
|
||||
bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
size_t pn = path.size();
|
||||
if (pn <= 3) return false;
|
||||
|
||||
// Treat path as a cycle: include the closing edge (pn-1 -> 0), consistent with the other
|
||||
// TSP helpers (2-opt, closing-edge rotation) that operate on the full cycle.
|
||||
size_t n_edges = pn;
|
||||
|
||||
// Scan for first crossing; returns {i, j} or {npos, npos} if none.
|
||||
auto find_crossing = [&]() -> std::pair<size_t, size_t> {
|
||||
for (size_t i = 0; i < n_edges; ++i) {
|
||||
const Point& ai = centers[path[i]];
|
||||
const Point& bi = centers[path[(i + 1) % pn]];
|
||||
|
||||
for (size_t j = i + 2; j < n_edges; ++j) {
|
||||
// Skip the (0, pn-1) pair: edges (0,1) and (pn-1,0) share node 0.
|
||||
if (i == 0 && j == pn - 1) continue;
|
||||
|
||||
const Point& aj = centers[path[j]];
|
||||
const Point& bj = centers[path[(j + 1) % pn]];
|
||||
|
||||
if (!bboxes_overlap(ai, bi, aj, bj)) continue;
|
||||
if (Geometry::segments_intersect(ai, bi, aj, bj))
|
||||
return {i, j};
|
||||
}
|
||||
}
|
||||
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
|
||||
};
|
||||
|
||||
// Process crossings one at a time: find first, reverse it, restart scan.
|
||||
// Cap iterations to prevent infinite loops on collinear/overlapping segments.
|
||||
int max_iters = static_cast<int>(pn * pn);
|
||||
bool improved = false;
|
||||
while (max_iters-- > 0) {
|
||||
auto [ci, cj] = find_crossing();
|
||||
if (ci == std::numeric_limits<size_t>::max()) break;
|
||||
improved = true;
|
||||
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1);
|
||||
}
|
||||
return improved;
|
||||
}
|
||||
|
||||
void tsp_rotate_minimize_closing(std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
size_t pn = path.size();
|
||||
size_t best_start = 0;
|
||||
double best_closing2 = std::numeric_limits<double>::max();
|
||||
for (size_t start = 0; start < pn; ++start) {
|
||||
size_t last = (start + pn - 1) % pn;
|
||||
double d2 = (centers[path[start]].cast<double>() - centers[path[last]].cast<double>()).squaredNorm();
|
||||
if (d2 < best_closing2) { best_closing2 = d2; best_start = start; }
|
||||
}
|
||||
std::rotate(path.begin(), path.begin() + best_start, path.end());
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* Snake ordering
|
||||
* ==================================================================== */
|
||||
|
||||
struct SnakeRow { double avg_y; std::vector<size_t> indices; };
|
||||
|
||||
// --- Row threshold computation ---
|
||||
// Extract unique Y values and use the median gap between them to determine
|
||||
// the row threshold.
|
||||
static double compute_row_threshold(const std::vector<double>& sorted_ys,
|
||||
double y_min, double y_max,
|
||||
size_t n,
|
||||
double fraction_of_y_range,
|
||||
double min_threshold_um)
|
||||
{
|
||||
constexpr double MIN_GAP_FILTER = 1.0; // ignore sub-micron gaps (coord_t = 1/100mm)
|
||||
|
||||
// Extract unique Y values
|
||||
std::vector<double> unique_ys;
|
||||
unique_ys.reserve(sorted_ys.size());
|
||||
unique_ys.push_back(sorted_ys[0]);
|
||||
for (size_t i = 1; i < sorted_ys.size(); ++i) {
|
||||
if (sorted_ys[i] - sorted_ys[i - 1] > MIN_GAP_FILTER)
|
||||
unique_ys.push_back(sorted_ys[i]);
|
||||
}
|
||||
|
||||
double fallback_threshold = (y_max - y_min) * fraction_of_y_range;
|
||||
if (unique_ys.size() <= 1) {
|
||||
return std::max(fallback_threshold, min_threshold_um);
|
||||
}
|
||||
|
||||
// Compute gaps between consecutive unique Y values
|
||||
std::vector<double> gaps;
|
||||
gaps.reserve(unique_ys.size() - 1);
|
||||
for (size_t i = 1; i < unique_ys.size(); ++i)
|
||||
gaps.push_back(unique_ys[i] - unique_ys[i - 1]);
|
||||
|
||||
if (gaps.empty()) {
|
||||
return std::max(fallback_threshold, min_threshold_um);
|
||||
}
|
||||
|
||||
// Sort gaps to find the median
|
||||
std::sort(gaps.begin(), gaps.end());
|
||||
double median_gap = gaps[gaps.size() / 2];
|
||||
double min_gap = gaps.front();
|
||||
|
||||
// Threshold: half the gap between consecutive unique Y values.
|
||||
double threshold = (median_gap < min_gap * 1.5) ? min_gap * 0.5 : median_gap * 0.5;
|
||||
|
||||
bool has_row_structure;
|
||||
if (unique_ys.size() * 2 <= n) {
|
||||
has_row_structure = true;
|
||||
} else {
|
||||
// Single-column or sparse: uniform gaps indicate a deliberate grid
|
||||
double max_gap = *std::max_element(gaps.begin(), gaps.end());
|
||||
has_row_structure = (max_gap < min_gap * 2.0);
|
||||
}
|
||||
|
||||
if (has_row_structure) {
|
||||
// For grid-like data, use the gap-based threshold directly.
|
||||
return threshold;
|
||||
}
|
||||
|
||||
return std::max(fallback_threshold, min_threshold_um);
|
||||
}
|
||||
|
||||
// --- Row grouping ---
|
||||
// Bin points into rows by quantising Y / threshold
|
||||
static std::vector<SnakeRow> group_into_rows(const Points& centers, double row_threshold)
|
||||
{
|
||||
size_t n = centers.size();
|
||||
std::unordered_map<int64_t, std::vector<size_t>> row_map;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
int64_t y_key = static_cast<int64_t>(std::floor(static_cast<double>(centers[i].y()) / row_threshold));
|
||||
row_map[y_key].push_back(i);
|
||||
}
|
||||
|
||||
std::vector<SnakeRow> rows;
|
||||
rows.reserve(row_map.size());
|
||||
for (auto& [key, indices] : row_map) {
|
||||
double avg_y = std::accumulate(indices.begin(), indices.end(), 0.0,
|
||||
[&](double acc, size_t idx) { return acc + static_cast<double>(centers[idx].y()); })
|
||||
/ indices.size();
|
||||
rows.push_back({avg_y, std::move(indices)});
|
||||
}
|
||||
|
||||
std::sort(rows.begin(), rows.end(),
|
||||
[](const SnakeRow& a, const SnakeRow& b) { return a.avg_y < b.avg_y; });
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
// Sort each row by X and greedily pick the direction (left->right or right->left)
|
||||
// that minimises the transition distance from the previous row's endpoint.
|
||||
static std::vector<size_t> build_serpentine_path(const Points& centers,
|
||||
std::vector<SnakeRow>& rows)
|
||||
{
|
||||
std::vector<size_t> path;
|
||||
path.reserve(centers.size());
|
||||
|
||||
for (size_t ri = 0; ri < rows.size(); ++ri) {
|
||||
auto& row = rows[ri].indices;
|
||||
std::sort(row.begin(), row.end(),
|
||||
[&](size_t a, size_t b) { return centers[a].x() < centers[b].x(); });
|
||||
|
||||
if (ri == 0) {
|
||||
path.insert(path.end(), row.begin(), row.end());
|
||||
} else {
|
||||
const Point& prev_end = centers[path.back()];
|
||||
double dist_to_left = (prev_end.cast<double>() - centers[row.front()].cast<double>()).squaredNorm();
|
||||
double dist_to_right = (prev_end.cast<double>() - centers[row.back()].cast<double>()).squaredNorm();
|
||||
|
||||
if (dist_to_left <= dist_to_right)
|
||||
path.insert(path.end(), row.begin(), row.end());
|
||||
else
|
||||
path.insert(path.end(), row.rbegin(), row.rend());
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// Row-based serpentine traversal: detect rows, bin points, snake through them.
|
||||
static std::vector<size_t> row_serpentine_path(const Points& centers,
|
||||
double fraction_of_y_range = 0.02,
|
||||
double min_threshold_um = 1e4)
|
||||
{
|
||||
if (centers.empty()) return {};
|
||||
|
||||
size_t n = centers.size();
|
||||
|
||||
// Collect and sort Y coordinates.
|
||||
std::vector<double> sorted_ys;
|
||||
sorted_ys.reserve(n);
|
||||
for (const auto& p : centers) sorted_ys.push_back(static_cast<double>(p.y()));
|
||||
std::sort(sorted_ys.begin(), sorted_ys.end());
|
||||
|
||||
auto [ymin, ymax] = std::minmax_element(sorted_ys.begin(), sorted_ys.end());
|
||||
double y_min = *ymin, y_max = *ymax;
|
||||
|
||||
double row_threshold = compute_row_threshold(sorted_ys, y_min, y_max, n,
|
||||
fraction_of_y_range, min_threshold_um);
|
||||
|
||||
auto rows = group_into_rows(centers, row_threshold);
|
||||
return build_serpentine_path(centers, rows);
|
||||
}
|
||||
|
||||
std::vector<size_t> snake_core(const Points& centers)
|
||||
{
|
||||
if (centers.empty()) return {};
|
||||
|
||||
std::vector<size_t> path = row_serpentine_path(centers);
|
||||
|
||||
for (int iter = 0; iter < 3; ++iter) {
|
||||
bool improved = tsp_2opt_improve(path, centers);
|
||||
improved |= tsp_remove_crossings(path, centers);
|
||||
if (!improved) break;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const std::vector<const PrintObject*>& print_objects, const Point* start_near)
|
||||
{
|
||||
return chain_instances_with_core(print_objects, start_near, snake_core);
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const Print& print)
|
||||
{
|
||||
return chain_print_object_instances_snake(print.objects().vector(), nullptr);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* Best-of-strategies meta-strategy
|
||||
* ==================================================================== */
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const std::vector<const PrintObject*>& print_objects, const Point* start_near)
|
||||
{
|
||||
if (print_objects.empty())
|
||||
return {};
|
||||
|
||||
// Run all strategies.
|
||||
std::vector<std::vector<const PrintInstance*>> candidates;
|
||||
candidates.push_back(chain_print_object_instances(print_objects, start_near));
|
||||
candidates.push_back(chain_print_object_instances_snake(print_objects, start_near));
|
||||
|
||||
// Compute metrics for each candidate.
|
||||
struct Candidate { double total_len; double max_edge; };
|
||||
std::vector<Candidate> metrics;
|
||||
metrics.reserve(candidates.size());
|
||||
|
||||
for (size_t i = 0; i < candidates.size(); ++i) {
|
||||
double total = 0.0;
|
||||
double mx = 0.0;
|
||||
for (size_t j = 0; j < candidates[i].size(); ++j) {
|
||||
size_t k = (j + 1) % candidates[i].size();
|
||||
double d = (candidates[i][j]->shift.cast<double>() - candidates[i][k]->shift.cast<double>()).norm();
|
||||
total += d;
|
||||
if (d > mx) mx = d;
|
||||
}
|
||||
metrics.push_back({total, mx});
|
||||
}
|
||||
|
||||
// Pick shortest total path; tiebreak on smallest max edge.
|
||||
auto best_it = std::min_element(metrics.begin(), metrics.end(),
|
||||
[](const Candidate& a, const Candidate& b) {
|
||||
return a.total_len < b.total_len ||
|
||||
(a.total_len == b.total_len && a.max_edge < b.max_edge);
|
||||
});
|
||||
size_t best = static_cast<size_t>(std::distance(metrics.begin(), best_it));
|
||||
|
||||
return candidates[best];
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const Print& print)
|
||||
{
|
||||
return chain_print_object_instances_best_of(print.objects().vector(), nullptr);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* Island-level ordering entry point
|
||||
* ==================================================================== */
|
||||
|
||||
std::vector<size_t> order_points_with_strategy(const Points& points, PrintOrder print_order, const Point* start_near)
|
||||
{
|
||||
if (points.empty())
|
||||
return {};
|
||||
|
||||
if (print_order != PrintOrder::Snake && print_order != PrintOrder::BestOfStrategies)
|
||||
// Nearest neighbor + post-processing; honours start_near natively.
|
||||
return chain_points_with_postprocessing(points, start_near);
|
||||
|
||||
auto run_snake = [&points, start_near]() {
|
||||
std::vector<size_t> path = snake_core(points);
|
||||
if (start_near != nullptr && !path.empty()) {
|
||||
// Start the cycle at the point closest to start_near.
|
||||
size_t best_start = 0;
|
||||
double best_d2 = std::numeric_limits<double>::max();
|
||||
for (size_t k = 0; k < points.size(); ++k) {
|
||||
double d2 = (points[k].cast<double>() - start_near->cast<double>()).squaredNorm();
|
||||
if (d2 < best_d2) { best_d2 = d2; best_start = k; }
|
||||
}
|
||||
auto it = std::find(path.begin(), path.end(), best_start);
|
||||
if (it != path.begin() && it != path.end())
|
||||
std::rotate(path.begin(), it, path.end());
|
||||
} else {
|
||||
tsp_rotate_minimize_closing(path, points);
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
if (print_order == PrintOrder::Snake)
|
||||
return run_snake();
|
||||
|
||||
// Best-of: pick the shortest total cycle; tiebreak on smallest max edge.
|
||||
std::vector<std::vector<size_t>> candidates;
|
||||
candidates.emplace_back(chain_points_with_postprocessing(points, start_near));
|
||||
candidates.emplace_back(run_snake());
|
||||
|
||||
size_t best = 0;
|
||||
double best_len = std::numeric_limits<double>::max();
|
||||
double best_edge = std::numeric_limits<double>::max();
|
||||
for (size_t i = 0; i < candidates.size(); ++i) {
|
||||
double len = tsp_cycle_path_length(candidates[i], points);
|
||||
double edge = tsp_max_edge_length(candidates[i], points);
|
||||
if (len < best_len || (len == best_len && edge < best_edge)) {
|
||||
best_len = len; best_edge = edge; best = i;
|
||||
}
|
||||
}
|
||||
return candidates[best];
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,148 @@
|
||||
// Print-object ordering strategies and shared TSP post-processing utilities.
|
||||
|
||||
#ifndef slic3r_OrderingStrategies_hpp_
|
||||
#define slic3r_OrderingStrategies_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../Point.hpp"
|
||||
|
||||
#ifndef SLIC3R_TEST_HARNESS
|
||||
#include "../Print.hpp"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// --- Path improvement (operate on index vectors into `centers`) ---
|
||||
|
||||
// 2-opt improvement: reverses segments that reduce total cycle path length.
|
||||
// Returns true if any improvement was made.
|
||||
bool tsp_2opt_improve(std::vector<size_t>& path, const Points& centers, int max_passes = 10);
|
||||
|
||||
// Crossing removal: reverse any segment pair whose edges geometrically cross.
|
||||
// Returns true if any crossing was removed.
|
||||
bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers);
|
||||
|
||||
// Rotate the cycle so the closing edge (last -> first) is minimized.
|
||||
void tsp_rotate_minimize_closing(std::vector<size_t>& path, const Points& centers);
|
||||
|
||||
// Total Euclidean path length of a cycle (including closing edge).
|
||||
inline double tsp_cycle_path_length(const std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
if (path.size() < 2) return 0.0;
|
||||
double total = 0.0;
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
size_t next = (i + 1) % path.size();
|
||||
total += (centers[path[i]].cast<double>() - centers[path[next]].cast<double>()).norm();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// Maximum edge length of a cycle (including closing edge).
|
||||
inline double tsp_max_edge_length(const std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
if (path.size() < 2) return 0.0;
|
||||
double mx = 0.0;
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
size_t next = (i + 1) % path.size();
|
||||
double d = (centers[path[i]].cast<double>() - centers[path[next]].cast<double>()).norm();
|
||||
if (d > mx) mx = d;
|
||||
}
|
||||
return mx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifndef SLIC3R_TEST_HARNESS
|
||||
|
||||
// --- Wrapper boilerplate ---
|
||||
|
||||
// Collect instance centers from PrintObjects, optionally pre-rotate to honour
|
||||
// start_near, call a core algorithm, and map the result back to PrintInstance*.
|
||||
template<typename CoreFn>
|
||||
std::vector<const PrintInstance*> chain_instances_with_core(
|
||||
const std::vector<const PrintObject*>& print_objects,
|
||||
const Point* start_near,
|
||||
CoreFn&& core_fn)
|
||||
{
|
||||
Points instance_centers;
|
||||
std::vector<std::pair<size_t, size_t>> instances;
|
||||
for (size_t i = 0; i < print_objects.size(); ++i) {
|
||||
const PrintObject& object = *print_objects[i];
|
||||
for (size_t j = 0; j < object.instances().size(); ++j) {
|
||||
instance_centers.emplace_back(object.instances()[j].shift);
|
||||
instances.emplace_back(i, j);
|
||||
}
|
||||
}
|
||||
|
||||
if (instance_centers.empty()) return {};
|
||||
|
||||
// If start_near is provided, pre-rotate so closest point is first.
|
||||
if (start_near != nullptr) {
|
||||
size_t best_start = 0;
|
||||
double best_d2 = std::numeric_limits<double>::max();
|
||||
for (size_t k = 0; k < instance_centers.size(); ++k) {
|
||||
double d2 = (instance_centers[k].cast<double>() - start_near->cast<double>()).squaredNorm();
|
||||
if (d2 < best_d2) { best_d2 = d2; best_start = k; }
|
||||
}
|
||||
std::rotate(instance_centers.begin(), instance_centers.begin() + best_start, instance_centers.end());
|
||||
std::rotate(instances.begin(), instances.begin() + best_start, instances.end());
|
||||
}
|
||||
|
||||
auto path = core_fn(instance_centers);
|
||||
|
||||
// Rotate the cycle so the first element is the best starting point.
|
||||
// When start_near is provided, pick the point closest to it (preserving
|
||||
// the pre-rotation). Otherwise minimise the closing edge.
|
||||
if (start_near != nullptr && !path.empty()) {
|
||||
// Pre-rotation already put the closest point at index 0.
|
||||
// Find where index 0 appears in the path and rotate it to the front.
|
||||
auto it = std::find(path.begin(), path.end(), size_t(0));
|
||||
if (it != path.begin())
|
||||
std::rotate(path.begin(), it, path.end());
|
||||
} else {
|
||||
tsp_rotate_minimize_closing(path, instance_centers);
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> out;
|
||||
out.reserve(path.size());
|
||||
for (size_t step : path) {
|
||||
out.emplace_back(&print_objects[instances[step].first]->instances()[instances[step].second]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif // SLIC3R_TEST_HARNESS
|
||||
|
||||
// --- Core algorithms (operate on raw Points, return index permutations) ---
|
||||
|
||||
// Snake ordering: row grouping + serpentine traversal + post-processing.
|
||||
std::vector<size_t> snake_core(const Points& centers);
|
||||
|
||||
#ifndef SLIC3R_TEST_HARNESS
|
||||
|
||||
// --- Production wrappers ---
|
||||
|
||||
// Snake ordering.
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const std::vector<const PrintObject*>& print_objects, const Point* start_near);
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const Print& print);
|
||||
|
||||
// Best-of-strategies: run all strategies and return the shortest result.
|
||||
// Primary: shortest total path; secondary tiebreaker: smallest max edge.
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const std::vector<const PrintObject*>& print_objects, const Point* start_near);
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const Print& print);
|
||||
|
||||
// Order raw points with the selected strategy, returning an index permutation. Island-level
|
||||
// counterpart of the chain_print_object_instances_* helpers. The returned cycle starts at the
|
||||
// point closest to start_near; orders without a dedicated strategy use nearest-neighbor chaining.
|
||||
std::vector<size_t> order_points_with_strategy(const Points& points, PrintOrder print_order, const Point* start_near);
|
||||
|
||||
#endif // SLIC3R_TEST_HARNESS
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_OrderingStrategies_hpp_ */
|
||||
@@ -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_ */
|
||||
@@ -143,7 +143,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
|
||||
double wipe_tower_y = print.config().wipe_tower_y.get_at(plate_idx) + plate_origin(1);
|
||||
Transform2d trafo =
|
||||
Eigen::Translation2d(wipe_tower_x, wipe_tower_y) *
|
||||
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value));
|
||||
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value)) *
|
||||
Eigen::Translation2d(print.wipe_tower_data().rib_offset.cast<double>()); // tower-local rib-wall shift, zero unless rib
|
||||
|
||||
BoundingBoxf bbox;
|
||||
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
+1404
-532
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
@@ -20,6 +20,17 @@ class WipeTowerWriter;
|
||||
class PrintConfig;
|
||||
enum GCodeFlavor : unsigned char;
|
||||
|
||||
// Cuts the tower wall polygon open at each skip point (a toolchange's entry position)
|
||||
// so the entry travel can pass through instead of crossing the printed wall. Defined in
|
||||
// WipeTower.cpp, shared by WipeTower and WipeTower2.
|
||||
Polylines construct_gap_for_skip_points(
|
||||
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon);
|
||||
|
||||
// Klipper acts on commands the instant it parses them, and its G4 reads only P (milliseconds),
|
||||
// so the zero-second and seconds-valued dwells every other flavor uses neither synchronize nor
|
||||
// pause there. Both defined in WipeTower.cpp, shared by WipeTower and WipeTower2.
|
||||
const char* flush_planner_queue_command(GCodeFlavor flavor); // finish queued moves, e.g. around M104/M109
|
||||
std::string wait_command(GCodeFlavor flavor, float seconds); // pause for `seconds`
|
||||
|
||||
class WipeTower
|
||||
{
|
||||
@@ -34,7 +45,11 @@ public:
|
||||
static TriangleMesh its_make_rib_tower(float width, float depth, float height, float rib_length, float rib_width, bool fillet_wall);
|
||||
static TriangleMesh its_make_rib_brim(const Polygon& brim, float layer_height);
|
||||
static Polygon rib_section(float width, float depth, float rib_length, float rib_width, bool fillet_wall);
|
||||
static Vec2f move_box_inside_box(const BoundingBox &box1, const BoundingBox &box2, int offset = 0);
|
||||
// Translation that brings a footprint inside the printable outline, padded by offset. The prime
|
||||
// tower is validated against the real outline (see layered_print_cleareance_valid), so clamping
|
||||
// against the bounding box alone would leave it off a delta or hexagonal bed. box and polygons
|
||||
// must share one scaled coordinate frame; the translation comes back in millimeters.
|
||||
static Vec2f move_box_inside_polygon(const BoundingBox &box, const Polygons &polygons, coord_t offset = 0);
|
||||
static Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double angle_tol = 30. / 180. * PI);
|
||||
struct Extrusion
|
||||
{
|
||||
@@ -58,6 +73,7 @@ public:
|
||||
Vec2f origin_start_pos; // not rotated
|
||||
|
||||
std::vector<Vec2f> wipe_path;
|
||||
bool is_extruder_change{true};
|
||||
};
|
||||
|
||||
struct ToolChangeResult
|
||||
@@ -83,7 +99,6 @@ public:
|
||||
bool priming;
|
||||
|
||||
bool is_tool_change{false};
|
||||
bool is_contact{false};
|
||||
Vec2f tool_change_start_pos;
|
||||
|
||||
// Pass a polyline so that normal G-code generator can do a wipe for us.
|
||||
@@ -107,6 +122,7 @@ public:
|
||||
// executing the gcode finish_layer_tcr.
|
||||
bool is_finish_first = false;
|
||||
|
||||
bool is_contact = false;
|
||||
NozzleChangeResult nozzle_change_result;
|
||||
|
||||
// Sum the total length of the extrusion.
|
||||
@@ -121,6 +137,8 @@ public:
|
||||
}
|
||||
return e_length;
|
||||
}
|
||||
// Orca: set by WipeTower2 (non-BBL tower) to force a travel to the tower even when the
|
||||
// previous position is unknown; read by WipeTowerIntegration::append_tcr2 (GCode.cpp).
|
||||
bool force_travel = false;
|
||||
};
|
||||
|
||||
@@ -161,15 +179,12 @@ public:
|
||||
bool priming,
|
||||
size_t old_tool,
|
||||
bool is_finish,
|
||||
bool is_tool_change,
|
||||
float purge_volume,
|
||||
bool is_contact = false) const;
|
||||
bool is_tool_change, float purge_volume, bool is_contact) const;
|
||||
|
||||
ToolChangeResult construct_block_tcr(WipeTowerWriter& writer,
|
||||
bool priming,
|
||||
size_t filament_id,
|
||||
bool is_finish,
|
||||
float purge_volume) const;
|
||||
bool is_finish, float purge_volume) const;
|
||||
|
||||
|
||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||
@@ -183,9 +198,14 @@ public:
|
||||
// Set the extruder properties.
|
||||
void set_extruder(size_t idx, const PrintConfig& config);
|
||||
|
||||
void set_shared_print_bed(const Polygons &bed) { m_shared_print_bed = bed; }
|
||||
// Orca: has_filament_switcher is not a static PrintConfig member here, 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; }
|
||||
// Appends into internal structure m_plan containing info about the future wipe tower
|
||||
// to be used before building begins. The entries must be added ordered in z.
|
||||
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f, float prime_volume = 0.f);
|
||||
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume_ec = 0.f, float wipe_volume_nc = 0.f, float prime_volume = 0.f);
|
||||
|
||||
|
||||
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
|
||||
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
||||
@@ -218,9 +238,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void set_wipe_volume(std::vector<std::vector<float>>& wiping_matrix) {
|
||||
wipe_volumes = wiping_matrix;
|
||||
}
|
||||
|
||||
// Switch to a next layer.
|
||||
void set_layer(
|
||||
@@ -249,7 +266,6 @@ public:
|
||||
|
||||
// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
|
||||
m_extrusion_flow = extrusion_flow(layer_height);
|
||||
|
||||
// Advance m_layer_info iterator, making sure we got it right
|
||||
while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
|
||||
++m_layer_info;
|
||||
@@ -308,11 +324,9 @@ public:
|
||||
std::vector<float> get_used_filament() const { return m_used_filament_length; }
|
||||
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; }
|
||||
|
||||
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
|
||||
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
||||
|
||||
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
int category;
|
||||
@@ -321,15 +335,15 @@ public:
|
||||
bool is_support = false;
|
||||
int nozzle_temperature = 0;
|
||||
int nozzle_temperature_initial_layer = 0;
|
||||
int interface_print_temperature = 0;
|
||||
float loading_speed = 0.f;
|
||||
float loading_speed_start = 0.f;
|
||||
float unloading_speed = 0.f;
|
||||
float unloading_speed_start = 0.f;
|
||||
float delay = 0.f ;
|
||||
int cooling_moves = 0;
|
||||
float cooling_initial_speed = 0.f;
|
||||
float cooling_final_speed = 0.f;
|
||||
// BBS: remove useless config
|
||||
//float loading_speed = 0.f;
|
||||
//float loading_speed_start = 0.f;
|
||||
//float unloading_speed = 0.f;
|
||||
//float unloading_speed_start = 0.f;
|
||||
//float delay = 0.f ;
|
||||
//int cooling_moves = 0;
|
||||
//float cooling_initial_speed = 0.f;
|
||||
//float cooling_final_speed = 0.f;
|
||||
float ramming_line_width_multiplicator = 1.f;
|
||||
float ramming_step_multiplicator = 1.f;
|
||||
float max_e_speed = std::numeric_limits<float>::max();
|
||||
@@ -339,29 +353,41 @@ public:
|
||||
float retract_length;
|
||||
float retract_speed;
|
||||
float wipe_dist;
|
||||
float tower_interface_pre_extrusion_dist = 0.f;
|
||||
float tower_interface_pre_extrusion_length = 0.f;
|
||||
float tower_ironing_area = 4.f;
|
||||
float tower_interface_purge_length = 0.f;
|
||||
std::pair<float,float> max_e_ramming_speed;//[0]extruder change [1]nozzle change
|
||||
std::pair<float, float> ramming_travel_time; // Travel time after ramming
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t;//Pre-cooling time, set to 0 to ensure the ramming speed is controlled solely by ramming volumetric speed.
|
||||
std::pair<std::vector<float>, std::vector<float>> precool_t_first_layer;
|
||||
std::pair<int,int> precool_target_temp;
|
||||
float filament_cooling_before_tower = 0.f;
|
||||
float flat_iron_area;
|
||||
float filament_tower_interface_print_temp;
|
||||
float filament_tower_interface_pre_extrusion_dist = 0;
|
||||
float filament_tower_interface_pre_extrusion_length = 0;
|
||||
float filament_petg_pre_extrusion_offset_dist = 0;
|
||||
};
|
||||
|
||||
|
||||
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
|
||||
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
|
||||
void set_filament_categories(const std::vector<int> & filament_categories) { m_filament_categories = filament_categories;};
|
||||
std::vector<int> m_used_filament_ids;
|
||||
void set_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult &multi_nozzle_group_result) { m_multi_nozzle_group_result = &multi_nozzle_group_result; };
|
||||
std::vector<int> m_used_filament_ids;
|
||||
std::vector<int> m_filament_categories;
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult *m_multi_nozzle_group_result{nullptr};
|
||||
|
||||
enum class WipeTowerLayerType : unsigned char { Normal, Contact, Solid, Contact_UP};// Contact layer should be solid and reduce feed
|
||||
|
||||
struct WipeTowerBlock
|
||||
{
|
||||
int block_id{0};
|
||||
int filament_adhesiveness_category{0};
|
||||
std::vector<float> layer_depths;
|
||||
std::vector<bool> solid_infill;
|
||||
//std::vector<bool> solid_infill;
|
||||
std::vector<float> finish_depth{0}; // the start pos of finish frame for every layer
|
||||
std::vector<WipeTowerLayerType> layers_type; // type of the layer, normal, Contact or Solid
|
||||
float depth{0};
|
||||
float start_depth{0};
|
||||
float cur_depth{0};
|
||||
int last_filament_change_id{-1};
|
||||
int last_filament_change_id{-1};
|
||||
int last_nozzle_change_id{-1};
|
||||
};
|
||||
|
||||
@@ -381,23 +407,33 @@ public:
|
||||
WipeTowerBlock* get_block_by_category(int filament_adhesiveness_category, bool create);
|
||||
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);
|
||||
void reset_block_status();
|
||||
int get_wall_filament_for_all_layer();
|
||||
// for generate new wipe tower
|
||||
void generate_new(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
|
||||
|
||||
void plan_tower_new();
|
||||
void generate_wipe_tower_blocks();
|
||||
void generate_wipe_tower_blocks(bool add_solid_flag);
|
||||
void update_all_layer_depth(float wipe_tower_depth);
|
||||
|
||||
void set_nozzle_last_layer_id();
|
||||
void set_first_layer_flow_ratio(const float flow_ratio);
|
||||
// Orca: default/initial-layer/travel acceleration are object-scope options here (PrintConfig
|
||||
// members in BBS), so Print pushes the resolved per-variant columns in via this setter.
|
||||
void set_accelerations(const std::vector<double> &normal, const std::vector<double> &first_layer_normal,
|
||||
const std::vector<double> &travel, const std::vector<double> &first_layer_travel);
|
||||
void calc_block_infill_gap();
|
||||
ToolChangeResult tool_change_new(size_t new_tool, bool solid_change = false, bool solid_nozzlechange=false);
|
||||
NozzleChangeResult nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_change = false);
|
||||
NozzleChangeResult ramming(int old_filament_id, int new_filament_id, bool solid_change = false, bool extruder_change = true); // extruder_chang means nozzle_change
|
||||
ToolChangeResult finish_layer_new(bool extrude_perimeter = true, bool extrude_fill = true, bool extrude_fill_wall = true);
|
||||
ToolChangeResult finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true);
|
||||
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true ,bool interface_solid =false);
|
||||
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true, WipeTowerLayerType layer_type = WipeTowerLayerType::Normal);
|
||||
void toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_length,bool solid_toolchange=false);
|
||||
Vec2f get_rib_offset() const { return m_rib_offset; }
|
||||
bool is_need_ramming(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
bool is_same_extruder(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
bool is_same_nozzle(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
int get_nozzle_id(int filament_id, int layer_id) const;
|
||||
int get_extruder_id(int filament_id, int layer_id) const;
|
||||
|
||||
private:
|
||||
enum wipe_shape // A fill-in direction
|
||||
@@ -417,7 +453,6 @@ private:
|
||||
bool m_enable_wrapping_detection = false;
|
||||
bool m_enable_timelapse_print = false;
|
||||
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
||||
bool m_purge_in_prime_tower = false; // Do we purge in the prime tower?
|
||||
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
||||
float m_wipe_tower_width; // Width of the wipe tower.
|
||||
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
||||
@@ -435,11 +470,11 @@ private:
|
||||
float m_travel_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
size_t m_first_layer_idx = size_t(-1);
|
||||
|
||||
std::vector<double> m_filaments_change_length;
|
||||
Vec2f m_origin;
|
||||
std::vector<int> m_last_layer_id;
|
||||
std::pair<std::vector<double>,std::vector<double>> m_filaments_change_length;//[0]extruder change [1]nozzle change
|
||||
size_t m_cur_layer_id;
|
||||
NozzleChangeResult m_nozzle_change_result;
|
||||
std::vector<int> m_filament_map;
|
||||
bool m_has_tpu_filament{false};
|
||||
bool m_is_multi_extruder{false};
|
||||
bool m_use_gap_wall{false};
|
||||
@@ -450,17 +485,32 @@ private:
|
||||
bool m_used_fillet{false};
|
||||
Vec2f m_rib_offset{Vec2f(0.f, 0.f)};
|
||||
bool m_tower_framework{false};
|
||||
|
||||
bool m_need_reverse_travel{false};
|
||||
bool m_enable_tower_interface_features{false};
|
||||
// G-code generator parameters.
|
||||
float m_cooling_tube_retraction = 0.f;
|
||||
float m_cooling_tube_length = 0.f;
|
||||
float m_parking_pos_retraction = 0.f;
|
||||
float m_extra_loading_move = 0.f;
|
||||
// BBS: remove useless config
|
||||
//float m_cooling_tube_retraction = 0.f;
|
||||
//float m_cooling_tube_length = 0.f;
|
||||
//float m_parking_pos_retraction = 0.f;
|
||||
//float m_extra_loading_move = 0.f;
|
||||
float m_bridging = 0.f;
|
||||
bool m_no_sparse_layers = false;
|
||||
bool m_set_extruder_trimpot = false;
|
||||
// BBS: remove useless config
|
||||
//bool m_set_extruder_trimpot = false;
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
bool m_is_multiple_nozzle = false;
|
||||
std::vector<unsigned int> m_normal_accels;
|
||||
std::vector<unsigned int> m_first_layer_normal_accels;
|
||||
std::vector<unsigned int> m_travel_accels;
|
||||
std::vector<unsigned int> m_first_layer_travel_accels;
|
||||
unsigned int m_max_accels;
|
||||
bool m_accel_to_decel_enable;
|
||||
float m_accel_to_decel_factor;
|
||||
bool m_enable_arc_fitting = true;
|
||||
std::vector<double> m_hotend_heating_rate;
|
||||
std::vector<double> m_hotend_cooling_rate;
|
||||
Polygons m_shared_print_bed;
|
||||
|
||||
// Bed properties
|
||||
enum {
|
||||
@@ -471,10 +521,11 @@ private:
|
||||
float m_bed_width; // width of the bed bounding box
|
||||
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
|
||||
|
||||
float m_first_layer_flow_ratio;
|
||||
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
|
||||
float m_nozzle_change_perimeter_width = 0.4f * Width_To_Nozzle_Ratio;
|
||||
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
||||
|
||||
std::unordered_map<int, std::pair<float,float>> m_block_infill_gap_width; // categories to infill_gap: toolchange gap, nozzlechange gap
|
||||
// Extruder specific parameters.
|
||||
std::vector<FilamentParameters> m_filpar;
|
||||
|
||||
@@ -487,43 +538,52 @@ private:
|
||||
// A fill-in direction (positive Y, negative Y) alternates with each layer.
|
||||
wipe_shape m_current_shape = SHAPE_NORMAL;
|
||||
size_t m_current_tool = 0;
|
||||
// Orca: support mmu wipe tower
|
||||
std::vector<std::vector<float>> wipe_volumes;
|
||||
// BBS
|
||||
//const std::vector<std::vector<float>> wipe_volumes;
|
||||
|
||||
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
|
||||
bool m_current_layer_finished = false;
|
||||
bool m_left_to_right = true;
|
||||
float m_extra_spacing = 1.f;
|
||||
float m_tpu_fixed_spacing = 2;
|
||||
std::vector<Vec2f> m_wall_skip_points;
|
||||
float m_max_speed = 5400.f; // the maximum printing speed on the prime tower.
|
||||
std::vector<std::vector<Vec2f>> m_wall_skip_points;
|
||||
std::map<float,Polylines> m_outer_wall;
|
||||
std::vector<double> m_printable_height;
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
bool is_valid_last_layer(int tool, int layer_id, double layer_z) const;
|
||||
bool m_flat_ironing=false;
|
||||
bool m_enable_tower_interface_features=false;
|
||||
bool m_enable_tower_interface_cooldown_during_tower=false;
|
||||
bool m_prev_layer_had_interface=false;
|
||||
bool m_current_layer_has_interface=false;
|
||||
bool m_contact_ironing = false;
|
||||
bool m_has_filament_switcher = false;
|
||||
float m_contact_speed = 20 * 60.f;
|
||||
std::vector<int> m_physical_extruder_map;
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
float volume_to_length(float volume, float line_width, float layer_height) const {
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
// Calculates volume of extrusion line
|
||||
float length_to_volume(float length,float line_width, float layer_height) const
|
||||
{
|
||||
return std::max(0.f, length * (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
// Calculates depth for all layers and propagates them downwards
|
||||
void plan_tower();
|
||||
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
|
||||
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool interface_layer, size_t interface_tool);
|
||||
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool solid_toolchange);
|
||||
|
||||
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
||||
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, bool extruder_change) const;
|
||||
// BBS
|
||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||
|
||||
void set_for_wipe_tower_writer(WipeTowerWriter &writer);
|
||||
|
||||
// to store information about tool changes for a given layer
|
||||
struct WipeTowerInfo{
|
||||
@@ -536,6 +596,7 @@ private:
|
||||
float wipe_volume;
|
||||
float wipe_length;
|
||||
float nozzle_change_depth{0};
|
||||
float nozzle_change_length{0};
|
||||
// BBS
|
||||
float purge_volume;
|
||||
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0)
|
||||
@@ -565,7 +626,7 @@ private:
|
||||
// ot -1 if there is no such toolchange.
|
||||
int first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||
|
||||
WipeTowerInfo::ToolChange set_toolchange(int old_tool, int new_tool, float layer_height, float wipe_volume, float purge_volume,int layer_id);
|
||||
void toolchange_Unload(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
@@ -585,7 +646,10 @@ private:
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
float wipe_volume);
|
||||
void get_wall_skip_points(const WipeTowerInfo &layer);
|
||||
void get_wall_skip_points(const WipeTowerInfo &layer,int layer_id);
|
||||
void get_all_wall_skip_points();
|
||||
ToolChangeResult merge_tcr(ToolChangeResult &first, ToolChangeResult &second);
|
||||
float get_block_gap_width(int tool, bool is_nozzlechangle = false);
|
||||
};
|
||||
|
||||
|
||||
|
||||
+435
-345
File diff suppressed because it is too large
Load Diff
@@ -17,13 +17,20 @@ namespace Slic3r
|
||||
|
||||
class WipeTowerWriter2;
|
||||
class PrintRegionConfig;
|
||||
class ConfigBase;
|
||||
|
||||
class WipeTower2
|
||||
{
|
||||
public:
|
||||
static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; }
|
||||
// Marks the wait-for-temp-on-wipe-tower M109 so the interface-temp deduplication pass
|
||||
// in WipeTowerIntegration::append_tcr2 does not strip it.
|
||||
static const std::string wait_for_temp_tag() { return ";_WAIT_FOR_TEMP_ON_WIPE_TOWER"; }
|
||||
static std::pair<double, double> get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg);
|
||||
static std::vector<std::vector<float>> extract_wipe_volumes(const PrintConfig& config);
|
||||
static std::vector<std::vector<float>> extract_wipe_volumes(const ConfigBase& config);
|
||||
// Estimated total flush volume of a SEMM print with the given number of filaments,
|
||||
// used to reserve wipe tower space before the tower is generated.
|
||||
static float estimate_semm_flush_volume(const ConfigBase& config, size_t filaments_cnt);
|
||||
|
||||
|
||||
// Construct ToolChangeResult from current state of WipeTower2 and WipeTowerWriter2.
|
||||
@@ -34,6 +41,15 @@ public:
|
||||
bool is_finish,
|
||||
bool is_contact = false) const;
|
||||
|
||||
// Whether this print cuts wall openings ("skip points") at the toolchange entries.
|
||||
// Shared with the entry routing in GCode.cpp so the router and the tower agree.
|
||||
static bool use_gap_wall(const PrintConfig& config);
|
||||
|
||||
// Whether the blocking toolchange temperature wait moves onto the wipe tower.
|
||||
// Shared with the defer flag in GCode.cpp append_tcr2 so the deferral and the
|
||||
// tower's tagged M109 can never disagree.
|
||||
static bool wait_for_temp_enabled(const PrintConfig& config);
|
||||
|
||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||
// y -- y coordinates of wipe tower in mm ( left bottom corner )
|
||||
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
|
||||
@@ -69,9 +85,9 @@ public:
|
||||
const float brim = m_wipe_tower_brim_width_real;
|
||||
return BoundingBoxf(Vec2d(-brim, -brim), Vec2d(double(m_wipe_tower_width) + brim, double(m_wipe_tower_depth) + brim));
|
||||
}
|
||||
// WT2 doesn't currently compute a rib-origin compensation like WipeTower (m_rib_offset),
|
||||
// so expose a zero offset for consistency purposes (to maintain API parity).
|
||||
Vec2f get_rib_offset() const { return Vec2f::Zero(); }
|
||||
// Tower-local shift that puts the rib wall's first-layer min corner at the configured
|
||||
// tower position, like WipeTower::get_rib_offset(). Zero unless the rib wall is used.
|
||||
Vec2f get_rib_offset() const { return m_rib_offset; }
|
||||
float get_rib_width() const { return m_rib_width; }
|
||||
float get_rib_length() const { return m_rib_length; }
|
||||
|
||||
@@ -149,6 +165,7 @@ public:
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
bool is_soluble = false;
|
||||
bool is_support = false;
|
||||
int temperature = 0;
|
||||
int first_layer_temperature = 0;
|
||||
int interface_print_temperature = 0;
|
||||
@@ -220,9 +237,9 @@ private:
|
||||
float m_perimeter_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
size_t m_first_layer_idx = size_t(-1);
|
||||
bool m_flat_ironing = false;
|
||||
bool m_enable_tower_interface_features = false;
|
||||
bool m_enable_tower_interface_cooldown_during_tower = false;
|
||||
bool m_wait_for_temp_on_wipe_tower = false;
|
||||
bool m_prev_layer_had_interface = false;
|
||||
bool m_current_layer_has_interface = false;
|
||||
|
||||
@@ -231,6 +248,12 @@ private:
|
||||
float m_rib_width = 10;
|
||||
float m_extra_rib_length = 0;
|
||||
float m_rib_length = 0;
|
||||
Vec2f m_rib_offset = Vec2f::Zero();
|
||||
bool m_use_gap_wall = false;
|
||||
// Per plan layer, each toolchange's entry position (tower-local, un-shifted frame):
|
||||
// where the wall is cut open so the entry travel does not cross the printed wall.
|
||||
// Filled by compute_wall_skip_points() once the plan is final.
|
||||
std::vector<std::vector<Vec2f>> m_wall_skip_points;
|
||||
|
||||
bool m_enable_arc_fitting = false;
|
||||
|
||||
@@ -253,6 +276,7 @@ private:
|
||||
} m_bed_shape;
|
||||
float m_bed_width; // width of the bed bounding box
|
||||
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
|
||||
Polygon m_bed_polygon; // printable_area contour (scaled)
|
||||
|
||||
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
|
||||
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
||||
@@ -278,6 +302,37 @@ private:
|
||||
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
|
||||
// Purge row lattice of toolchange_Wipe(): row pitch and extrusion width.
|
||||
float wipe_row_spacing(bool first_layer) const { return (first_layer ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; }
|
||||
float wipe_line_width() const { return m_perimeter_width * m_extra_flow; }
|
||||
|
||||
// Whether toolchange_Unload() rams this (old) tool out.
|
||||
bool tool_ramming_enabled(size_t tool) const { return (m_semm && m_enable_filament_ramming) || m_filpar[tool].multitool_ramming; }
|
||||
// Whether the wipe restarts at the box boundary on a fresh row below the quantized
|
||||
// ram band after ramming this (old) tool out (multi-tool gap wall; SEMM keeps the
|
||||
// stock continue-from-ram-end behavior).
|
||||
bool boundary_wipe_start_enabled(size_t tool) const { return tool_ramming_enabled(tool) && !m_semm && m_use_gap_wall; }
|
||||
|
||||
// With a boundary wipe start the wipe begins on a fresh row below the quantized ram
|
||||
// band. Y offset from the box start to that first wipe row.
|
||||
float wipe_start_offset_after_ram(float ramming_depth, bool first_layer) const
|
||||
{
|
||||
return ramming_depth + wipe_row_spacing(first_layer) - (m_perimeter_width + wipe_line_width()) / 2.f;
|
||||
}
|
||||
|
||||
// Tower-local entry position of a toolchange whose box starts depth_traversed into
|
||||
// the layer: the box corner, moved down to the first wipe row when the plan gives
|
||||
// it a boundary wipe start (ramming_depth > 0 iff the unload rams). tool_change()
|
||||
// enters here and compute_wall_skip_points() cuts the wall gap here, so the routed
|
||||
// entry, the gap and the wipe scrub all share one opening.
|
||||
Vec2f toolchange_entry_pos(float depth_traversed, float ramming_depth, bool first_layer) const
|
||||
{
|
||||
Vec2f pos(m_perimeter_width / 2.f, m_perimeter_width / 2.f + depth_traversed);
|
||||
if (!m_semm && m_use_gap_wall && ramming_depth > 0.f)
|
||||
pos.y() += wipe_start_offset_after_ram(ramming_depth, first_layer);
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Calculates extrusion flow needed to produce required line width for given layer height
|
||||
float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
|
||||
{
|
||||
@@ -328,9 +383,10 @@ private:
|
||||
std::vector<float> m_used_filament_length;
|
||||
std::vector<std::pair<float, std::vector<float>>> m_used_filament_length_until_layer;
|
||||
|
||||
// Return index of first toolchange that switches to non-soluble extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
int first_toolchange_to_nonsoluble(
|
||||
// Return the index of the toolchange whose new filament should print the layer's
|
||||
// finish extrusions (sparse infill + wall + brim), or -1 to print them with the
|
||||
// layer's incoming filament before any toolchange happens.
|
||||
int first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||
|
||||
void toolchange_Unload(
|
||||
@@ -343,7 +399,9 @@ private:
|
||||
void toolchange_Change(
|
||||
WipeTowerWriter2 &writer,
|
||||
const size_t new_tool,
|
||||
const std::string& new_material);
|
||||
const std::string& new_material,
|
||||
const int wait_for_temp,
|
||||
const bool wait_beside_tower);
|
||||
|
||||
void toolchange_Load(
|
||||
WipeTowerWriter2 &writer,
|
||||
@@ -353,7 +411,9 @@ private:
|
||||
WipeTowerWriter2 &writer,
|
||||
const WipeTower::box_coordinates &cleaning_box,
|
||||
float wipe_volume,
|
||||
bool interface_layer);
|
||||
bool interface_layer,
|
||||
bool priming = false,
|
||||
bool fill_box = false);
|
||||
|
||||
|
||||
Polygon generate_support_rib_wall(WipeTowerWriter2& writer,
|
||||
@@ -361,8 +421,7 @@ private:
|
||||
double feedrate,
|
||||
bool first_layer,
|
||||
bool rib_wall,
|
||||
bool extrude_perimeter,
|
||||
bool skip_points);
|
||||
bool extrude_perimeter);
|
||||
|
||||
Polygon generate_support_cone_wall(
|
||||
WipeTowerWriter2& writer,
|
||||
@@ -372,6 +431,12 @@ private:
|
||||
float spacing);
|
||||
|
||||
Polygon generate_rib_polygon(const WipeTower::box_coordinates& wt_box);
|
||||
|
||||
void compute_wall_skip_points();
|
||||
|
||||
// Computes the depth reserved for a toolchange (shared by plan_toolchange() and the
|
||||
// rib-wall square-tower replanning in generate()).
|
||||
WipeTowerInfo::ToolChange set_toolchange(size_t old_tool, size_t new_tool, float layer_height, float wipe_volume, bool first_layer_plan);
|
||||
};
|
||||
|
||||
|
||||
|
||||
+180
-59
@@ -2,6 +2,9 @@
|
||||
#include "CustomGCode.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Geometry/ArcWelder.hpp"
|
||||
#include "Line.hpp"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
@@ -99,9 +102,87 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
|
||||
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)
|
||||
@@ -110,6 +191,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)
|
||||
@@ -594,36 +676,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";
|
||||
@@ -632,6 +714,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.);
|
||||
@@ -658,7 +759,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_at("initial_layer_travel_speed", get_extruder_index(this->config, filament()->id())) : this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id()));
|
||||
? 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);
|
||||
@@ -711,14 +812,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
|
||||
@@ -744,7 +850,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_at("initial_layer_travel_speed", get_extruder_index(this->config, filament()->id())) : this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id()));
|
||||
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);
|
||||
@@ -773,7 +879,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 &&
|
||||
@@ -841,13 +955,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.get_at(get_extruder_index(this->config, filament()->id())) * 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.get_at(get_extruder_index(this->config, filament()->id())) * 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();
|
||||
}
|
||||
@@ -880,10 +994,10 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
|
||||
{
|
||||
m_pos(2) = z;
|
||||
|
||||
double speed = this->config.travel_speed_z.get_at(get_extruder_index(this->config, filament()->id()));
|
||||
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_at("initial_layer_travel_speed", get_extruder_index(this->config, filament()->id()))
|
||||
: this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id()));
|
||||
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;
|
||||
@@ -897,53 +1011,56 @@ 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.get_at(get_extruder_index(this->config, filament()->id()));
|
||||
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_at("initial_layer_travel_speed", get_extruder_index(this->config, filament()->id()))
|
||||
: this->config.travel_speed.get_at(get_extruder_index(this->config, filament()->id()));
|
||||
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
|
||||
std::ostringstream oss;
|
||||
const double z_start = m_pos(2); // starting Z height
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Determine number of segments based on Resolution
|
||||
// --------------------------------------------------------------------
|
||||
const double ref_resolution = 0.01; // reference resolution in mm
|
||||
const double ref_segments = 8.0; // reference number of segments at reference resolution
|
||||
|
||||
// number of linear segments to use for approximating the arc, clamp between 4 and 16
|
||||
const int segments = std::clamp(int(std::round(ref_segments * (ref_resolution / m_resolution))), 4, 16);
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
const double px = m_pos(0) - m_x_offset; // take plate offset into consideration
|
||||
const double py = m_pos(1) - m_y_offset; // take plate offset into consideration
|
||||
const double cx = px + ij_offset(0); // center x
|
||||
const double cy = py + ij_offset(1); // center y
|
||||
const double radius = ij_offset.norm(); // radius
|
||||
|
||||
// Number of linear segments approximating the circle, chosen so that a chord never deviates
|
||||
// from the true arc by more than the slicing resolution. A resolution of 0 means "no
|
||||
// simplification", which has no finite segment count, so it takes the upper bound.
|
||||
constexpr size_t min_segments = 8; // keep a small spiral visibly round
|
||||
constexpr size_t max_segments = 128; // bound the emitted G-code
|
||||
const int segments = int(m_resolution > 0. ?
|
||||
std::clamp(Geometry::ArcWelder::arc_discretization_steps(radius, 2. * M_PI, m_resolution), min_segments, max_segments) :
|
||||
max_segments);
|
||||
|
||||
const double a0 = std::atan2(py - cy, px - cx); // start angle
|
||||
const double delta = 2.0 * M_PI; // CCW full circle
|
||||
|
||||
if (full_gcode_comment)
|
||||
oss << ";" << comment << "\n";
|
||||
auto emit_point = [&output](const Vec3d &point) {
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(point);
|
||||
output += w.string();
|
||||
};
|
||||
|
||||
oss << "G1 F" << (speed * 60.0) << "\n"; // set feedrate
|
||||
output.reserve(size_t(segments) * 40); // ~40 characters per emitted G1 line
|
||||
|
||||
GCodeG1Formatter w; // set feedrate
|
||||
w.emit_f(speed * 60.0);
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
output += w.string();
|
||||
|
||||
// approximate the arc with small linear segments (without the last point which is added later to ensure exactness)
|
||||
for (int i = 1; i < segments; ++i) {
|
||||
double t = double(i) / segments; // parametric position along arc
|
||||
double a = a0 + delta * t; // CCW arc param
|
||||
double x = cx + radius * std::cos(a); // point on circle
|
||||
double y = cy + radius * std::sin(a); // point on circle
|
||||
double zz = z_start + (z - z_start) * t; // interpolated Z height
|
||||
|
||||
oss << "G1 X" << x << " Y" << y << " Z" << zz << "\n";
|
||||
const double t = double(i) / segments; // parametric position along arc
|
||||
const double a = a0 + 2. * M_PI * t; // CCW arc param, full circle
|
||||
emit_point(Vec3d(cx + radius * std::cos(a), // point on circle
|
||||
cy + radius * std::sin(a),
|
||||
z_start + (z - z_start) * t)); // interpolated Z height
|
||||
}
|
||||
|
||||
oss << "G1 X" << px << " Y" << py << " Z" << z << "\n"; // final point to ensure exactness
|
||||
output = oss.str();
|
||||
emit_point(Vec3d(px, py, z)); // final point to ensure exactness
|
||||
} else { // Orca: if arc fitting is enabled emit a G2/G3 command for the spiral lift
|
||||
output = std::string("G17") + (full_gcode_comment ? " ; XY plane for arc\n" : "\n");
|
||||
|
||||
@@ -1101,7 +1218,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;
|
||||
|
||||
@@ -1117,7 +1234,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");
|
||||
@@ -1250,8 +1369,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)
|
||||
@@ -1261,6 +1381,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,6 +144,8 @@ public:
|
||||
bool m_single_extruder_multi_material;
|
||||
std::vector<Extruder*> m_curr_filament_extruder;
|
||||
int m_curr_extruder_id;
|
||||
// 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;
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -157,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;
|
||||
|
||||
@@ -94,7 +94,7 @@ public:
|
||||
|
||||
void* volume{nullptr};
|
||||
std::vector<int>* plane_indices{nullptr};
|
||||
Transform3d world_tran;
|
||||
Transform3d world_tran = Transform3d::Identity();
|
||||
std::shared_ptr<std::vector<SurfaceFeature>> world_plane_features{nullptr};
|
||||
std::shared_ptr<SurfaceFeature> origin_surface_feature{nullptr};
|
||||
|
||||
|
||||
+17
-10
@@ -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();
|
||||
|
||||
|
||||
@@ -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,8 +1359,7 @@ 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
+48
-87
@@ -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
|
||||
@@ -40,100 +48,50 @@ struct OrientMesh {
|
||||
|
||||
};
|
||||
|
||||
// params for minimizing support area
|
||||
struct OrientParamsArea {
|
||||
float TAR_A = 0.015f;
|
||||
float TAR_B = 0.177f;
|
||||
float RELATIVE_F = 20;
|
||||
float CONTOUR_F = 0.5f;
|
||||
float BOTTOM_F = 2.5f;
|
||||
float BOTTOM_HULL_F = 0.1f;
|
||||
float TAR_C = 0.1f;
|
||||
float TAR_D = 1;
|
||||
float TAR_E = 0.0115f;
|
||||
float FIRST_LAY_H = 0.2f;//0.0475;
|
||||
float VECTOR_TOL = -0.00083f;
|
||||
float NEGL_FACE_SIZE = 0.01f;
|
||||
float ASCENT = -0.5f;
|
||||
float PLAFOND_ADV = 0.0599f;
|
||||
float CONTOUR_AMOUNT = 0.0182427f;
|
||||
float OV_H = 2.574f;
|
||||
float height_offset = 2.3728f;
|
||||
float height_log = 0.041375f;
|
||||
float height_log_k = 1.9325457f;
|
||||
float LAF_MAX = 0.999f; // cos(1.4\degree) for low angle face 0.9997f
|
||||
float LAF_MIN = 0.97f; // cos(14\degree) 0.9703f
|
||||
float TAR_LAF = 0.001f; //0.01f
|
||||
float TAR_PROJ_AREA = 0.1f;
|
||||
float BOTTOM_MIN = 0.1f; // min bottom area. If lower than it the object may be unstable
|
||||
float BOTTOM_MAX = 2000; // max bottom area. If get to it the object is stable enough (further increase bottom area won't do more help)
|
||||
float height_to_bottom_hull_ratio_MIN = 1;
|
||||
float BOTTOM_HULL_MAX = 2000;// max bottom hull area
|
||||
float APPERANCE_FACE_SUPP=3; // penalty of generating supports on appearance face
|
||||
|
||||
float overhang_angle = 60.f;
|
||||
bool use_low_angle_face = true;
|
||||
bool min_volume = false;
|
||||
Eigen::Vector3f fun_dir;
|
||||
|
||||
/// Allow parallel execution.
|
||||
bool parallel = true;
|
||||
|
||||
/// Progress indicator callback called when an object gets packed.
|
||||
/// The unsigned argument is the number of items remaining to pack.
|
||||
std::function<void(unsigned, std::string)> progressind = {};
|
||||
|
||||
/// A predicate returning true if abort is needed.
|
||||
std::function<bool(void)> stopcondition = {};
|
||||
|
||||
OrientParamsArea() = default;
|
||||
};
|
||||
|
||||
struct OrientParams {
|
||||
float TAR_A = 0.01f;//0.128f;
|
||||
float TAR_B = 0.177f;
|
||||
float RELATIVE_F= 6.610621027964314f;
|
||||
float CONTOUR_F = 0.23228623269775997f;
|
||||
float BOTTOM_F = 1.167152017941474f;
|
||||
float BOTTOM_HULL_F = 0.1f;
|
||||
float TAR_C = 0.24308070476924726f;
|
||||
float TAR_D = 0.6284515508160871f;
|
||||
float TAR_E = 0;//0.032157292647062234;
|
||||
float FIRST_LAY_H = 0.2f;//0.029;
|
||||
float VECTOR_TOL = -0.0011163303070972383f;
|
||||
float NEGL_FACE_SIZE = 0.1f;
|
||||
float ASCENT= -0.5f;
|
||||
float PLAFOND_ADV = 0.04079208948120519f;
|
||||
float CONTOUR_AMOUNT = 0.0101472219892684f;
|
||||
float OV_H = 1.0370178217794535f;
|
||||
float height_offset = 2.7417608343142073f;
|
||||
float height_log = 0.06442030687034085f;
|
||||
float height_log_k = 0.3933594673063997f;
|
||||
float LAF_MAX = 0.999f; // cos(1.4\degree) for low angle face //0.9997f;
|
||||
float LAF_MIN= 0.9703f; // cos(14\degree) 0.9703f;
|
||||
float TAR_LAF = 0.01f; //0.1f
|
||||
float TAR_PROJ_AREA = 0.1f;
|
||||
float BOTTOM_MIN = 0.1f; // min bottom area. If lower than it the objects may be unstable
|
||||
float BOTTOM_MAX = 2000; //400
|
||||
float height_to_bottom_hull_ratio_MIN = 1;
|
||||
float BOTTOM_HULL_MAX = 2000;// max bottom hull area to clip //600
|
||||
float APPERANCE_FACE_SUPP=3; // penalty of generating supports on appearance face
|
||||
|
||||
float overhang_angle = 60.f;
|
||||
bool use_low_angle_face = true;
|
||||
bool min_volume = false;
|
||||
Eigen::Vector3f fun_dir;
|
||||
float TAR_A { 0.01f }; // 0.128f;
|
||||
float TAR_B { 0.177f };
|
||||
float RELATIVE_F { 6.610621027964314f };
|
||||
float CONTOUR_F { 0.23228623269775997f };
|
||||
float BOTTOM_F { 1.167152017941474f };
|
||||
float BOTTOM_HULL_F { 0.1f };
|
||||
float TAR_C { 0.24308070476924726f };
|
||||
float TAR_D { 0.6284515508160871f };
|
||||
float TAR_E { 0}; // 0.032157292647062234;
|
||||
float FIRST_LAY_H { 0.2f}; // 0.029;
|
||||
float VECTOR_TOL { -0.0011163303070972383f };
|
||||
float NEGL_FACE_SIZE { 0.1f };
|
||||
float ASCENT { -0.5f };
|
||||
float PLAFOND_ADV { 0.04079208948120519f };
|
||||
float CONTOUR_AMOUNT { 0.0101472219892684f };
|
||||
float OV_H { 1.0370178217794535f };
|
||||
float height_offset { 2.7417608343142073f };
|
||||
float height_log { 0.06442030687034085f };
|
||||
float height_log_k { 0.3933594673063997f };
|
||||
float LAF_MAX { 0.999f }; // cos(1.4\degree) for low angle face //0.9997f;
|
||||
float LAF_MIN { 0.9703f }; // cos(14\degree) 0.9703f;
|
||||
float TAR_LAF { 0.01f }; // 0.1f
|
||||
float TAR_PROJ_AREA { 0.1f };
|
||||
float BOTTOM_MIN { 0.1f }; // min bottom area. If lower than it the objects may be unstable
|
||||
float BOTTOM_MAX { 2000 }; // 400
|
||||
float height_to_bottom_hull_ratio_MIN { 1 };
|
||||
float BOTTOM_HULL_MAX { 2000 }; // max bottom hull area to clip //600
|
||||
float APPERANCE_FACE_SUPP { 3 }; // penalty of generating supports on appearance face
|
||||
|
||||
float overhang_angle { 60.f };
|
||||
bool use_low_angle_face { true };
|
||||
bool min_volume { false };
|
||||
Eigen::Vector3f fun_dir {};
|
||||
|
||||
/// Allow parallel execution.
|
||||
bool parallel = false;
|
||||
bool parallel { false };
|
||||
|
||||
/// Progress indicator callback called when an object gets packed.
|
||||
/// The unsigned argument is the number of items remaining to pack.
|
||||
std::function<void(unsigned, std::string)> progressind = {};
|
||||
std::function<void(unsigned, std::string)> progressind {};
|
||||
|
||||
/// A predicate returning true if abort is needed.
|
||||
std::function<bool(void)> stopcondition = {};
|
||||
std::function<bool(void)> stopcondition {};
|
||||
|
||||
OrientParams() = default;
|
||||
};
|
||||
@@ -154,6 +112,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
|
||||
|
||||
@@ -360,6 +360,14 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path& subject, con
|
||||
return clipped_paths;
|
||||
}
|
||||
|
||||
static double clipper_z_path_length(const ClipperLib_Z::Path &path)
|
||||
{
|
||||
double len = 0.;
|
||||
for (size_t i = 1; i < path.size(); ++ i)
|
||||
len += (Vec2d(double(path[i].x()), double(path[i].y())) - Vec2d(double(path[i - 1].x()), double(path[i - 1].y()))).norm();
|
||||
return len;
|
||||
}
|
||||
|
||||
struct PerimeterGeneratorArachneExtrusion
|
||||
{
|
||||
Arachne::ExtrusionLine* extrusion = nullptr;
|
||||
@@ -571,6 +579,158 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||
return extrusion_coll;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top acts on top surfaces, so without a top shell there is nothing for it to act on: zero top
|
||||
// shell layers retype the top surfaces as internal, see LayerRegion::prepare_fill_surfaces(). A 0% top surface
|
||||
// density does leave a top surface - just an unfilled one - so it does not disable the feature.
|
||||
// ConfigManipulation::toggle_print_fff_options() hides the option under the same condition, so a profile that left
|
||||
// it enabled does not act behind a hidden checkbox.
|
||||
static bool has_top_shell_layers(const PrintRegionConfig &config)
|
||||
{
|
||||
return config.top_shell_layers.value > 0;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_first_layer thins the first layer to a single wall, the bottom counterpart of the above and
|
||||
// gated the same way: zero bottom shell layers retype the bottom surfaces as internal, so that wall would ring
|
||||
// sparse infill on the bed. The bottom surface density plays no part - an unfilled bottom surface is still a bottom
|
||||
// surface, exactly as for the top - and it cannot reach zero anyway, being capped at a 10% minimum.
|
||||
static bool has_bottom_shell_layers(const PrintRegionConfig &config)
|
||||
{
|
||||
return config.bottom_shell_layers.value > 0;
|
||||
}
|
||||
|
||||
// ORCA: the inner walls are only given up when a top fill takes their space, and it has to actually reach it -
|
||||
// a 0% top surface density leaves no fill at all, and without top_surface_expansion the fill never grows over
|
||||
// them. Either way the original generation is kept (re-onion the not-top region), which is what users of
|
||||
// only_one_wall_top alone have always got.
|
||||
static bool top_fill_replaces_inner_walls(const PrintRegionConfig &config)
|
||||
{
|
||||
return has_top_shell_layers(config) && config.top_surface_density.value > 0 && config.top_surface_expansion.value > 0;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top - cheap per-vertex classification of a wall against the top surface. Only Partial
|
||||
// needs the geometry clipped or measured; a segment crossing the top with no vertex inside is rare enough to ignore.
|
||||
enum class TopOverlap { None, Partial, Full };
|
||||
|
||||
static bool point_over_top(const Point &p, const ExPolygons &top_region, const BoundingBox &top_region_bbox)
|
||||
{
|
||||
if (! top_region_bbox.contains(p))
|
||||
return false;
|
||||
for (const ExPolygon &ex : top_region)
|
||||
if (ex.contains(p, false))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static TopOverlap classify_over_top(const Points &pts, const ExPolygons &top_region, const BoundingBox &top_region_bbox)
|
||||
{
|
||||
size_t inside = 0;
|
||||
for (const Point &p : pts)
|
||||
if (point_over_top(p, top_region, top_region_bbox))
|
||||
++ inside;
|
||||
return inside == 0 ? TopOverlap::None : inside == pts.size() ? TopOverlap::Full : TopOverlap::Partial;
|
||||
}
|
||||
|
||||
static TopOverlap classify_over_top(const Arachne::ExtrusionLine &el, const ExPolygons &top_region, const BoundingBox &top_region_bbox)
|
||||
{
|
||||
size_t inside = 0;
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions)
|
||||
if (point_over_top(j.p, top_region, top_region_bbox))
|
||||
++ inside;
|
||||
return inside == 0 ? TopOverlap::None : inside == el.junctions.size() ? TopOverlap::Full : TopOverlap::Partial;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top for Arachne - cut out of the already generated inner walls the parts running over the top
|
||||
// surface, so geometry that continues upward keeps its walls. A wall too short over the top to be worth slitting open
|
||||
// is left whole, its footprint reported in kept_over_top for the caller to withhold from the top fill.
|
||||
static void clip_inner_walls_over_top(std::vector<Arachne::VariableWidthLines> &inner_perimeters, const ExPolygons &top_region, coord_t perimeter_width, Polygons &kept_over_top)
|
||||
{
|
||||
const BoundingBox top_region_bbox = get_extents(top_region).inflated(SCALED_EPSILON);
|
||||
auto covered_by = [](const Arachne::ExtrusionLine &el) {
|
||||
Polyline centerline;
|
||||
centerline.points.reserve(el.junctions.size());
|
||||
coord_t width = 0;
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions) {
|
||||
centerline.points.emplace_back(j.p);
|
||||
width = std::max(width, j.w);
|
||||
}
|
||||
return offset(centerline, float(width) / 2.f);
|
||||
};
|
||||
// Pull the cut back by half a wall width: the clip severs the centerline, but the bead's rounded end
|
||||
// extends half a width past its endpoint and would otherwise overlap the top fill.
|
||||
ClipperLib_Z::Paths top_paths_z;
|
||||
for (const Polygon &poly : to_polygons(offset_ex(top_region, float(perimeter_width) / 2.f))) {
|
||||
top_paths_z.emplace_back();
|
||||
ClipperLib_Z::Path &out = top_paths_z.back();
|
||||
out.reserve(poly.points.size());
|
||||
for (const Point &pt : poly.points)
|
||||
out.emplace_back(pt.x(), pt.y(), 0);
|
||||
}
|
||||
for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters) {
|
||||
Arachne::VariableWidthLines kept;
|
||||
kept.reserve(inner_perimeter.size());
|
||||
for (Arachne::ExtrusionLine &el : inner_perimeter) {
|
||||
if (el.empty())
|
||||
continue;
|
||||
const TopOverlap overlap = classify_over_top(el, top_region, top_region_bbox);
|
||||
if (overlap == TopOverlap::None) {
|
||||
kept.emplace_back(std::move(el));
|
||||
continue;
|
||||
}
|
||||
if (overlap == TopOverlap::Full)
|
||||
continue; // the clip below would return nothing anyway
|
||||
ClipperLib_Z::Path subject;
|
||||
subject.reserve(el.size());
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions)
|
||||
subject.emplace_back(j.p.x(), j.p.y(), j.w);
|
||||
ClipperLib_Z::Paths pieces = clip_extrusion(subject, top_paths_z, ClipperLib_Z::ctDifference);
|
||||
|
||||
// Clipper treats the subject as an open polyline, so it also cuts a closed loop at its (arbitrary)
|
||||
// start vertex and may reverse pieces. Stitch pieces sharing an endpoint back together.
|
||||
auto same_pt = [](const ClipperLib_Z::IntPoint &p, const ClipperLib_Z::IntPoint &q) {
|
||||
return std::abs(p.x() - q.x()) <= SCALED_EPSILON && std::abs(p.y() - q.y()) <= SCALED_EPSILON;
|
||||
};
|
||||
for (size_t i = 0; i < pieces.size(); ++ i) {
|
||||
for (size_t j = i + 1; j < pieces.size();) {
|
||||
ClipperLib_Z::Path &a = pieces[i];
|
||||
ClipperLib_Z::Path &b = pieces[j];
|
||||
if (same_pt(a.front(), b.front()) || same_pt(a.front(), b.back()))
|
||||
std::reverse(a.begin(), a.end());
|
||||
if (same_pt(a.back(), b.back()))
|
||||
std::reverse(b.begin(), b.end());
|
||||
if (same_pt(a.back(), b.front())) {
|
||||
a.insert(a.end(), b.begin() + 1, b.end());
|
||||
pieces.erase(pieces.begin() + j);
|
||||
j = i + 1; // the merged path has new endpoints, restart the scan
|
||||
} else
|
||||
++ j;
|
||||
}
|
||||
}
|
||||
|
||||
// If the clip removed next to nothing, keep the loop untouched instead of slitting it open. The
|
||||
// half-width pull-back above already costs about one width per crossing, hence two widths.
|
||||
double kept_length = 0.;
|
||||
for (const ClipperLib_Z::Path &path : pieces)
|
||||
kept_length += clipper_z_path_length(path);
|
||||
if (clipper_z_path_length(subject) - kept_length < 2. * double(perimeter_width)) {
|
||||
append(kept_over_top, covered_by(el));
|
||||
kept.emplace_back(std::move(el));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const ClipperLib_Z::Path &path : pieces) {
|
||||
Arachne::ExtrusionLine clipped(el.inset_idx, el.is_odd);
|
||||
clipped.junctions.reserve(path.size());
|
||||
for (const ClipperLib_Z::IntPoint &pt : path)
|
||||
clipped.junctions.emplace_back(Point(pt.x(), pt.y()), coord_t(pt.z()), el.inset_idx);
|
||||
// Discard tiny leftovers that would print as zits.
|
||||
if (clipped.size() >= 2 && clipped.getLength() >= perimeter_width)
|
||||
kept.emplace_back(std::move(clipped));
|
||||
}
|
||||
}
|
||||
inner_perimeter = std::move(kept);
|
||||
}
|
||||
}
|
||||
|
||||
void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExPolygons &top_fills,
|
||||
ExPolygons &non_top_polygons, ExPolygons &fill_clip) const {
|
||||
// other perimeters
|
||||
@@ -636,6 +796,7 @@ 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);
|
||||
|
||||
// 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);
|
||||
@@ -1219,6 +1380,11 @@ void PerimeterGenerator::process_classic()
|
||||
for (const Surface &surface : all_surfaces)
|
||||
surface_exp.push_back(surface.expolygon);
|
||||
std::vector<size_t> surface_order = chain_expolygons(surface_exp);
|
||||
// ORCA: neither one-wall option has a surface to act on without the shell behind it, see
|
||||
// has_top_shell_layers() / has_bottom_shell_layers(). Gated here so every use below - including the
|
||||
// topmost and first layers - sees the same answer.
|
||||
const bool only_one_wall_top = this->config->only_one_wall_top && has_top_shell_layers(*this->config);
|
||||
const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config);
|
||||
for (size_t order_idx = 0; order_idx < surface_order.size(); order_idx++) {
|
||||
const Surface &surface = all_surfaces[surface_order[order_idx]];
|
||||
// detect how many perimeters must be generated for this island
|
||||
@@ -1226,16 +1392,23 @@ void PerimeterGenerator::process_classic()
|
||||
int sparse_infill_density = this->config->sparse_infill_density.value;
|
||||
if (this->config->alternate_extra_wall && this->layer_id % 2 == 1 && !m_spiral_vase && sparse_infill_density > 0) // add alternating extra wall
|
||||
loop_number++;
|
||||
if (this->layer_id == object_config->raft_layers && this->config->only_one_wall_first_layer)
|
||||
if (this->layer_id == object_config->raft_layers && only_one_wall_first_layer)
|
||||
loop_number = 0;
|
||||
// Set the topmost layer to be one wall
|
||||
if (loop_number > 0 && config->only_one_wall_top && this->upper_slices == nullptr)
|
||||
if (loop_number > 0 && only_one_wall_top && this->upper_slices == nullptr)
|
||||
loop_number = 0;
|
||||
|
||||
ExPolygons last = union_ex(surface.expolygon.simplify_p(surface_simplify_resolution));
|
||||
ExPolygons gaps;
|
||||
ExPolygons top_fills;
|
||||
ExPolygons fill_clip;
|
||||
// ORCA: only_one_wall_top, all empty unless this island has a top surface on this layer. See the
|
||||
// post-onion reduction below: the region to keep clear of inner walls, the space freed by the dropped
|
||||
// walls (goes to infill, not left as a void) and the space held by the kept ones (withheld from the fill).
|
||||
ExPolygons one_wall_top_region;
|
||||
ExPolygons one_wall_top_reclaimed;
|
||||
Polygons one_wall_top_kept_bands;
|
||||
bool apply_one_wall_top = false;
|
||||
if (loop_number >= 0) {
|
||||
// In case no perimeters are to be generated, loop_number will equal to -1.
|
||||
std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
|
||||
@@ -1374,8 +1547,19 @@ void PerimeterGenerator::process_classic()
|
||||
|
||||
//BBS: refer to superslicer
|
||||
//store surface for top infill if only_one_wall_top
|
||||
if (i == 0 && i!=loop_number && config->only_one_wall_top && !surface.is_bridge() && this->upper_slices != NULL) {
|
||||
this->split_top_surfaces(last, top_fills, last, fill_clip);
|
||||
if (i == 0 && i!=loop_number && only_one_wall_top && !surface.is_bridge() && this->upper_slices != NULL) {
|
||||
if (top_fill_replaces_inner_walls(*this->config)) {
|
||||
// ORCA: take the top fill and the keep-out region but leave `last` as the real geometry,
|
||||
// so the onion follows it and the walls over the top are reduced in one step below.
|
||||
ExPolygons non_top_polygons;
|
||||
this->split_top_surfaces(last, top_fills, non_top_polygons, fill_clip);
|
||||
apply_one_wall_top = !top_fills.empty();
|
||||
if (apply_one_wall_top)
|
||||
one_wall_top_region = diff_ex(last, non_top_polygons);
|
||||
} else {
|
||||
// Onion the not-top region only, so the remaining walls stop at the top boundary.
|
||||
this->split_top_surfaces(last, top_fills, last, fill_clip);
|
||||
}
|
||||
}
|
||||
|
||||
if (i == loop_number && (! has_gap_fill || this->config->sparse_infill_density.value == 0)) {
|
||||
@@ -1385,6 +1569,46 @@ void PerimeterGenerator::process_classic()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top reduction - drop the inner walls (depth > 0) running over the top surface and
|
||||
// take that space back from the gaps, leaving the top with the outer wall and the top infill. Classic
|
||||
// perimeters are closed loops, so a wall can only be kept or dropped whole; one that merely grazes the
|
||||
// top (same tolerance as the Arachne clip) is kept and withheld from the top fill instead.
|
||||
if (apply_one_wall_top) {
|
||||
const BoundingBox top_region_bbox = get_extents(one_wall_top_region).inflated(SCALED_EPSILON);
|
||||
const double grazing_tolerance = 2. * double(perimeter_width);
|
||||
// The band a wall covers, taken around its centerline so the orientation of holes does not matter.
|
||||
auto wall_band = [perimeter_spacing](const Polygon &poly) {
|
||||
Polygon centerline = poly;
|
||||
centerline.make_counter_clockwise();
|
||||
return diff(offset(centerline, float(perimeter_spacing) / 2.f),
|
||||
offset(centerline, -float(perimeter_spacing) / 2.f));
|
||||
};
|
||||
Polygons dropped_wall_bands;
|
||||
auto reduce_over_top = [&](PerimeterGeneratorLoops &loops) {
|
||||
loops.erase(std::remove_if(loops.begin(), loops.end(), [&](const PerimeterGeneratorLoop &loop) {
|
||||
const TopOverlap overlap = classify_over_top(loop.polygon.points, one_wall_top_region, top_region_bbox);
|
||||
if (overlap == TopOverlap::None)
|
||||
return false;
|
||||
// Only a wall straddling the boundary is worth measuring; a wall wholly over the top goes.
|
||||
if (overlap == TopOverlap::Partial &&
|
||||
total_length(intersection_pl(Polylines{ loop.polygon.split_at_first_point() }, one_wall_top_region)) < grazing_tolerance) {
|
||||
append(one_wall_top_kept_bands, wall_band(loop.polygon));
|
||||
return false;
|
||||
}
|
||||
append(dropped_wall_bands, wall_band(loop.polygon));
|
||||
return true;
|
||||
}), loops.end());
|
||||
};
|
||||
for (int d = 1; d <= loop_number; ++ d) {
|
||||
reduce_over_top(contours[d]);
|
||||
reduce_over_top(holes[d]);
|
||||
}
|
||||
if (! gaps.empty())
|
||||
gaps = diff_ex(gaps, one_wall_top_region);
|
||||
if (! dropped_wall_bands.empty())
|
||||
one_wall_top_reclaimed = diff_ex(dropped_wall_bands, one_wall_top_region);
|
||||
}
|
||||
|
||||
// nest loops: holes first
|
||||
for (int d = 0; d <= loop_number; ++ d) {
|
||||
PerimeterGeneratorLoops &holes_d = holes[d];
|
||||
@@ -1619,7 +1843,10 @@ void PerimeterGenerator::process_classic()
|
||||
and use zigzag). */
|
||||
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
|
||||
// therefore it may cover the area, but no the volume.
|
||||
last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f));
|
||||
Polygons gap_fill_covered = gap_fill.polygons_covered_by_width(10.f);
|
||||
last = diff_ex(last, gap_fill_covered);
|
||||
if (! one_wall_top_reclaimed.empty())
|
||||
one_wall_top_reclaimed = diff_ex(one_wall_top_reclaimed, gap_fill_covered);
|
||||
this->gap_fill->append(std::move(gap_fill.entities));
|
||||
|
||||
}
|
||||
@@ -1664,9 +1891,15 @@ void PerimeterGenerator::process_classic()
|
||||
// append infill areas to fill_surfaces
|
||||
//if any top_fills, grow them by ext_perimeter_spacing/2 to have the real un-anchored fill
|
||||
ExPolygons top_infill_exp = intersection_ex(fill_clip, offset_ex(top_fills, double(ext_perimeter_spacing / 2)));
|
||||
// ORCA: only_one_wall_top - route the top fill around the walls kept despite grazing the top.
|
||||
if (!one_wall_top_kept_bands.empty())
|
||||
top_infill_exp = diff_ex(top_infill_exp, one_wall_top_kept_bands);
|
||||
if (!top_fills.empty()) {
|
||||
infill_exp = union_ex(infill_exp, offset_ex(top_infill_exp, double(top_infill_peri_overlap)));
|
||||
}
|
||||
// ORCA: only_one_wall_top - what the top fill does not cover of the dropped walls goes to infill.
|
||||
if (!one_wall_top_reclaimed.empty())
|
||||
infill_exp = union_ex(infill_exp, one_wall_top_reclaimed);
|
||||
this->fill_surfaces->append(infill_exp, stInternal);
|
||||
|
||||
apply_extra_perimeters(infill_exp);
|
||||
@@ -1685,6 +1918,8 @@ void PerimeterGenerator::process_classic()
|
||||
double(-inset - infill_peri_overlap));
|
||||
if (!top_fills.empty())
|
||||
polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp);
|
||||
if (!one_wall_top_reclaimed.empty())
|
||||
polyWithoutOverlap = union_ex(polyWithoutOverlap, one_wall_top_reclaimed);
|
||||
this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end());
|
||||
}
|
||||
|
||||
@@ -1742,7 +1977,7 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
ExPolygons unsupported = diff_ex(last, *this->lower_slices, ApplySafetyOffset::Yes);
|
||||
if (!unsupported.empty()) {
|
||||
//remove small overhangs
|
||||
ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing), double(perimeter_spacing));
|
||||
ExPolygons unsupported_filtered = opening_ex(unsupported, perimeter_spacing);
|
||||
|
||||
if (!unsupported_filtered.empty()) {
|
||||
//to_draw.insert(to_draw.end(), last.begin(), last.end());
|
||||
@@ -1855,35 +2090,40 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
//TODO: add other polys as holes inside this one (-margin)
|
||||
} else { // if(this->config->counterbore_hole_bridging.value == chbBridges)
|
||||
// Orca: Partial counterbore bridging is mask-based. Preserve the supported
|
||||
// remainder (`last`) and use simplified BridgeDetector coverage to derive the
|
||||
// remainder and use simplified BridgeDetector coverage to derive the
|
||||
// bridgeable counterbore span. The span is grown from supported material,
|
||||
// shrunk back, stripped from `last`, and expanded back. It is then prevented
|
||||
// from intruding deeper into `last` than the explicit anchor overlap.
|
||||
// Finally, add the allowed anchor band from `last` then remove the
|
||||
// shrunk back, stripped from the remaining normal surface, and expanded back.
|
||||
// It is then prevented from intruding deeper into it than the explicit anchor overlap.
|
||||
// Finally, add the allowed anchor band from it then remove the
|
||||
// narrow hole-side wall contact, which must remain unbridgeable.
|
||||
|
||||
last = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
|
||||
const ExPolygons remaining = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons bridgeable_filtered;
|
||||
|
||||
for (ExPolygon& poly : bridgeable) {
|
||||
poly.simplify(perimeter_spacing, &bridgeable_filtered);
|
||||
}
|
||||
bridgeable_filtered = opening_ex(bridgeable_filtered, ext_perimeter_width);
|
||||
|
||||
// Get rid of coarseness of the resulted bridgeable area by using the original supported area as reference.
|
||||
// This is to avoid keeping tiny bridgeable areas that are far from the supported area, or protrude into it.
|
||||
bridgeable_filtered = union_ex(offset_ex(last, perimeter_spacing), bridgeable_filtered);
|
||||
// This is to avoid keeping tiny bridgeable areas that are far from the supported area, or protrude into it.
|
||||
bridgeable_filtered = union_ex(offset_ex(remaining, perimeter_spacing), bridgeable_filtered);
|
||||
bridgeable_filtered = offset_ex(bridgeable_filtered, -perimeter_spacing);
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, last, ApplySafetyOffset::Yes);
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, remaining, ApplySafetyOffset::Yes);
|
||||
bridgeable_filtered = opening_ex(bridgeable_filtered, perimeter_spacing); // filter noise from the diff_ex
|
||||
bridgeable_filtered = offset_ex(bridgeable_filtered, perimeter_spacing); // restore the size to the original bridgeable area
|
||||
// Safety measure: Keep the bridge mask from intruding deeper into the
|
||||
// supported anchor region (`last`) than the explicit anchor overlap.
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, offset_ex(last, -bridge_anchor_offset));
|
||||
// supported anchor region than the explicit anchor overlap.
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, offset_ex(remaining, -bridge_anchor_offset));
|
||||
|
||||
ExPolygons bridge_anchor_areas = intersection_ex(last, offset_ex(unsupported_filtered, bridge_anchor_offset));
|
||||
ExPolygons bridge_anchor_areas = intersection_ex(remaining, offset_ex(unsupported_filtered, bridge_anchor_offset));
|
||||
unsupported_filtered = union_ex(bridgeable_filtered, bridge_anchor_areas); // add bridge anchor
|
||||
unsupported_filtered = opening_ex(unsupported_filtered, bridge_anchor_offset); // remove anchor area from hole-side walls, it must remain unbridgeable
|
||||
|
||||
// update 'last' only if we have a valid bridgeable area, otherwise we will lose the original unsupported area
|
||||
if (!unsupported_filtered.empty())
|
||||
last = remaining;
|
||||
// TODO: Fix the case with thin outer walls around the bridge (1~2 walls) where classic wall
|
||||
// might generate two walls in a tiny space or non at all if "Detect thin walls" is not activated
|
||||
}
|
||||
@@ -2123,6 +2363,11 @@ void PerimeterGenerator::process_arachne()
|
||||
process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width);
|
||||
// BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
|
||||
double surface_simplify_resolution = (print_config->enable_arc_fitting && !this->has_fuzzy_skin) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
|
||||
// ORCA: neither one-wall option has a surface to act on without the shell behind it, see
|
||||
// has_top_shell_layers() / has_bottom_shell_layers(). Gated here so every use below - including the
|
||||
// topmost and first layers - sees the same answer.
|
||||
const bool only_one_wall_top = this->config->only_one_wall_top && has_top_shell_layers(*this->config);
|
||||
const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config);
|
||||
// we need to process each island separately because we might have different
|
||||
// extra perimeters for each one
|
||||
for (const Surface& surface : all_surfaces) {
|
||||
@@ -2135,12 +2380,12 @@ void PerimeterGenerator::process_arachne()
|
||||
|
||||
// Set the bottommost layer to be one wall
|
||||
const bool is_bottom_layer = (this->layer_id == object_config->raft_layers) ? true : false;
|
||||
if (is_bottom_layer && this->config->only_one_wall_first_layer)
|
||||
if (is_bottom_layer && only_one_wall_first_layer)
|
||||
loop_number = 0;
|
||||
|
||||
// Orca: set the topmost layer to be one wall according to the config
|
||||
const bool is_topmost_layer = (this->upper_slices == nullptr) ? true : false;
|
||||
if (is_topmost_layer && loop_number > 0 && config->only_one_wall_top)
|
||||
if (is_topmost_layer && loop_number > 0 && only_one_wall_top)
|
||||
loop_number = 0;
|
||||
|
||||
auto apply_precise_outer_wall = config->precise_outer_wall && config->wall_sequence == WallSequence::InnerOuter;
|
||||
@@ -2160,10 +2405,10 @@ void PerimeterGenerator::process_arachne()
|
||||
//PS: One wall top surface for Arachne
|
||||
ExPolygons top_expolygons;
|
||||
// Calculate how many inner loops remain when TopSurfaces is selected.
|
||||
const int inner_loop_number = (config->only_one_wall_top && upper_slices != nullptr) ? loop_number - 1 : -1;
|
||||
const int inner_loop_number = (only_one_wall_top && upper_slices != nullptr) ? loop_number - 1 : -1;
|
||||
|
||||
// Set one perimeter when TopSurfaces is selected.
|
||||
if (config->only_one_wall_top && loop_number > 0)
|
||||
if (only_one_wall_top && loop_number > 0)
|
||||
loop_number = 0;
|
||||
|
||||
Arachne::WallToolPathsParams input_params_tmp = input_params;
|
||||
@@ -2214,25 +2459,33 @@ void PerimeterGenerator::process_arachne()
|
||||
// due to thin lines being generated
|
||||
top_expolygons = offset2_ex(top_expolygons, -top_surface_min_width, top_surface_min_width + float(perimeter_width * 0.85));
|
||||
|
||||
// Get the not-top ExPolygons (including bridges) from current slices and expanded real top ExPolygons (without bridges).
|
||||
const ExPolygons not_top_expolygons = diff_ex(infill_contour, top_expolygons);
|
||||
|
||||
// Get final top ExPolygons.
|
||||
// Get final top ExPolygons (bridges were excluded above, so they stay walled).
|
||||
top_expolygons = intersection_ex(top_expolygons, infill_contour);
|
||||
|
||||
const Polygons not_top_polygons = to_polygons(offset_ex(not_top_expolygons,wall_0_inset));
|
||||
Arachne::WallToolPaths inner_wall_tool_paths(not_top_polygons, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, layer_height, input_params_tmp);
|
||||
// ORCA: onion the real region (inside the outer wall) so the remaining walls follow the actual
|
||||
// geometry, then cut away the parts over the top surface. Re-onioning the non-top complement
|
||||
// instead - the fallback when there is no top fill - walls the top/non-top interface and rings
|
||||
// top-surface islands with inner walls that don't exist when the feature is disabled.
|
||||
const bool clip_walls_over_top = top_fill_replaces_inner_walls(*this->config);
|
||||
const Polygons inner_region = to_polygons(offset_ex(clip_walls_over_top ? infill_contour
|
||||
: diff_ex(infill_contour, top_expolygons),
|
||||
wall_0_inset));
|
||||
Arachne::WallToolPaths inner_wall_tool_paths(inner_region, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, layer_height, input_params_tmp);
|
||||
std::vector<Arachne::VariableWidthLines> inner_perimeters = inner_wall_tool_paths.getToolPaths();
|
||||
|
||||
// Recalculate indexes of inner perimeters before merging them.
|
||||
if (!perimeters.empty()) {
|
||||
for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters) {
|
||||
if (inner_perimeter.empty())
|
||||
continue;
|
||||
if (clip_walls_over_top) {
|
||||
Polygons kept_over_top;
|
||||
clip_inner_walls_over_top(inner_perimeters, top_expolygons, perimeter_width, kept_over_top);
|
||||
// Route the top fill around the walls kept despite grazing the top.
|
||||
if (! kept_over_top.empty())
|
||||
top_expolygons = diff_ex(top_expolygons, kept_over_top);
|
||||
}
|
||||
|
||||
// Recalculate indexes of inner perimeters before merging them: they come after the single outer wall.
|
||||
if (!perimeters.empty())
|
||||
for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters)
|
||||
for (Arachne::ExtrusionLine &el : inner_perimeter)
|
||||
++el.inset_idx;
|
||||
}
|
||||
}
|
||||
|
||||
perimeters.insert(perimeters.end(), inner_perimeters.begin(), inner_perimeters.end());
|
||||
infill_contour = union_ex(top_expolygons, inner_wall_tool_paths.getInnerContour());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1771,6 +1791,8 @@ namespace client
|
||||
// from UTF8 to UTF16 don't bail out.
|
||||
msg += boost::nowide::narrow(boost::nowide::widen(error_line));
|
||||
msg += '\n';
|
||||
// The error dialog (MsgDialog.cpp) renders this excerpt monospaced. It recognizes a source
|
||||
// line directly above a caret line of spaces and a single '^'.
|
||||
for (size_t i = 0; i < error_pos; ++ i)
|
||||
msg += ' ';
|
||||
msg += "^\n";
|
||||
@@ -2323,6 +2345,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) ]
|
||||
@@ -2404,6 +2428,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);
|
||||
|
||||
|
||||
@@ -129,13 +129,13 @@ public:
|
||||
std::vector<PathFittingData> fitting_result;
|
||||
//BBS: simplify points by arc fitting
|
||||
void simplify_by_fitting_arc(double tolerance);
|
||||
//BBS:
|
||||
void reset_to_linear_move();
|
||||
//BBS:
|
||||
Polylines equally_spaced_lines(double distance) const;
|
||||
|
||||
private:
|
||||
void append_fitting_result_after_append_points();
|
||||
void append_fitting_result_after_append_polyline(const Polyline& src);
|
||||
void reset_to_linear_move();
|
||||
bool split_fitting_result_before_index(const size_t index, Point &new_endpoint, std::vector<PathFittingData>& data) const;
|
||||
bool split_fitting_result_after_index(const size_t index, Point &new_startpoint, std::vector<PathFittingData>& data) const;
|
||||
};
|
||||
|
||||
+114
-25
@@ -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());*/
|
||||
@@ -944,12 +946,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;
|
||||
}
|
||||
}
|
||||
@@ -961,11 +962,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;
|
||||
}
|
||||
}
|
||||
@@ -1091,6 +1091,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"fill_multiline",
|
||||
"gyroid_optimized",
|
||||
"sparse_infill_pattern",
|
||||
"sparse_infill_smooth_factor",
|
||||
"lateral_lattice_angle_1",
|
||||
"lateral_lattice_angle_2",
|
||||
"infill_overhang_angle",
|
||||
@@ -1098,7 +1099,12 @@ 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",
|
||||
@@ -1114,6 +1120,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",
|
||||
@@ -1137,7 +1145,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed",
|
||||
"bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed",
|
||||
"outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height","single_loop_draft_shield", "draft_shield",
|
||||
"brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
||||
"brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "brim_ears_outer_only", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
||||
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
|
||||
"support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style",
|
||||
// BBS
|
||||
@@ -1248,6 +1256,9 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"min_feature_size",
|
||||
"min_bead_width",
|
||||
"post_process",
|
||||
"slicing_pipeline_plugin",
|
||||
"plugins",
|
||||
"print_plugin_config_overrides",
|
||||
"process_change_extrusion_role_gcode",
|
||||
"min_length_factor",
|
||||
"wall_maximum_resolution",
|
||||
@@ -1326,6 +1337,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",
|
||||
@@ -1333,6 +1345,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",
|
||||
@@ -1365,7 +1378,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",
|
||||
@@ -1381,8 +1394,22 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
|
||||
//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_retract_length_toolchange",
|
||||
"filament_retract_restart_extra_toolchange",
|
||||
"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
|
||||
@@ -1400,8 +1427,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",
|
||||
"filament_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 {
|
||||
@@ -1411,6 +1449,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
|
||||
@@ -1420,7 +1460,7 @@ static std::vector<std::string> s_Preset_machine_limits_options {
|
||||
static std::vector<std::string> s_Preset_printer_options {
|
||||
"printer_technology",
|
||||
"printable_area", "extruder_printable_area", "support_parallel_printheads", "parallel_printheads_count", "parallel_printheads_bed_exclude_areas", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
|
||||
"fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
|
||||
"gcode_skip_config_block", "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
|
||||
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
|
||||
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
|
||||
"printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
|
||||
@@ -1428,7 +1468,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",
|
||||
@@ -1437,10 +1477,17 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"use_relative_e_distances", "extruder_type", "use_firmware_retraction", "printer_notes",
|
||||
"grab_length", "support_object_skip_flush", "physical_extruder_map",
|
||||
"cooling_tube_retraction",
|
||||
"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",
|
||||
"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", "wait_for_temp_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",
|
||||
"printer_plugin_config_overrides"
|
||||
};
|
||||
|
||||
static std::vector<std::string> s_Preset_sla_print_options {
|
||||
@@ -1552,6 +1599,15 @@ const std::vector<std::string>& Preset::printer_options()
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
const char* Preset::plugin_overrides_key(Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case TYPE_PRINTER: return "printer_plugin_config_overrides";
|
||||
case TYPE_FILAMENT: return "filament_plugin_config_overrides";
|
||||
default: return "print_plugin_config_overrides";
|
||||
}
|
||||
}
|
||||
|
||||
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) :
|
||||
m_type(type),
|
||||
m_edited_preset(type, "", false),
|
||||
@@ -3468,7 +3524,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)) {
|
||||
@@ -3503,7 +3570,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);
|
||||
@@ -3773,12 +3840,14 @@ void PresetCollection::update_library_profile_excluded_from()
|
||||
}
|
||||
|
||||
// Check all presets that has the same alias as the filament presets with empty compatible_printers in Orca Filament Library.
|
||||
// A printer specific profile supersedes the generic one, no matter whether it lives in a vendor bundle or in the
|
||||
// library itself.
|
||||
for (const Preset& preset : m_presets) {
|
||||
if (preset.vendor == nullptr || preset.vendor->name == PresetBundle::ORCA_FILAMENT_LIBRARY)
|
||||
if (preset.vendor == nullptr)
|
||||
continue;
|
||||
|
||||
const auto* compatible_printers = dynamic_cast<const ConfigOptionStrings*>(preset.config.option("compatible_printers"));
|
||||
// All profiles in concrete vendor profile shouldn't have empty compatible_printers, but here we check it for safety.
|
||||
// Profiles with empty compatible_printers are the generic ones, they never supersede anything.
|
||||
if (compatible_printers == nullptr || compatible_printers->values.empty())
|
||||
continue;
|
||||
auto itr = excluded_froms.find(preset.alias);
|
||||
@@ -3834,6 +3903,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"
|
||||
@@ -167,6 +168,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) {
|
||||
@@ -422,6 +424,11 @@ public:
|
||||
// Printer machine limits, those are contained in printer_options().
|
||||
static const std::vector<std::string>& machine_limits_options();
|
||||
|
||||
// Option key holding this preset type's plugin capability overrides. Each type has its own key so
|
||||
// the values survive the merge into a single full config; print is the fallback for the types with
|
||||
// no plugin-backed options.
|
||||
static const char* plugin_overrides_key(Type type);
|
||||
|
||||
static const std::vector<std::string>& sla_printer_options();
|
||||
static const std::vector<std::string>& sla_material_options();
|
||||
static const std::vector<std::string>& sla_print_options();
|
||||
@@ -820,6 +827,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:
|
||||
|
||||
+190
-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);
|
||||
@@ -721,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;
|
||||
}
|
||||
}
|
||||
@@ -2680,6 +2726,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(","));
|
||||
@@ -2824,6 +2876,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(","));
|
||||
@@ -3000,7 +3058,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
|
||||
@@ -3010,6 +3069,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
|
||||
@@ -3037,7 +3098,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
|
||||
@@ -3047,6 +3109,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
|
||||
@@ -3087,15 +3151,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
|
||||
@@ -3357,6 +3431,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++) {
|
||||
@@ -3538,6 +3613,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(),
|
||||
@@ -3592,12 +3668,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;
|
||||
@@ -3904,10 +3982,26 @@ bool PresetBundle::support_different_extruders() const
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -3921,16 +4015,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());
|
||||
@@ -3944,8 +4074,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);
|
||||
@@ -3953,6 +4092,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.
|
||||
@@ -3982,18 +4124,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());
|
||||
@@ -4086,8 +4244,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.
|
||||
@@ -4331,6 +4489,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(),
|
||||
@@ -4819,6 +4981,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();
|
||||
}
|
||||
@@ -5577,7 +5741,7 @@ void PresetBundle::set_default_suppressed(bool default_suppressed)
|
||||
printers.set_default_suppressed(default_suppressed);
|
||||
}
|
||||
|
||||
bool PresetBundle::has_errors(bool check_duplicate_filament_subtypes, bool check_references) const
|
||||
bool PresetBundle::has_errors(bool check_duplicate_filament_subtypes) const
|
||||
{
|
||||
if (m_errors != 0 || printers.m_errors != 0 || filaments.m_errors != 0 || prints.m_errors != 0)
|
||||
return true;
|
||||
@@ -5600,7 +5764,7 @@ bool PresetBundle::has_errors(bool check_duplicate_filament_subtypes, bool check
|
||||
if (check_duplicate_filament_subtypes && this->check_duplicate_filament_subtypes())
|
||||
has_errors = true;
|
||||
|
||||
if (check_references && this->check_preset_references())
|
||||
if (this->check_preset_references())
|
||||
has_errors = true;
|
||||
|
||||
return has_errors;
|
||||
|
||||
@@ -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,10 +384,19 @@ 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() const;
|
||||
@@ -485,9 +514,8 @@ public:
|
||||
return { Preset::TYPE_PRINTER, Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL };
|
||||
}
|
||||
|
||||
// Orca: for validation only. The duplicate filament subtype and preset-reference checks are
|
||||
// opt-in for now (enabled per-vendor by the profile-check CI as vendors are cleaned up).
|
||||
bool has_errors(bool check_duplicate_filament_subtypes = false, bool check_preset_references = false) const;
|
||||
// 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.
|
||||
@@ -520,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
|
||||
|
||||
+814
-146
File diff suppressed because it is too large
Load Diff
+168
-13
@@ -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};
|
||||
|
||||
+179
-32
@@ -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,25 +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) {
|
||||
// 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, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
|
||||
//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>();
|
||||
@@ -1188,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);
|
||||
@@ -1196,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");
|
||||
@@ -1208,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;
|
||||
|
||||
@@ -1227,14 +1302,66 @@ 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]);
|
||||
// Orca: when the process variant columns cannot be matched (degenerate
|
||||
// print_extruder_id), key the override by plain extruder index like the seeding
|
||||
// above instead of poisoning the map with -1.
|
||||
int slot_index = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
m_config.filament_map_2.values[index] = slot_index >= 0 ? slot_index : filament_maps[index] - 1;
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -1282,11 +1409,22 @@ 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;
|
||||
}
|
||||
}
|
||||
else if (! print_diff.empty()) {
|
||||
// Orca: m_config can diverge from an unchanged full config (e.g. the in-slice retract
|
||||
// override recompute writing different values than the apply-time computation). The
|
||||
// invalidation above already fired for print_diff, so repair m_config here as well;
|
||||
// otherwise the divergence is never corrected and every subsequent apply of the same
|
||||
// config invalidates the result again, forever.
|
||||
m_placeholder_parser.apply_config(filament_overrides);
|
||||
m_config.apply_only(new_full_config, print_diff, true);
|
||||
m_config.apply(filament_overrides);
|
||||
}
|
||||
|
||||
ModelObjectStatusDB model_object_status_db;
|
||||
|
||||
@@ -1475,7 +1613,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()) {
|
||||
@@ -1541,10 +1679,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()) {
|
||||
@@ -1654,7 +1792,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.
|
||||
@@ -1715,7 +1860,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.
|
||||
@@ -1737,6 +1883,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)
|
||||
|
||||
+1369
-220
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
@@ -156,6 +214,8 @@ enum class PrintOrder
|
||||
{
|
||||
Default,
|
||||
AsObjectList,
|
||||
BestOfStrategies, // run all custom strategies, pick the shortest total path
|
||||
Snake, // snake-like row traversal (back-and-forth) + 2-opt
|
||||
Count,
|
||||
};
|
||||
|
||||
@@ -300,6 +360,12 @@ enum class PerimeterGeneratorType
|
||||
Arachne
|
||||
};
|
||||
|
||||
enum class ToolChangeOrderingType
|
||||
{
|
||||
Default,
|
||||
Cyclic,
|
||||
};
|
||||
|
||||
// BBS
|
||||
enum OverhangFanThreshold {
|
||||
Overhang_threshold_none = 0,
|
||||
@@ -335,6 +401,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,24 +491,49 @@ 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);
|
||||
// TODO: Orca: Support hybrid
|
||||
//if (t == nvtHybrid) continue;
|
||||
// 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;
|
||||
@@ -521,11 +619,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();
|
||||
@@ -534,6 +641,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)
|
||||
@@ -561,7 +669,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
|
||||
|
||||
@@ -613,6 +723,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,
|
||||
@@ -670,9 +797,26 @@ public:
|
||||
//BBS
|
||||
bool is_using_different_extruders();
|
||||
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);
|
||||
@@ -698,8 +842,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);
|
||||
|
||||
@@ -935,6 +1082,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, brim_width))
|
||||
((ConfigOptionFloat, brim_ears_detection_length))
|
||||
((ConfigOptionFloat, brim_ears_max_angle))
|
||||
((ConfigOptionBool, brim_ears_outer_only))
|
||||
((ConfigOptionFloat, skirt_start_angle))
|
||||
((ConfigOptionBool, bridge_no_support))
|
||||
((ConfigOptionFloat, elefant_foot_compensation))
|
||||
@@ -1102,6 +1250,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((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))
|
||||
((ConfigOptionFloatsNullable, outer_wall_speed))
|
||||
@@ -1115,6 +1265,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionString, sparse_infill_rotate_template))
|
||||
((ConfigOptionPercent, sparse_infill_density))
|
||||
((ConfigOptionEnum<InfillPattern>, sparse_infill_pattern))
|
||||
((ConfigOptionPercent, sparse_infill_smooth_factor))
|
||||
((ConfigOptionFloat, lateral_lattice_angle_1))
|
||||
((ConfigOptionFloat, lateral_lattice_angle_2))
|
||||
((ConfigOptionFloat, infill_overhang_angle))
|
||||
@@ -1122,6 +1273,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))
|
||||
@@ -1187,6 +1340,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloatOrPercent, top_surface_line_width))
|
||||
((ConfigOptionInt, top_shell_layers))
|
||||
((ConfigOptionFloat, top_shell_thickness))
|
||||
((ConfigOptionFloat, top_surface_expansion))
|
||||
((ConfigOptionFloat, top_surface_expansion_margin))
|
||||
((ConfigOptionEnum<TopSurfaceExpansionDirection>, top_surface_expansion_direction))
|
||||
((ConfigOptionFloatsNullable, top_surface_speed))
|
||||
//BBS
|
||||
((ConfigOptionBoolsNullable, enable_overhang_speed))
|
||||
@@ -1225,6 +1381,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((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))
|
||||
@@ -1295,6 +1453,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))
|
||||
@@ -1349,6 +1513,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))
|
||||
@@ -1357,14 +1522,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))
|
||||
@@ -1376,7 +1547,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, gcode_add_line_number))
|
||||
((ConfigOptionBool, bbl_bed_temperature_gcode))
|
||||
((ConfigOptionEnum<GCodeFlavor>, gcode_flavor))
|
||||
|
||||
((ConfigOptionBool, gcode_skip_config_block))
|
||||
((ConfigOptionFloat, time_cost))
|
||||
((ConfigOptionString, layer_change_gcode))
|
||||
((ConfigOptionString, time_lapse_gcode))
|
||||
@@ -1388,6 +1559,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
|
||||
|
||||
((ConfigOptionPercents, retract_before_wipe))
|
||||
// Orca
|
||||
((ConfigOptionPercents, retract_after_wipe))
|
||||
|
||||
((ConfigOptionFloats, retraction_length))
|
||||
((ConfigOptionFloats, retract_length_toolchange))
|
||||
((ConfigOptionInt, enable_long_retraction_when_cut))
|
||||
@@ -1411,6 +1585,7 @@ 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))
|
||||
@@ -1425,12 +1600,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))
|
||||
@@ -1481,6 +1660,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, purge_in_prime_tower))
|
||||
((ConfigOptionBool, enable_filament_ramming))
|
||||
((ConfigOptionBool, tool_change_on_wipe_tower))
|
||||
((ConfigOptionBool, wait_for_temp_on_wipe_tower))
|
||||
((ConfigOptionBool, support_multi_bed_types))
|
||||
((ConfigOptionBool, use_3mf))
|
||||
|
||||
@@ -1488,6 +1668,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.
|
||||
@@ -1574,6 +1784,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionBool, ooze_prevention))
|
||||
((ConfigOptionString, filename_format))
|
||||
((ConfigOptionStrings, post_process))
|
||||
((ConfigOptionStrings, slicing_pipeline_plugin))
|
||||
((ConfigOptionString, print_plugin_config_overrides))
|
||||
((ConfigOptionString, printer_model))
|
||||
((ConfigOptionFloat, resolution))
|
||||
((ConfigOptionFloats, retraction_minimum_travel))
|
||||
@@ -1634,7 +1846,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))
|
||||
@@ -1642,6 +1863,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))
|
||||
|
||||
+196
-30
@@ -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;
|
||||
|
||||
@@ -1107,6 +1175,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "brim_type"
|
||||
|| opt_key == "brim_ears_max_angle"
|
||||
|| opt_key == "brim_ears_detection_length"
|
||||
|| opt_key == "brim_ears_outer_only"
|
||||
// BBS: brim generation depends on printing speed
|
||||
|| opt_key == "outer_wall_speed"
|
||||
|| opt_key == "small_perimeter_speed"
|
||||
@@ -1205,6 +1274,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") {
|
||||
@@ -1295,6 +1365,8 @@ 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_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"
|
||||
@@ -1321,13 +1393,16 @@ 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"
|
||||
|| opt_key == "infill_anchor_max"
|
||||
|| 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"
|
||||
@@ -1335,6 +1410,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_overhang_angle") {
|
||||
steps.emplace_back(posInfill);
|
||||
} else if (opt_key == "sparse_infill_pattern"
|
||||
|| opt_key == "sparse_infill_smooth_factor"
|
||||
|| opt_key == "symmetric_infill_y_axis"
|
||||
|| opt_key == "infill_shift_step"
|
||||
|| opt_key == "sparse_infill_rotate_template"
|
||||
@@ -1358,6 +1434,24 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
is_approx(new_density->value, 0.) || is_approx(new_density->value, 100.))
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
} else if (opt_key == "top_surface_density") {
|
||||
// ORCA: 0% means no top solid fill, which switches off both the top surface expansion and the wall
|
||||
// removal over top surfaces. Only crossing zero matters; posPerimeters cascades to posPrepareInfill.
|
||||
const auto *old_density = old_config.option<ConfigOptionPercent>(opt_key);
|
||||
const auto *new_density = new_config.option<ConfigOptionPercent>(opt_key);
|
||||
assert(old_density && new_density);
|
||||
if (is_approx(old_density->value, 0.) || is_approx(new_density->value, 0.))
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posInfill);
|
||||
} else if (opt_key == "top_surface_expansion") {
|
||||
// ORCA: without the expansion the top fill never reaches the space freed by only_one_wall_top, so the
|
||||
// walls over top surfaces are kept. Only crossing zero matters; posPerimeters cascades to posPrepareInfill.
|
||||
const auto *old_expansion = old_config.option<ConfigOptionFloat>(opt_key);
|
||||
const auto *new_expansion = new_config.option<ConfigOptionFloat>(opt_key);
|
||||
assert(old_expansion && new_expansion);
|
||||
if (old_expansion->value <= 0. || new_expansion->value <= 0.)
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
} else if (opt_key == "internal_solid_infill_line_width") {
|
||||
// This value is used for calculating perimeter - infill overlap, thus perimeters need to be recalculated.
|
||||
steps.emplace_back(posPerimeters);
|
||||
@@ -1684,6 +1778,68 @@ void PrintObject::detect_surfaces_type()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: Grow the top surfaces by top_surface_expansion, so the top solid infill also covers the
|
||||
// material left by features rising from the middle of a top surface (filling the holes and
|
||||
// joining the tops, so the features rest on solid infill). Each connected island is grown and
|
||||
// clipped separately: growing one island's top across a gap into another - which may have no top
|
||||
// surface at all, leaving a partially filled layer - is never allowed. The original top is
|
||||
// unioned back in and bottom surfaces are never claimed, so this can only add area.
|
||||
const PrintRegionConfig ®ion_config = layerm->region().config();
|
||||
const double top_expansion = region_config.top_surface_expansion.value;
|
||||
// Nothing to expand without a top fill: a 0% top surface density leaves the top layer with
|
||||
// walls only, and zero top shell layers retypes it as internal in prepare_fill_surfaces().
|
||||
if (top_expansion > 0. && region_config.top_shell_layers.value > 0 &&
|
||||
region_config.top_surface_density.value > 0. && ! top.empty()) {
|
||||
const double d = scale_(top_expansion);
|
||||
const ExPolygons T = union_ex(to_expolygons(top));
|
||||
// Walls are laid out on spacing, not width; and only_one_wall_top leaves a single wall over
|
||||
// a top surface, which is exactly the situation handled here.
|
||||
const int wall_loops = region_config.only_one_wall_top.value ? std::min(region_config.wall_loops.value, 1)
|
||||
: region_config.wall_loops.value;
|
||||
const double wall_band = wall_loops <= 0 ? 0. :
|
||||
double(layerm->flow(frExternalPerimeter).scaled_width()) +
|
||||
double(layerm->flow(frPerimeter).scaled_spacing()) * double(wall_loops - 1);
|
||||
const double margin = scale_(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 = 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 exposed top
|
||||
// is just the walls themselves is then skipped instead of being flooded inward. Clip the
|
||||
// layer's tops to the island first, to keep the boolean ops proportional to the island.
|
||||
const ExPolygons infill_region = wall_band > 0. ? offset_ex(island, -float(wall_band)) : ExPolygons{ island };
|
||||
const ExPolygons island_top = intersection_ex(
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(T, get_extents(island).inflated(SCALED_EPSILON)),
|
||||
infill_region);
|
||||
if (opening_ex(island_top, min_top).empty())
|
||||
continue; // no real top infill in this section - never expand into it
|
||||
|
||||
// Grow, then keep only what the configured direction allows, using the top's own filled
|
||||
// outline (same outer edge, holes closed) to tell the two apart.
|
||||
ExPolygons expanded = offset_ex_2(island_top, d, Clipper2Lib::JoinType::Miter);
|
||||
if (direction != TopSurfaceExpansionDirection::InwardAndOutward) {
|
||||
ExPolygons outline;
|
||||
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;
|
||||
@@ -2180,7 +2336,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;
|
||||
@@ -2222,7 +2378,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);
|
||||
}
|
||||
@@ -2289,12 +2445,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);
|
||||
@@ -2598,7 +2754,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) {
|
||||
@@ -3573,13 +3729,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);
|
||||
@@ -3607,7 +3763,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);
|
||||
@@ -3648,8 +3804,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.
|
||||
@@ -3669,7 +3835,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;
|
||||
@@ -3687,17 +3853,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);
|
||||
@@ -3747,7 +3913,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;
|
||||
@@ -3757,14 +3923,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)
|
||||
@@ -3776,7 +3942,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);
|
||||
}
|
||||
|
||||
@@ -56,12 +56,12 @@ public:
|
||||
int & i,
|
||||
Eigen::Matrix<double, 1, 3> &closest)
|
||||
{
|
||||
size_t idx_unsigned = 0;
|
||||
Vec3d closest_vec3d(closest);
|
||||
double dist =
|
||||
size_t idx_unsigned { 0 };
|
||||
Vec3d closest_vec3d { Vec3d::Zero() };
|
||||
const double dist {
|
||||
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
its.vertices, its.indices, m_tree, point, idx_unsigned,
|
||||
closest_vec3d);
|
||||
closest_vec3d) };
|
||||
i = int(idx_unsigned);
|
||||
closest = closest_vec3d;
|
||||
return dist;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "MutablePriorityQueue.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "GCode/OrderingStrategies.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
@@ -1103,7 +1104,7 @@ std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy) {
|
||||
return chain_points(points);
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_points(const Points &points, Point *start_near)
|
||||
std::vector<size_t> chain_points(const Points &points, const Point *start_near)
|
||||
{
|
||||
auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; };
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, points.size(), start_near);
|
||||
@@ -1111,9 +1112,26 @@ std::vector<size_t> chain_points(const Points &points, Point *start_near)
|
||||
out.reserve(ordered.size());
|
||||
for (auto &segment_and_reversal : ordered)
|
||||
out.emplace_back(segment_and_reversal.first);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_points_with_postprocessing(const Points &points, const Point *start_near)
|
||||
{
|
||||
std::vector<size_t> path = chain_points(points, start_near);
|
||||
// Alternate 2-opt and crossing removal until convergence.
|
||||
// 2-opt can create new crossings, and crossing removal can create new
|
||||
// opportunities for 2-opt improvement. Break early if neither improves.
|
||||
for (int iter = 0; iter < 3; ++iter) {
|
||||
bool improved = tsp_2opt_improve(path, points);
|
||||
improved |= tsp_remove_crossings(path, points);
|
||||
if (!improved) break;
|
||||
}
|
||||
if (start_near == nullptr)
|
||||
tsp_rotate_minimize_closing(path, points);
|
||||
return path;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// #define DEBUG_SVG_OUTPUT
|
||||
#endif /* NDEBUG */
|
||||
@@ -2025,12 +2043,13 @@ std::vector<const PrintInstance*> chain_print_object_instances(const std::vector
|
||||
instances.emplace_back(i, j);
|
||||
}
|
||||
}
|
||||
auto segment_end_point = [&object_reference_points](size_t idx, bool /* first_point */) -> const Point& { return object_reference_points[idx]; };
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, instances.size(), start_near);
|
||||
// Order objects using nearest neighbor + post-processing (crossing removal + 2-opt).
|
||||
std::vector<size_t> path = chain_points_with_postprocessing(object_reference_points, start_near);
|
||||
|
||||
std::vector<const PrintInstance*> out;
|
||||
out.reserve(instances.size());
|
||||
for (auto& segment_and_reversal : ordered) {
|
||||
const std::pair<size_t, size_t>& inst = instances[segment_and_reversal.first];
|
||||
out.reserve(path.size());
|
||||
for (size_t idx : path) {
|
||||
const std::pair<size_t, size_t>& inst = instances[idx];
|
||||
out.emplace_back(&print_objects[inst.first]->instances()[inst.second]);
|
||||
}
|
||||
return out;
|
||||
|
||||
@@ -15,7 +15,9 @@ namespace Slic3r {
|
||||
using PolyNodes = std::vector<PolyNode*, PointsAllocator<PolyNode*>>;
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr);
|
||||
std::vector<size_t> chain_points(const Points &points, const Point *start_near = nullptr);
|
||||
// Variant with post-processing (crossing removal + 2-opt) for object ordering.
|
||||
std::vector<size_t> chain_points_with_postprocessing(const Points &points, const Point *start_near = nullptr);
|
||||
std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy);
|
||||
|
||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
|
||||
|
||||
@@ -65,6 +65,15 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
const bool smooth_supports = support_params.support_style != smsGrid;
|
||||
SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first;
|
||||
SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second;
|
||||
// The user-facing interface layer counts include the contact layer. Internally,
|
||||
// contact layers are generated separately, so only the remaining layers are
|
||||
// projected into intermediate interface/base-interface layers here.
|
||||
const size_t num_top_interface_layers = support_params.has_top_contacts ? support_params.num_top_interface_layers - 1 : 0;
|
||||
const size_t num_bottom_interface_layers = support_params.has_bottom_contacts ? support_params.num_bottom_interface_layers - 1 : 0;
|
||||
const size_t num_top_base_interface_layers = std::min(support_params.num_top_base_interface_layers, num_top_interface_layers);
|
||||
const size_t num_bottom_base_interface_layers = std::min(support_params.num_bottom_base_interface_layers, num_bottom_interface_layers);
|
||||
const size_t num_top_interface_layers_only = num_top_interface_layers - num_top_base_interface_layers;
|
||||
const size_t num_bottom_interface_layers_only = num_bottom_interface_layers - num_bottom_base_interface_layers;
|
||||
|
||||
interface_layers.assign(intermediate_layers.size(), nullptr);
|
||||
if (support_params.has_base_interfaces())
|
||||
@@ -124,6 +133,8 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
};
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())),
|
||||
[&bottom_contacts, &top_contacts, &top_interface_layers, &top_base_interface_layers, &intermediate_layers, &insert_layer, &support_params,
|
||||
num_top_interface_layers, num_bottom_interface_layers, num_top_base_interface_layers, num_bottom_base_interface_layers,
|
||||
num_top_interface_layers_only, num_bottom_interface_layers_only,
|
||||
snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range<int>& range) {
|
||||
// Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below
|
||||
// this intermediate layer.
|
||||
@@ -142,16 +153,16 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
Polygons polygons_top_contact_projected_base;
|
||||
Polygons polygons_bottom_contact_projected_interface;
|
||||
Polygons polygons_bottom_contact_projected_base;
|
||||
if (support_params.num_top_interface_layers > 0) {
|
||||
if (num_top_interface_layers > 0) {
|
||||
// Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
|
||||
coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers) - 1)]->print_z;
|
||||
coordf_t top_inteface_z = std::numeric_limits<coordf_t>::max();
|
||||
if (support_params.num_top_base_interface_layers > 0)
|
||||
coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(num_top_interface_layers) - 1)]->print_z;
|
||||
coordf_t top_interface_z = std::numeric_limits<coordf_t>::max();
|
||||
if (num_top_base_interface_layers > 0)
|
||||
// Some top base interface layers will be generated.
|
||||
top_inteface_z = support_params.num_top_interface_layers_only() == 0 ?
|
||||
top_interface_z = num_top_interface_layers_only == 0 ?
|
||||
// Only base interface layers to generate.
|
||||
- std::numeric_limits<coordf_t>::max() :
|
||||
intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers_only()) - 1)]->print_z;
|
||||
intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(num_top_interface_layers_only) - 1)]->print_z;
|
||||
// Move idx_top_contact_first up until above the current print_z.
|
||||
idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
|
||||
// Collect the top contact areas above this intermediate layer, below top_z.
|
||||
@@ -160,22 +171,22 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
//FIXME maybe this adds one interface layer in excess?
|
||||
if (top_contact_layer.bottom_z - EPSILON > top_z)
|
||||
break;
|
||||
polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface,
|
||||
polygons_append(top_contact_layer.bottom_z - EPSILON > top_interface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface,
|
||||
// For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers.
|
||||
// For grid supports, merging of support regions will be performed by the projection into grid.
|
||||
snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons);
|
||||
}
|
||||
}
|
||||
if (support_params.num_bottom_interface_layers > 0) {
|
||||
if (num_bottom_interface_layers > 0) {
|
||||
// Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
|
||||
coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers) + 1)]->bottom_z;
|
||||
coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - int(num_bottom_interface_layers) + 1)]->bottom_z;
|
||||
coordf_t bottom_interface_z = - std::numeric_limits<coordf_t>::max();
|
||||
if (support_params.num_bottom_base_interface_layers > 0)
|
||||
if (num_bottom_base_interface_layers > 0)
|
||||
// Some bottom base interface layers will be generated.
|
||||
bottom_interface_z = support_params.num_bottom_interface_layers_only() == 0 ?
|
||||
bottom_interface_z = num_bottom_interface_layers_only == 0 ?
|
||||
// Only base interface layers to generate.
|
||||
std::numeric_limits<coordf_t>::max() :
|
||||
intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers_only()))]->bottom_z;
|
||||
intermediate_layers[std::max(0, idx_intermediate_layer - int(num_bottom_interface_layers_only))]->bottom_z;
|
||||
// Move idx_bottom_contact_first up until touching bottom_z.
|
||||
idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const SupportGeneratorLayer *layer){ return layer->print_z >= bottom_z - EPSILON; });
|
||||
// Collect the top contact areas above this intermediate layer, below top_z.
|
||||
@@ -376,10 +387,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())));
|
||||
@@ -1566,13 +1574,17 @@ void generate_support_toolpaths(
|
||||
// Pointer to the 1st layer interface filler.
|
||||
auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get();
|
||||
// Filler for the 1st layer interface, if different from filler_interface.
|
||||
auto filler_raft_contact_ptr = std::unique_ptr<Fill>(range.begin() == n_raft_layers && config.support_interface_top_layers.value == 0 ?
|
||||
const bool top_interfaces_enabled = support_params.num_top_interface_layers > 0;
|
||||
const bool bottom_interfaces_enabled = support_params.num_bottom_interface_layers > 0;
|
||||
const coordf_t base_interface_density = top_interfaces_enabled || !bottom_interfaces_enabled ?
|
||||
support_params.top_interface_density : support_params.bottom_interface_density;
|
||||
auto filler_raft_contact_ptr = std::unique_ptr<Fill>(range.begin() == n_raft_layers && !top_interfaces_enabled ?
|
||||
Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr);
|
||||
// Pointer to the 1st layer interface filler.
|
||||
auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get();
|
||||
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
|
||||
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr :
|
||||
Fill::new_from_type(support_params.top_interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
|
||||
Fill::new_from_type(base_interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
|
||||
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(support_params.base_fill_pattern));
|
||||
filler_interface->set_bounding_box(bbox_object);
|
||||
if (filler_first_layer_ptr)
|
||||
@@ -1586,10 +1598,7 @@ void generate_support_toolpaths(
|
||||
{
|
||||
SupportLayer &support_layer = *support_layers[support_layer_id];
|
||||
LayerCache &layer_cache = layer_caches[support_layer_id];
|
||||
const float support_interface_angle = (config.support_interface_pattern == smipRectilinearInterlaced) ?
|
||||
support_params.raft_interface_angle(support_layer.interface_id()) :
|
||||
((support_params.support_style == smsGrid || config.support_interface_pattern == smipRectilinear) ?
|
||||
support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()));
|
||||
const float support_interface_angle = support_params.support_interface_angle(support_layer.interface_id());
|
||||
|
||||
// Find polygons with the same print_z.
|
||||
SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer;
|
||||
@@ -1622,7 +1631,9 @@ void generate_support_toolpaths(
|
||||
bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z);
|
||||
// ORCA: Organic tree uses projected contacts to build the interface stack; avoid extra bottom-contact extrusion.
|
||||
const bool organic_tree = support_params.support_style == SupportMaterialStyle::smsTreeOrganic;
|
||||
if (config.support_interface_top_layers == 0) {
|
||||
const bool top_interfaces = support_params.num_top_interface_layers > 0;
|
||||
const bool bottom_interfaces = support_params.num_bottom_interface_layers > 0;
|
||||
if (!top_interfaces) {
|
||||
// If no top interface layers were requested, we treat the contact layer exactly as a generic base layer.
|
||||
// Don't merge the raft contact layer though.
|
||||
if (support_params.can_merge_support_regions && ! raft_layer) {
|
||||
@@ -1645,15 +1656,29 @@ void generate_support_toolpaths(
|
||||
if (top_contact_layer.could_merge(interface_layer) && ! raft_layer)
|
||||
top_contact_layer.merge(std::move(interface_layer));
|
||||
}
|
||||
if ((config.support_interface_top_layers == 0 || config.support_interface_bottom_layers == 0) && support_params.can_merge_support_regions) {
|
||||
if (!bottom_interfaces && support_params.can_merge_support_regions) {
|
||||
if (base_layer.could_merge(bottom_contact_layer))
|
||||
base_layer.merge(std::move(bottom_contact_layer));
|
||||
else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging)
|
||||
base_layer = std::move(bottom_contact_layer);
|
||||
} else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) {
|
||||
top_contact_layer.merge(std::move(bottom_contact_layer));
|
||||
if (top_interfaces && bottom_interfaces) {
|
||||
top_contact_layer.merge(std::move(bottom_contact_layer));
|
||||
} else if (bottom_interfaces) {
|
||||
top_contact_layer.set_polygons_to_extrude(
|
||||
diff(top_contact_layer.polygons_to_extrude(), bottom_contact_layer.polygons_to_extrude()));
|
||||
} else {
|
||||
bottom_contact_layer.set_polygons_to_extrude(
|
||||
diff(bottom_contact_layer.polygons_to_extrude(), top_contact_layer.polygons_to_extrude()));
|
||||
}
|
||||
} else if (bottom_contact_layer.could_merge(interface_layer) && ! organic_tree) {
|
||||
bottom_contact_layer.merge(std::move(interface_layer));
|
||||
const bool interface_layer_is_bottom = interface_layer.layer->layer_type == SupporLayerType::BottomInterface;
|
||||
if (bottom_interfaces && interface_layer_is_bottom) {
|
||||
bottom_contact_layer.merge(std::move(interface_layer));
|
||||
} else {
|
||||
bottom_contact_layer.set_polygons_to_extrude(
|
||||
diff(bottom_contact_layer.polygons_to_extrude(), interface_layer.polygons_to_extrude()));
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: For organic trees the support-material regions are generated from
|
||||
@@ -1733,12 +1758,12 @@ void generate_support_toolpaths(
|
||||
interface_as_base ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, interface_flow);
|
||||
}
|
||||
};
|
||||
const bool top_interfaces = support_params.num_top_interface_layers > 0;
|
||||
const bool bottom_interfaces = top_interfaces && support_params.num_bottom_interface_layers > 0;
|
||||
extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase);
|
||||
if (!organic_tree)
|
||||
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);
|
||||
extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase);
|
||||
const bool interface_layer_enabled = !interface_layer.empty() &&
|
||||
(interface_layer.layer->layer_type == SupporLayerType::BottomInterface ? bottom_interfaces : top_interfaces);
|
||||
extrude_interface(interface_layer, interface_layer_enabled ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase);
|
||||
// Base interface layers under soluble interfaces
|
||||
if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) {
|
||||
Fill *filler = filler_base_interface.get();
|
||||
@@ -1748,7 +1773,7 @@ void generate_support_toolpaths(
|
||||
Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
|
||||
filler->angle = support_interface_angle;
|
||||
filler->spacing = support_params.support_material_interface_flow.spacing();
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.top_interface_density));
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / base_interface_density));
|
||||
fill_expolygons_generate_paths(
|
||||
// Destination
|
||||
base_interface_layer.extrusions,
|
||||
@@ -1756,7 +1781,7 @@ void generate_support_toolpaths(
|
||||
// Regions to fill
|
||||
union_safety_offset_ex(base_interface_layer.polygons_to_extrude()),
|
||||
// Filler and its parameters
|
||||
filler, float(support_params.top_interface_density),
|
||||
filler, float(base_interface_density),
|
||||
// Extrusion parameters
|
||||
ExtrusionRole::erSupportMaterial, interface_flow);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ struct SupportParameters {
|
||||
|
||||
{
|
||||
this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value);
|
||||
this->num_bottom_interface_layers = number_of_support_interface_bottom_layers(object_config);
|
||||
this->num_bottom_interface_layers = std::max(0, number_of_support_interface_bottom_layers(object_config));
|
||||
this->has_top_contacts = num_top_interface_layers > 0;
|
||||
this->has_bottom_contacts = num_bottom_interface_layers > 0;
|
||||
// BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion
|
||||
@@ -46,15 +46,15 @@ struct SupportParameters {
|
||||
if (non_soluble_base_top) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
|
||||
this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2));
|
||||
} else {
|
||||
this->num_top_base_interface_layers =
|
||||
(different_support_interface_filament && this->zero_gap_interface_top) ? 1 : 0;
|
||||
// Keep at least one configured layer on the interface filament.
|
||||
this->num_top_base_interface_layers = different_support_interface_filament && num_top_interface_layers > 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
if (non_soluble_base_bottom) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
|
||||
this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2));
|
||||
} else {
|
||||
this->num_bottom_base_interface_layers =
|
||||
(different_support_interface_filament && this->zero_gap_interface_bottom) ? 1 : 0;
|
||||
// Keep at least one configured layer on the interface filament.
|
||||
this->num_bottom_base_interface_layers = different_support_interface_filament && num_bottom_interface_layers > 1 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height));
|
||||
@@ -74,7 +74,7 @@ struct SupportParameters {
|
||||
for (auto layer : object.layers())
|
||||
this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height));
|
||||
|
||||
if (object_config.support_interface_top_layers.value == 0) {
|
||||
if (this->num_top_interface_layers == 0 && this->num_bottom_interface_layers == 0) {
|
||||
// No interface layers allowed, print everything with the base support pattern.
|
||||
this->support_material_interface_flow = this->support_material_flow;
|
||||
}
|
||||
@@ -120,8 +120,8 @@ struct SupportParameters {
|
||||
this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing);
|
||||
this->support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing();
|
||||
this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_spacing);
|
||||
if (object_config.support_interface_top_layers.value == 0) {
|
||||
// No interface layers allowed, print everything with the base support pattern.
|
||||
if (this->num_top_interface_layers == 0) {
|
||||
// No top interface layers allowed; keep unused top interface parameters aligned with base support.
|
||||
this->top_interface_spacing = this->support_spacing;
|
||||
this->top_interface_density = this->support_density;
|
||||
}
|
||||
@@ -133,16 +133,20 @@ struct SupportParameters {
|
||||
this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase;
|
||||
this->interface_fill_pattern = (this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase;
|
||||
const coordf_t contact_interface_density = this->num_top_interface_layers > 0 ?
|
||||
this->top_interface_density : this->bottom_interface_density;
|
||||
const bool zero_gap_contact_interface = this->num_top_interface_layers > 0 ?
|
||||
this->zero_gap_interface_top : this->zero_gap_interface_bottom;
|
||||
if (object_config.support_interface_pattern == smipGrid)
|
||||
this->contact_fill_pattern = ipGrid;
|
||||
else if (object_config.support_interface_pattern == smipRectilinearInterlaced)
|
||||
this->contact_fill_pattern = ipRectilinear;
|
||||
else
|
||||
this->contact_fill_pattern =
|
||||
(object_config.support_interface_pattern == smipAuto && this->zero_gap_interface_top) ||
|
||||
(object_config.support_interface_pattern == smipAuto && zero_gap_contact_interface) ||
|
||||
object_config.support_interface_pattern == smipConcentric ?
|
||||
ipConcentric :
|
||||
(this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
(contact_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
|
||||
this->raft_angle_1st_layer = 0.f;
|
||||
this->raft_angle_base = 0.f;
|
||||
@@ -188,6 +192,7 @@ struct SupportParameters {
|
||||
std::numeric_limits<double>::max();
|
||||
|
||||
support_style = object_config.support_style;
|
||||
support_interface_pattern = object_config.support_interface_pattern;
|
||||
if (support_style != smsDefault) {
|
||||
if ((support_style == smsSnug || support_style == smsGrid) && is_tree(object_config.support_type)) support_style = smsDefault;
|
||||
if ((support_style == smsTreeSlim || support_style == smsTreeStrong || support_style == smsTreeHybrid || support_style == smsTreeOrganic) &&
|
||||
@@ -211,9 +216,9 @@ struct SupportParameters {
|
||||
bool has_top_contacts;
|
||||
// Is there at least a bottom contact layer extruded below support base?
|
||||
bool has_bottom_contacts;
|
||||
// Number of top interface layers without counting the contact layer.
|
||||
// User-configured number of top interface layers, including the contact layer.
|
||||
size_t num_top_interface_layers;
|
||||
// Number of bottom interface layers without counting the contact layer.
|
||||
// User-configured number of bottom interface layers, including the contact layer.
|
||||
size_t num_bottom_interface_layers;
|
||||
// Number of top base interface layers.
|
||||
size_t num_top_base_interface_layers;
|
||||
@@ -235,7 +240,7 @@ struct SupportParameters {
|
||||
Flow support_material_interface_flow;
|
||||
// Flow at the bottom interfaces and contacts.
|
||||
Flow support_material_bottom_interface_flow;
|
||||
// Flow at raft inteface & contact layers.
|
||||
// Flow at raft interface & contact layers.
|
||||
Flow raft_interface_flow;
|
||||
coordf_t support_extrusion_width;
|
||||
// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
|
||||
@@ -262,6 +267,7 @@ struct SupportParameters {
|
||||
// Density of the base support layers.
|
||||
coordf_t support_density;
|
||||
SupportMaterialStyle support_style = smsDefault;
|
||||
SupportMaterialInterfacePattern support_interface_pattern = smipAuto;
|
||||
|
||||
// Pattern of the sparse infill including sparse raft layers.
|
||||
InfillPattern base_fill_pattern;
|
||||
@@ -280,9 +286,33 @@ struct SupportParameters {
|
||||
float raft_angle_base;
|
||||
float raft_angle_interface;
|
||||
|
||||
// Produce a raft interface angle for a given SupportLayer::interface_id()
|
||||
// Produce a +/-45deg alternating raft interface angle for a given SupportLayer::interface_id().
|
||||
float raft_interface_angle(size_t interface_id) const
|
||||
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); }
|
||||
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI_4) : float(+ M_PI_4)); }
|
||||
|
||||
// Produce support interface angle for a given SupportLayer::interface_id().
|
||||
// Angle will be shifted/rotated based on interface pattern.
|
||||
float support_interface_angle(size_t interface_id) const
|
||||
{
|
||||
float angle;
|
||||
|
||||
switch (this->support_interface_pattern) {
|
||||
case SupportMaterialInterfacePattern::smipRectilinear:
|
||||
angle = support_style == SupportMaterialStyle::smsSnug ? this->interface_angle - float(M_PI_4) : this->interface_angle;
|
||||
break;
|
||||
case SupportMaterialInterfacePattern::smipRectilinearInterlaced:
|
||||
angle = this->interface_angle + ((interface_id & 1) ? float(M_PI_4) : float(-M_PI_4));
|
||||
break;
|
||||
case SupportMaterialInterfacePattern::smipGrid:
|
||||
angle = this->base_angle;
|
||||
break;
|
||||
default:
|
||||
angle = this->interface_angle;
|
||||
break;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
bool independent_layer_height = false;
|
||||
const double thresh_big_overhang = Slic3r::sqr(scale_(10));
|
||||
|
||||
@@ -22,7 +22,7 @@ struct Params
|
||||
: /*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()) {
|
||||
|
||||
@@ -469,7 +469,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
});
|
||||
|
||||
// 2) Sum over top / bottom ranges.
|
||||
const bool processing_last_mesh = outline_idx == layer_outline_indices.size();
|
||||
const bool processing_last_mesh = outline_idx == layer_outline_indices.back();
|
||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(data.begin(), data.end()),
|
||||
[&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius,
|
||||
xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, processing_last_mesh, &throw_on_cancel]
|
||||
|
||||
@@ -1511,7 +1511,9 @@ void TreeSupport::generate_toolpaths()
|
||||
// ORCA: reset interface Fill state per area group to keep angles deterministic.
|
||||
filler_interface->fixed_angle = false;
|
||||
filler_interface->layer_id = size_t(-1);
|
||||
filler_interface->angle = base_support_angle + M_PI_2; // default interface angle is perpendicular to support angle
|
||||
filler_Roof1stLayer->fixed_angle = false;
|
||||
filler_Roof1stLayer->layer_id = size_t(-1);
|
||||
filler_interface->angle = m_support_params.support_interface_angle(area_group.interface_id);
|
||||
if (area_group.type != SupportLayer::BaseType) {
|
||||
// interface
|
||||
if (layer_id == 0) {
|
||||
@@ -1537,8 +1539,10 @@ void TreeSupport::generate_toolpaths()
|
||||
fill_params.density = interface_density;
|
||||
// Note: spacing means the separation between two lines as if they are tightly extruded
|
||||
filler_Roof1stLayer->spacing = interface_flow.spacing();
|
||||
filler_Roof1stLayer->angle = base_support_angle;
|
||||
filler_Roof1stLayer->angle = m_support_params.support_interface_angle(area_group.interface_id);
|
||||
fill_params.dont_sort = true;
|
||||
filler_Roof1stLayer->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||
m_object_config->support_interface_pattern == smipRectilinear);
|
||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||
// generate a perimeter first to support interface better
|
||||
@@ -1556,18 +1560,11 @@ void TreeSupport::generate_toolpaths()
|
||||
fill_params.density = bottom_interface_density;
|
||||
filler_interface->spacing = interface_flow.spacing();
|
||||
|
||||
if (m_object_config->support_interface_pattern == smipGrid) {
|
||||
filler_interface->angle = base_support_angle;
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
|
||||
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
|
||||
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
|
||||
filler_interface->fixed_angle = true;
|
||||
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
fill_params.dont_sort = (m_object_config->support_interface_pattern == smipGrid ||
|
||||
m_object_config->support_interface_pattern == smipRectilinearInterlaced);
|
||||
|
||||
filler_interface->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||
m_object_config->support_interface_pattern == smipRectilinear);
|
||||
|
||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||
@@ -1579,17 +1576,11 @@ void TreeSupport::generate_toolpaths()
|
||||
fill_params.density = interface_density;
|
||||
filler_interface->spacing = interface_flow.spacing();
|
||||
|
||||
if (m_object_config->support_interface_pattern == smipGrid) {
|
||||
filler_interface->angle = base_support_angle;
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
fill_params.dont_sort = (m_object_config->support_interface_pattern == smipGrid ||
|
||||
m_object_config->support_interface_pattern == smipRectilinearInterlaced);
|
||||
|
||||
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
|
||||
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
|
||||
filler_interface->fixed_angle = true;
|
||||
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
filler_interface->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||
m_object_config->support_interface_pattern == smipRectilinear);
|
||||
|
||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||
@@ -2014,6 +2005,9 @@ void TreeSupport::draw_circles()
|
||||
// generate areas
|
||||
const coordf_t layer_height = config.layer_height.value;
|
||||
const size_t top_interface_layers = m_support_params.num_top_interface_layers;
|
||||
const int top_base_interface_layers = std::min<int>(
|
||||
int(m_support_params.num_top_base_interface_layers),
|
||||
top_interface_layers > 0 ? int(top_interface_layers) - 1 : 0);
|
||||
const size_t bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||
const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0);
|
||||
const coordf_t line_width = config.get_abs_value("support_line_width", nozzle_diameter);
|
||||
@@ -2054,12 +2048,14 @@ void TreeSupport::draw_circles()
|
||||
|
||||
ExPolygons& base_areas = ts_layer->base_areas;
|
||||
ExPolygons& roof_areas = ts_layer->roof_areas;
|
||||
ExPolygons roof_base_areas;
|
||||
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
|
||||
ExPolygons& floor_areas = ts_layer->floor_areas;
|
||||
ExPolygons& roof_gap_areas = ts_layer->roof_gap_areas;
|
||||
coordf_t max_layers_above_base = 0;
|
||||
coordf_t max_layers_above_roof = 0;
|
||||
coordf_t max_layers_above_roof1 = 0;
|
||||
size_t first_base_roof_area = 0;
|
||||
bool floor_interface_as_base = false;
|
||||
bool has_circle_node = false;
|
||||
bool need_extra_wall = false;
|
||||
@@ -2094,8 +2090,6 @@ void TreeSupport::draw_circles()
|
||||
break;
|
||||
|
||||
const SupportNode& node = *p_node;
|
||||
// ORCA: Cap top interface height in mm based on per-node support layer height.
|
||||
const coordf_t top_interface_height = coordf_t(top_interface_layers) * node.height;
|
||||
ExPolygons area;
|
||||
// Generate directly from overhang polygon if one of the following is true:
|
||||
// 1) node is a normal part of hybrid support
|
||||
@@ -2159,18 +2153,16 @@ void TreeSupport::draw_circles()
|
||||
|
||||
if (obj_layer_nr>0 && node.distance_to_top < 0)
|
||||
append(roof_gap_areas, area);
|
||||
// ORCA: Roof1stLayer must also fit inside the mm cap.
|
||||
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
|
||||
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail==false)
|
||||
node.is_sharp_tail == false)
|
||||
{
|
||||
append(roof_1st_layer, area);
|
||||
max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top);
|
||||
}
|
||||
// ORCA: Roof layers must also fit inside the mm cap.
|
||||
else if (obj_layer_nr > 0 && node.support_roof_layers_below > 1 &&
|
||||
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail == false)
|
||||
node.is_sharp_tail == false)
|
||||
{
|
||||
append(roof_areas, area);
|
||||
append(node.support_roof_layers_below <= top_base_interface_layers ? roof_base_areas : roof_areas, area);
|
||||
max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top);
|
||||
}
|
||||
else
|
||||
@@ -2184,9 +2176,17 @@ void TreeSupport::draw_circles()
|
||||
//m_object->print()->set_status(65, (boost::format( _u8L("Support: generate polygons at layer %d")) % layer_nr).str());
|
||||
|
||||
// join roof segments
|
||||
roof_areas = diff_clipped(offset2_ex(roof_areas, line_width_scaled, -line_width_scaled), get_collision(false));
|
||||
roof_areas = diff_clipped(closing_ex(roof_areas, line_width_scaled), get_collision(false));
|
||||
roof_areas = intersection_ex(roof_areas, m_machine_border);
|
||||
roof_1st_layer = diff_clipped(offset2_ex(roof_1st_layer, line_width_scaled, -line_width_scaled), get_collision(false));
|
||||
roof_base_areas = diff_clipped(closing_ex(roof_base_areas, line_width_scaled), get_collision(false));
|
||||
roof_base_areas = intersection_ex(roof_base_areas, m_machine_border);
|
||||
if (!roof_base_areas.empty() && !roof_areas.empty())
|
||||
roof_base_areas = diff_ex(roof_base_areas,
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas, get_extents(roof_base_areas)));
|
||||
|
||||
first_base_roof_area = roof_areas.size();
|
||||
append(roof_areas, std::move(roof_base_areas));
|
||||
roof_1st_layer = diff_clipped(closing_ex(roof_1st_layer, line_width_scaled), get_collision(false));
|
||||
|
||||
// roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer
|
||||
roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer)));
|
||||
@@ -2366,9 +2366,11 @@ void TreeSupport::draw_circles()
|
||||
area_groups.back().need_infill = overlaps({ expoly }, area_poly);
|
||||
area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill;
|
||||
}
|
||||
for (auto& expoly : ts_layer->roof_areas) {
|
||||
for (size_t roof_idx = 0; roof_idx < ts_layer->roof_areas.size(); ++roof_idx) {
|
||||
auto &expoly = ts_layer->roof_areas[roof_idx];
|
||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||
area_groups.emplace_back(&expoly, SupportLayer::RoofType, max_layers_above_roof);
|
||||
area_groups.back().interface_as_base = roof_idx >= first_base_roof_area;
|
||||
}
|
||||
for (auto &expoly : ts_layer->floor_areas) {
|
||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||
@@ -2378,6 +2380,7 @@ void TreeSupport::draw_circles()
|
||||
for (auto &expoly : ts_layer->roof_1st_layer) {
|
||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||
area_groups.emplace_back(&expoly, SupportLayer::Roof1stLayer, max_layers_above_roof1);
|
||||
area_groups.back().interface_as_base = top_base_interface_layers > 0;
|
||||
}
|
||||
|
||||
for (auto &area_group : area_groups) {
|
||||
@@ -2406,7 +2409,6 @@ void TreeSupport::draw_circles()
|
||||
}
|
||||
});
|
||||
// ORCA: normalize interface_id sequencing to follow printed interface layers only.
|
||||
const int top_base_layers = int(m_support_params.num_top_base_interface_layers);
|
||||
const bool interlaced = m_object_config->support_interface_pattern == smipRectilinearInterlaced;
|
||||
int roof_interface_id = 0;
|
||||
int floor_interface_id = 0;
|
||||
@@ -2425,7 +2427,6 @@ void TreeSupport::draw_circles()
|
||||
if (area_group.type == SupportLayer::RoofType || area_group.type == SupportLayer::Roof1stLayer) {
|
||||
if (interlaced)
|
||||
area_group.interface_id = roof_interface_id;
|
||||
area_group.interface_as_base = top_base_layers > 0 && roof_interface_id < top_base_layers;
|
||||
has_roof_interface = true;
|
||||
} else if (area_group.type == SupportLayer::FloorType) {
|
||||
if (interlaced)
|
||||
@@ -2897,7 +2898,7 @@ void TreeSupport::drop_nodes()
|
||||
node_parent->merged_neighbours.push_front(node_parent == p_node ? neighbour : p_node);
|
||||
const bool to_buildplate = !is_inside_ex(get_collision(0, obj_layer_nr_next), next_position);
|
||||
SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next,
|
||||
node_parent->support_roof_layers_below - (node_parent->distance_to_top > 0 ? 1 : 0),
|
||||
node_parent->support_roof_layers_below - (node_parent->distance_to_top >= 0 ? 1 : 0),
|
||||
to_buildplate, node_parent, print_z_next, height_next);
|
||||
get_max_move_dist(next_node);
|
||||
m_ts_data->m_mutex.lock();
|
||||
@@ -2949,7 +2950,7 @@ void TreeSupport::drop_nodes()
|
||||
for(auto& overhang:overhangs_next) {
|
||||
Point next_pt = overhang.contour.centroid();
|
||||
SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next,
|
||||
p_node->support_roof_layers_below - (p_node->distance_to_top > 0 ? 1 : 0),
|
||||
p_node->support_roof_layers_below - (p_node->distance_to_top >= 0 ? 1 : 0),
|
||||
to_buildplate, p_node, print_z_next, height_next);
|
||||
next_node->max_move_dist = 0;
|
||||
next_node->overhang = std::move(overhang);
|
||||
@@ -3096,7 +3097,7 @@ void TreeSupport::drop_nodes()
|
||||
auto next_collision = get_collision(0, obj_layer_nr_next);
|
||||
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
|
||||
SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next,
|
||||
node.support_roof_layers_below - (node.distance_to_top > 0 ? 1 : 0),
|
||||
node.support_roof_layers_below - (node.distance_to_top >= 0 ? 1 : 0),
|
||||
to_buildplate, p_node, print_z_next, height_next);
|
||||
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
|
||||
to_outside = projection_onto(next_collision, next_node->position);
|
||||
@@ -3376,21 +3377,6 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: Recompute support_roof_layers_below from remaining interface height (independent heights).
|
||||
const int top_layers = m_object->config().support_interface_top_layers.value;
|
||||
if (m_support_params.independent_layer_height && top_layers > 0) {
|
||||
const coordf_t interface_height_mm = coordf_t(top_layers) * m_slicing_params.layer_height;
|
||||
for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) {
|
||||
if (contact_nodes[layer_nr].empty()) continue;
|
||||
for (SupportNode *node : contact_nodes[layer_nr]) {
|
||||
if (node->height <= EPSILON) continue;
|
||||
const coordf_t remaining_mm = interface_height_mm - (node->dist_mm_to_top - this->top_z_distance);
|
||||
const int layers_fit = remaining_mm < -EPSILON ? 0 : int(std::floor((remaining_mm + EPSILON) / node->height));
|
||||
node->support_roof_layers_below = std::min(layers_fit, top_layers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log layer_heights
|
||||
for (size_t i = 0; i < layer_heights.size(); i++) {
|
||||
//if (layer_heights[i].height > EPSILON)
|
||||
@@ -3498,7 +3484,7 @@ void TreeSupport::generate_contact_points()
|
||||
if (force_add || !already_inserted.count(hash_pos)) {
|
||||
already_inserted.emplace(hash_pos);
|
||||
bool to_buildplate = true;
|
||||
size_t roof_layers = add_interface ? (support_roof_layers > 0 ? support_roof_layers - 1 : 0) : 0; // subtract 1 because the contact node itself counts as one layer
|
||||
size_t roof_layers = add_interface ? support_roof_layers : 0;
|
||||
// add a new node as a virtual node which acts as the invisible gap between support and object
|
||||
// distance_to_top=-1: it's virtual
|
||||
// print_z=object_layer->bottom_z: it directly contacts the bottom
|
||||
|
||||
@@ -706,7 +706,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
|
||||
filler->spacing = flow.spacing();
|
||||
filler->angle = roof ?
|
||||
//fixme support_layer.interface_id() instead of layer_idx
|
||||
(support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) :
|
||||
(support_params.interface_angle + ((layer_idx & 1) ? float(- M_PI_4) : float(+ M_PI_4))) :
|
||||
support_params.base_angle;
|
||||
|
||||
// ORCA: use top-specific interface density after separating top/bottom settings.
|
||||
|
||||
@@ -62,7 +62,7 @@ struct TreeSupportMeshGroupSettings {
|
||||
this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width();
|
||||
this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width();
|
||||
const int bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||
this->support_bottom_enable = config.support_interface_top_layers.value > 0 && bottom_interface_layers > 0;
|
||||
this->support_bottom_enable = bottom_interface_layers > 0;
|
||||
this->support_bottom_height = this->support_bottom_enable ?
|
||||
bottom_interface_layers * this->layer_height :
|
||||
0;
|
||||
@@ -705,7 +705,7 @@ public:
|
||||
SupportGeneratorLayersPtr& top_contacts_mutable() { return this->top_contacts; }
|
||||
|
||||
public:
|
||||
// Insert the contact layer and some of the inteface and base interface layers below.
|
||||
// Insert the contact layer and some of the interface and base interface layers below.
|
||||
void add_roofs(std::vector<Polygons> &&new_roofs, const size_t insert_layer_idx)
|
||||
{
|
||||
if (! new_roofs.empty()) {
|
||||
|
||||
@@ -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.
|
||||
@@ -300,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);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user