mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-20 06:01:16 +00:00
Merge branch 'main' into pr/Noisyfox/13712
This commit is contained in:
commit
ca388f2d9e
2782 files changed
+331936
-215943
No files matched your search
+1
-1
@@ -158,7 +158,7 @@ target_link_libraries(OrcaSlicer libslic3r_gui)
|
||||
if (MSVC)
|
||||
# Generate debug symbols even in release mode.
|
||||
target_link_options(OrcaSlicer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
||||
target_link_libraries(OrcaSlicer user32.lib Setupapi.lib)
|
||||
target_link_libraries(OrcaSlicer ws2_32.lib user32.lib Setupapi.lib)
|
||||
elseif (MINGW)
|
||||
target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi)
|
||||
elseif (APPLE)
|
||||
|
||||
+125
-37
@@ -23,6 +23,7 @@
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
#include <csignal>
|
||||
|
||||
#if defined(__linux__) || defined(__LINUX__)
|
||||
#include <condition_variable>
|
||||
@@ -1191,43 +1192,108 @@ int CLI::run(int argc, char **argv)
|
||||
save_main_thread_id();
|
||||
|
||||
#ifdef __WXGTK__
|
||||
// Safety fallback: if wxWidgets was not built with EGL support, native
|
||||
// Wayland will crash in wxGLCanvas::IsDisplaySupported() because the GLX
|
||||
// backend cannot access an X11 display. Force X11 mode in that case.
|
||||
// NOTE: Do NOT remove this block even after enabling wxHAS_EGL
|
||||
// in the build — it protects against builds where deps were not rebuilt.
|
||||
#if !defined(wxHAS_EGL) || !wxHAS_EGL
|
||||
// ------------------------------------------------------------------
|
||||
// Linux backend selection — runtime, based on GDK_BACKEND env var.
|
||||
//
|
||||
// GDK_BACKEND unset (DEFAULT) Wayland path.
|
||||
// GTK auto-picks Wayland on Wayland
|
||||
// sessions and X11 otherwise. WebKit
|
||||
// XWayland-compositing workaround
|
||||
// applied only when actually on
|
||||
// XWayland. XInitThreads gated on
|
||||
// DISPLAY presence.
|
||||
//
|
||||
// GDK_BACKEND=x11 (OPT-IN) X11/XWayland mode. Applies the
|
||||
// v2.3.2 workarounds that the X11
|
||||
// path benefits from: DRI_PRIME for
|
||||
// AMD/nouveau PRIME, NVIDIA PRIME
|
||||
// render offload (when nvidia.ko is
|
||||
// loaded), XInitThreads.
|
||||
// Multi monitor is compromised
|
||||
//
|
||||
// WEBKIT_DISABLE_COMPOSITING_MODE is deliberately NOT set on the X11
|
||||
// opt-in path. The ~2020-era WebKit2GTK XWayland compositor bug it
|
||||
// worked around appears fixed in WebKit2GTK >= 2.42; leaving it
|
||||
// unset preserves WebKit hardware acceleration on Device / Setup
|
||||
// Wizard / login / store. The default path still applies it on
|
||||
// XWayland sessions as a conservative fallback for older WebKit.
|
||||
// ------------------------------------------------------------------
|
||||
{
|
||||
const char* wayland_env = ::getenv("WAYLAND_DISPLAY");
|
||||
if (wayland_env && *wayland_env) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Wayland detected but wxWidgets has no EGL support (wxHAS_EGL is OFF). Forcing X11 backend.";
|
||||
::setenv("GDK_BACKEND", "x11", true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
const char* gdk_backend = ::getenv("GDK_BACKEND");
|
||||
// Match "x11" and comma-prefixed forms like "x11,wayland" (GTK
|
||||
// honours the first backend in the comma-separated list).
|
||||
const bool x11_opt_in = gdk_backend && boost::starts_with(gdk_backend, "x11");
|
||||
|
||||
// WebKit2GTK compositing can fail under XWayland. Only disable it when
|
||||
// both DISPLAY and WAYLAND_DISPLAY are set (i.e., XWayland is in use).
|
||||
// On pure X11 or native Wayland, compositing is left enabled.
|
||||
{
|
||||
const char* display_env_wk = ::getenv("DISPLAY");
|
||||
const char* wayland_env_wk = ::getenv("WAYLAND_DISPLAY");
|
||||
if (display_env_wk && *display_env_wk && wayland_env_wk && *wayland_env_wk) {
|
||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false);
|
||||
}
|
||||
}
|
||||
if (x11_opt_in) {
|
||||
// ===== X11 / XWayland (opted in via GDK_BACKEND=x11) =====
|
||||
BOOST_LOG_TRIVIAL(info) << "GDK_BACKEND=x11 detected; applying v2.3.2 X11/XWayland workarounds.";
|
||||
|
||||
// XInitThreads is needed before GStreamer may use Xlib. On native
|
||||
// Wayland without DISPLAY, GStreamer uses waylandsink (no Xlib).
|
||||
#if __has_include(<X11/Xlib.h>)
|
||||
{
|
||||
const char* display_env = ::getenv("DISPLAY");
|
||||
if (display_env && *display_env) {
|
||||
// On Linux dual-GPU systems, request the high-performance
|
||||
// discrete GPU. DRI_PRIME=1 handles AMD and nouveau PRIME
|
||||
// setups; harmless on single-GPU systems (Mesa ignores it).
|
||||
::setenv("DRI_PRIME", "1", /* replace */ false);
|
||||
|
||||
// For NVIDIA proprietary driver PRIME render offload, set
|
||||
// additional variables. Only set if the NVIDIA kernel module
|
||||
// is loaded to avoid breaking systems without NVIDIA.
|
||||
if (::access("/proc/driver/nvidia/version", F_OK) == 0) {
|
||||
::setenv("__NV_PRIME_RENDER_OFFLOAD", "1", /* replace */ false);
|
||||
::setenv("__GLX_VENDOR_LIBRARY_NAME", "nvidia", /* replace */ false);
|
||||
}
|
||||
|
||||
// Tell Xlib we will be using threads, lest we crash when
|
||||
// GStreamer fires up. Safe to call here since the user has
|
||||
// explicitly opted into X11.
|
||||
#if __has_include(<X11/Xlib.h>)
|
||||
XInitThreads();
|
||||
#endif
|
||||
} else {
|
||||
// ===== Wayland default =====
|
||||
|
||||
// Safety fallback: if wxWidgets was not built with EGL
|
||||
// support, native Wayland will crash in
|
||||
// wxGLCanvas::IsDisplaySupported() because the GLX backend
|
||||
// cannot access an X11 display. Force X11 mode in that case.
|
||||
// NOTE: Do NOT remove this block even after enabling
|
||||
// wxHAS_EGL in the build — it protects against builds where
|
||||
// deps were not rebuilt.
|
||||
#if !defined(wxHAS_EGL) || !wxHAS_EGL
|
||||
{
|
||||
const char* wayland_env = ::getenv("WAYLAND_DISPLAY");
|
||||
if (wayland_env && *wayland_env) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Wayland detected but wxWidgets has no EGL support (wxHAS_EGL is OFF). Forcing X11 backend.";
|
||||
::setenv("GDK_BACKEND", "x11", true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// WebKit2GTK compositing can fail under XWayland on older
|
||||
// WebKit releases. Disable it only when both DISPLAY and
|
||||
// WAYLAND_DISPLAY are set (i.e. an XWayland session is in
|
||||
// use). On pure X11 or native Wayland, compositing is left
|
||||
// enabled so WebKit retains HW acceleration.
|
||||
{
|
||||
const char* display_env_wk = ::getenv("DISPLAY");
|
||||
const char* wayland_env_wk = ::getenv("WAYLAND_DISPLAY");
|
||||
if (display_env_wk && *display_env_wk && wayland_env_wk && *wayland_env_wk) {
|
||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false);
|
||||
}
|
||||
}
|
||||
|
||||
// XInitThreads is needed before GStreamer may use Xlib. On
|
||||
// native Wayland without DISPLAY, GStreamer uses waylandsink
|
||||
// (no Xlib involved), so the call is skipped.
|
||||
#if __has_include(<X11/Xlib.h>)
|
||||
{
|
||||
const char* display_env = ::getenv("DISPLAY");
|
||||
if (display_env && *display_env) {
|
||||
XInitThreads();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif // __WXGTK__
|
||||
|
||||
// Switch boost::filesystem to utf8.
|
||||
try {
|
||||
@@ -1356,6 +1422,10 @@ int CLI::run(int argc, char **argv)
|
||||
else {
|
||||
set_logging_level(2);
|
||||
}
|
||||
const ConfigOptionString* opt_logfile = m_config.opt<ConfigOptionString>("logfile");
|
||||
if (opt_logfile) {
|
||||
set_logging_file(opt_logfile->value);
|
||||
}
|
||||
|
||||
global_begin_time = (long long)Slic3r::Utils::get_current_time_utc();
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("cli mode, Current OrcaSlicer Version %1%")%SoftFever_VERSION;
|
||||
@@ -1605,6 +1675,10 @@ int CLI::run(int argc, char **argv)
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("old 3mf version %1%, need to set enable_wrapping_detection to false")%file_version.to_string();
|
||||
}
|
||||
|
||||
// ORCA: legacy feature-filament default migration (1 -> 0) is now handled
|
||||
// uniformly in PrintConfigDef::handle_legacy() via the old->new key rename
|
||||
// (wall_filament -> wall_filament_id, etc.), which covers presets too.
|
||||
|
||||
if (normative_check) {
|
||||
ConfigOptionStrings* postprocess_scripts = config.option<ConfigOptionStrings>("post_process");
|
||||
if (postprocess_scripts) {
|
||||
@@ -5984,9 +6058,9 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
(dynamic_cast<Print*>(print))->is_BBL_printer() = is_bbl_vendor_preset;
|
||||
|
||||
StringObjectException warning;
|
||||
std::vector<StringObjectException> warnings;
|
||||
print_fff->set_check_multi_filaments_compatibility(!allow_mix_temp);
|
||||
auto err = print->validate(&warning);
|
||||
auto err = print->validate(&warnings);
|
||||
if (!err.string.empty()) {
|
||||
if ((STRING_EXCEPT_LAYER_HEIGHT_EXCEEDS_LIMIT == err.type) && no_check) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "got warnings: "<< err.string << std::endl;
|
||||
@@ -6020,8 +6094,9 @@ int CLI::run(int argc, char **argv)
|
||||
flush_and_exit(validate_error);
|
||||
}
|
||||
}
|
||||
else if (!warning.string.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "got warnings: "<< warning.string << std::endl;
|
||||
else if (!warnings.empty()) {
|
||||
for (const auto& w : warnings)
|
||||
BOOST_LOG_TRIVIAL(warning) << "got warnings: "<< w.string << std::endl;
|
||||
}
|
||||
|
||||
if (print->empty()) {
|
||||
@@ -6041,8 +6116,14 @@ int CLI::run(int argc, char **argv)
|
||||
BOOST_LOG_TRIVIAL(info) << "set print's callback to cli_status_callback.";
|
||||
print->set_status_callback(cli_status_callback);
|
||||
g_cli_callback_mgr.set_plate_info(index+1, (plate_to_slice== 0)?partplate_list.get_plate_count():1);
|
||||
if (!warning.string.empty()) {
|
||||
PrintBase::SlicingStatus slicing_status{4, warning.string, 0, 0};
|
||||
if (!warnings.empty()) {
|
||||
std::string warning_text;
|
||||
for (const auto& w : warnings) {
|
||||
if (!warning_text.empty())
|
||||
warning_text += "\n";
|
||||
warning_text += w.string;
|
||||
}
|
||||
PrintBase::SlicingStatus slicing_status{4, warning_text, 0, 0};
|
||||
cli_status_callback(slicing_status);
|
||||
}
|
||||
else {
|
||||
@@ -7459,6 +7540,13 @@ extern "C" {
|
||||
#else /* _MSC_VER */
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
// Ignore SIGPIPE so a write to a closed socket (e.g. a dropped printer
|
||||
// network connection) returns EPIPE to the caller instead of terminating
|
||||
// the whole process. Without this, losing the printer link kills
|
||||
// OrcaSlicer with SIGPIPE (exit 141) and produces no crash report.
|
||||
std::signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
return CLI().run(argc, argv);
|
||||
}
|
||||
#endif /* _MSC_VER */
|
||||
@@ -28,17 +28,34 @@ function(encoding_check TARGET)
|
||||
# Obtain target source files
|
||||
get_target_property(T_SOURCES ${TARGET} SOURCES)
|
||||
|
||||
# Define top-level encoding check target for this ${TARGET}
|
||||
add_custom_target(encoding-check-${TARGET}
|
||||
DEPENDS encoding-check ${T_SOURCES}
|
||||
COMMAND $<TARGET_FILE:encoding-check> ${TARGET} ${T_SOURCES}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
set(STAMP_FILE "${CMAKE_CURRENT_BINARY_DIR}/encoding-check-${TARGET}.stamp")
|
||||
set(RSP_FILE "${CMAKE_CURRENT_BINARY_DIR}/encoding-check-${TARGET}.rsp")
|
||||
|
||||
# Resolve absolute paths
|
||||
set(ABS_SOURCES "")
|
||||
foreach(file ${T_SOURCES})
|
||||
if(IS_ABSOLUTE "${file}")
|
||||
list(APPEND ABS_SOURCES "${file}")
|
||||
else()
|
||||
list(APPEND ABS_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/${file}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Write response file at configure time
|
||||
string(REPLACE ";" "\n" RSP_CONTENT "${ABS_SOURCES}")
|
||||
file(WRITE "${RSP_FILE}" "${RSP_CONTENT}")
|
||||
|
||||
# Single process call via response file — no command line length limit
|
||||
add_custom_command(
|
||||
OUTPUT "${STAMP_FILE}"
|
||||
DEPENDS encoding-check ${ABS_SOURCES}
|
||||
COMMENT "Checking source files encodings for target ${TARGET}"
|
||||
COMMAND $<TARGET_FILE:encoding-check> ${TARGET} "@${RSP_FILE}"
|
||||
COMMAND ${CMAKE_COMMAND} -E touch "${STAMP_FILE}"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
|
||||
# This adds dependency on encoding-check-${TARGET} to ${TARET}
|
||||
# via the global-encoding-check
|
||||
add_custom_target(encoding-check-${TARGET} DEPENDS "${STAMP_FILE}")
|
||||
add_dependencies(global-encoding-check encoding-check-${TARGET})
|
||||
add_dependencies(${TARGET} global-encoding-check)
|
||||
endif()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <string>
|
||||
|
||||
/*
|
||||
* The utf8_check() function scans the '\0'-terminated string starting
|
||||
@@ -68,63 +68,97 @@ unsigned char *utf8_check(unsigned char *s)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static const char* target;
|
||||
|
||||
void error_exit(const char* error, const char* filename)
|
||||
{
|
||||
std::cerr << "\n\tError: " << error << ": " << filename << "\n"
|
||||
<< "\tTarget: " << target << "\n"
|
||||
<< std::endl;
|
||||
std::exit(-2);
|
||||
}
|
||||
|
||||
|
||||
void utf8_check_file(const char* filename)
|
||||
// ORCA
|
||||
bool check_file(const char* target, const char* filename)
|
||||
{
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
const auto size = file.tellg();
|
||||
|
||||
if (size == 0) {
|
||||
return;
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "\n\tError: Could not open file: " << filename
|
||||
<< "\n\tTarget: " << target << "\n" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streampos pos = file.tellg();
|
||||
|
||||
// Fix: tellg() returns -1 (cast to streampos) on failure.
|
||||
// Also guard against implausibly large files before casting to size_t.
|
||||
if (pos < 0) {
|
||||
std::cerr << "\n\tError: Could not determine file size: " << filename
|
||||
<< "\n\tTarget: " << target << "\n" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto size = static_cast<std::size_t>(pos);
|
||||
|
||||
if (size == 0)
|
||||
return true;
|
||||
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::vector<char> buffer(size);
|
||||
|
||||
if (file.read(buffer.data(), size)) {
|
||||
buffer.push_back('\0');
|
||||
|
||||
// Check UTF-8 validity
|
||||
if (utf8_check(reinterpret_cast<unsigned char*>(buffer.data())) != nullptr) {
|
||||
error_exit("Source file does not contain (valid) UTF-8", filename);
|
||||
}
|
||||
|
||||
// Check against a BOM mark
|
||||
if (buffer.size() >= 3
|
||||
&& buffer[0] == '\xef'
|
||||
&& buffer[1] == '\xbb'
|
||||
&& buffer[2] == '\xbf') {
|
||||
error_exit("Source file is valid UTF-8 but contains a BOM mark", filename);
|
||||
}
|
||||
} else {
|
||||
error_exit("Could not read source file", filename);
|
||||
// Fix: cast size to streamsize explicitly — streampos and streamsize are
|
||||
// distinct types and passing streampos where streamsize is expected is
|
||||
// implementation-defined behaviour on some platforms.
|
||||
if (!file.read(buffer.data(), static_cast<std::streamsize>(size))) {
|
||||
std::cerr << "\n\tError: Could not read source file: " << filename
|
||||
<< "\n\tTarget: " << target << "\n" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.push_back('\0');
|
||||
|
||||
if (utf8_check(reinterpret_cast<unsigned char*>(buffer.data())) != nullptr) {
|
||||
std::cerr << "\n\tError: Source file does not contain (valid) UTF-8: " << filename
|
||||
<< "\n\tTarget: " << target << "\n" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer.size() >= 3
|
||||
&& buffer[0] == '\xef'
|
||||
&& buffer[1] == '\xbb'
|
||||
&& buffer[2] == '\xbf') {
|
||||
std::cerr << "\n\tError: Source file is valid UTF-8 but contains a BOM mark: " << filename
|
||||
<< "\n\tTarget: " << target << "\n" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char const *argv[])
|
||||
{
|
||||
if (argc < 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " <program/library> <file[s]>" << std::endl;
|
||||
std::cerr << "Usage: " << argv[0] << " <target> <file|@responsefile> [...]" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
target = argv[1];
|
||||
const char* target = argv[1];
|
||||
|
||||
// Collect all files — support @responsefile syntax
|
||||
std::vector<std::string> files;
|
||||
for (int i = 2; i < argc; i++) {
|
||||
utf8_check_file(argv[i]);
|
||||
if (argv[i][0] == '@') {
|
||||
// Response file — read paths from it, one per line
|
||||
std::ifstream rsp(argv[i] + 1);
|
||||
if (!rsp.is_open()) {
|
||||
std::cerr << "Could not open response file: " << (argv[i]+1) << std::endl;
|
||||
return -1;
|
||||
}
|
||||
std::string line;
|
||||
while (std::getline(rsp, line)) {
|
||||
if (!line.empty())
|
||||
files.push_back(line);
|
||||
}
|
||||
} else {
|
||||
files.push_back(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
bool all_ok = true;
|
||||
for (const auto& f : files) {
|
||||
if (!check_file(target, f.c_str()))
|
||||
all_ok = false;
|
||||
}
|
||||
|
||||
return all_ok ? 0 : -2;
|
||||
}
|
||||
@@ -266,10 +266,44 @@ std::vector<WaveSeed> wave_seeds(
|
||||
//(front.z() < 0 && back.z() < 0));
|
||||
// Hope that at least one end of an open polyline is clipped by the boundary, thus an intersection point is created.
|
||||
(front.z() < 0 || back.z() < 0));
|
||||
// However, with complex geometry, both endpoints may coincide with existing polygon
|
||||
// vertices (z >= 0), which is handled below.
|
||||
|
||||
if (front != back && front.z() >= 0 && back.z() >= 0) {
|
||||
// Very rare case when both endpoints intersect boundary ExPolygons in existing points.
|
||||
// So the ZFillFunction callback hasn't been called.
|
||||
// Both endpoints coincide with existing polygon vertices, so the
|
||||
// ZFillFunction callback was never called. With complex geometry
|
||||
// this is common because source and boundary contours share many
|
||||
// vertices. Determine src_id / boundary_id from Z coordinates
|
||||
// (and fall back to an AABB-tree point-in-polygon test when a
|
||||
// boundary ID is not directly available).
|
||||
coord_t src_z = -1, boundary_z = -1;
|
||||
// Scan all path points for the information we need.
|
||||
for (const ClipperLib_Z::IntPoint &point : path) {
|
||||
if (point.z() >= idx_boundary_end && point.z() < idx_src_end && src_z < 0)
|
||||
src_z = point.z();
|
||||
else if (point.z() >= idx_boundary_begin && point.z() < idx_boundary_end && boundary_z < 0)
|
||||
boundary_z = point.z();
|
||||
if (src_z >= 0 && boundary_z >= 0)
|
||||
break;
|
||||
}
|
||||
if (src_z >= 0) {
|
||||
uint32_t src_id = uint32_t(src_z - idx_boundary_end);
|
||||
if (boundary_z >= 0) {
|
||||
out.push_back({ src_id, uint32_t(boundary_z - 1), ClipperZUtils::from_zpath(path) });
|
||||
} else {
|
||||
// Source ID known but boundary unknown – use AABB tree.
|
||||
if (aabb_tree.empty())
|
||||
aabb_tree = build_aabb_tree_over_expolygons(boundary);
|
||||
int boundary_id = sample_in_expolygons(aabb_tree, boundary, Point(front.x(), front.y()));
|
||||
if (boundary_id >= 0)
|
||||
out.push_back({ src_id, uint32_t(boundary_id), ClipperZUtils::from_zpath(path) });
|
||||
}
|
||||
++ iseed;
|
||||
continue;
|
||||
}
|
||||
// Unable to determine source ID – drop the segment.
|
||||
continue;
|
||||
} else
|
||||
if (front == back && (front.z() < idx_boundary_end)) {
|
||||
|
||||
@@ -93,6 +93,11 @@ bool AppConfig::get_stealth_mode()
|
||||
return get_bool("stealth_mode");
|
||||
}
|
||||
|
||||
bool AppConfig::get_hide_login_side_panel()
|
||||
{
|
||||
return get_bool("hide_login_side_panel");
|
||||
}
|
||||
|
||||
void AppConfig::reset()
|
||||
{
|
||||
m_storage.clear();
|
||||
@@ -259,6 +264,24 @@ void AppConfig::set_defaults()
|
||||
if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty())
|
||||
set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false);
|
||||
|
||||
if (get(SETTING_OPENGL_REALISTIC_MODE).empty())
|
||||
set_bool(SETTING_OPENGL_REALISTIC_MODE, false);
|
||||
|
||||
if (get(SETTING_OPENGL_REALISTIC_PHONG).empty())
|
||||
set_bool(SETTING_OPENGL_REALISTIC_PHONG, true);
|
||||
|
||||
if (get(SETTING_OPENGL_SHADING_MODEL).empty())
|
||||
set(SETTING_OPENGL_SHADING_MODEL, "gouraud");
|
||||
|
||||
if (get(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS).empty())
|
||||
set_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS, false);
|
||||
|
||||
if (get(SETTING_OPENGL_PHONG_SMOOTH_NORMALS).empty())
|
||||
set_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS, false);
|
||||
|
||||
if (get(SETTING_OPENGL_PHONG_SSAO).empty())
|
||||
set_bool(SETTING_OPENGL_PHONG_SSAO, false);
|
||||
|
||||
if (get("export_sources_full_pathnames").empty())
|
||||
set_bool("export_sources_full_pathnames", false);
|
||||
|
||||
@@ -321,6 +344,9 @@ void AppConfig::set_defaults()
|
||||
if (get("developer_mode").empty())
|
||||
set_bool("developer_mode", false);
|
||||
|
||||
if (get("show_unsupported_presets").empty())
|
||||
set_bool("show_unsupported_presets", false);
|
||||
|
||||
if (get("enable_ssl_for_mqtt").empty())
|
||||
set_bool("enable_ssl_for_mqtt", true);
|
||||
|
||||
@@ -347,6 +373,9 @@ void AppConfig::set_defaults()
|
||||
if (get("stealth_mode").empty()) {
|
||||
set_bool("stealth_mode", false);
|
||||
}
|
||||
if (get("hide_login_side_panel").empty()) {
|
||||
set_bool("hide_login_side_panel", false);
|
||||
}
|
||||
if (get("allow_abnormal_storage").empty()) {
|
||||
set_bool("allow_abnormal_storage", false);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,12 @@ using namespace nlohmann;
|
||||
#define SETTING_OPENGL_FXAA_ENABLED "opengl_fxaa_enabled"
|
||||
#define SETTING_OPENGL_FPS_CAP "opengl_fps_cap"
|
||||
#define SETTING_OPENGL_SHOW_FPS_OVERLAY "opengl_show_fps_overlay"
|
||||
#define SETTING_OPENGL_REALISTIC_MODE "opengl_realistic_mode"
|
||||
#define SETTING_OPENGL_REALISTIC_PHONG "opengl_realistic_phong"
|
||||
#define SETTING_OPENGL_SHADING_MODEL "opengl_shading_model"
|
||||
#define SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS "opengl_phong_basic_plate_shadows"
|
||||
#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao"
|
||||
#define SETTING_OPENGL_PHONG_SMOOTH_NORMALS "opengl_phong_smooth_normals"
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
|
||||
@@ -85,6 +91,7 @@ public:
|
||||
std::string get_language_code();
|
||||
std::string get_hms_host();
|
||||
bool get_stealth_mode();
|
||||
bool get_hide_login_side_panel();
|
||||
|
||||
// Clear and reset to defaults.
|
||||
void reset();
|
||||
|
||||
@@ -26,7 +26,13 @@ std::string WideningBeadingStrategy::toString() const
|
||||
|
||||
WideningBeadingStrategy::Beading WideningBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
if (thickness < optimal_width) {
|
||||
// Use getTransitionThickness(1) to determine if this is a thin wall that should produce
|
||||
// a single bead. This ensures consistency with getOptimalBeadCount() which uses the same
|
||||
// threshold (via RedistributeBeadingStrategy) to decide between 1 and 2 beads.
|
||||
// Previously used optimal_width which could differ from the outer wall width used in
|
||||
// bead count calculations, causing inconsistency where bead_count=2 was requested but
|
||||
// only 1 bead was produced.
|
||||
if (thickness < getTransitionThickness(1)) {
|
||||
Beading ret;
|
||||
ret.total_thickness = thickness;
|
||||
if (thickness >= min_input_width) {
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Slic3r::Arachne::LinearAlg2D
|
||||
{
|
||||
|
||||
/*!
|
||||
* Returns the determinant of the 2D matrix defined by the the vectors ab and ap as rows.
|
||||
* Returns the determinant of the 2D matrix defined by the vectors ab and ap as rows.
|
||||
*
|
||||
* The returned value is zero for \p p lying (approximately) on the line going through \p a and \p b
|
||||
* The value is positive for values lying to the left and negative for values lying to the right when looking from \p a to \p b.
|
||||
|
||||
+17
-2
@@ -879,7 +879,9 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
|
||||
std::vector<std::pair<ObjectID, unsigned int>> &objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders)
|
||||
std::vector<unsigned int>& printExtruders,
|
||||
std::map<ObjectID, ExPolygons>* objectBrimAreasOut,
|
||||
std::map<ObjectID, ExPolygons>* supportBrimAreasOut)
|
||||
{
|
||||
std::map<ObjectID, double> brim_width_map;
|
||||
std::map<ObjectID, ExPolygons> brimAreaMap;
|
||||
@@ -928,12 +930,25 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
||||
for (size_t iia = 0; iia < islands_area.size(); ++iia)
|
||||
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;
|
||||
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);
|
||||
|
||||
const bool combine_brims = print.config().combine_brims.value;
|
||||
const bool is_by_object = (print.config().print_sequence == PrintSequence::ByObject);
|
||||
const bool can_combine_brims = combine_brims && !is_by_object;
|
||||
|
||||
if (!can_combine_brims) {
|
||||
// Orca: Generate brims separately for each object when multiple extruders are used
|
||||
// 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)));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef slic3r_Brim_hpp_
|
||||
#define slic3r_Brim_hpp_
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
#include<map>
|
||||
@@ -19,7 +20,9 @@ void make_brim(const Print& print, PrintTryCancel try_cancel,
|
||||
Polygons& islands_area, std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
|
||||
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders);
|
||||
std::vector<unsigned int>& printExtruders,
|
||||
std::map<ObjectID, ExPolygons>* objectBrimAreasOut = nullptr,
|
||||
std::map<ObjectID, ExPolygons>* supportBrimAreasOut = nullptr);
|
||||
|
||||
// BBS: automatically make brim
|
||||
ExtrusionEntityCollection make_brim_auto(const Print &print, PrintTryCancel try_cancel, Polygons &islands_area);
|
||||
|
||||
@@ -520,7 +520,7 @@ ClipperLib::Paths expolygon_offset(const Slic3r::ExPolygon &expolygon, const flo
|
||||
|
||||
// This is a safe variant of the polygons offset, tailored for multiple ExPolygons.
|
||||
// It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours.
|
||||
// Each ExPolygon is offsetted separately. For outer offset, the the offsetted ExPolygons shall be united outside of this function.
|
||||
// Each ExPolygon is offsetted separately. For outer offset, the offsetted ExPolygons shall be united outside of this function.
|
||||
template<typename ExPolygonVector>
|
||||
static std::pair<ClipperLib::Paths, size_t> expolygons_offset_raw(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
|
||||
@@ -2192,7 +2192,7 @@ private:
|
||||
else
|
||||
throw ConfigurationError("Serializing NaN");
|
||||
}
|
||||
else {
|
||||
else if (this->keys_map != nullptr) {
|
||||
for (const auto& kvp : *this->keys_map)
|
||||
if (kvp.second == v)
|
||||
ss << kvp.first;
|
||||
|
||||
@@ -118,6 +118,9 @@ public:
|
||||
virtual void reverse() = 0;
|
||||
virtual Point first_point() const = 0;
|
||||
virtual Point last_point() const = 0;
|
||||
virtual const Point3& first_point3() const = 0;
|
||||
virtual const Point3& last_point3() const = 0;
|
||||
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0;
|
||||
@@ -253,9 +256,9 @@ public:
|
||||
ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); }
|
||||
void reverse() override { this->polyline.reverse(); }
|
||||
Point first_point() const override { return this->polyline.points.front().to_point(); }
|
||||
Point3 first_point3() const { return this->polyline.points.front(); }
|
||||
const Point3& first_point3() const override { return this->polyline.points.front(); }
|
||||
Point last_point() const override { return this->polyline.points.back().to_point(); }
|
||||
Point3 last_point3() const { return this->polyline.points.back(); }
|
||||
const Point3& last_point3() const override { return this->polyline.points.back(); }
|
||||
size_t size() const { return this->polyline.size(); }
|
||||
bool empty() const { return this->polyline.empty(); }
|
||||
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
|
||||
@@ -403,7 +406,9 @@ public:
|
||||
ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); }
|
||||
void reverse() override;
|
||||
Point first_point() const override { return this->paths.front().polyline.points.front().to_point(); }
|
||||
const Point3& first_point3() const override { return this->paths.front().polyline.points.front(); }
|
||||
Point last_point() const override { return this->paths.back().polyline.points.back().to_point(); }
|
||||
const Point3& last_point3() const override { return this->paths.back().polyline.points.back(); }
|
||||
size_t size() const { return this->paths.size(); }
|
||||
bool empty() const { return this->paths.empty(); }
|
||||
double length() const override;
|
||||
@@ -459,7 +464,9 @@ public:
|
||||
bool is_counter_clockwise() { return this->polygon().is_counter_clockwise(); }
|
||||
void reverse() override;
|
||||
Point first_point() const override { return this->paths.front().polyline.points.front().to_point(); }
|
||||
const Point3& first_point3() const override { return this->paths.front().polyline.points.front(); }
|
||||
Point last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back().to_point()); return this->first_point(); }
|
||||
const Point3& last_point3() const override { assert(this->first_point3() == this->paths.back().polyline.points.back()); return this->first_point3(); }
|
||||
Polygon polygon() const;
|
||||
double length() const override;
|
||||
bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled<double>(0.001));
|
||||
@@ -510,7 +517,7 @@ public:
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool validate() const {
|
||||
assert(this->first_point() == this->paths.back().polyline.points.back());
|
||||
assert(this->first_point3() == this->paths.back().polyline.points.back());
|
||||
for (size_t i = 1; i < paths.size(); ++ i)
|
||||
assert(this->paths[i - 1].polyline.points.back() == this->paths[i].polyline.points.front());
|
||||
return true;
|
||||
|
||||
@@ -119,7 +119,10 @@ public:
|
||||
{ return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); }
|
||||
void reverse() override;
|
||||
Point first_point() const override { return this->entities.front()->first_point(); }
|
||||
const Point3& first_point3() const override { return this->entities.front()->first_point3(); }
|
||||
Point last_point() const override { return this->entities.back()->last_point(); }
|
||||
const Point3& last_point3() const override { return this->entities.back()->last_point3(); }
|
||||
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
|
||||
|
||||
+62
-50
@@ -256,17 +256,15 @@ struct SurfaceFillParams
|
||||
|
||||
// Index of this entry in a linear vector.
|
||||
size_t idx = 0;
|
||||
// infill speed settings
|
||||
float sparse_infill_speed = 0;
|
||||
float top_surface_speed = 0;
|
||||
float solid_infill_speed = 0;
|
||||
// Infill speed setting for the effective extrusion role.
|
||||
float role_speed = 0;
|
||||
|
||||
// Params for lattice infill angles
|
||||
float lateral_lattice_angle_1 = 0.f;
|
||||
float lateral_lattice_angle_2 = 0.f;
|
||||
float infill_lock_depth = 0;
|
||||
float skin_infill_depth = 0;
|
||||
bool symmetric_infill_y_axis = false;
|
||||
float infill_lock_depth = 0;
|
||||
float skin_infill_depth = 0;
|
||||
bool symmetric_infill_y_axis = false;
|
||||
|
||||
// Params for Lateral honeycomb
|
||||
float infill_overhang_angle = 60.f;
|
||||
@@ -298,9 +296,7 @@ struct SurfaceFillParams
|
||||
RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter());
|
||||
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, bridge);
|
||||
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role);
|
||||
RETURN_COMPARE_NON_EQUAL(sparse_infill_speed);
|
||||
RETURN_COMPARE_NON_EQUAL(top_surface_speed);
|
||||
RETURN_COMPARE_NON_EQUAL(solid_infill_speed);
|
||||
RETURN_COMPARE_NON_EQUAL(role_speed);
|
||||
RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_1);
|
||||
RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_2);
|
||||
RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis);
|
||||
@@ -312,30 +308,28 @@ struct SurfaceFillParams
|
||||
}
|
||||
|
||||
bool operator==(const SurfaceFillParams &rhs) const {
|
||||
return this->extruder == rhs.extruder &&
|
||||
this->pattern == rhs.pattern &&
|
||||
this->spacing == rhs.spacing &&
|
||||
this->overlap == rhs.overlap &&
|
||||
this->angle == rhs.angle &&
|
||||
this->fixed_angle == rhs.fixed_angle &&
|
||||
this->bridge == rhs.bridge &&
|
||||
this->bridge_angle == rhs.bridge_angle &&
|
||||
this->density == rhs.density &&
|
||||
this->multiline == rhs.multiline &&
|
||||
// this->dont_adjust == rhs.dont_adjust &&
|
||||
this->anchor_length == rhs.anchor_length &&
|
||||
this->anchor_length_max == rhs.anchor_length_max &&
|
||||
this->flow == rhs.flow &&
|
||||
this->extrusion_role == rhs.extrusion_role &&
|
||||
this->sparse_infill_speed == rhs.sparse_infill_speed &&
|
||||
this->top_surface_speed == rhs.top_surface_speed &&
|
||||
this->solid_infill_speed == rhs.solid_infill_speed &&
|
||||
this->lateral_lattice_angle_1 == rhs.lateral_lattice_angle_1 &&
|
||||
this->lateral_lattice_angle_2 == rhs.lateral_lattice_angle_2 &&
|
||||
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;
|
||||
return this->extruder == rhs.extruder &&
|
||||
this->pattern == rhs.pattern &&
|
||||
this->spacing == rhs.spacing &&
|
||||
this->overlap == rhs.overlap &&
|
||||
this->angle == rhs.angle &&
|
||||
this->fixed_angle == rhs.fixed_angle &&
|
||||
this->bridge == rhs.bridge &&
|
||||
this->bridge_angle == rhs.bridge_angle &&
|
||||
this->density == rhs.density &&
|
||||
this->multiline == rhs.multiline &&
|
||||
// this->dont_adjust == rhs.dont_adjust &&
|
||||
this->anchor_length == rhs.anchor_length &&
|
||||
this->anchor_length_max == rhs.anchor_length_max &&
|
||||
this->flow == rhs.flow &&
|
||||
this->extrusion_role == rhs.extrusion_role &&
|
||||
this->role_speed == rhs.role_speed &&
|
||||
this->lateral_lattice_angle_1 == rhs.lateral_lattice_angle_1 &&
|
||||
this->lateral_lattice_angle_2 == rhs.lateral_lattice_angle_2 &&
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -922,6 +916,12 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.extrusion_role = erSolidInfill;
|
||||
}
|
||||
}
|
||||
if (params.extrusion_role == erTopSolidInfill)
|
||||
params.extruder = region_config.top_surface_filament_id;
|
||||
else if (params.extrusion_role == erBottomSurface)
|
||||
params.extruder = region_config.bottom_surface_filament_id;
|
||||
else if (params.extrusion_role == erSolidInfill)
|
||||
params.extruder = region_config.internal_solid_filament_id;
|
||||
// Orca: apply fill multiline only for sparse infill
|
||||
params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1;
|
||||
|
||||
@@ -941,10 +941,13 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.fixed_angle = !region_config.solid_infill_rotate_template.value.empty();
|
||||
}
|
||||
params.bridge_angle = float(surface.bridge_angle);
|
||||
|
||||
|
||||
// ORCA: Align infill angle to model
|
||||
float align_offset = 0.f;
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
auto m = layer.object()->trafo().matrix();
|
||||
params.angle += atan2((float) m(1, 0), (float) m(0, 0));
|
||||
align_offset = atan2((float)m(1, 0), (float)m(0, 0));
|
||||
params.angle += align_offset;
|
||||
}
|
||||
|
||||
// Calculate the actual flow we'll be using for this infill.
|
||||
@@ -954,15 +957,18 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
//Orca: enable thick bridge based on config
|
||||
layerm.bridging_flow(extrusion_role, is_thick_bridge) :
|
||||
layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
|
||||
// record speed params
|
||||
if (!params.bridge) {
|
||||
if (params.extrusion_role == erInternalInfill)
|
||||
params.sparse_infill_speed = region_config.sparse_infill_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erTopSolidInfill) {
|
||||
params.top_surface_speed = region_config.top_surface_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
} else if (params.extrusion_role == erSolidInfill)
|
||||
params.solid_infill_speed = region_config.internal_solid_infill_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
}
|
||||
|
||||
params.role_speed = 0;
|
||||
if (params.extrusion_role == erBridgeInfill)
|
||||
params.role_speed = region_config.bridge_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erInternalBridgeInfill)
|
||||
params.role_speed = region_config.get_abs_value_at("internal_bridge_speed", layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erInternalInfill)
|
||||
params.role_speed = region_config.sparse_infill_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erTopSolidInfill)
|
||||
params.role_speed = region_config.top_surface_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erSolidInfill)
|
||||
params.role_speed = region_config.internal_solid_infill_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
// Calculate flow spacing for infill pattern generation.
|
||||
if (surface.is_solid() || is_bridge) {
|
||||
params.spacing = params.flow.spacing();
|
||||
@@ -1027,6 +1033,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
if (fill.region_id == size_t(-1)) {
|
||||
fill.region_id = region_id;
|
||||
fill.surface = surface;
|
||||
fill.surface.bridge_angle = params->bridge_angle;
|
||||
fill.expolygons.emplace_back(std::move(fill.surface.expolygon));
|
||||
//BBS
|
||||
fill.region_id_group.push_back(region_id);
|
||||
@@ -1570,18 +1577,18 @@ void Layer::make_ironing()
|
||||
((config.top_shell_layers > 0 || (this->object()->print()->config().spiral_mode && config.bottom_shell_layers > 1)) &&
|
||||
(config.ironing_type == IroningType::TopSurfaces ||
|
||||
(config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) {
|
||||
if (config.wall_filament == config.solid_infill_filament || config.wall_loops == 0) {
|
||||
if (config.outer_wall_filament_id == config.top_surface_filament_id || config.wall_loops == 0) {
|
||||
// Iron the whole face.
|
||||
ironing_params.extruder = config.solid_infill_filament;
|
||||
ironing_params.extruder = config.top_surface_filament_id;
|
||||
} else {
|
||||
// Iron just the infill.
|
||||
ironing_params.extruder = config.solid_infill_filament;
|
||||
ironing_params.extruder = config.top_surface_filament_id;
|
||||
}
|
||||
}
|
||||
if (ironing_params.extruder != -1) {
|
||||
//TODO just_infill is currently not used.
|
||||
ironing_params.just_infill = false;
|
||||
// Get filament-specific overrides if configured, otherwise use default values
|
||||
// ORCA: Get filament-specific overrides if configured, otherwise use process values
|
||||
size_t extruder_idx = ironing_params.extruder - 1;
|
||||
ironing_params.line_spacing = (!config.filament_ironing_spacing.is_nil(extruder_idx)
|
||||
? config.filament_ironing_spacing.get_at(extruder_idx)
|
||||
@@ -1595,7 +1602,12 @@ void Layer::make_ironing()
|
||||
ironing_params.speed = (!config.filament_ironing_speed.is_nil(extruder_idx)
|
||||
? config.filament_ironing_speed.get_at(extruder_idx)
|
||||
: config.ironing_speed);
|
||||
ironing_params.angle = (config.ironing_angle_fixed ? 0 : calculate_infill_rotation_angle(this->object(), this->id(), config.solid_infill_direction.value, config.solid_infill_rotate_template.value)) + config.ironing_angle * M_PI / 180.;
|
||||
double ironing_angle = (config.ironing_angle_fixed ? 0 : calculate_infill_rotation_angle(this->object(), this->id(), config.solid_infill_direction.value, config.solid_infill_rotate_template.value)) + config.ironing_angle * M_PI / 180.;
|
||||
if (config.align_infill_direction_to_model) {
|
||||
auto m = this->object()->trafo().matrix();
|
||||
ironing_angle += atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
ironing_params.angle = ironing_angle;
|
||||
ironing_params.fixed_angle = config.ironing_angle_fixed || !config.solid_infill_rotate_template.value.empty();
|
||||
ironing_params.pattern = config.ironing_pattern;
|
||||
ironing_params.layerm = layerm;
|
||||
|
||||
@@ -1025,7 +1025,7 @@ void mark_boundary_segments_touching_infill(
|
||||
#endif // INFILL_DEBUG_OUTPUT
|
||||
|
||||
EdgeGrid::Grid grid;
|
||||
// Make sure that the the grid is big enough for queries against the thick segment.
|
||||
// Make sure that the grid is big enough for queries against the thick segment.
|
||||
grid.set_bbox(boundary_bbox.inflated(distance_colliding * 1.43));
|
||||
// Inflate the bounding box by a thick line width.
|
||||
grid.create(boundary, coord_t(std::max(clip_distance, distance_colliding) + scale_(10.)));
|
||||
|
||||
@@ -2318,7 +2318,7 @@ static std::vector<MonotonicRegionLink> chain_monotonic_regions(
|
||||
constexpr float const pheromone_evaporation = 0.1f;
|
||||
// Evaporation rate to diversify paths taken by individual ants.
|
||||
constexpr float const pheromone_diversification = 0.1f;
|
||||
// Probability at which to take the next best path. Otherwise take the the path based on the cost distribution.
|
||||
// Probability at which to take the next best path. Otherwise take the path based on the cost distribution.
|
||||
constexpr float const probability_take_best = 0.9f;
|
||||
// Exponents of the cost function.
|
||||
constexpr float const pheromone_alpha = 1.f; // pheromone exponent
|
||||
|
||||
@@ -89,9 +89,9 @@ Generator::Generator(const PrintObject &print_object, const std::function<void()
|
||||
|
||||
m_supporting_radius = coord_t(m_infill_extrusion_width) * 100 * n_multiline / region_config.sparse_infill_density;
|
||||
|
||||
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_straightening_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_overhang_angle = region_config.lightning_overhang_angle.value * M_PI / 180.0;
|
||||
const double lightning_infill_prune_angle = region_config.lightning_prune_angle.value * M_PI / 180.0;
|
||||
const double lightning_infill_straightening_angle = region_config.lightning_straightening_angle.value * M_PI / 180.0;
|
||||
m_wall_supporting_radius = coord_t(layer_thickness * std::tan(lightning_infill_overhang_angle));
|
||||
m_prune_length = coord_t(layer_thickness * std::tan(lightning_infill_prune_angle));
|
||||
m_straightening_max_distance = coord_t(layer_thickness * std::tan(lightning_infill_straightening_angle));
|
||||
@@ -128,7 +128,7 @@ Generator::Generator(PrintObject* m_object, std::vector<Polygons>& contours, std
|
||||
//TODO: decide whether enable density controller in advanced options or not
|
||||
density = std::max(0.15f, density);
|
||||
m_supporting_radius = coord_t(m_infill_extrusion_width) / density;
|
||||
|
||||
// Keep support-lightning behavior fixed and independent of user print-region angles.
|
||||
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_straightening_angle = M_PI / 4; // 45 degrees
|
||||
|
||||
+25
-7
@@ -49,6 +49,8 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
|
||||
return frInfill;
|
||||
else if (opt_key == "internal_solid_infill_line_width")
|
||||
return frSolidInfill;
|
||||
else if (opt_key == "bridge_line_width")
|
||||
return frSolidInfill;
|
||||
else if (opt_key == "top_surface_line_width")
|
||||
return frTopSolidInfill;
|
||||
else if (opt_key == "support_line_width")
|
||||
@@ -59,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 \"%2%\" ")) % opt_key % dependent_opt_key).str());
|
||||
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());
|
||||
}
|
||||
|
||||
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
|
||||
@@ -67,6 +69,26 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionFloat
|
||||
{
|
||||
assert(opt != nullptr);
|
||||
|
||||
auto opt_nozzle_diameters = config.option<ConfigOptionFloats>("nozzle_diameter");
|
||||
if (opt_nozzle_diameters == nullptr)
|
||||
throw_on_missing_variable(opt_key, "nozzle_diameter");
|
||||
const float nozzle_diameter = float(opt_nozzle_diameters->get_at(first_printing_extruder));
|
||||
|
||||
if (opt_key == "bridge_line_width") {
|
||||
if (opt->percent) {
|
||||
const double bridge_width = opt->get_abs_value(nozzle_diameter);
|
||||
if (bridge_width > 0.)
|
||||
return bridge_width;
|
||||
} else if (opt->value > 0.) {
|
||||
return opt->value;
|
||||
}
|
||||
|
||||
opt = config.option<ConfigOptionFloatOrPercent>("internal_solid_infill_line_width");
|
||||
if (opt == nullptr)
|
||||
throw_on_missing_variable(opt_key, "internal_solid_infill_line_width");
|
||||
return extrusion_width("internal_solid_infill_line_width", opt, config, first_printing_extruder);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// This is the logic used for skit / brim, but not for the rest of the 1st layer.
|
||||
if (opt->value == 0. && first_layer) {
|
||||
@@ -84,17 +106,13 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionFloat
|
||||
throw_on_missing_variable(opt_key, "line_width");
|
||||
}
|
||||
|
||||
auto opt_nozzle_diameters = config.option<ConfigOptionFloats>("nozzle_diameter");
|
||||
if (opt_nozzle_diameters == nullptr)
|
||||
throw_on_missing_variable(opt_key, "nozzle_diameter");
|
||||
|
||||
if (opt->percent) {
|
||||
return opt->get_abs_value(float(opt_nozzle_diameters->get_at(first_printing_extruder)));
|
||||
return opt->get_abs_value(nozzle_diameter);
|
||||
}
|
||||
|
||||
if (opt->value == 0.) {
|
||||
// If user left option to 0, calculate a sane default width.
|
||||
return auto_extrusion_width(opt_key_to_flow_role(opt_key), float(opt_nozzle_diameters->get_at(first_printing_extruder)));
|
||||
return auto_extrusion_width(opt_key_to_flow_role(opt_key), nozzle_diameter);
|
||||
}
|
||||
|
||||
return opt->value;
|
||||
|
||||
@@ -107,7 +107,7 @@ public:
|
||||
double linear_defletion = 0.003,
|
||||
double angle_defletion = 0.5);
|
||||
|
||||
std::atomic<bool> m_stop_mesh;
|
||||
std::atomic<bool> m_stop_mesh{false};
|
||||
void update_process(int load_stage, int current, int total, bool& cancel);
|
||||
private:
|
||||
std::string m_path;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
|
||||
#include <boost/assign.hpp>
|
||||
#include <boost/bimap.hpp>
|
||||
@@ -8137,6 +8138,21 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Orca: replicates GCode's object-label sanitization (sanitize_instance_name in GCode.cpp), used to
|
||||
// build the per-instance object name written into slice_info.config so it matches the EXCLUDE_OBJECT /
|
||||
// M486 object name embedded in the g-code. Keep this in sync with GCode.cpp.
|
||||
static std::string sanitize_object_label(const std::string& name)
|
||||
{
|
||||
// Compiled once: building a std::regex is expensive and this runs per object instance.
|
||||
static const std::regex non_word_re("[ !@#$%^&*()=+\\[\\]{};:\",']+");
|
||||
std::string result = std::regex_replace(name, non_word_re, "_");
|
||||
if (!result.empty() && result.front() == '_')
|
||||
result.erase(result.begin());
|
||||
if (!result.empty() && result.back() == '_')
|
||||
result.erase(result.end() - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Exporter::_add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config)
|
||||
{
|
||||
std::stringstream stream;
|
||||
@@ -8217,6 +8233,21 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << "\"/>\n";
|
||||
}
|
||||
|
||||
// Orca: for non-BambuLab printers that label objects in the g-code (Klipper/Marlin/RRF via
|
||||
// EXCLUDE_OBJECT / M486), write the per-instance g-code object name into slice_info.config
|
||||
// (e.g. "OrcaCube_v2.drc_id_0_copy_0") so the printer can correlate objects between the 3MF
|
||||
// and the g-code. The g-code names objects per plate as
|
||||
// "<name>_id_<object index>_copy_<instance index>" (see GCode::set_object_info), which we
|
||||
// reconstruct here from the (sorted) objects_and_instances list. identify_id is unchanged.
|
||||
// BambuLab printers keep the raw object name.
|
||||
const GCodeFlavor slice_gcode_flavor = config.opt_enum<GCodeFlavor>("gcode_flavor");
|
||||
const bool use_gcode_object_name = !GCodeProcessor::s_IsBBLPrinter &&
|
||||
(slice_gcode_flavor == gcfKlipper || slice_gcode_flavor == gcfMarlinLegacy ||
|
||||
slice_gcode_flavor == gcfMarlinFirmware || slice_gcode_flavor == gcfRepRapFirmware);
|
||||
int gcode_object_index = -1;
|
||||
int gcode_copy_index = 0;
|
||||
int last_object_id = -1;
|
||||
|
||||
for (auto it = plate_data->objects_and_instances.begin(); it != plate_data->objects_and_instances.end(); it++)
|
||||
{
|
||||
int obj_id = it->first;
|
||||
@@ -8241,7 +8272,24 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
identify_id = inst->id().id;
|
||||
bool skipped = std::find(plate_data->skipped_objects.begin(), plate_data->skipped_objects.end(), identify_id) !=
|
||||
plate_data->skipped_objects.end();
|
||||
stream << " <" << OBJECT_TAG << " " << IDENTIFYID_ATTR << "=\"" << std::to_string(identify_id) << "\" " << NAME_ATTR << "=\"" << xml_escape(obj->name)
|
||||
|
||||
// Advance the per-plate g-code object/copy index. objects_and_instances is sorted,
|
||||
// so all instances of the same object are contiguous.
|
||||
if (obj_id != last_object_id) {
|
||||
++gcode_object_index;
|
||||
gcode_copy_index = 0;
|
||||
last_object_id = obj_id;
|
||||
} else {
|
||||
++gcode_copy_index;
|
||||
}
|
||||
|
||||
std::string object_name = obj->name;
|
||||
if (use_gcode_object_name)
|
||||
// Matches GCode::get_instance_name(): sanitize(sanitize(name) + "_id_<obj>_copy_<inst>").
|
||||
object_name = sanitize_object_label(sanitize_object_label(obj->name) + "_id_" +
|
||||
std::to_string(gcode_object_index) + "_copy_" + std::to_string(gcode_copy_index));
|
||||
|
||||
stream << " <" << OBJECT_TAG << " " << IDENTIFYID_ATTR << "=\"" << std::to_string(identify_id) << "\" " << NAME_ATTR << "=\"" << xml_escape(object_name)
|
||||
<< "\" " << SKIPPED_ATTR << "=\"" << (skipped ? "true" : "false")
|
||||
<< "\" />\n";
|
||||
}
|
||||
|
||||
+205
-109
@@ -1817,10 +1817,10 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
|
||||
std::string warning;
|
||||
size_t i = 0;
|
||||
for (i = 0; i < std::min(warning_ranges.size(), size_t(5)); ++i)
|
||||
warning += Slic3r::format(_(L("Object can't be printed for empty layer between %1% and %2%.")),
|
||||
warning += Slic3r::format(_(L("The object has empty layers between %1% and %2% and can\u2019t be printed.")),
|
||||
warning_ranges[i].first, warning_ranges[i].second) + "\n";
|
||||
warning += Slic3r::format(_(L("Object: %1%")), object.model_object()->name) + "\n"
|
||||
+ _(L("Maybe parts of the object at these height are too thin, or the object has faulty mesh"));
|
||||
+ _(L("Parts of the object at these heights may be too thin or the object may have a faulty mesh."));
|
||||
|
||||
const_cast<Print*>(object.print())->active_step_add_warning(
|
||||
PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingEmptyGcodeLayers);
|
||||
@@ -2195,7 +2195,9 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info();
|
||||
print->set_done(psGCodeExport);
|
||||
|
||||
if(is_BBL_Printer())
|
||||
// Orca: label_object_enabled reflects whether objects are labeled in the g-code (EXCLUDE_OBJECT /
|
||||
// M486), which is driven by exclude_object for every printer
|
||||
if(result != nullptr)
|
||||
result->label_object_enabled = m_enable_exclude_object;
|
||||
// Write the profiler measurements to file
|
||||
PROFILE_UPDATE();
|
||||
@@ -2279,17 +2281,45 @@ namespace DoExport {
|
||||
ooze_prevention.enable = print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material;
|
||||
}
|
||||
|
||||
// Count tool/filament changes across the print from the tool ordering. Used as a fallback when no
|
||||
// wipe tower populated WipeTowerData::number_of_toolchanges (left at -1). Covers non-sequential
|
||||
// prints without a wipe tower (manual swaps, toolchanger/IDEX). Note: sequential (by-object) prints
|
||||
// leave print.tool_ordering() empty, so total_toolchanges stays 0 there (unchanged from before).
|
||||
static int total_toolchanges_from_ordering(const ToolOrdering &tool_ordering)
|
||||
{
|
||||
int changes = 0;
|
||||
int last = -1;
|
||||
for (const LayerTools < : tool_ordering)
|
||||
for (unsigned int extruder : lt.extruders) {
|
||||
if (last >= 0 && int(extruder) != last)
|
||||
++ changes;
|
||||
last = int(extruder);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
// Total tool changes for the print, preferring the wipe-tower count and falling back to the tool
|
||||
// ordering when no wipe tower populated it (number_of_toolchanges < 0).
|
||||
static int resolve_total_toolchanges(const WipeTowerData &wipe_tower_data, const ToolOrdering &tool_ordering)
|
||||
{
|
||||
int changes = wipe_tower_data.number_of_toolchanges;
|
||||
if (changes < 0)
|
||||
changes = total_toolchanges_from_ordering(tool_ordering);
|
||||
return std::max(0, changes);
|
||||
}
|
||||
|
||||
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
|
||||
static std::string update_print_stats_and_format_filament_stats(
|
||||
const bool has_wipe_tower,
|
||||
const WipeTowerData &wipe_tower_data,
|
||||
const std::vector<Extruder> &extruders,
|
||||
PrintStatistics &print_statistics)
|
||||
PrintStatistics &print_statistics,
|
||||
const ToolOrdering &tool_ordering)
|
||||
{
|
||||
std::string filament_stats_string_out;
|
||||
|
||||
print_statistics.clear();
|
||||
print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
|
||||
print_statistics.total_toolchanges = resolve_total_toolchanges(wipe_tower_data, tool_ordering);
|
||||
if (! extruders.empty()) {
|
||||
std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
|
||||
std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0);
|
||||
@@ -2522,6 +2552,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
std::string top_gcode_template = print.config().file_start_gcode.value;
|
||||
if (!top_gcode_template.empty()) {
|
||||
DynamicConfig top_config;
|
||||
// file_start_gcode runs before the parser copy that normally restores these, so set them here.
|
||||
PlaceholderParser::update_timestamp(top_config);
|
||||
PlaceholderParser::update_user_name(top_config);
|
||||
top_config.set_key_value("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder)));
|
||||
top_config.set_key_value("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
||||
std::string top_gcode = print.placeholder_parser().process(top_gcode_template, 0, &top_config);
|
||||
@@ -2720,7 +2753,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
}
|
||||
if (initial_extruder_id == static_cast<unsigned int>(-1))
|
||||
// No object to print was found, cancel the G-code export.
|
||||
throw Slic3r::SlicingError(_(L("No object can be printed. Maybe too small")));
|
||||
throw Slic3r::SlicingError(_(L("No object can be printed. It may be too small.")));
|
||||
// We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode.
|
||||
// Use the extruder IDs collected from Regions.
|
||||
this->set_extruders(print.extruders());
|
||||
@@ -2736,7 +2769,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
max_additional_fan = temp_max_additional_fan;
|
||||
if (tool_ordering.all_extruders().empty())
|
||||
// No object to print was found, cancel the G-code export.
|
||||
throw Slic3r::SlicingError(_(L("No object can be printed. Maybe too small")));
|
||||
throw Slic3r::SlicingError(_(L("No object can be printed. It may be too small.")));
|
||||
has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
|
||||
// Orca: support all extruder priming
|
||||
initial_extruder_id = (wipe_tower_type == WipeTowerType::Type2 && has_wipe_tower && !print.config().single_extruder_multi_material_priming) ?
|
||||
@@ -2859,7 +2892,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
|
||||
this->placeholder_parser().set("has_wipe_tower", has_wipe_tower);
|
||||
this->placeholder_parser().set("has_single_extruder_multi_material_priming", wipe_tower_type == WipeTowerType::Type2 && has_wipe_tower && print.config().single_extruder_multi_material_priming);
|
||||
this->placeholder_parser().set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
|
||||
this->placeholder_parser().set("total_toolchanges", DoExport::resolve_total_toolchanges(print.wipe_tower_data(), print.tool_ordering()));
|
||||
this->placeholder_parser().set("num_extruders", int(print.config().nozzle_diameter.values.size()));
|
||||
this->placeholder_parser().set("retract_length", new ConfigOptionFloats(print.config().retraction_length));
|
||||
|
||||
@@ -3142,18 +3175,24 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
if (is_bbl_printers) {
|
||||
this->_print_first_layer_extruder_temperatures(file, print, machine_start_gcode, initial_extruder_id, true);
|
||||
}
|
||||
// Orca: when activate_air_filtration is set on any extruder, find and set the highest during_print_exhaust_fan_speed
|
||||
bool activate_air_filtration_during_print = false;
|
||||
int during_print_exhaust_fan_speed = 0;
|
||||
for (const auto &extruder : m_writer.extruders()) {
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_during_print.get_at(extruder.id())) {
|
||||
activate_air_filtration_during_print = true;
|
||||
during_print_exhaust_fan_speed = std::max(during_print_exhaust_fan_speed,
|
||||
m_config.during_print_exhaust_fan_speed.get_at(extruder.id()));
|
||||
|
||||
// Orca: when air filtration is supported, check if it needs to be activated during printing and set the exhaust fan speed accordingly
|
||||
if (m_config.support_air_filtration.value) {
|
||||
bool activate_air_filtration_during_print = false;
|
||||
int during_print_exhaust_fan_speed = 0;
|
||||
|
||||
// Orca: when activate_air_filtration is set on any extruder, find and set the highest during_print_exhaust_fan_speed
|
||||
for (const auto &extruder : m_writer.extruders()) {
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_during_print.get_at(extruder.id())) {
|
||||
activate_air_filtration_during_print = true;
|
||||
during_print_exhaust_fan_speed = std::max(during_print_exhaust_fan_speed,
|
||||
m_config.during_print_exhaust_fan_speed.get_at(extruder.id()));
|
||||
}
|
||||
}
|
||||
|
||||
if (activate_air_filtration_during_print)
|
||||
file.write(m_writer.set_exhaust_fan(during_print_exhaust_fan_speed));
|
||||
}
|
||||
if (activate_air_filtration_during_print)
|
||||
file.write(m_writer.set_exhaust_fan(during_print_exhaust_fan_speed, true));
|
||||
|
||||
print.throw_if_canceled();
|
||||
|
||||
@@ -3452,16 +3491,23 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
if (activate_chamber_temp_control && max_chamber_temp > 0)
|
||||
file.write(m_writer.set_chamber_temperature(0, false)); //close chamber_temperature
|
||||
|
||||
bool activate_air_filtration_on_completion = false;
|
||||
int complete_print_exhaust_fan_speed = 0;
|
||||
for (const auto& extruder : m_writer.extruders()) {
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_on_completion.get_at(extruder.id())) {
|
||||
activate_air_filtration_on_completion = true;
|
||||
complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id()));
|
||||
// Orca: when air filtration is supported, check if it needs to be activated after print completion and set the exhaust fan speed accordingly
|
||||
if (m_config.support_air_filtration.value) {
|
||||
bool activate_air_filtration_on_completion = false;
|
||||
int complete_print_exhaust_fan_speed = 0;
|
||||
|
||||
// Orca: when activate_air_filtration is set on any extruder, find and set the highest complete_print_exhaust_fan_speed
|
||||
for (const auto& extruder : m_writer.extruders()) {
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_on_completion.get_at(extruder.id())) {
|
||||
activate_air_filtration_on_completion = true;
|
||||
complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id()));
|
||||
}
|
||||
}
|
||||
|
||||
if (activate_air_filtration_on_completion)
|
||||
file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed));
|
||||
}
|
||||
if (activate_air_filtration_on_completion)
|
||||
file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed, true));
|
||||
|
||||
// adds tags for time estimators
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str());
|
||||
file.write_format("; EXECUTABLE_BLOCK_END\n\n");
|
||||
@@ -3474,7 +3520,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
has_wipe_tower, print.wipe_tower_data(),
|
||||
m_writer.extruders(),
|
||||
// Modifies
|
||||
print.m_print_statistics));
|
||||
print.m_print_statistics,
|
||||
// Const input (tool-change fallback for non-wipe-tower prints)
|
||||
print.tool_ordering()));
|
||||
print.m_print_statistics.initial_tool = initial_extruder_id;
|
||||
if (!is_bbl_printers) {
|
||||
file.write_format("; total filament used [g] = %.2lf\n",
|
||||
@@ -4737,6 +4785,7 @@ LayerResult GCode::process_layer(
|
||||
|
||||
// Group extrusions by an extruder, then by an object, an island and a region.
|
||||
std::map<unsigned int, std::vector<ObjectByExtruder>> by_extruder;
|
||||
std::vector<std::unique_ptr<ExtrusionEntityCollection>> split_perimeter_storage;
|
||||
bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();
|
||||
for (const LayerToPrint &layer_to_print : layers) {
|
||||
if (layer_to_print.support_layer != nullptr) {
|
||||
@@ -4892,55 +4941,83 @@ LayerResult GCode::process_layer(
|
||||
if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail.
|
||||
continue;
|
||||
|
||||
// This extrusion is part of certain Region, which tells us which extruder should be used for it:
|
||||
int correct_extruder_id = layer_tools.extruder(*extrusions, region);
|
||||
auto process_extrusions = [&](const ExtrusionEntityCollection *current_extrusions,
|
||||
const ExtrusionEntityCollection *overrides_key,
|
||||
bool use_overrides) {
|
||||
// This extrusion is part of certain Region, which tells us which extruder should be used for it.
|
||||
int correct_extruder_id = layer_tools.extruder(*current_extrusions, region);
|
||||
|
||||
// Let's recover vector of extruder overrides:
|
||||
const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr;
|
||||
if (! layer_tools.has_extruder(correct_extruder_id)) {
|
||||
// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
|
||||
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
|
||||
correct_extruder_id = layer_tools.extruders.back();
|
||||
}
|
||||
printing_extruders.clear();
|
||||
if (is_anything_overridden) {
|
||||
entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, layer_to_print.original_object, correct_extruder_id, layer_to_print.object()->instances().size());
|
||||
if (entity_overrides == nullptr) {
|
||||
const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr;
|
||||
if (! layer_tools.has_extruder(correct_extruder_id)) {
|
||||
// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
|
||||
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
|
||||
correct_extruder_id = layer_tools.extruders.back();
|
||||
}
|
||||
printing_extruders.clear();
|
||||
if (is_anything_overridden && use_overrides) {
|
||||
entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(overrides_key, layer_to_print.original_object, correct_extruder_id, layer_to_print.object()->instances().size());
|
||||
if (entity_overrides == nullptr) {
|
||||
printing_extruders.emplace_back(correct_extruder_id);
|
||||
} else {
|
||||
printing_extruders.reserve(entity_overrides->size());
|
||||
for (int extruder : *entity_overrides)
|
||||
printing_extruders.emplace_back(extruder >= 0 ?
|
||||
// at least one copy is overridden to use this extruder
|
||||
extruder :
|
||||
// at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
|
||||
static_cast<unsigned int>(- extruder - 1));
|
||||
Slic3r::sort_remove_duplicates(printing_extruders);
|
||||
}
|
||||
} else {
|
||||
printing_extruders.emplace_back(correct_extruder_id);
|
||||
} else {
|
||||
printing_extruders.reserve(entity_overrides->size());
|
||||
for (int extruder : *entity_overrides)
|
||||
printing_extruders.emplace_back(extruder >= 0 ?
|
||||
// at least one copy is overridden to use this extruder
|
||||
extruder :
|
||||
// at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
|
||||
static_cast<unsigned int>(- extruder - 1));
|
||||
Slic3r::sort_remove_duplicates(printing_extruders);
|
||||
}
|
||||
} else
|
||||
printing_extruders.emplace_back(correct_extruder_id);
|
||||
|
||||
// Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
|
||||
for (unsigned int extruder : printing_extruders)
|
||||
{
|
||||
std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder(
|
||||
by_extruder,
|
||||
extruder,
|
||||
&layer_to_print - layers.data(),
|
||||
layers.size(), n_slices+1);
|
||||
for (size_t i = 0; i <= n_slices; ++ i) {
|
||||
bool last = i == n_slices;
|
||||
size_t island_idx = last ? n_slices : slices_test_order[i];
|
||||
if (// extrusions->first_point does not fit inside any slice
|
||||
last ||
|
||||
// extrusions->first_point fits inside ith slice
|
||||
point_inside_surface(island_idx, extrusions->first_point())) {
|
||||
if (islands[island_idx].by_region.empty())
|
||||
islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region());
|
||||
islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides);
|
||||
break;
|
||||
// Now we must add this extrusion into the by_extruder map, once for each extruder that will print it.
|
||||
for (unsigned int extruder : printing_extruders) {
|
||||
std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder(
|
||||
by_extruder,
|
||||
extruder,
|
||||
&layer_to_print - layers.data(),
|
||||
layers.size(), n_slices + 1);
|
||||
for (size_t i = 0; i <= n_slices; ++i) {
|
||||
bool last = i == n_slices;
|
||||
size_t island_idx = last ? n_slices : slices_test_order[i];
|
||||
if (last || point_inside_surface(island_idx, current_extrusions->first_point())) {
|
||||
if (islands[island_idx].by_region.empty())
|
||||
islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region());
|
||||
islands[island_idx].by_region[region.print_region_id()].append(entity_type, current_extrusions, entity_overrides);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool split_mixed_perimeters =
|
||||
entity_type == ObjectByExtruder::Island::Region::PERIMETERS &&
|
||||
region.config().outer_wall_filament_id.value != region.config().inner_wall_filament_id.value &&
|
||||
extrusions->role() == erMixed;
|
||||
|
||||
if (split_mixed_perimeters) {
|
||||
auto outer_perimeters = std::make_unique<ExtrusionEntityCollection>();
|
||||
auto inner_perimeters = std::make_unique<ExtrusionEntityCollection>();
|
||||
for (const ExtrusionEntity *entity : extrusions->entities) {
|
||||
const ExtrusionRole role = entity->role();
|
||||
if (role == erExternalPerimeter || role == erOverhangPerimeter)
|
||||
outer_perimeters->append(*entity);
|
||||
else if (role == erPerimeter)
|
||||
inner_perimeters->append(*entity);
|
||||
}
|
||||
|
||||
if (!outer_perimeters->entities.empty()) {
|
||||
split_perimeter_storage.emplace_back(std::move(outer_perimeters));
|
||||
process_extrusions(split_perimeter_storage.back().get(), nullptr, false);
|
||||
}
|
||||
if (!inner_perimeters->entities.empty()) {
|
||||
split_perimeter_storage.emplace_back(std::move(inner_perimeters));
|
||||
process_extrusions(split_perimeter_storage.back().get(), nullptr, false);
|
||||
}
|
||||
} else {
|
||||
process_extrusions(extrusions, extrusions, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5082,44 +5159,30 @@ LayerResult GCode::process_layer(
|
||||
bool has_insert_wrapping_detection_gcode = false;
|
||||
|
||||
// Extrude the skirt, brim, support, perimeters, infill ordered by the extruders.
|
||||
// Orca: Print unified global brim before any object.
|
||||
// Only do this if `combine_brims` is enabled and we are printing by layer.
|
||||
if (first_layer && sequence_by_layer && m_config.combine_brims && !print.m_brimMap.empty()) {
|
||||
const ObjectID unified_object_id = [&]() -> ObjectID {
|
||||
ObjectID id;
|
||||
bool found = false;
|
||||
for (const auto& [obj_id, brim] : print.m_brimMap) {
|
||||
const bool has_printable_entities = std::any_of(brim.entities.begin(), brim.entities.end(),
|
||||
[](const ExtrusionEntity* ee) { return ee != nullptr; });
|
||||
if (!has_printable_entities)
|
||||
continue;
|
||||
if (found)
|
||||
return ObjectID();
|
||||
id = obj_id;
|
||||
found = true;
|
||||
}
|
||||
return found ? id : ObjectID();
|
||||
}();
|
||||
|
||||
if (unified_object_id.valid()) {
|
||||
const auto it = print.m_brimMap.find(unified_object_id);
|
||||
if (it != print.m_brimMap.end()) {
|
||||
this->set_origin(0., 0.);
|
||||
for (const ExtrusionEntity* ee : it->second.entities)
|
||||
if (ee != nullptr)
|
||||
gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed));
|
||||
|
||||
// Mark brim as printed for this object to avoid per-object brim emission later.
|
||||
this->m_objsWithBrim.erase(unified_object_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int extruder_id : layer_tools.extruders)
|
||||
{
|
||||
if (print.config().skirt_type == stCombined && !print.skirt().empty())
|
||||
gcode += generate_skirt(print, print.skirt(), Point(0, 0), layer.object()->config().skirt_start_angle, layer_tools, layer,
|
||||
extruder_id);
|
||||
if ((print.config().skirt_type == stCombined ||
|
||||
(print.config().skirt_type == stPerObject && print.config().print_sequence == PrintSequence::ByLayer)) &&
|
||||
!print.skirt_groups().empty()) {
|
||||
bool skirt_generated_for_current_print_z = false;
|
||||
for (const ExtrusionEntityCollection& skirt_group : print.skirt_groups()) {
|
||||
if (skirt_group.empty())
|
||||
continue;
|
||||
|
||||
// Orca: each grouped skirt is emitted as its own collection so higher skirt layers
|
||||
// follow the same per-group behavior as the first layer.
|
||||
if (first_layer)
|
||||
m_skirt_done.clear();
|
||||
else if (skirt_generated_for_current_print_z && !m_skirt_done.empty())
|
||||
m_skirt_done.pop_back();
|
||||
|
||||
std::string skirt_gcode = generate_skirt(print, skirt_group, Point(0, 0), layer.object()->config().skirt_start_angle,
|
||||
layer_tools, layer, extruder_id);
|
||||
if (!skirt_gcode.empty())
|
||||
skirt_generated_for_current_print_z = true;
|
||||
gcode += std::move(skirt_gcode);
|
||||
}
|
||||
}
|
||||
|
||||
if (print.config().print_sequence == PrintSequence::ByLayer && m_enable_exclude_object && print.config().support_object_skip_flush.value) {
|
||||
std::vector<size_t> filament_instances_id;
|
||||
@@ -5220,7 +5283,38 @@ LayerResult GCode::process_layer(
|
||||
}
|
||||
}
|
||||
|
||||
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
|
||||
// Orca: Print unified global brim after the skirt and before any object.
|
||||
// Only do this if `combine_brims` is enabled and we are printing by layer.
|
||||
if (first_layer && sequence_by_layer && m_config.combine_brims && !print.m_brimMap.empty()) {
|
||||
const ObjectID unified_object_id = [&]() -> ObjectID {
|
||||
ObjectID id;
|
||||
for (const auto& [obj_id, brim] : print.m_brimMap) {
|
||||
const bool has_printable_entities = std::any_of(brim.entities.begin(), brim.entities.end(),
|
||||
[](const ExtrusionEntity* ee) { return ee != nullptr; });
|
||||
if (!has_printable_entities)
|
||||
continue;
|
||||
|
||||
if (id.valid())
|
||||
return ObjectID();
|
||||
|
||||
id = obj_id;
|
||||
}
|
||||
return id;
|
||||
}();
|
||||
|
||||
if (unified_object_id.valid() && this->m_objsWithBrim.find(unified_object_id) != this->m_objsWithBrim.end()) {
|
||||
const ExtrusionEntityCollection& unified_brim = print.m_brimMap.at(unified_object_id);
|
||||
this->set_origin(0., 0.);
|
||||
for (const ExtrusionEntity* ee : unified_brim.entities)
|
||||
if (ee != nullptr)
|
||||
gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed));
|
||||
|
||||
// Mark brim as printed for this object to avoid per-object brim emission later.
|
||||
this->m_objsWithBrim.erase(unified_object_id);
|
||||
}
|
||||
}
|
||||
|
||||
// We are almost ready to print. However, we must go through all the objects twice to print the overridden extrusions first (infill/perimeter wiping feature):
|
||||
std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
|
||||
for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
|
||||
if (is_anything_overridden && print_wipe_extrusions == 0)
|
||||
@@ -5258,7 +5352,7 @@ LayerResult GCode::process_layer(
|
||||
gcode += std::move(skirt_gcode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id];
|
||||
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
|
||||
// To control print speed of the 1st object layer printed over raft interface.
|
||||
@@ -6136,7 +6230,9 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
|
||||
if (extrusions.empty())
|
||||
return gcode;
|
||||
|
||||
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
|
||||
//ORCA: Respect no_sort to preserve support base outline->fill order.
|
||||
if (!support_fills.no_sort)
|
||||
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
|
||||
|
||||
//const double support_speed = m_config.support_speed.value;
|
||||
//const double support_interface_speed = m_config.get_abs_value("support_interface_speed");
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -282,4 +283,81 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::string AdaptivePAProcessor::validate_adaptive_pa_model(const std::string& model_str)
|
||||
{
|
||||
if (model_str.empty())
|
||||
return {}; // Empty model is valid
|
||||
|
||||
std::istringstream model_stream(model_str);
|
||||
std::string line;
|
||||
int line_number = 0;
|
||||
|
||||
while (std::getline(model_stream, line)) {
|
||||
++line_number;
|
||||
|
||||
// Trim whitespace
|
||||
const auto first = line.find_first_not_of(" \t\r\n");
|
||||
if (first == std::string::npos)
|
||||
continue; // Skip empty lines
|
||||
|
||||
const auto last = line.find_last_not_of(" \t\r\n");
|
||||
line = line.substr(first, last - first + 1);
|
||||
|
||||
// Only numbers, commas and dots are allowed (no letters or other characters)
|
||||
for (char c : line) {
|
||||
if (!std::isdigit(static_cast<unsigned char>(c)) && c != ',' && c != '.') {
|
||||
return "Line " + std::to_string(line_number) +
|
||||
": only numbers, commas and dots are allowed";
|
||||
}
|
||||
}
|
||||
|
||||
// Count commas to validate format (should be exactly 2 for 3 values)
|
||||
int comma_count = 0;
|
||||
for (char c : line) {
|
||||
if (c == ',') comma_count++;
|
||||
}
|
||||
|
||||
if (comma_count != 2) {
|
||||
return "Line " + std::to_string(line_number) +
|
||||
": must contain exactly 3 comma-separated values (PA, flow, acceleration)";
|
||||
}
|
||||
|
||||
// Parse and validate the values
|
||||
try {
|
||||
std::istringstream line_stream(line);
|
||||
std::string value;
|
||||
|
||||
// Parse PA
|
||||
if (!std::getline(line_stream, value, ','))
|
||||
return "Line " + std::to_string(line_number) + ": missing PA value";
|
||||
double pa = std::stod(value);
|
||||
|
||||
// Parse flow
|
||||
if (!std::getline(line_stream, value, ','))
|
||||
return "Line " + std::to_string(line_number) + ": missing flow value";
|
||||
double flow = std::stod(value);
|
||||
|
||||
// Parse acceleration
|
||||
if (!std::getline(line_stream, value, ','))
|
||||
return "Line " + std::to_string(line_number) + ": missing acceleration value";
|
||||
double accel = std::stod(value);
|
||||
|
||||
// Validate constraints
|
||||
if (pa >= 2.0) {
|
||||
return "Line " + std::to_string(line_number) + ": PA value must be less than 2";
|
||||
}
|
||||
if (flow <= pa) {
|
||||
return "Line " + std::to_string(line_number) + ": flow value must be greater than PA value";
|
||||
}
|
||||
if (accel <= flow) {
|
||||
return "Line " + std::to_string(line_number) + ": acceleration value must be greater than flow value";
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
return "Line " + std::to_string(line_number) + ": invalid numeric value";
|
||||
}
|
||||
}
|
||||
|
||||
return {}; // All validations passed
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -54,6 +54,20 @@ public:
|
||||
*/
|
||||
void resetPreviousPA(double PA){ m_last_predicted_pa = PA; };
|
||||
|
||||
/**
|
||||
* @brief Validates an adaptive pressure advance model string.
|
||||
*
|
||||
* Checks that:
|
||||
* - Each non-empty line has exactly 3 comma-separated values (PA, flow, accel)
|
||||
* - PA value is less than 2
|
||||
* - Flow value is greater than PA value
|
||||
* - Accel value is greater than flow value
|
||||
*
|
||||
* @param model_str The model string to validate (typically from config)
|
||||
* @return Empty string if valid, or an error message describing the first issue found
|
||||
*/
|
||||
static std::string validate_adaptive_pa_model(const std::string& model_str);
|
||||
|
||||
private:
|
||||
GCode &m_gcodegen; ///< Reference to the GCode object.
|
||||
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and tool ID's
|
||||
|
||||
@@ -3120,7 +3120,7 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
|
||||
// Orca: Integrate filament consumption for purging performed to an external device and controlled via macros
|
||||
// (eg. Happy Hare) in the filament consumption stats.
|
||||
if (boost::starts_with(comment, GCodeProcessor::External_Purge_Tag)) {
|
||||
std::regex numberRegex(R"(\d+\.\d+)");
|
||||
static const std::regex numberRegex(R"(\d+\.\d+)");
|
||||
std::smatch match;
|
||||
std::string line(comment);
|
||||
if (std::regex_search(line, match, numberRegex)) {
|
||||
@@ -4976,7 +4976,7 @@ void GCodeProcessor::process_M572(const GCodeReader::GCodeLine &line)
|
||||
|
||||
void GCodeProcessor::process_SET_PRESSURE_ADVANCE(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
std::regex regex(R"(SET_PRESSURE_ADVANCE\s+(?:.*\s+)?ADVANCE\s*=\s*([\d.]+))");
|
||||
static const std::regex regex(R"(SET_PRESSURE_ADVANCE\s+(?:.*\s+)?ADVANCE\s*=\s*([\d.]+))");
|
||||
std::smatch matches;
|
||||
|
||||
if (std::regex_search(line.raw(), matches, regex) && matches.size() > 1) {
|
||||
@@ -5198,9 +5198,9 @@ void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line)
|
||||
void GCodeProcessor::process_SET_VELOCITY_LIMIT(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
// handle SQUARE_CORNER_VELOCITY
|
||||
std::regex pattern("\\sSQUARE_CORNER_VELOCITY\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
static const std::regex square_corner_velocity_pattern("\\sSQUARE_CORNER_VELOCITY\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
std::smatch matches;
|
||||
if (std::regex_search(line.raw(), matches, pattern) && matches.size() == 2) {
|
||||
if (std::regex_search(line.raw(), matches, square_corner_velocity_pattern) && matches.size() == 2) {
|
||||
float _jerk = 0;
|
||||
try
|
||||
{
|
||||
@@ -5213,8 +5213,8 @@ void GCodeProcessor::process_SET_VELOCITY_LIMIT(const GCodeReader::GCodeLine& li
|
||||
}
|
||||
}
|
||||
|
||||
pattern = std::regex("\\sACCEL\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
if (std::regex_search(line.raw(), matches, pattern) && matches.size() == 2) {
|
||||
static const std::regex accel_pattern("\\sACCEL\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
if (std::regex_search(line.raw(), matches, accel_pattern) && matches.size() == 2) {
|
||||
float _accl = 0;
|
||||
try
|
||||
{
|
||||
@@ -5227,8 +5227,8 @@ void GCodeProcessor::process_SET_VELOCITY_LIMIT(const GCodeReader::GCodeLine& li
|
||||
}
|
||||
}
|
||||
|
||||
pattern = std::regex("\\sVELOCITY\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
if (std::regex_search(line.raw(), matches, pattern) && matches.size() == 2) {
|
||||
static const std::regex velocity_pattern("\\sVELOCITY\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
if (std::regex_search(line.raw(), matches, velocity_pattern) && matches.size() == 2) {
|
||||
float _speed = 0;
|
||||
try
|
||||
{
|
||||
|
||||
@@ -80,40 +80,54 @@ bool check_filament_printable_after_group(const std::vector<unsigned int> &used_
|
||||
}
|
||||
|
||||
// Return a zero based extruder from the region, or extruder_override if overriden.
|
||||
unsigned int LayerTools::wall_filament(const PrintRegion ®ion) const
|
||||
unsigned int LayerTools::wall_extruder_id(const PrintRegion ®ion) const
|
||||
{
|
||||
assert(region.config().wall_filament.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().wall_filament.value : this->extruder_override) - 1;
|
||||
assert(region.config().outer_wall_filament_id.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().outer_wall_filament_id.value : this->extruder_override) - 1;
|
||||
}
|
||||
|
||||
unsigned int LayerTools::sparse_infill_filament(const PrintRegion ®ion) const
|
||||
unsigned int LayerTools::sparse_infill_filament_id(const PrintRegion ®ion) const
|
||||
{
|
||||
assert(region.config().sparse_infill_filament.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().sparse_infill_filament.value : this->extruder_override) - 1;
|
||||
assert(region.config().sparse_infill_filament_id.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().sparse_infill_filament_id.value : this->extruder_override) - 1;
|
||||
}
|
||||
|
||||
unsigned int LayerTools::solid_infill_filament(const PrintRegion ®ion) const
|
||||
unsigned int LayerTools::internal_solid_filament_id(const PrintRegion ®ion) const
|
||||
{
|
||||
assert(region.config().solid_infill_filament.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().solid_infill_filament.value : this->extruder_override) - 1;
|
||||
assert(region.config().internal_solid_filament_id.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().internal_solid_filament_id.value : this->extruder_override) - 1;
|
||||
}
|
||||
|
||||
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
|
||||
unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const
|
||||
{
|
||||
assert(region.config().wall_filament.value > 0);
|
||||
assert(region.config().sparse_infill_filament.value > 0);
|
||||
assert(region.config().solid_infill_filament.value > 0);
|
||||
assert(region.config().outer_wall_filament_id.value > 0);
|
||||
assert(region.config().sparse_infill_filament_id.value > 0);
|
||||
assert(region.config().internal_solid_filament_id.value > 0);
|
||||
assert(region.config().top_surface_filament_id.value > 0);
|
||||
assert(region.config().bottom_surface_filament_id.value > 0);
|
||||
// 1 based extruder ID.
|
||||
unsigned int extruder = 1;
|
||||
if (this->extruder_override == 0) {
|
||||
if (extrusions.has_infill()) {
|
||||
if (extrusions.has_solid_infill())
|
||||
extruder = region.config().solid_infill_filament;
|
||||
if (extrusions.has_solid_infill()) {
|
||||
ExtrusionRole role = extrusions.role();
|
||||
if (role == erTopSolidInfill || role == erIroning)
|
||||
extruder = region.config().top_surface_filament_id;
|
||||
else if (role == erBottomSurface)
|
||||
extruder = region.config().bottom_surface_filament_id;
|
||||
else
|
||||
extruder = region.config().internal_solid_filament_id;
|
||||
} else {
|
||||
extruder = region.config().sparse_infill_filament_id;
|
||||
}
|
||||
} else {
|
||||
const ExtrusionRole role = extrusions.role();
|
||||
if (role == erPerimeter)
|
||||
extruder = region.config().inner_wall_filament_id.value;
|
||||
else
|
||||
extruder = region.config().sparse_infill_filament;
|
||||
} else
|
||||
extruder = region.config().wall_filament.value;
|
||||
extruder = region.config().outer_wall_filament_id.value;
|
||||
}
|
||||
} else
|
||||
extruder = this->extruder_override;
|
||||
|
||||
@@ -527,7 +541,7 @@ std::vector<unsigned int> ToolOrdering::generate_first_layer_tool_order(const Pr
|
||||
return tool_order;
|
||||
|
||||
for (auto layerm : target_layer->regions()) {
|
||||
int extruder_id = layerm->region().config().option("wall_filament")->getInt();
|
||||
int extruder_id = layerm->region().config().option("outer_wall_filament_id")->getInt();
|
||||
|
||||
for (auto expoly : layerm->raw_slices) {
|
||||
const double nozzle_diameter = print.config().nozzle_diameter.get_at(0);
|
||||
@@ -591,7 +605,7 @@ std::vector<unsigned int> ToolOrdering::generate_first_layer_tool_order(const Pr
|
||||
return tool_order;
|
||||
|
||||
for (auto layerm : target_layer->regions()) {
|
||||
int extruder_id = layerm->region().config().option("wall_filament")->getInt();
|
||||
int extruder_id = layerm->region().config().option("outer_wall_filament_id")->getInt();
|
||||
for (auto expoly : layerm->raw_slices) {
|
||||
const double nozzle_diameter = object.print()->config().nozzle_diameter.get_at(0);
|
||||
const coordf_t line_width = object.config().get_abs_value("line_width", nozzle_diameter);
|
||||
@@ -682,24 +696,32 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
}
|
||||
|
||||
if (something_nonoverriddable){
|
||||
layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().wall_filament.value : extruder_override);
|
||||
layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().outer_wall_filament_id.value : extruder_override);
|
||||
if (extruder_override == 0 && region.config().wall_loops.value > 1)
|
||||
layer_tools.extruders.emplace_back(region.config().inner_wall_filament_id.value);
|
||||
if (layerCount == 0) {
|
||||
firstLayerExtruders.emplace_back((extruder_override == 0) ? region.config().wall_filament.value : extruder_override);
|
||||
firstLayerExtruders.emplace_back((extruder_override == 0) ? region.config().outer_wall_filament_id.value : extruder_override);
|
||||
}
|
||||
}
|
||||
|
||||
layer_tools.has_object = true;
|
||||
}
|
||||
|
||||
bool has_infill = false;
|
||||
bool has_solid_infill = false;
|
||||
bool has_infill = false;
|
||||
bool has_internal_solid = false;
|
||||
bool has_top_solid_surface = false;
|
||||
bool has_bottom_surface = false;
|
||||
bool something_nonoverriddable = false;
|
||||
for (const ExtrusionEntity *ee : layerm->fills.entities) {
|
||||
// fill represents infill extrusions of a single island.
|
||||
const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
|
||||
ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role();
|
||||
if (is_solid_infill(role))
|
||||
has_solid_infill = true;
|
||||
if (role == erTopSolidInfill || role == erIroning)
|
||||
has_top_solid_surface = true;
|
||||
else if (role == erBottomSurface)
|
||||
has_bottom_surface = true;
|
||||
else if (is_solid_infill(role))
|
||||
has_internal_solid = true;
|
||||
else if (role != erNone)
|
||||
has_infill = true;
|
||||
|
||||
@@ -711,14 +733,18 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
|
||||
if (something_nonoverriddable || !m_print_config_ptr) {
|
||||
if (extruder_override == 0) {
|
||||
if (has_solid_infill)
|
||||
layer_tools.extruders.emplace_back(region.config().solid_infill_filament);
|
||||
if (has_internal_solid)
|
||||
layer_tools.extruders.emplace_back(region.config().internal_solid_filament_id);
|
||||
if (has_top_solid_surface)
|
||||
layer_tools.extruders.emplace_back(region.config().top_surface_filament_id);
|
||||
if (has_bottom_surface)
|
||||
layer_tools.extruders.emplace_back(region.config().bottom_surface_filament_id);
|
||||
if (has_infill)
|
||||
layer_tools.extruders.emplace_back(region.config().sparse_infill_filament);
|
||||
} else if (has_solid_infill || has_infill)
|
||||
layer_tools.extruders.emplace_back(region.config().sparse_infill_filament_id);
|
||||
} else if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_infill)
|
||||
layer_tools.extruders.emplace_back(extruder_override);
|
||||
}
|
||||
if (has_solid_infill || has_infill)
|
||||
if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_infill)
|
||||
layer_tools.has_object = true;
|
||||
}
|
||||
layerCount++;
|
||||
@@ -1657,7 +1683,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
||||
if (wipe_into_infill_only && ! is_infill_first)
|
||||
// In this case we must check that the original extruder is used on this layer before the one we are overridding
|
||||
// (and the perimeters will be finished before the infill is printed):
|
||||
if (!lt.is_extruder_order(lt.wall_filament(region), new_extruder))
|
||||
if (!lt.is_extruder_order(lt.wall_extruder_id(region), new_extruder))
|
||||
continue;
|
||||
|
||||
if ((!is_entity_overridden(fill, object, copy) && fill->total_volume() > min_infill_volume))
|
||||
@@ -1775,8 +1801,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
||||
if (is_infill_first
|
||||
//BBS
|
||||
//|| object->config().flush_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|
||||
|| lt.is_extruder_order(lt.wall_filament(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|
||||
|| ! lt.has_extruder(lt.sparse_infill_filament(region)))) // we have to force override - this could violate infill_first (FIXME)
|
||||
|| lt.is_extruder_order(lt.wall_extruder_id(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|
||||
|| ! lt.has_extruder(lt.sparse_infill_filament_id(region)))) // we have to force override - this could violate infill_first (FIXME)
|
||||
set_extruder_override(fill, object, copy, (is_infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
|
||||
else {
|
||||
// In this case we can (and should) leave it to be printed normally.
|
||||
|
||||
@@ -139,9 +139,9 @@ public:
|
||||
bool has_extruder(unsigned int extruder) const { return std::find(this->extruders.begin(), this->extruders.end(), extruder) != this->extruders.end(); }
|
||||
|
||||
// Return a zero based extruder from the region, or extruder_override if overriden.
|
||||
unsigned int wall_filament(const PrintRegion ®ion) const;
|
||||
unsigned int sparse_infill_filament(const PrintRegion ®ion) const;
|
||||
unsigned int solid_infill_filament(const PrintRegion ®ion) const;
|
||||
unsigned int wall_extruder_id(const PrintRegion ®ion) const;
|
||||
unsigned int sparse_infill_filament_id(const PrintRegion ®ion) const;
|
||||
unsigned int internal_solid_filament_id(const PrintRegion ®ion) const;
|
||||
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
|
||||
unsigned int extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const;
|
||||
|
||||
|
||||
@@ -3881,7 +3881,7 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
for (auto &used : m_used_filament_length) // reset used filament stats
|
||||
used = 0.f;
|
||||
|
||||
int wall_filament = get_wall_filament_for_all_layer();
|
||||
int wall_filament_id = get_wall_filament_for_all_layer();
|
||||
|
||||
std::vector<WipeTower::ToolChangeResult> layer_result;
|
||||
int index = 0;
|
||||
@@ -3909,24 +3909,24 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
ToolChangeResult finish_layer_tcr;
|
||||
ToolChangeResult timelapse_wall;
|
||||
|
||||
auto get_wall_filament_for_this_layer = [this, &layer, &wall_filament]() -> int {
|
||||
auto get_wall_filament_for_this_layer = [this, &layer, &wall_filament_id]() -> int {
|
||||
if (layer.tool_changes.size() == 0)
|
||||
return -1;
|
||||
|
||||
int candidate_id = -1;
|
||||
for (size_t idx = 0; idx < layer.tool_changes.size(); ++idx) {
|
||||
if (idx == 0) {
|
||||
if (layer.tool_changes[idx].old_tool == wall_filament)
|
||||
return wall_filament;
|
||||
else if (m_filpar[layer.tool_changes[idx].old_tool].category == m_filpar[wall_filament].category) {
|
||||
if (layer.tool_changes[idx].old_tool == wall_filament_id)
|
||||
return wall_filament_id;
|
||||
else if (m_filpar[layer.tool_changes[idx].old_tool].category == m_filpar[wall_filament_id].category) {
|
||||
candidate_id = layer.tool_changes[idx].old_tool;
|
||||
}
|
||||
}
|
||||
if (layer.tool_changes[idx].new_tool == wall_filament) {
|
||||
return wall_filament;
|
||||
if (layer.tool_changes[idx].new_tool == wall_filament_id) {
|
||||
return wall_filament_id;
|
||||
}
|
||||
|
||||
if ((candidate_id == -1) && (m_filpar[layer.tool_changes[idx].new_tool].category == m_filpar[wall_filament].category))
|
||||
if ((candidate_id == -1) && (m_filpar[layer.tool_changes[idx].new_tool].category == m_filpar[wall_filament_id].category))
|
||||
candidate_id = layer.tool_changes[idx].new_tool;
|
||||
}
|
||||
return candidate_id == -1 ? layer.tool_changes[0].new_tool : candidate_id;
|
||||
|
||||
@@ -462,10 +462,10 @@ std::string GCodeWriter::set_input_shaping(char axis, float damp, float freq, st
|
||||
break;
|
||||
}
|
||||
case gcfMarlinLegacy: {
|
||||
throw std::runtime_error(_u8L("Input shaping is not supported by Marlin < 2.1.2.\nCheck your firmware version and update your G-code flavor to ´Marlin 2´"));
|
||||
throw std::runtime_error(_u8L("Input shaping is not supported by Marlin < 2.1.2.\nCheck your firmware version and update your G-code flavor to ´Marlin 2´."));
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(_u8L("Input shaping is only supported by Klipper, RepRapFirmware and Marlin 2"));
|
||||
throw std::runtime_error(_u8L("Input shaping is only supported by Klipper, RepRapFirmware and Marlin 2."));
|
||||
}
|
||||
if (!gcode.str().empty()) {
|
||||
if (GCodeWriter::full_gcode_comment) {
|
||||
@@ -1154,13 +1154,19 @@ std::string GCodeWriter::set_additional_fan(unsigned int speed)
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_exhaust_fan( int speed,bool add_eol)
|
||||
std::string GCodeWriter::set_exhaust_fan(int speed)
|
||||
{
|
||||
std::ostringstream gcode;
|
||||
gcode << "M106" << " P3" << " S" << (int)(speed / 100.0 * 255);
|
||||
|
||||
if(add_eol)
|
||||
gcode << "\n";
|
||||
if (GCodeWriter::full_gcode_comment) {
|
||||
if (speed == 0)
|
||||
gcode << " ; disable exhaust fan ";
|
||||
else
|
||||
gcode << " ; enable exhaust fan ";
|
||||
}
|
||||
|
||||
gcode << "\n";
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
std::string set_fan(unsigned int speed) const;
|
||||
//BBS: set additional fan speed for BBS machine only
|
||||
static std::string set_additional_fan(unsigned int speed);
|
||||
static std::string set_exhaust_fan(int speed,bool add_eol);
|
||||
static std::string set_exhaust_fan(int speed);
|
||||
//BBS
|
||||
void set_object_start_str(std::string start_string) { m_gcode_label_objects_start = start_string; }
|
||||
bool is_object_start_str_empty() { return m_gcode_label_objects_start.empty(); }
|
||||
|
||||
@@ -1236,7 +1236,7 @@ std::vector<Vec2d> edge_offset_contour_intersections(
|
||||
// 2) offset_distance == dmin_new -> one duplicate point is found.
|
||||
// If 2) is ignored, then two tangentially touching offset curves are created.
|
||||
// If not ignored, then the two offset curves merge at this double point.
|
||||
// We should merge the contours while pushing the the two copies of the tangent point away a bit.
|
||||
// We should merge the contours while pushing the two copies of the tangent point away a bit.
|
||||
dmin = dmin_new;
|
||||
num_intersections = (offset_distance > dmin) + 1;
|
||||
}
|
||||
|
||||
@@ -141,14 +141,15 @@ bool Layer::is_perimeter_compatible(const Print& print, const PrintRegion& a, co
|
||||
const PrintRegionConfig& config = a.config();
|
||||
const PrintRegionConfig& other_config = b.config();
|
||||
|
||||
return config.wall_filament == other_config.wall_filament
|
||||
return config.outer_wall_filament_id == other_config.outer_wall_filament_id
|
||||
&& config.inner_wall_filament_id == other_config.inner_wall_filament_id
|
||||
&& config.wall_loops == other_config.wall_loops
|
||||
&& config.wall_sequence == other_config.wall_sequence
|
||||
&& config.is_infill_first == other_config.is_infill_first
|
||||
&& config.inner_wall_speed.get_at(print.get_extruder_id(config.wall_filament)) == other_config.inner_wall_speed.get_at(print.get_extruder_id(config.wall_filament))
|
||||
&& config.outer_wall_speed.get_at(print.get_extruder_id(config.wall_filament)) == other_config.outer_wall_speed.get_at(print.get_extruder_id(config.wall_filament))
|
||||
&& config.small_perimeter_speed.get_at(print.get_extruder_id(config.wall_filament)) == other_config.small_perimeter_speed.get_at(print.get_extruder_id(config.wall_filament))
|
||||
&& config.gap_infill_speed.get_at(print.get_extruder_id(config.wall_filament)) == other_config.gap_infill_speed.get_at(print.get_extruder_id(config.wall_filament))
|
||||
&& config.inner_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.inner_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.outer_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.outer_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.small_perimeter_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.small_perimeter_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.gap_infill_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.gap_infill_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.filter_out_gap_fill.value == other_config.filter_out_gap_fill.value
|
||||
&& config.detect_overhang_wall == other_config.detect_overhang_wall
|
||||
&& config.overhang_reverse == other_config.overhang_reverse
|
||||
|
||||
@@ -34,16 +34,26 @@ Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const
|
||||
const PrintRegionConfig ®ion_config = region.config();
|
||||
const PrintObject &print_object = *this->layer()->object();
|
||||
Flow bridge_flow;
|
||||
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will fall back to zero'th element, so everything is all right.
|
||||
auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1));
|
||||
const ConfigOptionFloatOrPercent& bridge_width_opt = region_config.bridge_line_width;
|
||||
const double bridge_width = bridge_width_opt.get_abs_value(nozzle_diameter);
|
||||
const bool has_bridge_width = bridge_width > 0.;
|
||||
const double bridge_flow_ratio = region_config.bridge_flow;
|
||||
|
||||
if (thick_bridge) {
|
||||
// The old Slic3r way (different from all other slicers): Use rounded extrusions.
|
||||
// Get the configured nozzle_diameter for the extruder associated to the flow role requested.
|
||||
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
|
||||
// Applies default bridge spacing.
|
||||
bridge_flow = Flow::bridging_flow(float(sqrt(region_config.bridge_flow)) * nozzle_diameter, nozzle_diameter);
|
||||
float thread_diameter = has_bridge_width ? float(bridge_width) : nozzle_diameter;
|
||||
if (bridge_flow_ratio > 0.)
|
||||
thread_diameter *= float(sqrt(bridge_flow_ratio));
|
||||
bridge_flow = Flow::bridging_flow(thread_diameter, nozzle_diameter);
|
||||
} else {
|
||||
// The same way as other slicers: Use normal extrusions. Apply bridge_flow while maintaining the original spacing.
|
||||
bridge_flow = this->flow(role).with_flow_ratio(region_config.bridge_flow);
|
||||
Flow base_flow = this->flow(role);
|
||||
if (has_bridge_width)
|
||||
base_flow = Flow(float(bridge_width), base_flow.height(), nozzle_diameter);
|
||||
bridge_flow = base_flow.with_flow_ratio(bridge_flow_ratio);
|
||||
}
|
||||
return bridge_flow;
|
||||
|
||||
@@ -83,6 +93,12 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
|
||||
(this->layer()->id() >= size_t(region_config.bottom_shell_layers.value) &&
|
||||
this->layer()->print_z >= region_config.bottom_shell_thickness - EPSILON);
|
||||
|
||||
double model_rotation_rad = 0.0;
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
auto m = this->layer()->object()->trafo().matrix();
|
||||
model_rotation_rad = std::atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
|
||||
PerimeterGenerator g(
|
||||
// input:
|
||||
&slices,
|
||||
@@ -94,6 +110,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
|
||||
&this->layer()->object()->config(),
|
||||
&print_config,
|
||||
spiral_mode,
|
||||
model_rotation_rad,
|
||||
|
||||
// output:
|
||||
&this->perimeters,
|
||||
@@ -517,10 +534,27 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
SurfaceCollection bridges;
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z;
|
||||
const double custom_angle = this->region().config().bridge_angle.value;
|
||||
bridges.surfaces = custom_angle > 0 ?
|
||||
expand_merge_surfaces(this->fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, Geometry::deg2rad(custom_angle)) :
|
||||
// ORCA: Relative/Align Bridge Angle
|
||||
const auto ®ion_config = this->region().config();
|
||||
const double custom_angle_deg = region_config.bridge_angle.value;
|
||||
const bool relative_angle = region_config.relative_bridge_angle.value;
|
||||
const double custom_angle_rad = Geometry::deg2rad(custom_angle_deg);
|
||||
|
||||
double align_offset_rad = 0.0;
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
auto m = this->layer()->object()->trafo().matrix();
|
||||
align_offset_rad = std::atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
|
||||
bridges.surfaces = (custom_angle_deg > 0.0 && !relative_angle) ?
|
||||
expand_merge_surfaces(this->fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, custom_angle_rad + align_offset_rad) :
|
||||
expand_bridges_detect_orientations(this->fill_surfaces.surfaces, expansion_zones, closing_radius);
|
||||
if (custom_angle_deg > 0.0 && relative_angle) {
|
||||
for (Surface &bridge_surface : bridges.surfaces) {
|
||||
if (bridge_surface.bridge_angle >= 0)
|
||||
bridge_surface.bridge_angle += custom_angle_rad;
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
@@ -782,12 +816,25 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
// would get merged into a single one while they need different directions
|
||||
// also, supply the original expolygon instead of the grown one, because in case
|
||||
// of very thin (but still working) anchors, the grown expolygon would go beyond them
|
||||
double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value);
|
||||
if (custom_angle > 0.0) {
|
||||
bridges[idx_last].bridge_angle = custom_angle;
|
||||
// ORCA: Relative/Align Bridge Angle
|
||||
const auto ®ion_config = this->region().config();
|
||||
const double custom_angle_deg = region_config.bridge_angle.value;
|
||||
const bool relative_angle = region_config.relative_bridge_angle.value;
|
||||
const double custom_angle_rad = Geometry::deg2rad(custom_angle_deg);
|
||||
|
||||
double align_offset_rad = 0.0;
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
auto m = this->layer()->object()->trafo().matrix();
|
||||
align_offset_rad = std::atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
|
||||
if (custom_angle_deg > 0.0 && !relative_angle) {
|
||||
bridges[idx_last].bridge_angle = custom_angle_rad + align_offset_rad;
|
||||
} else {
|
||||
auto [bridging_dir, unsupported_dist] = detect_bridging_direction(to_polygons(initial), to_polygons(lower_layer->lslices));
|
||||
bridges[idx_last].bridge_angle = PI + std::atan2(bridging_dir.y(), bridging_dir.x());
|
||||
if (custom_angle_deg > 0.0 && relative_angle)
|
||||
bridges[idx_last].bridge_angle += custom_angle_rad;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+19
-14
@@ -225,7 +225,7 @@ _finished:
|
||||
}
|
||||
|
||||
if (model.objects.empty())
|
||||
throw Slic3r::RuntimeError(_L("The supplied file couldn't be read because it's empty"));
|
||||
throw Slic3r::RuntimeError(_L("The supplied file couldn\'t be read because it\'s empty."));
|
||||
|
||||
for (ModelObject *o : model.objects)
|
||||
o->input_file = input_file;
|
||||
@@ -337,7 +337,7 @@ Model Model::read_from_file(const std::string&
|
||||
}
|
||||
#endif
|
||||
else
|
||||
throw Slic3r::RuntimeError(_L("Unknown file format. Input file must have .stl, .obj, .amf(.xml) extension."));
|
||||
throw Slic3r::RuntimeError(_L("Unknown file format: input file must have .stl, .obj, or .amf(.xml) extension."));
|
||||
|
||||
if (is_cb_cancel) {
|
||||
Model empty_model;
|
||||
@@ -352,7 +352,7 @@ Model Model::read_from_file(const std::string&
|
||||
}
|
||||
|
||||
if (model.objects.empty())
|
||||
throw Slic3r::RuntimeError(_L("The supplied file couldn't be read because it's empty"));
|
||||
throw Slic3r::RuntimeError(_L("The supplied file couldn\'t be read because it\'s empty."));
|
||||
|
||||
for (ModelObject *o : model.objects)
|
||||
o->input_file = input_file;
|
||||
@@ -399,7 +399,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
|
||||
else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
|
||||
result = load_amf(input_file.c_str(), config, config_substitutions, &model, &is_bbl_3mf);
|
||||
else
|
||||
throw Slic3r::RuntimeError(_L("Unknown file format. Input file must have .3mf or .zip.amf extension."));
|
||||
throw Slic3r::RuntimeError(_L("Unknown file format: input file must have .3mf or .zip.amf extension."));
|
||||
|
||||
if (out_file_type != En3mfType::From_Prusa) {
|
||||
if (is_orca_3mf)
|
||||
@@ -696,8 +696,7 @@ unsigned int Model::update_print_volume_state(const BuildVolume &build_volume)
|
||||
num_printable += model_object->update_instances_print_volume_state(build_volume);
|
||||
//BBS: add logs for build_volume
|
||||
const BoundingBoxf3& print_volume = build_volume.bounding_volume();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", print_volume {%1%, %2%, %3%} to {%4%, %5%, %6%}, got %7% printable istances")\
|
||||
%print_volume.min.x() %print_volume.min.y() %print_volume.min.z()%print_volume.max.x() %print_volume.max.y() %print_volume.max.z() %num_printable;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", print_volume {%1%, %2%, %3%} to {%4%, %5%, %6%}, got %7% printable istances") %print_volume.min.x() %print_volume.min.y() %print_volume.min.z()%print_volume.max.x() %print_volume.max.y() %print_volume.max.z() %num_printable;
|
||||
return num_printable;
|
||||
}
|
||||
|
||||
@@ -2127,10 +2126,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects, const bool remap_paint)
|
||||
|
||||
if (is_multi_volume_object) {
|
||||
// BBS: volume geometry not changed, so we can keep the paint facets
|
||||
#define COPY_FACETS(f) \
|
||||
if (new_vol->f.timestamp() == volume->f.timestamp()) \
|
||||
new_vol->f.reset(); /* BBS: let next assign take effect */ \
|
||||
new_vol->f.assign(volume->f)
|
||||
#define COPY_FACETS(f) if (new_vol->f.timestamp() == volume->f.timestamp()) new_vol->f.reset(); /* BBS: let next assign take effect */ new_vol->f.assign(volume->f)
|
||||
|
||||
COPY_FACETS(supported_facets);
|
||||
COPY_FACETS(seam_facets);
|
||||
@@ -2372,8 +2368,7 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume
|
||||
unsigned int num_printable = 0;
|
||||
//BBS: add logs for build_volume
|
||||
//const BoundingBoxf3& print_volume = build_volume.bounding_volume();
|
||||
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", print_volume {%1%, %2%, %3%} to {%4%, %5%, %6%}")\
|
||||
// %print_volume.min.x() %print_volume.min.y() %print_volume.min.z()%print_volume.max.x() %print_volume.max.y() %print_volume.max.z();
|
||||
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", print_volume {%1%, %2%, %3%} to {%4%, %5%, %6%}") // %print_volume.min.x() %print_volume.min.y() %print_volume.min.z()%print_volume.max.x() %print_volume.max.y() %print_volume.max.z();
|
||||
for (ModelInstance* model_instance : this->instances) {
|
||||
if (model_instance->update_print_volume_state(build_volume) == ModelInstancePVS_Inside) {
|
||||
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", object %1%'s instance inside print volum")%this->name;
|
||||
@@ -2573,6 +2568,12 @@ void ModelVolume::update_extruder_count(size_t extruder_count)
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Clear a stale per-volume filament assignment that no longer exists after the extruder count
|
||||
// shrank (e.g. printer switch to one with fewer filaments), so downstream readers never index
|
||||
// per-filament config vectors out of range. Ported from BambuStudio (STUDIO-15763).
|
||||
if (extruder_id() > extruder_count) {
|
||||
this->config.erase("extruder");
|
||||
}
|
||||
}
|
||||
|
||||
void ModelVolume::update_extruder_count_when_delete_filament(size_t extruder_count, size_t filament_id, int replace_filament_id)
|
||||
@@ -2584,6 +2585,11 @@ void ModelVolume::update_extruder_count_when_delete_filament(size_t extruder_cou
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Same stale-assignment cleanup as update_extruder_count, for the filament-delete path.
|
||||
// Ported from BambuStudio (STUDIO-15763).
|
||||
if (extruder_id() > extruder_count) {
|
||||
this->config.erase("extruder");
|
||||
}
|
||||
}
|
||||
|
||||
void ModelVolume::center_geometry_after_creation(bool update_source_offset)
|
||||
@@ -3423,8 +3429,7 @@ ModelInstanceEPrintVolumeState ModelInstance::calc_print_volume_state(const Buil
|
||||
BoundingBoxf3 bb = vol->get_convex_hull().bounding_box();
|
||||
Vec3d size = bb.size();
|
||||
if ((size.x() == 0.f) || (size.y() == 0.f) || (size.z() == 0.f)) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", object %1%'s vol %2% is empty, skip it, box: {%3%, %4%, %5%} to {%6%, %7%, %8%}")%this->object->name %vol->name\
|
||||
%bb.min.x() %bb.min.y() %bb.min.z()%bb.max.x() %bb.max.y() %bb.max.z();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", object %1%'s vol %2% is empty, skip it, box: {%3%, %4%, %5%} to {%6%, %7%, %8%}")%this->object->name %vol->name %bb.min.x() %bb.min.y() %bb.min.z()%bb.max.x() %bb.max.y() %bb.max.z();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ arrangement::ArrangePolygons get_arrange_polys(const Model &model, ModelInstance
|
||||
for (ModelObject *mo : model.objects)
|
||||
for (ModelInstance *minst : mo->instances) {
|
||||
minst->get_arrange_polygon(&ap);
|
||||
// ModelInstance::get_arrange_polygon leaves bed_idx at its UNARRANGED
|
||||
// default; seed it to bed 0 (as get_instance_arrange_poly does) so the
|
||||
// nester treats the item as placeable instead of returning it unplaced.
|
||||
ap.bed_idx = 0;
|
||||
input.emplace_back(ap);
|
||||
instances.emplace_back(minst);
|
||||
}
|
||||
|
||||
@@ -1345,14 +1345,14 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
||||
if (const PrintRegionConfig &config = region->region().config();
|
||||
// color_idx == 0 means "don't know" extruder aka the underlying extruder.
|
||||
// As this region may split existing regions, we collect statistics over all regions for color_idx == 0.
|
||||
color_idx == 0 || config.wall_filament == int(color_idx)) {
|
||||
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);
|
||||
out.extrusion_width = std::max<float>(out.extrusion_width, outer_wall_line_width);
|
||||
out.top_shell_layers = std::max<int>(out.top_shell_layers, config.top_shell_layers);
|
||||
out.bottom_shell_layers = std::max<int>(out.bottom_shell_layers, config.bottom_shell_layers);
|
||||
out.small_region_threshold = config.gap_infill_speed.get_at(print_object.print()->get_extruder_id(config.wall_filament - 1)) > 0 ?
|
||||
out.small_region_threshold = config.gap_infill_speed.get_at(print_object.print()->get_extruder_id(config.outer_wall_filament_id - 1)) > 0 ?
|
||||
// Gap fill enabled. Enable a single line of 1/2 extrusion width.
|
||||
0.5f * outer_wall_line_width :
|
||||
// Gap fill disabled. Enable two lines slightly overlapping.
|
||||
|
||||
@@ -179,7 +179,7 @@ static bool clip_narrow_corner(
|
||||
bool blocked = forward == Blocked || backward == Blocked;
|
||||
assert(polygon.size() < 3 ||
|
||||
// Remaining triangle is CCW oriented. Both sides must be "blocked", but the other side may have not been
|
||||
// updated after the the p02 / p22 became united into a single point.
|
||||
// updated after the p02 / p22 became united into a single point.
|
||||
blocked ||
|
||||
// Remaining triangle is concave, however both of its arms are long.
|
||||
(forward == Far && backward == Far));
|
||||
|
||||
@@ -581,7 +581,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP
|
||||
coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
|
||||
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
|
||||
|
||||
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->wall_filament - 1)) > 0;
|
||||
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->outer_wall_filament_id - 1)) > 0;
|
||||
|
||||
// split the polygons with top/not_top
|
||||
// get the offset from solid surface anchor
|
||||
@@ -620,7 +620,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP
|
||||
// get the real top surface
|
||||
ExPolygons grown_lower_slices;
|
||||
ExPolygons bridge_checker;
|
||||
auto nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1);
|
||||
auto nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1);
|
||||
// Check whether surface be bridge or not
|
||||
if (this->lower_slices != NULL) {
|
||||
// BBS: get the Polygons below the polygon this layer
|
||||
@@ -1173,7 +1173,7 @@ void PerimeterGenerator::process_classic()
|
||||
// We consider overhang any part where the entire nozzle diameter is not supported by the
|
||||
// lower layer, so we take lower slices and offset them by half the nozzle diameter used
|
||||
// in the current layer
|
||||
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1);
|
||||
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1);
|
||||
m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2)));
|
||||
}
|
||||
|
||||
@@ -1189,7 +1189,7 @@ void PerimeterGenerator::process_classic()
|
||||
// internal flow which is unrelated.
|
||||
coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
|
||||
coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
|
||||
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->wall_filament - 1)) > 0;
|
||||
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->outer_wall_filament_id - 1)) > 0;
|
||||
|
||||
// BBS: this flow is for smaller external perimeter for small area
|
||||
coord_t ext_min_spacing_smaller = coord_t(ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE));
|
||||
@@ -1727,9 +1727,12 @@ void PerimeterGenerator::add_infill_contour_for_arachne( ExPolygons infil
|
||||
// Orca: sacrificial bridge layer algorithm ported from SuperSlicer
|
||||
void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perimeter_spacing, coord_t ext_perimeter_width)
|
||||
{
|
||||
|
||||
if (this->config->counterbore_hole_bridging == chbNone)
|
||||
return; // return if counterbore hole is not enabled
|
||||
|
||||
//store surface for bridge infill to avoid unsupported perimeters (but the first one, this one is always good)
|
||||
if (this->config->counterbore_hole_bridging != chbNone
|
||||
&& this->lower_slices != NULL && !this->lower_slices->empty()) {
|
||||
if (this->lower_slices != NULL && !this->lower_slices->empty()) {
|
||||
const coordf_t bridged_infill_margin = scale_(BRIDGE_INFILL_MARGIN);
|
||||
|
||||
for (size_t surface_idx = 0; surface_idx < all_surfaces.size(); surface_idx++) {
|
||||
@@ -1738,11 +1741,8 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
//compute our unsupported surface
|
||||
ExPolygons unsupported = diff_ex(last, *this->lower_slices, ApplySafetyOffset::Yes);
|
||||
if (!unsupported.empty()) {
|
||||
// remove small overhangs (when using chbFilled we need to be less aggressive in removing small overhangs,
|
||||
// to avoid affecting bridging detection.)
|
||||
const int outset_divisor = this->config->counterbore_hole_bridging.value == chbFilled ? 2 : 1;
|
||||
ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing),
|
||||
double(perimeter_spacing) / outset_divisor);
|
||||
//remove small overhangs
|
||||
ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing), double(perimeter_spacing));
|
||||
|
||||
if (!unsupported_filtered.empty()) {
|
||||
//to_draw.insert(to_draw.end(), last.begin(), last.end());
|
||||
@@ -1759,13 +1759,24 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
for (ExPolygon unsupported : unsupported_filtered) {
|
||||
BridgeDetector detector{ unsupported,
|
||||
lower_island.expolygons,
|
||||
perimeter_spacing };
|
||||
if (detector.detect_angle(Geometry::deg2rad(this->config->bridge_angle.value)))
|
||||
perimeter_spacing / 4}; // Use a finer BridgeDetector. This affects coverage resolution, not extrusion spacing.
|
||||
// ORCA: Relative/Align Bridge Angle
|
||||
const double custom_angle_deg = this->config->bridge_angle.value;
|
||||
const bool relative_angle = this->config->relative_bridge_angle.value;
|
||||
const double detect_angle_rad = (custom_angle_deg > 0.0 && !relative_angle)
|
||||
? Geometry::deg2rad(custom_angle_deg) +
|
||||
(this->config->align_infill_direction_to_model ? this->m_model_rotation_rad : 0.0)
|
||||
: 0.0;
|
||||
if (detector.detect_angle(detect_angle_rad))
|
||||
expolygons_append(bridgeable, union_ex(detector.coverage(-1, true)));
|
||||
}
|
||||
if (!bridgeable.empty()) {
|
||||
//check if we get everything or just the bridgeable area
|
||||
if (/*this->config->counterbore_hole_bridging.value == chbNoPeri || */this->config->counterbore_hole_bridging.value == chbFilled) {
|
||||
if (!bridgeable.empty() && !surface->expolygon.holes.empty()) { // keep out if cannot be bridged or no holes to bridge
|
||||
const coordf_t bridge_anchor_offset = std::min({bridged_infill_margin, coordf_t(perimeter_spacing), coordf_t(ext_perimeter_width)});
|
||||
|
||||
// Handle filled vs partial counterbore bridging modes.
|
||||
if (this->config->counterbore_hole_bridging.value == chbFilled) {
|
||||
unsupported_filtered = offset_ex(unsupported_filtered, -perimeter_spacing); // shrink it to survive the strict bridge-candidate filter
|
||||
|
||||
//we bridge everything, even the not-bridgeable bits
|
||||
for (size_t i = 0; i < unsupported_filtered.size();) {
|
||||
ExPolygon& poly_unsupp = *(unsupported_filtered.begin() + i);
|
||||
@@ -1785,139 +1796,96 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
unsupported_filtered.erase(unsupported_filtered.begin() + i);
|
||||
}
|
||||
}
|
||||
unsupported_filtered = intersection_ex(last,
|
||||
offset_ex(unsupported_filtered, 0.5 * double(bridged_infill_margin)));
|
||||
if (this->config->counterbore_hole_bridging.value == chbFilled) {
|
||||
for (ExPolygon& expol : unsupported_filtered) {
|
||||
//check if the holes won't be covered by the upper layer
|
||||
//TODO: if we want to do that, we must modify the geometry before making perimeters.
|
||||
//if (this->upper_slices != nullptr && !this->upper_slices->expolygons.empty()) {
|
||||
// for (Polygon &poly : expol.holes) poly.make_counter_clockwise();
|
||||
// float perimeterwidth = this->config->perimeters == 0 ? 0 : (this->ext_perimeter_flow.scaled_width() + (this->config->perimeters - 1) + this->perimeter_flow.scaled_spacing());
|
||||
// std::cout << "test upper slices with perimeterwidth=" << perimeterwidth << "=>" << offset_ex(this->upper_slices->expolygons, -perimeterwidth).size();
|
||||
// if (intersection(Polygons() = { expol.holes }, to_polygons(offset_ex(this->upper_slices->expolygons, -this->ext_perimeter_flow.scaled_width() / 2))).empty()) {
|
||||
// std::cout << " EMPTY";
|
||||
// expol.holes.clear();
|
||||
// } else {
|
||||
// }
|
||||
// std::cout << "\n";
|
||||
//} else {
|
||||
expol.holes.clear();
|
||||
//}
|
||||
|
||||
//detect inside volume
|
||||
for (size_t surface_idx_other = 0; surface_idx_other < all_surfaces.size(); surface_idx_other++) {
|
||||
if (surface_idx == surface_idx_other) continue;
|
||||
if (intersection_ex(ExPolygons() = { expol }, ExPolygons() = { all_surfaces[surface_idx_other].expolygon }).size() > 0) {
|
||||
//this means that other_surf was inside an expol holes
|
||||
//as we removed them, we need to add a new one
|
||||
ExPolygons new_poly = offset2_ex(ExPolygons{ all_surfaces[surface_idx_other].expolygon }, double(-bridged_infill_margin - perimeter_spacing), double(perimeter_spacing));
|
||||
if (new_poly.size() == 1) {
|
||||
all_surfaces[surface_idx_other].expolygon = new_poly[0];
|
||||
expol.holes.push_back(new_poly[0].contour);
|
||||
unsupported_filtered = offset_ex(unsupported_filtered, perimeter_spacing + bridge_anchor_offset); // restore it back to its original size and add anchor
|
||||
unsupported_filtered = intersection_ex(last, unsupported_filtered); // clamp to the original surface, to avoid creating new unsupported areas
|
||||
|
||||
for (ExPolygon& expol : unsupported_filtered) {
|
||||
// Remove holes that need sacrificial fill, but keep holes
|
||||
// whose wall is already supported by the lower layer.
|
||||
const float hole_wall_width = float(ext_perimeter_width / 2);
|
||||
for (size_t hole_idx = 0; hole_idx < expol.holes.size();) {
|
||||
Polygon hole_area_contour = expol.holes[hole_idx];
|
||||
hole_area_contour.make_counter_clockwise();
|
||||
|
||||
const ExPolygons hole_area = { ExPolygon(hole_area_contour) };
|
||||
ExPolygons hole_wall_area = diff_ex(
|
||||
offset_ex(hole_area_contour, hole_wall_width),
|
||||
hole_area,
|
||||
ApplySafetyOffset::Yes);
|
||||
hole_wall_area = intersection_ex(hole_wall_area, ExPolygons{ expol }, ApplySafetyOffset::Yes);
|
||||
if (!hole_wall_area.empty() &&
|
||||
intersection_ex(hole_wall_area, *this->lower_slices, ApplySafetyOffset::Yes).empty())
|
||||
expol.holes.erase(expol.holes.begin() + hole_idx);
|
||||
// After erase(), the next hole shifts into the same index. So hole_idx
|
||||
// must not be incremented, otherwise the next hole would be skipped.
|
||||
else
|
||||
++hole_idx; // keep this hole, it won't be bridged, so we need to keep it as a hole
|
||||
}
|
||||
|
||||
//detect inside volume
|
||||
for (size_t surface_idx_other = 0; surface_idx_other < all_surfaces.size(); surface_idx_other++) {
|
||||
if (surface_idx == surface_idx_other) continue;
|
||||
if (intersection_ex(ExPolygons() = { expol }, ExPolygons() = { all_surfaces[surface_idx_other].expolygon }).size() > 0) {
|
||||
//this means that other_surf was inside an expol holes
|
||||
//as we removed them, we need to add a new one
|
||||
ExPolygons new_poly = offset2_ex(ExPolygons{ all_surfaces[surface_idx_other].expolygon }, double(-bridged_infill_margin - perimeter_spacing), double(perimeter_spacing));
|
||||
if (new_poly.size() == 1) {
|
||||
all_surfaces[surface_idx_other].expolygon = new_poly[0];
|
||||
expol.holes.push_back(new_poly[0].contour);
|
||||
expol.holes.back().make_clockwise();
|
||||
} else {
|
||||
for (size_t idx = 0; idx < new_poly.size(); idx++) {
|
||||
Surface new_surf = all_surfaces[surface_idx_other];
|
||||
new_surf.expolygon = new_poly[idx];
|
||||
all_surfaces.push_back(new_surf);
|
||||
expol.holes.push_back(new_poly[idx].contour);
|
||||
expol.holes.back().make_clockwise();
|
||||
} else {
|
||||
for (size_t idx = 0; idx < new_poly.size(); idx++) {
|
||||
Surface new_surf = all_surfaces[surface_idx_other];
|
||||
new_surf.expolygon = new_poly[idx];
|
||||
all_surfaces.push_back(new_surf);
|
||||
expol.holes.push_back(new_poly[idx].contour);
|
||||
expol.holes.back().make_clockwise();
|
||||
}
|
||||
all_surfaces.erase(all_surfaces.begin() + surface_idx_other);
|
||||
if (surface_idx_other < surface_idx) {
|
||||
surface_idx--;
|
||||
surface = &all_surfaces[surface_idx];
|
||||
}
|
||||
surface_idx_other--;
|
||||
}
|
||||
all_surfaces.erase(all_surfaces.begin() + surface_idx_other);
|
||||
if (surface_idx_other < surface_idx) {
|
||||
surface_idx--;
|
||||
surface = &all_surfaces[surface_idx];
|
||||
}
|
||||
surface_idx_other--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//TODO: add other polys as holes inside this one (-margin)
|
||||
} else if (/*this->config->counterbore_hole_bridging.value == chbBridgesOverhangs || */this->config->counterbore_hole_bridging.value == chbBridges) {
|
||||
// Partially bridged counterbore handling should not rewrite generic bridge islands
|
||||
// because by doing so regular bridges will lose their overhang-wall perimeters.
|
||||
if (surface->expolygon.holes.empty()) {
|
||||
unsupported_filtered.clear(); // "Partially bridged" only applies to hole-bearing bridge islands.
|
||||
continue;
|
||||
}
|
||||
//simplify to avoid most of artefacts from printing lines.
|
||||
ExPolygons bridgeable_simplified;
|
||||
} 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
|
||||
// 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
|
||||
// narrow hole-side wall contact, which must remain unbridgeable.
|
||||
|
||||
last = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons bridgeable_filtered;
|
||||
for (ExPolygon& poly : bridgeable) {
|
||||
poly.simplify(perimeter_spacing, &bridgeable_simplified);
|
||||
poly.simplify(perimeter_spacing, &bridgeable_filtered);
|
||||
}
|
||||
bridgeable_simplified = offset2_ex(bridgeable_simplified, -ext_perimeter_width, ext_perimeter_width);
|
||||
//bridgeable_simplified = intersection_ex(bridgeable_simplified, unsupported_filtered);
|
||||
//offset by perimeter spacing because the simplify may have reduced it a bit.
|
||||
//it's not dangerous as it will be intersected by 'unsupported' later
|
||||
//FIXME: add overlap in this->fill_surfaces->append
|
||||
//FIXME: it overlap inside unsuppported not-bridgeable area!
|
||||
bridgeable_filtered = opening_ex(bridgeable_filtered, ext_perimeter_width);
|
||||
|
||||
//bridgeable_simplified = offset2_ex(bridgeable_simplified, (double)-perimeter_spacing, (double)perimeter_spacing * 2);
|
||||
//ExPolygons unbridgeable = offset_ex(diff_ex(unsupported, bridgeable_simplified), perimeter_spacing * 3 / 2);
|
||||
//ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2)));
|
||||
//unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, 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);
|
||||
bridgeable_filtered = offset_ex(bridgeable_filtered, -perimeter_spacing);
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, last, 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));
|
||||
|
||||
|
||||
// if (this->config->counterbore_hole_bridging.value == chbBridges) {
|
||||
ExPolygons unbridgeable = unsupported_filtered;
|
||||
for (ExPolygon& expol : unbridgeable)
|
||||
expol.holes.clear();
|
||||
unbridgeable = diff_ex(unbridgeable, bridgeable_simplified);
|
||||
unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width * 2, ext_perimeter_width * 2);
|
||||
ExPolygons bridges_temp = offset2_ex(intersection_ex(last, diff_ex(unsupported_filtered, unbridgeable), ApplySafetyOffset::Yes), -ext_perimeter_width / 4, ext_perimeter_width / 4);
|
||||
//remove the overhangs section from the surface polygons
|
||||
ExPolygons reference = last;
|
||||
last = diff_ex(last, unsupported_filtered);
|
||||
//ExPolygons no_bridge = diff_ex(offset_ex(unbridgeable, ext_perimeter_width * 3 / 2), last);
|
||||
//bridges_temp = diff_ex(bridges_temp, no_bridge);
|
||||
coordf_t offset_to_do = bridged_infill_margin;
|
||||
bool first = true;
|
||||
unbridgeable = diff_ex(unbridgeable, offset_ex(bridges_temp, ext_perimeter_width));
|
||||
while (offset_to_do > ext_perimeter_width * 1.5) {
|
||||
unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width / 4, ext_perimeter_width * 2.25, ClipperLib::jtSquare);
|
||||
bridges_temp = diff_ex(bridges_temp, unbridgeable);
|
||||
bridges_temp = offset_ex(bridges_temp, ext_perimeter_width, ClipperLib::jtMiter, 6.);
|
||||
unbridgeable = diff_ex(unbridgeable, offset_ex(bridges_temp, ext_perimeter_width));
|
||||
offset_to_do -= ext_perimeter_width;
|
||||
first = false;
|
||||
}
|
||||
unbridgeable = offset_ex(unbridgeable, ext_perimeter_width + offset_to_do, ClipperLib::jtSquare);
|
||||
bridges_temp = diff_ex(bridges_temp, unbridgeable);
|
||||
unsupported_filtered = offset_ex(bridges_temp, offset_to_do);
|
||||
unsupported_filtered = intersection_ex(unsupported_filtered, reference);
|
||||
|
||||
// Normalize anchor size for partial bridges:
|
||||
// derive the bridge core first, then add a fixed overlap into support.
|
||||
const coordf_t anchor_overlap = bridged_infill_margin;
|
||||
ExPolygons bridge_core = diff_ex(unsupported_filtered, support, ApplySafetyOffset::Yes);
|
||||
if (bridge_core.empty()) {
|
||||
bridge_core = unsupported_filtered;
|
||||
}
|
||||
ExPolygons anchor_overlap_area = intersection_ex(
|
||||
offset_ex(bridge_core, anchor_overlap),
|
||||
support,
|
||||
ApplySafetyOffset::Yes);
|
||||
unsupported_filtered = union_ex(bridge_core, anchor_overlap_area);
|
||||
unsupported_filtered = intersection_ex(unsupported_filtered, reference);
|
||||
// } else {
|
||||
// ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2)));
|
||||
// unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, ext_perimeter_width);
|
||||
// unsupported_filtered = unbridgeable;
|
||||
|
||||
// ////put the bridge area inside the unsupported_filtered variable
|
||||
// //unsupported_filtered = intersection_ex(last,
|
||||
// // diff_ex(
|
||||
// // offset_ex(bridgeable_simplified, (double)perimeter_spacing / 2),
|
||||
// // unbridgeable
|
||||
// // )
|
||||
// // );
|
||||
// }
|
||||
} else {
|
||||
unsupported_filtered.clear();
|
||||
ExPolygons bridge_anchor_areas = intersection_ex(last, 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
|
||||
// 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
|
||||
}
|
||||
} else {
|
||||
unsupported_filtered.clear();
|
||||
@@ -2146,7 +2114,7 @@ void PerimeterGenerator::process_arachne()
|
||||
// We consider overhang any part where the entire nozzle diameter is not supported by the
|
||||
// lower layer, so we take lower slices and offset them by half the nozzle diameter used
|
||||
// in the current layer
|
||||
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1);
|
||||
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1);
|
||||
m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2)));
|
||||
}
|
||||
|
||||
@@ -2579,7 +2547,7 @@ bool PerimeterGeneratorLoop::is_internal_contour() const
|
||||
|
||||
std::vector<Polygons> PerimeterGenerator::generate_lower_polygons_series(float width)
|
||||
{
|
||||
float nozzle_diameter = print_config->nozzle_diameter.get_at(config->wall_filament - 1);
|
||||
float nozzle_diameter = print_config->nozzle_diameter.get_at(config->outer_wall_filament_id - 1);
|
||||
float start_offset = -0.5 * width;
|
||||
float end_offset = 0.5 * nozzle_diameter;
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ public:
|
||||
const PrintObjectConfig* object_config,
|
||||
const PrintConfig* print_config,
|
||||
const bool spiral_mode,
|
||||
const double model_rotation_rad,
|
||||
// Output:
|
||||
// Loops with the external thin walls
|
||||
ExtrusionEntityCollection* loops,
|
||||
@@ -132,6 +133,7 @@ public:
|
||||
config(config), object_config(object_config), print_config(print_config),
|
||||
m_spiral_vase(spiral_mode),
|
||||
m_scaled_resolution(scaled<double>(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)),
|
||||
m_model_rotation_rad(model_rotation_rad),
|
||||
loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), fill_no_overlap(fill_no_overlap),
|
||||
m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_smaller_width(-1)
|
||||
{}
|
||||
@@ -157,6 +159,7 @@ private:
|
||||
private:
|
||||
bool m_spiral_vase;
|
||||
double m_scaled_resolution;
|
||||
double m_model_rotation_rad;
|
||||
double m_ext_mm3_per_mm;
|
||||
double m_mm3_per_mm;
|
||||
double m_mm3_per_mm_overhang;
|
||||
|
||||
@@ -1005,6 +1005,9 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"lateral_lattice_angle_1",
|
||||
"lateral_lattice_angle_2",
|
||||
"infill_overhang_angle",
|
||||
"lightning_overhang_angle",
|
||||
"lightning_prune_angle",
|
||||
"lightning_straightening_angle",
|
||||
"top_surface_pattern",
|
||||
"bottom_surface_pattern",
|
||||
"infill_direction",
|
||||
@@ -1069,10 +1072,13 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"print_order",
|
||||
"support_remove_small_overhang",
|
||||
"filename_format",
|
||||
"wall_filament",
|
||||
"outer_wall_filament_id",
|
||||
"inner_wall_filament_id",
|
||||
"support_bottom_z_distance",
|
||||
"sparse_infill_filament",
|
||||
"solid_infill_filament",
|
||||
"sparse_infill_filament_id",
|
||||
"internal_solid_filament_id",
|
||||
"top_surface_filament_id",
|
||||
"bottom_surface_filament_id",
|
||||
"support_filament",
|
||||
"support_interface_filament",
|
||||
"support_interface_not_for_body",
|
||||
@@ -1094,6 +1100,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"infill_wall_overlap",
|
||||
"top_bottom_infill_wall_overlap",
|
||||
"bridge_flow",
|
||||
"bridge_line_width",
|
||||
"internal_bridge_flow",
|
||||
"elefant_foot_compensation",
|
||||
"elefant_foot_compensation_layers",
|
||||
@@ -1158,6 +1165,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"small_perimeter_threshold",
|
||||
"bridge_angle",
|
||||
"internal_bridge_angle",
|
||||
"relative_bridge_angle",
|
||||
"filter_out_gap_fill",
|
||||
"travel_acceleration",
|
||||
"inner_wall_acceleration",
|
||||
@@ -1318,7 +1326,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", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
|
||||
"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",
|
||||
"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",
|
||||
@@ -1329,7 +1337,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"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",
|
||||
"best_object_pos", "head_wrap_detect_zone",
|
||||
"host_type", "print_host", "printhost_apikey", "bbl_use_printhost", "printer_agent",
|
||||
"host_type", "print_host", "printhost_apikey", "flashforge_serial_number", "bbl_use_printhost", "printer_agent",
|
||||
"print_host_webui",
|
||||
"printhost_cafile","printhost_port","printhost_authorization_type",
|
||||
"printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "thumbnails", "thumbnails_format",
|
||||
@@ -1338,7 +1346,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"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",
|
||||
"z_offset",
|
||||
"disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "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",
|
||||
"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"
|
||||
};
|
||||
|
||||
@@ -2354,6 +2362,10 @@ bool PresetCollection::validate_preset(const std::string &preset_name, std::stri
|
||||
const std::string canonical_inherit_name = this->canonical_preset_name(inherit_name);
|
||||
it = this->find_preset_internal(canonical_inherit_name);
|
||||
found = it != m_presets.end() && it->name == canonical_inherit_name && is_trusted(*it);
|
||||
if (!found) {
|
||||
it = this->find_preset_renamed(canonical_inherit_name);
|
||||
found = it != m_presets.end() && is_trusted(*it);
|
||||
}
|
||||
if (found)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": preset_name %1%, inherit_name %2%, found inherit in list")%preset_name %inherit_name;
|
||||
else
|
||||
@@ -2445,7 +2457,9 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
|
||||
if (!inherits.empty() && (different_settings_list.size() > 0)) {
|
||||
auto iter = this->find_preset_internal(inherits);
|
||||
if (iter != m_presets.end() && iter->name == inherits) {
|
||||
if (iter == m_presets.end() || iter->name != inherits)
|
||||
iter = this->find_preset_renamed(inherits);
|
||||
if (iter != m_presets.end()) {
|
||||
//std::vector<std::string> dirty_options = cfg.diff(iter->config);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path;
|
||||
cfg.update_non_diff_values_to_base_config(iter->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
||||
@@ -2497,7 +2511,9 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
// and override its settings with the loaded ones.
|
||||
assert(it == m_presets.end());
|
||||
it = this->find_preset_internal(inherits);
|
||||
found = it != m_presets.end() && it->name == inherits;
|
||||
if (it == m_presets.end() || it->name != inherits)
|
||||
it = this->find_preset_renamed(inherits);
|
||||
found = it != m_presets.end();
|
||||
if (found && profile_print_params_same(it->config, cfg)) {
|
||||
// The system preset exists and it matches the values stored inside config.
|
||||
if (select == LoadAndSelect::Always)
|
||||
@@ -3839,6 +3855,7 @@ static std::vector<std::string> s_PhysicalPrinter_opts {
|
||||
"print_host",
|
||||
"print_host_webui",
|
||||
"printhost_apikey",
|
||||
"flashforge_serial_number",
|
||||
"printhost_cafile",
|
||||
"printhost_port",
|
||||
"printhost_authorization_type",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "libslic3r.h"
|
||||
#include "I18N.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "LocalesUtils.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
|
||||
@@ -2231,6 +2232,7 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
||||
[&](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
auto bundle = std::make_unique<PresetBundle>();
|
||||
bundle->set_is_validation_mode(validation_mode);
|
||||
try {
|
||||
auto result = bundle->load_vendor_configs_from_json(
|
||||
dir.string(), other_vendors[i], PresetBundle::LoadSystem,
|
||||
@@ -2871,6 +2873,14 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
|
||||
if (use_default_nozzle_volume_type) {
|
||||
project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values = current_printer.config.option<ConfigOptionEnumsGeneric>("default_nozzle_volume_type")->values;
|
||||
} else {
|
||||
// Orca: make sure `nozzle_volume_type` not shorter than `default_nozzle_volume_type`, otherwise we got array out of bound access
|
||||
// later in `Tab::switch_excluder`
|
||||
auto& opt = project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
|
||||
const auto& opt_default = current_printer.config.option<ConfigOptionEnumsGeneric>("default_nozzle_volume_type")->values;
|
||||
while (opt.size() < opt_default.size()) {
|
||||
opt.emplace_back(opt_default[opt.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the initial physical printer name.
|
||||
@@ -4095,13 +4105,16 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
|
||||
opt->value = boost::algorithm::clamp<int>(opt->value, 0, int(num_filaments));
|
||||
}
|
||||
|
||||
static const char* keys_1based[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament"};
|
||||
for (size_t i = 0; i < sizeof(keys_1based) / sizeof(keys_1based[0]); ++ i) {
|
||||
std::string key = std::string(keys_1based[i]);
|
||||
static const char* keys_with_default[] = {
|
||||
"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id",
|
||||
"internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id"
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(keys_with_default) / sizeof(keys_with_default[0]); ++ i) {
|
||||
std::string key = std::string(keys_with_default[i]);
|
||||
auto *opt = dynamic_cast<ConfigOptionInt*>(out.option(key, false));
|
||||
assert(opt != nullptr);
|
||||
if(opt->value < 1 || opt->value > int(num_filaments))
|
||||
opt->value = 1;
|
||||
if(opt->value < 0 || opt->value > int(num_filaments))
|
||||
opt->value = 0;
|
||||
}
|
||||
out.option<ConfigOptionString >("print_settings_id", true)->value = this->prints.get_selected_preset_name();
|
||||
out.option<ConfigOptionStrings>("filament_settings_id", true)->values = this->filament_presets;
|
||||
@@ -4971,6 +4984,39 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
reason = std::string("can not find printer_variant in vendor profile");
|
||||
return reason;
|
||||
}
|
||||
// An instantiation printer profile's nozzle_diameter must match the numeric (diameter)
|
||||
// prefix of its printer_variant: "0.4" -> {0.4}, "0.8HF" -> {0.8} (a trailing
|
||||
// non-numeric suffix such as "HF"/"HS" distinguishes a hardware sub-variant and is
|
||||
// ignored here), and for multi-nozzle printers "0.4+0.6" -> {0.4, 0.6}.
|
||||
// Note: a variant may legitimately repeat across presets of the same model (e.g. speed
|
||||
// modes, IDEX copy/mirror, or different control boards), so only the diameter is
|
||||
// validated, not variant uniqueness. Validation-only so the app keeps loading existing
|
||||
// profiles unchanged.
|
||||
if (validation_mode && instantiation == "true") {
|
||||
const auto *nd = config.option<ConfigOptionFloats>("nozzle_diameter");
|
||||
std::set<double> nozzles, variant_nozzles;
|
||||
if (nd != nullptr)
|
||||
nozzles.insert(nd->values.begin(), nd->values.end());
|
||||
std::vector<std::string> variant_tokens;
|
||||
boost::algorithm::split(variant_tokens, printer_variant, boost::algorithm::is_any_of("+"));
|
||||
bool variant_ok = true; // printer_variant is already guaranteed non-empty above
|
||||
for (const std::string &tok : variant_tokens) {
|
||||
size_t consumed = 0;
|
||||
double d = string_to_double_decimal_point(tok, &consumed);
|
||||
// Require a leading numeric diameter; a trailing suffix (e.g. "HF") is allowed.
|
||||
if (consumed == 0) { variant_ok = false; break; }
|
||||
variant_nozzles.insert(d);
|
||||
}
|
||||
if (!variant_ok || variant_nozzles != nozzles) {
|
||||
++m_errors;
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" <<
|
||||
preset_name << "\" has printer_variant \"" << printer_variant <<
|
||||
"\" that does not match its nozzle_diameter \"" << (nd ? nd->serialize() : std::string()) << "\". "
|
||||
"printer_variant must begin with the nozzle diameter, optionally followed by a non-numeric suffix "
|
||||
"(e.g. \"0.4\", \"0.8HF\"); for multi-nozzle printers, join the per-nozzle diameters with \"+\" in "
|
||||
"nozzle order (e.g. \"0.4+0.6\").";
|
||||
}
|
||||
}
|
||||
}
|
||||
const Preset *preset_existing = presets_collection->find_preset(preset_name, false);
|
||||
if (preset_existing != nullptr) {
|
||||
|
||||
+352
-200
@@ -23,7 +23,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_set>
|
||||
#include <sstream>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
@@ -237,7 +239,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
"bed_temperature_formula",
|
||||
"filament_notes",
|
||||
"process_notes",
|
||||
"printer_notes"
|
||||
"printer_notes",
|
||||
"use_3mf"
|
||||
};
|
||||
|
||||
static std::unordered_set<std::string> steps_ignore;
|
||||
@@ -669,13 +672,13 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
|
||||
//juedge the exclude area
|
||||
if (!intersection(exclude_polys, convex_hull_no_offset).empty()) {
|
||||
if (single_object_exception.string.empty()) {
|
||||
single_object_exception.string = (boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) %instance.model_instance->get_object()->name).str();
|
||||
single_object_exception.string = (boost::format(L("%1% is too close to exclusion area. There may be collisions when printing.")) %instance.model_instance->get_object()->name).str();
|
||||
// single_object_exception.object = instance.model_instance->get_object();
|
||||
//ORCA: Pass ModelInstance instead of ModelObject
|
||||
single_object_exception.object = instance.model_instance;
|
||||
}
|
||||
else {
|
||||
single_object_exception.string += "\n"+(boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) %instance.model_instance->get_object()->name).str();
|
||||
single_object_exception.string += "\n"+(boost::format(L("%1% is too close to exclusion area. There may be collisions when printing.")) %instance.model_instance->get_object()->name).str();
|
||||
single_object_exception.object = nullptr;
|
||||
}
|
||||
//if (polygons) {
|
||||
@@ -1038,7 +1041,7 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
|
||||
/*if (warning) {
|
||||
warning->string += L("Prime Tower is too close to exclusion area, there may be collisions when printing.\n");
|
||||
}*/
|
||||
return {L("Prime Tower") + L(" is too close to exclusion area, and collisions will be caused.\n")};
|
||||
return {L("Prime Tower") + L(" is too close to an exclusion area, and collisions will be caused.\n")};
|
||||
}
|
||||
if (print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, convex_hulls_temp).empty()) {
|
||||
return {L("Prime Tower") + L(" is too close to clumping detection area, and collisions will be caused.\n")};
|
||||
@@ -1254,14 +1257,23 @@ StringObjectException Print::check_multi_filament_valid(const Print& print)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Orca: this g92e0 regex is used copied from PrusaSlicer
|
||||
// Matches "G92 E0" with various forms of writing the zero and with an optional comment.
|
||||
boost::regex regex_g92e0 { "^[ \\t]*[gG]92[ \\t]*[eE](0(\\.0*)?|\\.0+)[ \\t]*(;.*)?$" };
|
||||
|
||||
// Precondition: Print::validate() requires the Print::apply() to be called its invocation.
|
||||
//BBS: refine seq-print validation logic.....FIXME:StringObjectException *warning can only contain one warning, but there might be many warnings, need a vector<StringObjectException>
|
||||
StringObjectException Print::validate(StringObjectException *warning, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons) const
|
||||
//BBS: refine seq-print validation logic
|
||||
StringObjectException Print::validate(std::vector<StringObjectException> *warnings, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons) const
|
||||
{
|
||||
auto add_warning = [warnings](StringObjectException w) {
|
||||
w.is_warning = true;
|
||||
if (warnings != nullptr)
|
||||
warnings->push_back(std::move(w));
|
||||
};
|
||||
auto warn = [&](std::string msg, std::string opt_key = "", const ObjectBase* object = nullptr) {
|
||||
StringObjectException w;
|
||||
w.string = std::move(msg);
|
||||
w.opt_key = std::move(opt_key);
|
||||
w.object = object;
|
||||
add_warning(std::move(w));
|
||||
};
|
||||
|
||||
std::vector<unsigned int> extruders = this->extruders();
|
||||
unsigned int nozzles = m_config.nozzle_diameter.size();
|
||||
|
||||
@@ -1276,9 +1288,8 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
if (!ret.string.empty())
|
||||
{
|
||||
ret.type = STRING_EXCEPT_FILAMENTS_DIFFERENT_TEMP;
|
||||
if (ret.is_warning && warning != nullptr) {
|
||||
*warning = ret;
|
||||
//return {};
|
||||
if (ret.is_warning) {
|
||||
add_warning(ret);
|
||||
}else
|
||||
return ret;
|
||||
}
|
||||
@@ -1304,32 +1315,26 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
}
|
||||
else {
|
||||
//BBS
|
||||
auto ret = layered_print_cleareance_valid(*this, warning);
|
||||
StringObjectException layer_warning;
|
||||
auto ret = layered_print_cleareance_valid(*this, &layer_warning);
|
||||
if (!ret.string.empty()) {
|
||||
ret.type = STRING_EXCEPT_OBJECT_COLLISION_IN_LAYER_PRINT;
|
||||
return ret;
|
||||
}
|
||||
if (!layer_warning.string.empty())
|
||||
add_warning(layer_warning);
|
||||
}
|
||||
|
||||
if (m_config.enable_prime_tower) {
|
||||
for (const PrintObject* object : m_objects) {
|
||||
if (object->config().precise_z_height.value && warning != nullptr) {
|
||||
StringObjectException warningtemp;
|
||||
warningtemp.string = L("Enabling both precise Z height and the prime tower may cause slicing errors.");
|
||||
warningtemp.opt_key = "precise_z_height";
|
||||
warningtemp.is_warning = true;
|
||||
*warning = warningtemp;
|
||||
if (object->config().precise_z_height.value) {
|
||||
warn(L("Enabling both precise Z height and the prime tower may cause slicing errors."), "precise_z_height");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (m_config.enable_wrapping_detection && warning!=nullptr) {
|
||||
StringObjectException warningtemp;
|
||||
warningtemp.string = L("A prime tower is required for clumping detection; otherwise, there may be flaws on the model.");
|
||||
warningtemp.opt_key = "enable_prime_tower";
|
||||
warningtemp.is_warning = true;
|
||||
*warning = warningtemp;
|
||||
}
|
||||
if (m_config.enable_wrapping_detection)
|
||||
warn(L("A prime tower is required for clumping detection; otherwise, there may be flaws on the model."), "enable_prime_tower");
|
||||
}
|
||||
|
||||
if (m_config.spiral_mode) {
|
||||
@@ -1346,7 +1351,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
if (std::any_of(all_regions.begin() + 1, all_regions.end(), [this, ra = all_regions.front()](const auto rb) {
|
||||
return !Layer::is_perimeter_compatible(*this, ra, rb);
|
||||
})) {
|
||||
return {L("The spiral vase mode does not work when an object contains more than one materials."), nullptr, "spiral_mode"};
|
||||
return {L("Spiral (vase) mode does not work when an object contains more than one material."), nullptr, "spiral_mode"};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1425,8 +1430,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam
|
||||
|| std::abs((filament_diam - first_filament_diam) / first_filament_diam) > 0.1) {
|
||||
// return { L("Different nozzle diameters and different filament diameters may not work well when prime tower is enabled. It's very experimental, please proceed with caucious.") };
|
||||
warning->string = L("Different nozzle diameters and different filament diameters may not work well when the prime tower is enabled. It's very experimental, so please proceed with caution.");
|
||||
warning->opt_key = "nozzle_diameter";
|
||||
warn(L("Different nozzle diameters and different filament diameters may not work well when the prime tower is enabled. It's very experimental, so please proceed with caution."), "nozzle_diameter");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1443,14 +1447,14 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
return { L("The prime tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors.")};
|
||||
|
||||
if ((m_config.print_sequence == PrintSequence::ByObject) && extruders.size() > 1)
|
||||
return { L("The prime tower is not supported in \"By object\" print."), nullptr, "enable_prime_tower" };
|
||||
return { L("A prime tower is not supported in \u201cBy object\u201d print."), nullptr, "enable_prime_tower" };
|
||||
|
||||
// BBS: When prime tower is on, object layer and support layer must be aligned. So support gap should be multiple of object layer height.
|
||||
for (size_t i = 0; i < m_objects.size(); i++) {
|
||||
const PrintObject* object = m_objects[i];
|
||||
const SlicingParameters& slicing_params = object->slicing_parameters();
|
||||
if (object->config().adaptive_layer_height) {
|
||||
return { L("The prime tower is not supported when adaptive layer height is on. It requires that all objects have the same layer height."), object, "adaptive_layer_height" };
|
||||
return { L("A prime tower is not supported when adaptive layer height is on. It requires that all objects have the same layer height."), object, "adaptive_layer_height" };
|
||||
}
|
||||
|
||||
if (!object->config().enable_support)
|
||||
@@ -1458,7 +1462,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
|
||||
double gap_layers = slicing_params.gap_object_support / slicing_params.layer_height;
|
||||
if (gap_layers - (int)gap_layers > EPSILON) {
|
||||
return {L("The prime tower requires \"support gap\" to be multiple of layer height."), object};
|
||||
return {L("A prime tower requires any \u201csupport gap\u201d to be a multiple of layer height."), object};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1471,9 +1475,9 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
const SlicingParameters &slicing_params = object->slicing_parameters();
|
||||
if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON ||
|
||||
std::abs(slicing_params.layer_height - slicing_params0.layer_height ) > EPSILON)
|
||||
return {L("The prime tower requires that all objects have the same layer heights."), object, "initial_layer_print_height"};
|
||||
return {L("A prime tower requires that all objects have the same layer height."), object, "initial_layer_print_height"};
|
||||
if (slicing_params.raft_layers() != slicing_params0.raft_layers())
|
||||
return {L("The prime tower requires that all objects are printed over the same number of raft layers."), object, "raft_layers"};
|
||||
return {L("A prime tower requires that all objects are printed over the same number of raft layers."), object, "raft_layers"};
|
||||
// BBS: support gap can be multiple of object layer height, remove _L()
|
||||
#if 0
|
||||
if (slicing_params0.gap_object_support != slicing_params.gap_object_support ||
|
||||
@@ -1481,7 +1485,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
return {L("The prime tower is only supported for multiple objects if they are printed with the same support_top_z_distance."), object};
|
||||
#endif
|
||||
if (!equal_layering(slicing_params, slicing_params0))
|
||||
return { L("The prime tower requires that all objects are sliced with the same layer heights."), object };
|
||||
return { L("A prime tower requires that all objects are sliced with the same layer height."), object };
|
||||
if (has_custom_layering) {
|
||||
auto &lh = layer_height_profile(i);
|
||||
auto &lh_tallest = layer_height_profile(tallest_object_idx);
|
||||
@@ -1544,13 +1548,13 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
auto validate_extrusion_width = [min_nozzle_diameter, max_nozzle_diameter](const ConfigBase &config, const char *opt_key, double layer_height, std::string &err_msg) -> bool {
|
||||
double extrusion_width_min = config.get_abs_value(opt_key, min_nozzle_diameter);
|
||||
double extrusion_width_max = config.get_abs_value(opt_key, max_nozzle_diameter);
|
||||
if (extrusion_width_min == 0) {
|
||||
// Default "auto-generated" extrusion width is always valid.
|
||||
} else if (extrusion_width_min <= layer_height) {
|
||||
err_msg = L("Too small line width");
|
||||
return false;
|
||||
} else if (extrusion_width_max > max_nozzle_diameter * MAX_LINE_WIDTH_MULTIPLIER) {
|
||||
err_msg = L("Too large line width");
|
||||
if (extrusion_width_min == 0) {
|
||||
// Default "auto-generated" extrusion width is always valid.
|
||||
} else if (extrusion_width_min <= layer_height) {
|
||||
err_msg = L("Line width too small");
|
||||
return false;
|
||||
} else if (extrusion_width_max > max_nozzle_diameter * MAX_LINE_WIDTH_MULTIPLIER) {
|
||||
err_msg = L("Line width too large");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1572,7 +1576,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
// BBS
|
||||
#if 0
|
||||
if (this->has_wipe_tower() && object->config().independent_support_layer_height) {
|
||||
return {L("The prime tower requires that support has the same layer height with object."), object, "support_filament"};
|
||||
return {L("A prime tower requires that support has the same layer height as the object."), object, "support_filament"};
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1583,22 +1587,15 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
// Orca: use organic as default
|
||||
object->config().support_style == smsDefault) {
|
||||
|
||||
if (warning) {
|
||||
// Orca: check the support wall count and the base pattern
|
||||
if (object->config().tree_support_wall_count > 1 &&
|
||||
object->config().support_base_pattern != SupportMaterialPattern::smpNone &&
|
||||
object->config().support_base_pattern != SupportMaterialPattern::smpDefault) {
|
||||
warning->string = L("For Organic supports, two walls are supported only with the Hollow/Default base pattern.");
|
||||
warning->opt_key = "support_base_pattern";
|
||||
}
|
||||
// Orca: check the support wall count and the base pattern
|
||||
if (object->config().tree_support_wall_count > 1 &&
|
||||
object->config().support_base_pattern != SupportMaterialPattern::smpNone &&
|
||||
object->config().support_base_pattern != SupportMaterialPattern::smpDefault)
|
||||
warn(L("For Organic supports, two walls are supported only with the Hollow/Default base pattern."), "support_base_pattern");
|
||||
|
||||
// Orca: check if the Lightning base pattern selected
|
||||
if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning) {
|
||||
warning->string = L(
|
||||
"The Lightning base pattern is not supported by this support type; Rectilinear will be used instead.");
|
||||
warning->opt_key = "support_base_pattern";
|
||||
}
|
||||
}
|
||||
// Orca: check if the Lightning base pattern selected
|
||||
if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning)
|
||||
warn(L("The Lightning base pattern is not supported by this support type; Rectilinear will be used instead."), "support_base_pattern");
|
||||
|
||||
float extrusion_width = std::min(
|
||||
support_material_flow(object).width(),
|
||||
@@ -1610,28 +1607,23 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
if (object->config().tree_support_branch_diameter_organic < object->config().tree_support_tip_diameter)
|
||||
return { L("Organic support branch diameter must not be smaller than support tree tip diameter."), object, "tree_support_branch_diameter_organic" };
|
||||
}
|
||||
} else if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning && warning) {
|
||||
} else if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning) {
|
||||
// Orca: check if the Lightning base pattern selected
|
||||
warning->string = L("The Lightning base pattern is not supported by this support type; Rectilinear will be used instead.");
|
||||
warning->opt_key = "support_base_pattern";
|
||||
} else if (object->config().support_base_pattern == SupportMaterialPattern::smpNone && warning) {
|
||||
warn(L("The Lightning base pattern is not supported by this support type; Rectilinear will be used instead."), "support_base_pattern");
|
||||
} else if (object->config().support_base_pattern == SupportMaterialPattern::smpNone) {
|
||||
// Orca: check if the Hollow base pattern selected
|
||||
warning->string = L("The Hollow base pattern is not supported by this support type; Rectilinear will be used instead.");
|
||||
warning->opt_key = "support_base_pattern";
|
||||
warn(L("The Hollow base pattern is not supported by this support type; Rectilinear will be used instead."), "support_base_pattern");
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have custom support data that would not be used?
|
||||
// Notify the user in that case.
|
||||
if (! object->has_support() && warning) {
|
||||
if (! object->has_support()) {
|
||||
for (const ModelVolume* mv : object->model_object()->volumes) {
|
||||
bool has_enforcers = mv->is_support_enforcer() ||
|
||||
(mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER));
|
||||
if (has_enforcers) {
|
||||
StringObjectException warningtemp;
|
||||
warningtemp.string = L("Support enforcers are used but support is not enabled. Please enable support.");
|
||||
warningtemp.object = object;
|
||||
*warning = warningtemp;
|
||||
warn(L("Support enforcers are used but support is not enabled. Please enable support."), "", object);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1671,30 +1663,83 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
for (const PrintRegion ®ion : object->all_regions())
|
||||
if (!validate_extrusion_width(region.config(), opt_key, layer_height, err_msg))
|
||||
return {err_msg, object, opt_key};
|
||||
|
||||
const bool allow_thin_bridge_width = object->config().thick_bridges && object->config().thick_internal_bridges;
|
||||
for (const PrintRegion ®ion : object->all_regions()) {
|
||||
const auto &bridge_width_opt = region.config().bridge_line_width;
|
||||
for (FlowRole bridge_role : { frPerimeter, frInfill, frSolidInfill, frTopSolidInfill }) {
|
||||
const double nozzle_diameter = m_config.nozzle_diameter.get_at(region.extruder(bridge_role) - 1);
|
||||
const double bridge_width = bridge_width_opt.get_abs_value(nozzle_diameter);
|
||||
if (bridge_width <= 0.)
|
||||
continue;
|
||||
if (bridge_width > nozzle_diameter) {
|
||||
err_msg = L("Bridge line width must not exceed nozzle diameter");
|
||||
return { err_msg, object, "bridge_line_width" };
|
||||
}
|
||||
if (!allow_thin_bridge_width && bridge_width <= layer_height) {
|
||||
err_msg = L("Line width too small");
|
||||
return { err_msg, object, "bridge_line_width" };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: G92 E0 is not supported when using absolute extruder addressing
|
||||
// This check is copied from PrusaSlicer, the original author is Vojtech Bubnik
|
||||
if(!is_BBL_printer()) {
|
||||
bool before_layer_gcode_resets_extruder =
|
||||
boost::regex_search(m_config.before_layer_change_gcode.value, regex_g92e0);
|
||||
bool layer_gcode_resets_extruder = boost::regex_search(m_config.layer_change_gcode.value, regex_g92e0);
|
||||
if (m_config.use_relative_e_distances) {
|
||||
// See GH issues #6336 #5073
|
||||
if ((m_config.gcode_flavor == gcfMarlinLegacy || m_config.gcode_flavor == gcfMarlinFirmware) &&
|
||||
!before_layer_gcode_resets_extruder && !layer_gcode_resets_extruder)
|
||||
return {L("Relative extruder addressing requires resetting the extruder position at each layer to "
|
||||
"prevent loss of floating point accuracy. Add \"G92 E0\" to layer_gcode."),
|
||||
nullptr, "before_layer_change_gcode"};
|
||||
} else if (before_layer_gcode_resets_extruder)
|
||||
return {L("\"G92 E0\" was found in before_layer_gcode, which is incompatible with absolute extruder "
|
||||
// This check is modified from PrusaSlicer, the original author is Vojtech Bubnik
|
||||
// Orca: case‑sensitive match for exactly "G92 E0" (uppercase G and E only)
|
||||
// because gcode is case sensitive and G92 e0 satisfies the regex but causes a slicing error
|
||||
// https://github.com/OrcaSlicer/OrcaSlicer/issues/13927
|
||||
|
||||
// Matches any case of "G92 E0" (original pattern)
|
||||
static const boost::regex regex_g92e0 {
|
||||
"^[ \\t]*[gG]92[ \\t]*[eE](0(\\.0*)?|\\.0+)[ \\t]*(;.*)?$"
|
||||
};
|
||||
// Matches only the exact uppercase "G92 E0"
|
||||
static const boost::regex regex_g92e0_correct {
|
||||
"^[ \\t]*G92[ \\t]*E(0(\\.0*)?|\\.0+)[ \\t]*(;.*)?$"
|
||||
};
|
||||
|
||||
const bool before_has_g92_any = boost::regex_search(
|
||||
m_config.before_layer_change_gcode.value, regex_g92e0);
|
||||
const bool layer_has_g92_any = boost::regex_search(
|
||||
m_config.layer_change_gcode.value, regex_g92e0);
|
||||
|
||||
if (m_config.use_relative_e_distances) {
|
||||
// Relative mode: "G92 E0" is required to reset extruder position.
|
||||
const bool before_has_g92_exact = boost::regex_search(
|
||||
m_config.before_layer_change_gcode.value, regex_g92e0_correct);
|
||||
const bool layer_has_g92_exact = boost::regex_search(
|
||||
m_config.layer_change_gcode.value, regex_g92e0_correct);
|
||||
|
||||
// Wrong case found?
|
||||
if (before_has_g92_any && !before_has_g92_exact)
|
||||
return {L("\"G92 E0\" was found in before_layer_change_gcode, but the G or E are not uppercase. "
|
||||
"Please change them to the exact uppercase \"G92 E0\"."),
|
||||
nullptr, "before_layer_change_gcode"};
|
||||
if (layer_has_g92_any && !layer_has_g92_exact)
|
||||
return {L("\"G92 E0\" was found in layer_change_gcode, but the G or E are not uppercase. "
|
||||
"Please change them to the exact uppercase \"G92 E0\"."),
|
||||
nullptr, "layer_change_gcode"};
|
||||
|
||||
// Only Marlin flavours need the reset; BBL printers do not.
|
||||
if ((m_config.gcode_flavor == gcfMarlinLegacy || m_config.gcode_flavor == gcfMarlinFirmware) &&
|
||||
!is_BBL_printer() &&
|
||||
!before_has_g92_exact && !layer_has_g92_exact)
|
||||
return {L("Relative extruder addressing requires resetting the extruder position at each layer to "
|
||||
"prevent loss of floating point accuracy. Add \"G92 E0\" to layer_gcode."),
|
||||
nullptr, "before_layer_change_gcode"};
|
||||
} else {
|
||||
// Absolute mode: any occurrence of "G92 E0" is incompatible.
|
||||
if (before_has_g92_any)
|
||||
return {L("\"G92 E0\" was found in before_layer_change_gcode, which is incompatible with absolute extruder "
|
||||
"addressing."),
|
||||
nullptr, "before_layer_change_gcode"};
|
||||
else if (layer_gcode_resets_extruder)
|
||||
return {L("\"G92 E0\" was found in layer_gcode, which is incompatible with absolute extruder addressing."),
|
||||
if (layer_has_g92_any)
|
||||
return {L("\"G92 E0\" was found in layer_change_gcode, which is incompatible with absolute extruder "
|
||||
"addressing."),
|
||||
nullptr, "layer_change_gcode"};
|
||||
}
|
||||
}
|
||||
|
||||
const ConfigOptionDef* bed_type_def = print_config_def.get("curr_bed_type");
|
||||
assert(bed_type_def != nullptr);
|
||||
@@ -1730,7 +1775,11 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
}
|
||||
|
||||
// check if print speed/accel/jerk is higher than the maximum speed of the printer
|
||||
if (warning) {
|
||||
if (warnings) {
|
||||
// The motion-ability checks are mutually exclusive (gated on warning_key), so collect the
|
||||
// single one that fires into a local and push it once - separate from the precise-wall and
|
||||
// shrinkage warnings below.
|
||||
StringObjectException motion_warning;
|
||||
try {
|
||||
auto check_extruder = [&](const int extruder_id) {
|
||||
auto check_motion_ability_object_setting = [&](const std::vector<std::string>& keys_to_check, double limit) -> std::string {
|
||||
@@ -1762,7 +1811,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
if (!ignore_jerk_validation) {
|
||||
if (m_default_object_config.default_jerk.get_at(extruder_id) == 1 || m_default_object_config.outer_wall_jerk.get_at(extruder_id) == 1 ||
|
||||
m_default_object_config.inner_wall_jerk.get_at(extruder_id) == 1) {
|
||||
warning->string = L("Setting the jerk speed too low could lead to artifacts on curved surfaces");
|
||||
motion_warning.string = L("Setting the jerk speed too low could lead to artifacts on curved surfaces");
|
||||
if (m_default_object_config.outer_wall_jerk.get_at(extruder_id) == 1)
|
||||
warning_key = "outer_wall_jerk";
|
||||
else if (m_default_object_config.inner_wall_jerk.get_at(extruder_id) == 1)
|
||||
@@ -1770,7 +1819,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
else
|
||||
warning_key = "default_jerk";
|
||||
|
||||
warning->opt_key = warning_key;
|
||||
motion_warning.opt_key = warning_key;
|
||||
}
|
||||
|
||||
if (warning_key.empty() && m_default_object_config.default_jerk.get_at(extruder_id) > 0) {
|
||||
@@ -1780,11 +1829,11 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
warning_key.clear();
|
||||
warning_key = check_motion_ability_object_setting(jerk_to_check, max_jerk);
|
||||
if (!warning_key.empty()) {
|
||||
warning->string = L(
|
||||
motion_warning.string = L(
|
||||
"The jerk setting exceeds the printer's maximum jerk (machine_max_jerk_x/machine_max_jerk_y).\n"
|
||||
"Orca will automatically cap the jerk speed to ensure it doesn't surpass the printer's capabilities.\n"
|
||||
"You can adjust the maximum jerk setting in your printer's configuration to get higher speeds.");
|
||||
warning->opt_key = warning_key;
|
||||
motion_warning.opt_key = warning_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1793,10 +1842,10 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
// Orca: Only marlin FW supports max junction deviation. Dont display warning if firmware is not supporting it.
|
||||
const bool support_max_junction_deviation = ( m_config.gcode_flavor == gcfMarlinFirmware);
|
||||
if (warning_key.empty() && m_default_object_config.default_junction_deviation.get_at(extruder_id) > max_junction_deviation && support_max_junction_deviation) {
|
||||
warning->string = L( "Junction deviation setting exceeds the printer's maximum value (machine_max_junction_deviation).\n"
|
||||
motion_warning.string = L( "Junction deviation setting exceeds the printer's maximum value (machine_max_junction_deviation).\n"
|
||||
"Orca will automatically cap the junction deviation to ensure it doesn't surpass the printer's capabilities.\n"
|
||||
"You can adjust the machine_max_junction_deviation value in your printer's configuration to get higher limits.");
|
||||
warning->opt_key = warning_key;
|
||||
motion_warning.opt_key = "default_junction_deviation";
|
||||
}
|
||||
|
||||
// check acceleration
|
||||
@@ -1831,12 +1880,12 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
};
|
||||
warning_key = check_motion_ability_object_setting(accel_to_check, max_accel);
|
||||
if (!warning_key.empty()) {
|
||||
warning->string = L("The acceleration setting exceeds the printer's maximum acceleration "
|
||||
motion_warning.string = L("The acceleration setting exceeds the printer's maximum acceleration "
|
||||
"(machine_max_acceleration_extruding).\nOrca will "
|
||||
"automatically cap the acceleration speed to ensure it doesn't surpass the printer's "
|
||||
"capabilities.\nYou can adjust the "
|
||||
"machine_max_acceleration_extruding value in your printer's configuration to get higher speeds.");
|
||||
warning->opt_key = warning_key;
|
||||
motion_warning.opt_key = warning_key;
|
||||
}
|
||||
if (support_travel_acc) {
|
||||
const auto max_travel = m_config.machine_max_acceleration_travel.values[0];
|
||||
@@ -1846,13 +1895,13 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
};
|
||||
warning_key = check_motion_ability_object_setting(accel_to_check, max_travel);
|
||||
if (!warning_key.empty()) {
|
||||
warning->string = L(
|
||||
motion_warning.string = L(
|
||||
"The travel acceleration setting exceeds the printer's maximum travel acceleration "
|
||||
"(machine_max_acceleration_travel).\nOrca will "
|
||||
"automatically cap the travel acceleration speed to ensure it doesn't surpass the printer's "
|
||||
"capabilities.\nYou can adjust the "
|
||||
"machine_max_acceleration_travel value in your printer's configuration to get higher speeds.");
|
||||
warning->opt_key = warning_key;
|
||||
motion_warning.opt_key = warning_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1869,30 +1918,45 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
// if (warning_key.empty() && m_config.travel_speed > max_speed)
|
||||
// warning_key = "travel_speed";
|
||||
// if (!warning_key.empty()) {
|
||||
// warning->string = L(
|
||||
// motion_warning.string = L(
|
||||
// "The speed setting exceeds the printer's maximum speed (machine_max_speed_x/machine_max_speed_y).\nOrca will "
|
||||
// "automatically cap the print speed to ensure it doesn't surpass the printer's capabilities.\nYou can adjust the "
|
||||
// "maximum speed setting in your printer's configuration to get higher speeds.");
|
||||
// warning->opt_key = warning_key;
|
||||
// motion_warning.opt_key = warning_key;
|
||||
// }
|
||||
// }
|
||||
};
|
||||
check_extruder(0); // TODO: check used extruder variants
|
||||
|
||||
// check wall sequence and precise outer wall
|
||||
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter) {
|
||||
warning->string = L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences.");
|
||||
warning->opt_key = "precise_outer_wall";
|
||||
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter)
|
||||
warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall");
|
||||
|
||||
// check adaptive pressure advance model
|
||||
for (unsigned int extruder_id : extruders) {
|
||||
if (m_config.adaptive_pressure_advance.get_at(extruder_id) &&
|
||||
m_config.enable_pressure_advance.get_at(extruder_id)) {
|
||||
|
||||
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(extruder_id);
|
||||
if (!pa_model.empty()) {
|
||||
std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model);
|
||||
if (!validation_error.empty()) {
|
||||
warn(L("The Adaptive Pressure Advance model for one or more extruders may contain invalid values."),
|
||||
"adaptive_pressure_advance_model");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Orca: validate motion ability failed: " << e.what() << std::endl;
|
||||
}
|
||||
if (!motion_warning.string.empty())
|
||||
add_warning(motion_warning);
|
||||
}
|
||||
if (!this->has_same_shrinkage_compensations()){
|
||||
warning->string = L("Filament shrinkage will not be used because filament shrinkage for the used filaments does not match.");
|
||||
warning->opt_key = "";
|
||||
}
|
||||
if (!this->has_same_shrinkage_compensations())
|
||||
warn(L("Filament shrinkage will not be used because filament shrinkage for the used filaments does not match."));
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -1975,7 +2039,7 @@ Flow Print::brim_flow() const
|
||||
frPerimeter,
|
||||
// Flow::new_from_config_width takes care of the percent to value substitution
|
||||
width,
|
||||
(float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().wall_filament-1),
|
||||
(float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().outer_wall_filament_id-1),
|
||||
(float)this->skirt_first_layer_height());
|
||||
}
|
||||
|
||||
@@ -2398,7 +2462,10 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
start_time = (long long)Slic3r::Utils::get_current_time_utc();
|
||||
|
||||
m_skirt.clear();
|
||||
m_skirt_groups.clear();
|
||||
m_skirt_convex_hull.clear();
|
||||
m_objectBrimAreas.clear();
|
||||
m_supportBrimAreas.clear();
|
||||
m_first_layer_convex_hull.points.clear();
|
||||
for (PrintObject *object : m_objects) object->m_skirt.clear();
|
||||
|
||||
@@ -2492,7 +2559,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
if (this->has_brim()) {
|
||||
Polygons islands_area;
|
||||
make_brim(*this, this->make_try_cancel(), islands_area, m_brimMap,
|
||||
m_supportBrimMap, objPrintVec, printExtruders);
|
||||
m_supportBrimMap, objPrintVec, printExtruders, &m_objectBrimAreas, &m_supportBrimAreas);
|
||||
for (Polygon& poly_ex : islands_area)
|
||||
poly_ex.douglas_peucker(SCALED_RESOLUTION);
|
||||
for (Polygon &poly : union_(this->first_layer_islands(), islands_area))
|
||||
@@ -2589,7 +2656,8 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
||||
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
|
||||
gcode.export_layer_filaments(result);
|
||||
//BBS
|
||||
result->conflict_result = m_conflict_result;
|
||||
if (result != nullptr)
|
||||
result->conflict_result = m_conflict_result;
|
||||
return path.c_str();
|
||||
}
|
||||
|
||||
@@ -2613,11 +2681,13 @@ void Print::_make_skirt()
|
||||
skirt_height_z = std::max(skirt_height_z, object->m_layers[skirt_layers-1]->print_z);
|
||||
}
|
||||
|
||||
// Collect points from all layers contained in skirt height.
|
||||
Points points;
|
||||
struct ObjectSkirtHull {
|
||||
PrintObject* object;
|
||||
Polygon hull;
|
||||
};
|
||||
|
||||
// BBS
|
||||
std::map<PrintObject*, Polygon> object_convex_hulls;
|
||||
// Orca: build one local occupied hull per object from object and support geometry up to skirt height.
|
||||
std::vector<ObjectSkirtHull> object_convex_hulls;
|
||||
for (PrintObject *object : m_objects) {
|
||||
Points object_points;
|
||||
// Get object layers up to skirt_height_z.
|
||||
@@ -2635,30 +2705,13 @@ void Print::_make_skirt()
|
||||
layer->support_fills.collect_points(object_points);
|
||||
}
|
||||
|
||||
object_convex_hulls.insert({ object, Slic3r::Geometry::convex_hull(object_points) });
|
||||
|
||||
// Repeat points for each object copy.
|
||||
for (const PrintInstance &instance : object->instances()) {
|
||||
Points copy_points = object_points;
|
||||
for (Point &pt : copy_points)
|
||||
pt += instance.shift;
|
||||
append(points, copy_points);
|
||||
}
|
||||
object_convex_hulls.push_back({ object, Slic3r::Geometry::convex_hull(object_points) });
|
||||
}
|
||||
|
||||
// Include the wipe tower.
|
||||
append(points, this->first_layer_wipe_tower_corners());
|
||||
|
||||
// Unless draft shield is enabled, include all brims as well.
|
||||
if (config().draft_shield == dsDisabled)
|
||||
append(points, m_first_layer_convex_hull.points);
|
||||
|
||||
if (points.size() < 3)
|
||||
// At least three points required for a convex hull.
|
||||
if (object_convex_hulls.empty())
|
||||
return;
|
||||
|
||||
this->throw_if_canceled();
|
||||
Polygon convex_hull = Slic3r::Geometry::convex_hull(points);
|
||||
|
||||
// Skirt may be printed on several layers, having distinct layer heights,
|
||||
// but loops must be aligned so can't vary width/spacing
|
||||
@@ -2680,13 +2733,13 @@ void Print::_make_skirt()
|
||||
}
|
||||
}
|
||||
|
||||
// Initial offset of the brim inner edge from the object (possible with a support & raft).
|
||||
// The skirt will touch the brim if the brim is extruded.
|
||||
auto distance = float(scale_(m_config.skirt_distance.value - spacing/2.));
|
||||
// Draw outlines from outside to inside.
|
||||
// Initial skirt centerline offset from the occupied outline.
|
||||
// The skirt will touch the occupied outline if skirt_distance is zero.
|
||||
// Generate loops inward to outward; callers reverse them before G-code export.
|
||||
// Loop while we have less skirts than required or any extruder hasn't reached the min length if any.
|
||||
std::vector<coordf_t> extruded_length(extruders.size(), 0.);
|
||||
if (m_config.skirt_type == stCombined) {
|
||||
auto append_skirt_loops_for_hull = [&](const Polygon& hull, ExtrusionEntityCollection& dst, bool collect_skirt_hull) {
|
||||
float distance = float(scale_(m_config.skirt_distance.value - spacing/2.));
|
||||
std::vector<coordf_t> extruded_length(extruders.size(), 0.);
|
||||
for (size_t i = m_config.skirt_loops, extruder_idx = 0; i > 0; -- i) {
|
||||
this->throw_if_canceled();
|
||||
// Offset the skirt outside.
|
||||
@@ -2694,8 +2747,8 @@ void Print::_make_skirt()
|
||||
// Generate the skirt centerline.
|
||||
Polygon loop;
|
||||
{
|
||||
// BBS. skirt_distance is defined as the gap between skirt and outer most brim, so no need to add max_brim_width
|
||||
Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
// Orca: the hull already represents the occupied outline used for this skirt.
|
||||
Polygons loops = offset(hull, distance, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
Geometry::simplify_polygons(loops, scale_(0.05), &loops);
|
||||
if (loops.empty())
|
||||
break;
|
||||
@@ -2711,7 +2764,7 @@ void Print::_make_skirt()
|
||||
(float)initial_layer_print_height // this will be overridden at G-code export time
|
||||
)));
|
||||
eloop.paths.back().polyline = Polyline3(loop.split_at_first_point());
|
||||
m_skirt.append(eloop);
|
||||
dst.append(eloop);
|
||||
if (m_config.min_skirt_length.value > 0) {
|
||||
// The skirt length is limited. Sum the total amount of filament length extruded, in mm.
|
||||
extruded_length[extruder_idx] += unscale<double>(loop.length()) * extruders_e_per_mm[extruder_idx];
|
||||
@@ -2727,69 +2780,147 @@ void Print::_make_skirt()
|
||||
++ extruder_idx;
|
||||
}
|
||||
} else {
|
||||
// The skirt lenght is not limited, extrude the skirt with the 1st extruder only.
|
||||
// The skirt length is not limited, extrude the skirt with the 1st extruder only.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_skirt.clear();
|
||||
}
|
||||
// Brims were generated inside out, reverse to print the outmost contour first.
|
||||
m_skirt.reverse();
|
||||
|
||||
// Remember the outer edge of the last skirt line extruded as m_skirt_convex_hull.
|
||||
for (Polygon &poly : offset(convex_hull, distance + 0.5f * float(scale_(spacing)), ClipperLib::jtRound, float(scale_(0.1))))
|
||||
append(m_skirt_convex_hull, std::move(poly.points));
|
||||
if (collect_skirt_hull)
|
||||
for (Polygon &poly : offset(hull, distance + 0.5f * float(scale_(spacing)), ClipperLib::jtRound, float(scale_(0.1))))
|
||||
append(m_skirt_convex_hull, std::move(poly.points));
|
||||
};
|
||||
|
||||
if (m_config.skirt_type == stPerObject) {
|
||||
// BBS
|
||||
for (auto obj_cvx_hull : object_convex_hulls) {
|
||||
double object_skirt_distance = float(scale_(m_config.skirt_distance.value - spacing/2.));
|
||||
PrintObject* object = obj_cvx_hull.first;
|
||||
object->m_skirt.clear();
|
||||
extruded_length.assign(extruded_length.size(), 0.);
|
||||
for (size_t i = m_config.skirt_loops.value, extruder_idx = 0; i > 0; -- i) {
|
||||
object_skirt_distance += float(scale_(spacing));
|
||||
Polygon loop;
|
||||
{
|
||||
// BBS. skirt_distance is defined as the gap between skirt and outer most brim, so no need to add max_brim_width
|
||||
Polygons loops = offset(obj_cvx_hull.second, object_skirt_distance, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
Geometry::simplify_polygons(loops, scale_(0.05), &loops);
|
||||
if (loops.empty())
|
||||
break;
|
||||
loop = loops.front();
|
||||
}
|
||||
m_skirt.clear();
|
||||
m_skirt_groups.clear();
|
||||
|
||||
// Extrude the skirt loop.
|
||||
ExtrusionLoop eloop(elrSkirt);
|
||||
eloop.paths.emplace_back(ExtrusionPath(
|
||||
ExtrusionPath(
|
||||
erSkirt,
|
||||
(float)mm3_per_mm, // this will be overridden at G-code export time
|
||||
flow.width(),
|
||||
(float)initial_layer_print_height // this will be overridden at G-code export time
|
||||
)));
|
||||
eloop.paths.back().polyline = Polyline3(loop.split_at_first_point());
|
||||
object->m_skirt.append(std::move(eloop));
|
||||
if (m_config.min_skirt_length.value > 0) {
|
||||
// The skirt length is limited. Sum the total amount of filament length extruded, in mm.
|
||||
extruded_length[extruder_idx] += unscale<double>(loop.length()) * extruders_e_per_mm[extruder_idx];
|
||||
if (extruded_length[extruder_idx] < m_config.min_skirt_length.value) {
|
||||
// Not extruded enough yet with the current extruder. Add another loop.
|
||||
if (i == 1)
|
||||
++ i;
|
||||
} else {
|
||||
assert(extruded_length[extruder_idx] >= m_config.min_skirt_length.value);
|
||||
// Enough extruded with the current extruder. Extrude with the next one,
|
||||
// until the prescribed number of skirt loops is extruded.
|
||||
if (extruder_idx + 1 < extruders.size())
|
||||
++ extruder_idx;
|
||||
}
|
||||
} else {
|
||||
// The skirt lenght is not limited, extrude the skirt with the 1st extruder only.
|
||||
}
|
||||
if (m_config.skirt_type == stPerObject && m_config.print_sequence == PrintSequence::ByObject) {
|
||||
for (const ObjectSkirtHull& object_hull : object_convex_hulls) {
|
||||
object_hull.object->m_skirt.clear();
|
||||
append_skirt_loops_for_hull(object_hull.hull, object_hull.object->m_skirt, false);
|
||||
object_hull.object->m_skirt.reverse();
|
||||
}
|
||||
} else if (m_config.skirt_type == stCombined || m_config.skirt_type == stPerObject) {
|
||||
struct SkirtGroupItem {
|
||||
Points occupied_points;
|
||||
bool emits_skirt;
|
||||
};
|
||||
|
||||
// Orca: group items represent occupied first-layer areas. Object items emit skirts;
|
||||
// obstacle-only items, such as wipe tower, only force nearby object groups to merge.
|
||||
std::vector<SkirtGroupItem> group_items;
|
||||
const coord_t grouping_offset = scale_(m_config.skirt_distance.value + m_config.skirt_loops.value * spacing);
|
||||
for (const ObjectSkirtHull& object_hull : object_convex_hulls) {
|
||||
PrintObject* object = object_hull.object;
|
||||
Points occupied_points;
|
||||
for (const PrintInstance &instance : object->instances()) {
|
||||
Points copy_points = object_hull.hull.points;
|
||||
for (Point &pt : copy_points)
|
||||
pt += instance.shift;
|
||||
append(occupied_points, copy_points);
|
||||
}
|
||||
|
||||
auto append_brim_points = [&occupied_points](const ExPolygons& areas) {
|
||||
for (const ExPolygon& area : areas)
|
||||
append(occupied_points, area.contour.points);
|
||||
};
|
||||
if (auto it = m_objectBrimAreas.find(object->id()); it != m_objectBrimAreas.end())
|
||||
append_brim_points(it->second);
|
||||
if (auto it = m_supportBrimAreas.find(object->id()); it != m_supportBrimAreas.end())
|
||||
append_brim_points(it->second);
|
||||
if (occupied_points.size() < 3)
|
||||
continue;
|
||||
|
||||
// Orca: include the object's brim/support-brim footprint before checking skirt collisions.
|
||||
group_items.push_back({ std::move(occupied_points), true });
|
||||
}
|
||||
|
||||
// Orca: the wipe tower contributes occupied area, but does not emit a skirt by itself.
|
||||
Points wipe_tower_points = this->first_layer_wipe_tower_corners();
|
||||
if (wipe_tower_points.size() >= 3)
|
||||
group_items.push_back({ std::move(wipe_tower_points), false });
|
||||
|
||||
std::vector<size_t> parent(group_items.size());
|
||||
std::iota(parent.begin(), parent.end(), 0);
|
||||
// Orca: union-find keeps collision merging local without repeatedly rebuilding item lists.
|
||||
auto find_parent = [&parent](size_t idx) {
|
||||
while (parent[idx] != idx) {
|
||||
parent[idx] = parent[parent[idx]];
|
||||
idx = parent[idx];
|
||||
}
|
||||
return idx;
|
||||
};
|
||||
auto unite = [&parent, &find_parent](size_t a, size_t b) {
|
||||
a = find_parent(a);
|
||||
b = find_parent(b);
|
||||
if (a != b)
|
||||
parent[b] = a;
|
||||
};
|
||||
|
||||
// Orca: combined skirt is the same grouping model with all items forced into one group.
|
||||
if (m_config.skirt_type == stCombined && !group_items.empty())
|
||||
for (size_t i = 1; i < group_items.size(); ++i)
|
||||
unite(0, i);
|
||||
|
||||
auto build_grouped_points = [&]() {
|
||||
struct GroupData {
|
||||
Points points;
|
||||
bool emits_skirt = false;
|
||||
};
|
||||
|
||||
std::map<size_t, GroupData> grouped;
|
||||
for (size_t i = 0; i < group_items.size(); ++i) {
|
||||
GroupData& group = grouped[find_parent(i)];
|
||||
append(group.points, group_items[i].occupied_points);
|
||||
group.emits_skirt = group.emits_skirt || group_items[i].emits_skirt;
|
||||
}
|
||||
return grouped;
|
||||
};
|
||||
|
||||
bool groups_changed = m_config.skirt_type == stPerObject;
|
||||
while (groups_changed) {
|
||||
groups_changed = false;
|
||||
auto grouped_points = build_grouped_points();
|
||||
std::vector<std::pair<size_t, Polygon>> group_envelopes;
|
||||
for (const auto& [root, group] : grouped_points) {
|
||||
if (group.points.size() < 3)
|
||||
continue;
|
||||
|
||||
// Orca: emitting groups are expanded to their final skirt reach; obstacle groups are not.
|
||||
Polygon envelope = Geometry::convex_hull(group.points);
|
||||
if (group.emits_skirt) {
|
||||
// Orca: merge groups when a skirt envelope intersects another group or obstacle.
|
||||
Polygons envelopes = offset(envelope, grouping_offset, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
if (envelopes.empty())
|
||||
continue;
|
||||
envelope = std::move(envelopes.front());
|
||||
}
|
||||
group_envelopes.emplace_back(root, std::move(envelope));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < group_envelopes.size(); ++i) {
|
||||
for (size_t j = i + 1; j < group_envelopes.size(); ++j) {
|
||||
const size_t root_i = find_parent(group_envelopes[i].first);
|
||||
const size_t root_j = find_parent(group_envelopes[j].first);
|
||||
if (root_i != root_j && !intersection(group_envelopes[i].second, group_envelopes[j].second).empty()) {
|
||||
unite(root_i, root_j);
|
||||
groups_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto grouped_points = build_grouped_points();
|
||||
for (auto& [_, group] : grouped_points) {
|
||||
if (!group.emits_skirt || group.points.size() < 3)
|
||||
continue;
|
||||
// Orca: after merging, use the occupied outline directly; do not add skirt distance twice.
|
||||
ExtrusionEntityCollection group_skirt;
|
||||
append_skirt_loops_for_hull(Geometry::convex_hull(group.points), group_skirt, true);
|
||||
if (!group_skirt.empty()) {
|
||||
group_skirt.reverse();
|
||||
// Orca: keep m_skirt as a flattened compatibility mirror for preview/extents.
|
||||
m_skirt.append(group_skirt.entities);
|
||||
m_skirt_groups.push_back(std::move(group_skirt));
|
||||
}
|
||||
object->m_skirt.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3502,6 +3633,24 @@ std::string Print::output_filename(const std::string &filename_base) const
|
||||
config.set_key_value("plate_number", new ConfigOptionString(get_plate_number_formatted()));
|
||||
config.set_key_value("model_name", new ConfigOptionString(get_model_name()));
|
||||
|
||||
// the same type of filament contains multiple names, support exporting according to the filament name
|
||||
auto full_print_config = this->full_print_config();
|
||||
const ConfigOptionStrings* filament_settings_id = full_print_config.option<ConfigOptionStrings>("filament_settings_id");
|
||||
std::string filament_name = "";
|
||||
auto extruders = this->extruders(true);
|
||||
if(!extruders.empty()) {
|
||||
// first extruder is the default extruder
|
||||
int extruder_id = extruders.front();
|
||||
if(filament_settings_id->values.size() > extruder_id) {
|
||||
filament_name = filament_settings_id->values[extruder_id];
|
||||
}
|
||||
}
|
||||
size_t end_pos = filament_name.find_first_of("@");
|
||||
if (end_pos != std::string::npos) {
|
||||
filament_name = filament_name.substr(0, end_pos);
|
||||
}
|
||||
config.set_key_value("filament_name", new ConfigOptionString(filament_name));
|
||||
|
||||
return this->PrintBase::output_filename(m_config.filename_format.value, ".gcode", filename_base, &config);
|
||||
}
|
||||
|
||||
@@ -3594,9 +3743,12 @@ DynamicConfig PrintStatistics::config() const
|
||||
config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost));
|
||||
config.set_key_value("total_toolchanges", new ConfigOptionInt(this->total_toolchanges));
|
||||
config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight));
|
||||
config.set_key_value("extruded_weight_total", new ConfigOptionFloat(this->total_weight));
|
||||
config.set_key_value("extruded_volume_total", new ConfigOptionFloat(this->total_extruded_volume));
|
||||
config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat(this->total_wipe_tower_cost));
|
||||
config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat(this->total_wipe_tower_filament));
|
||||
config.set_key_value("initial_tool", new ConfigOptionInt(static_cast<int>(this->initial_tool)));
|
||||
config.set_key_value("initial_extruder", new ConfigOptionInt(static_cast<int>(this->initial_tool)));
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -3605,8 +3757,8 @@ DynamicConfig PrintStatistics::placeholders()
|
||||
DynamicConfig config;
|
||||
for (const std::string key : {
|
||||
"print_time", "normal_print_time", "silent_print_time",
|
||||
"used_filament", "extruded_volume", "total_cost", "total_weight",
|
||||
"initial_tool", "total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament"})
|
||||
"used_filament", "extruded_volume", "extruded_volume_total", "total_cost", "total_weight", "extruded_weight_total",
|
||||
"initial_tool", "initial_extruder", "total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament"})
|
||||
config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}"));
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -932,7 +932,7 @@ public:
|
||||
}
|
||||
|
||||
// Returns an empty string if valid, otherwise returns an error message.
|
||||
StringObjectException validate(StringObjectException *warning = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const override;
|
||||
StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const override;
|
||||
double skirt_first_layer_height() const;
|
||||
Flow brim_flow() const;
|
||||
Flow skirt_flow() const;
|
||||
@@ -971,6 +971,7 @@ public:
|
||||
PrintRegionPtrs& print_regions_mutable() { return m_print_regions; }
|
||||
std::vector<size_t> layers_sorted_for_object(float start, float end, std::vector<LayerPtrs> &layers_of_objects, std::vector<BoundingBox> &boundingBox_for_objects, VecOfPoints& objects_instances_shift);
|
||||
const ExtrusionEntityCollection& skirt() const { return m_skirt; }
|
||||
const std::vector<ExtrusionEntityCollection>& skirt_groups() const { return m_skirt_groups; }
|
||||
// Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
|
||||
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
|
||||
// It does NOT encompass user extrusions generated by custom G-code,
|
||||
@@ -1144,9 +1145,13 @@ private:
|
||||
|
||||
// Ordered collections of extrusion paths to build skirt loops and brim.
|
||||
ExtrusionEntityCollection m_skirt;
|
||||
std::vector<ExtrusionEntityCollection> m_skirt_groups;
|
||||
// BBS: collecting extrusion paths to build brim by objs
|
||||
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;
|
||||
// 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,
|
||||
|
||||
@@ -815,9 +815,12 @@ bool verify_update_print_object_regions(
|
||||
for (const PrintObjectRegions::PaintedRegion ®ion : layer_range.painted_regions) {
|
||||
const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent];
|
||||
PrintRegionConfig cfg = parent_region.region->config();
|
||||
cfg.wall_filament.value = region.extruder_id;
|
||||
cfg.solid_infill_filament.value = region.extruder_id;
|
||||
cfg.sparse_infill_filament.value = region.extruder_id;
|
||||
cfg.outer_wall_filament_id.value = region.extruder_id;
|
||||
cfg.inner_wall_filament_id.value = region.extruder_id;
|
||||
cfg.internal_solid_filament_id.value = region.extruder_id;
|
||||
cfg.top_surface_filament_id.value = region.extruder_id;
|
||||
cfg.bottom_surface_filament_id.value = region.extruder_id;
|
||||
cfg.sparse_infill_filament_id.value = region.extruder_id;
|
||||
if (cfg != region.region->config()) {
|
||||
// Region configuration changed.
|
||||
if (print_region_ref_cnt(*region.region) == 0) {
|
||||
@@ -1060,9 +1063,12 @@ static PrintObjectRegions* generate_print_object_regions(
|
||||
if (const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
|
||||
parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) {
|
||||
PrintRegionConfig cfg = parent_region.region->config();
|
||||
cfg.wall_filament.value = painted_extruder_id;
|
||||
cfg.solid_infill_filament.value = painted_extruder_id;
|
||||
cfg.sparse_infill_filament.value = painted_extruder_id;
|
||||
cfg.outer_wall_filament_id.value = painted_extruder_id;
|
||||
cfg.inner_wall_filament_id.value = painted_extruder_id;
|
||||
cfg.internal_solid_filament_id.value = painted_extruder_id;
|
||||
cfg.top_surface_filament_id.value = painted_extruder_id;
|
||||
cfg.bottom_surface_filament_id.value = painted_extruder_id;
|
||||
cfg.sparse_infill_filament_id.value = painted_extruder_id;
|
||||
layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))});
|
||||
}
|
||||
// Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MM segmentation.
|
||||
@@ -1254,6 +1260,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: found full_config_diff changed.")%__LINE__;
|
||||
update_apply_status(this->invalidate_step(psGCodeExport));
|
||||
m_placeholder_parser.clear_config();
|
||||
// clear_config() wiped the constructor-set "version"; restore it for custom G-code.
|
||||
m_placeholder_parser.set("version", std::string(SoftFever_VERSION));
|
||||
// Set the profile aliases for the PrintBase::output_filename()
|
||||
m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone());
|
||||
m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone());
|
||||
@@ -1630,6 +1638,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: full_config_diff previous empty, need to apply now.")%__LINE__;
|
||||
|
||||
m_placeholder_parser.clear_config();
|
||||
// clear_config() wiped the constructor-set "version"; restore it for custom G-code.
|
||||
m_placeholder_parser.set("version", std::string(SoftFever_VERSION));
|
||||
// Set the profile aliases for the PrintBase::output_filename()
|
||||
m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone());
|
||||
m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone());
|
||||
|
||||
@@ -83,7 +83,7 @@ std::string PrintBase::output_filename(const std::string &format, const std::str
|
||||
filename.replace_extension(default_ext);
|
||||
return filename.string();
|
||||
} catch (std::runtime_error &err) {
|
||||
throw Slic3r::PlaceholderParserError(L("Failed processing of the filename_format template.") + "\n" + err.what());
|
||||
throw Slic3r::PlaceholderParserError(L("Processing of the filename_format template failed.") + "\n" + err.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ struct StringObjectException
|
||||
std::string string;
|
||||
ObjectBase const *object = nullptr;
|
||||
std::string opt_key;
|
||||
StringExceptionType type; // warning type for tips
|
||||
StringExceptionType type = STRING_EXCEPT_NOT_DEFINED; // warning type for tips
|
||||
bool is_warning = false;
|
||||
std::vector<std::string> params; // warning params for tips
|
||||
};
|
||||
@@ -396,7 +396,7 @@ public:
|
||||
|
||||
// Validate the print, return empty string if valid, return error if process() cannot (or should not) be started.
|
||||
//BBS: add more paremeters to validate
|
||||
virtual StringObjectException validate(StringObjectException *warning = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const { return {}; }
|
||||
virtual StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const { return {}; }
|
||||
|
||||
enum ApplyStatus {
|
||||
// No change after the Print::apply() call.
|
||||
|
||||
+487
-336
File diff suppressed because it is too large.
Load diff
@@ -77,7 +77,7 @@ enum class WipeTowerType {
|
||||
};
|
||||
|
||||
enum PrintHostType {
|
||||
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htESP3D, htCrealityPrint, htObico, htFlashforge, htSimplyPrint, htElegooLink
|
||||
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htESP3D, htCrealityPrint, htObico, htFlashforge, htSimplyPrint, htElegooLink, ht3DPrinterOS, htMoonraker
|
||||
};
|
||||
|
||||
enum AuthorizationType {
|
||||
@@ -1091,7 +1091,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, bottom_shell_thickness))
|
||||
((ConfigOptionFloat, bridge_angle))
|
||||
((ConfigOptionFloat, internal_bridge_angle)) // ORCA: Internal bridge angle override
|
||||
((ConfigOptionBool, relative_bridge_angle)) // ORCA: Relative bridge angle flag
|
||||
((ConfigOptionFloat, bridge_flow))
|
||||
((ConfigOptionFloatOrPercent, bridge_line_width))
|
||||
((ConfigOptionFloat, internal_bridge_flow))
|
||||
((ConfigOptionFloatsNullable, bridge_speed))
|
||||
((ConfigOptionFloatsOrPercentsNullable, internal_bridge_speed))
|
||||
@@ -1114,6 +1116,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, lateral_lattice_angle_1))
|
||||
((ConfigOptionFloat, lateral_lattice_angle_2))
|
||||
((ConfigOptionFloat, infill_overhang_angle))
|
||||
((ConfigOptionFloat, lightning_overhang_angle))
|
||||
((ConfigOptionFloat, lightning_prune_angle))
|
||||
((ConfigOptionFloat, lightning_straightening_angle))
|
||||
((ConfigOptionBool, align_infill_direction_to_model))
|
||||
((ConfigOptionString, extra_solid_infills))
|
||||
((ConfigOptionEnum<FuzzySkinType>, fuzzy_skin))
|
||||
@@ -1129,7 +1134,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionPercent, fuzzy_skin_ripple_offset))
|
||||
((ConfigOptionInt, fuzzy_skin_layers_between_ripple_offset))
|
||||
((ConfigOptionFloatsNullable, gap_infill_speed))
|
||||
((ConfigOptionInt, sparse_infill_filament))
|
||||
((ConfigOptionInt, sparse_infill_filament_id))
|
||||
((ConfigOptionFloatOrPercent, sparse_infill_line_width))
|
||||
((ConfigOptionPercent, infill_wall_overlap))
|
||||
((ConfigOptionPercent, top_bottom_infill_wall_overlap))
|
||||
@@ -1162,14 +1167,17 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloatsNullable, filament_ironing_speed))
|
||||
// Detect bridging perimeters
|
||||
((ConfigOptionBool, detect_overhang_wall))
|
||||
((ConfigOptionInt, wall_filament))
|
||||
((ConfigOptionInt, outer_wall_filament_id))
|
||||
((ConfigOptionInt, inner_wall_filament_id))
|
||||
((ConfigOptionFloatOrPercent, inner_wall_line_width))
|
||||
((ConfigOptionFloatsNullable, inner_wall_speed))
|
||||
// Total number of perimeters.
|
||||
((ConfigOptionInt, wall_loops))
|
||||
((ConfigOptionBool, alternate_extra_wall))
|
||||
((ConfigOptionFloat, minimum_sparse_infill_area))
|
||||
((ConfigOptionInt, solid_infill_filament))
|
||||
((ConfigOptionInt, internal_solid_filament_id))
|
||||
((ConfigOptionInt, top_surface_filament_id))
|
||||
((ConfigOptionInt, bottom_surface_filament_id))
|
||||
((ConfigOptionFloatOrPercent, internal_solid_infill_line_width))
|
||||
((ConfigOptionFloatsNullable, internal_solid_infill_speed))
|
||||
// Detect thin walls.
|
||||
@@ -1470,6 +1478,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, enable_filament_ramming))
|
||||
((ConfigOptionBool, tool_change_on_wipe_tower))
|
||||
((ConfigOptionBool, support_multi_bed_types))
|
||||
((ConfigOptionBool, use_3mf))
|
||||
|
||||
// Small Area Infill Flow Compensation
|
||||
((ConfigOptionStrings, small_area_infill_flow_compensation_model))
|
||||
@@ -1491,6 +1500,9 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionFloatOrPercent, max_travel_detour_distance))
|
||||
((ConfigOptionPoints, printable_area))
|
||||
((ConfigOptionPointsGroups, extruder_printable_area))
|
||||
((ConfigOptionBool, support_parallel_printheads))
|
||||
((ConfigOptionInt, parallel_printheads_count))
|
||||
((ConfigOptionStrings, parallel_printheads_bed_exclude_areas))
|
||||
//BBS: add bed_exclude_area
|
||||
((ConfigOptionPoints, bed_exclude_area))
|
||||
((ConfigOptionPoints, head_wrap_detect_zone))
|
||||
|
||||
+368
-116
@@ -232,7 +232,7 @@ void PrintObject::_transform_hole_to_polyholes()
|
||||
bool twist = this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_twisted.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().wall_filament.value, max_variation, twist}, & hole);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,18 +316,29 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
continue;
|
||||
for (auto layerm : layer->regions()) {
|
||||
auto region = layerm->region();
|
||||
int wall_filament = region.config().wall_filament;
|
||||
int solid_infill_filament = region.config().solid_infill_filament;
|
||||
int sparse_infill_filament = region.config().sparse_infill_filament;
|
||||
int outer_wall_filament_id = region.config().outer_wall_filament_id;
|
||||
int inner_wall_filament_id = region.config().inner_wall_filament_id;
|
||||
int internal_solid_filament_id = region.config().internal_solid_filament_id;
|
||||
int top_surface_filament_id = region.config().top_surface_filament_id;
|
||||
int bottom_surface_filament_id = region.config().bottom_surface_filament_id;
|
||||
int sparse_infill_filament_id = region.config().sparse_infill_filament_id;
|
||||
|
||||
if (!layerm->fills.entities.empty()) {
|
||||
if (solid_infill_filament > 0)
|
||||
geometric_unprintables[extruder_id].insert(solid_infill_filament - 1);
|
||||
if (sparse_infill_filament > 0)
|
||||
geometric_unprintables[extruder_id].insert(sparse_infill_filament - 1);
|
||||
if (internal_solid_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(internal_solid_filament_id - 1);
|
||||
if (top_surface_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(top_surface_filament_id - 1);
|
||||
if (bottom_surface_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(bottom_surface_filament_id - 1);
|
||||
if (sparse_infill_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(sparse_infill_filament_id - 1);
|
||||
}
|
||||
if (!layerm->perimeters.entities.empty()) {
|
||||
if (outer_wall_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(outer_wall_filament_id - 1);
|
||||
if (inner_wall_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(inner_wall_filament_id - 1);
|
||||
}
|
||||
if (!layerm->perimeters.entities.empty() && wall_filament > 0)
|
||||
geometric_unprintables[extruder_id].insert(wall_filament - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,21 +363,28 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
auto layer = m_layers[j];
|
||||
for (auto layerm : layer->regions()) {
|
||||
const auto& region = layerm->region();
|
||||
int wall_filament = region.config().wall_filament;
|
||||
int solid_infill_filament = region.config().solid_infill_filament;
|
||||
int sparse_infill_filament = region.config().sparse_infill_filament;
|
||||
int outer_wall_filament_id = region.config().outer_wall_filament_id;
|
||||
int inner_wall_filament_id = region.config().inner_wall_filament_id;
|
||||
int internal_solid_filament_id = region.config().internal_solid_filament_id;
|
||||
int top_surface_filament_id = region.config().top_surface_filament_id;
|
||||
int bottom_surface_filament_id = region.config().bottom_surface_filament_id;
|
||||
int sparse_infill_filament_id = region.config().sparse_infill_filament_id;
|
||||
std::optional<ExPolygons> fill_expolys;
|
||||
BoundingBox fill_bbox;
|
||||
std::optional<ExPolygons> wall_expolys;
|
||||
BoundingBox wall_bbox;
|
||||
|
||||
for (size_t idx = 0; idx < unprintable_area_in_obj_coord.size(); ++idx) {
|
||||
bool do_infill_filament_detect = (solid_infill_filament > 0 && tbb_geometric_unprintables[idx].count(solid_infill_filament - 1) == 0) ||
|
||||
(sparse_infill_filament > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament-1) == 0);
|
||||
bool do_infill_filament_detect = (internal_solid_filament_id > 0 && tbb_geometric_unprintables[idx].count(internal_solid_filament_id - 1) == 0) ||
|
||||
(top_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(top_surface_filament_id - 1) == 0) ||
|
||||
(bottom_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(bottom_surface_filament_id - 1) == 0) ||
|
||||
(sparse_infill_filament_id > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament_id-1) == 0);
|
||||
|
||||
bool infill_unprintable = !layerm->fills.entities.empty() &&
|
||||
((solid_infill_filament > 0 && tbb_geometric_unprintables[idx].count(solid_infill_filament - 1) > 0) ||
|
||||
(sparse_infill_filament > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament - 1) > 0));
|
||||
((internal_solid_filament_id > 0 && tbb_geometric_unprintables[idx].count(internal_solid_filament_id - 1) > 0) ||
|
||||
(top_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(top_surface_filament_id - 1) > 0) ||
|
||||
(bottom_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(bottom_surface_filament_id - 1) > 0) ||
|
||||
(sparse_infill_filament_id > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament_id - 1) > 0));
|
||||
|
||||
if (!layerm->fills.entities.empty() && do_infill_filament_detect) {
|
||||
if (!fill_expolys) {
|
||||
@@ -375,19 +393,27 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
}
|
||||
if (fill_bbox.overlap(unprintable_area_bbox[idx]) &&
|
||||
!intersection(*fill_expolys, unprintable_area_in_obj_coord[idx]).empty()) {
|
||||
if (solid_infill_filament > 0)
|
||||
tbb_geometric_unprintables[idx].insert(solid_infill_filament - 1);
|
||||
if (sparse_infill_filament > 0)
|
||||
tbb_geometric_unprintables[idx].insert(sparse_infill_filament - 1);
|
||||
if (internal_solid_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(internal_solid_filament_id - 1);
|
||||
if (top_surface_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(top_surface_filament_id - 1);
|
||||
if (bottom_surface_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(bottom_surface_filament_id - 1);
|
||||
if (sparse_infill_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(sparse_infill_filament_id - 1);
|
||||
infill_unprintable = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool do_wall_filament_detect = wall_filament > 0 && tbb_geometric_unprintables[idx].count(wall_filament - 1) == 0;
|
||||
if (!layerm->perimeters.entities.empty() && do_wall_filament_detect) {
|
||||
bool do_outer_wall_filament_detect = outer_wall_filament_id > 0 && tbb_geometric_unprintables[idx].count(outer_wall_filament_id - 1) == 0;
|
||||
bool do_inner_wall_filament_detect = inner_wall_filament_id > 0 && tbb_geometric_unprintables[idx].count(inner_wall_filament_id - 1) == 0;
|
||||
if (!layerm->perimeters.entities.empty() && (do_outer_wall_filament_detect || do_inner_wall_filament_detect)) {
|
||||
// if infill is unprintable, no need to check wall since wall contour surrounds infill contour
|
||||
if (infill_unprintable) {
|
||||
tbb_geometric_unprintables[idx].insert(wall_filament - 1);
|
||||
if (outer_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(outer_wall_filament_id - 1);
|
||||
if (inner_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(inner_wall_filament_id - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -402,7 +428,10 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
|
||||
if (wall_bbox.overlap(unprintable_area_bbox[idx]) &&
|
||||
!intersection(*wall_expolys, unprintable_area_in_obj_coord[idx]).empty()) {
|
||||
tbb_geometric_unprintables[idx].insert(wall_filament - 1);
|
||||
if (outer_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(outer_wall_filament_id - 1);
|
||||
if (inner_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(inner_wall_filament_id - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1265,8 +1294,10 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "bottom_shell_thickness"
|
||||
|| opt_key == "top_shell_thickness"
|
||||
|| opt_key == "minimum_sparse_infill_area"
|
||||
|| opt_key == "sparse_infill_filament"
|
||||
|| opt_key == "solid_infill_filament"
|
||||
|| opt_key == "sparse_infill_filament_id"
|
||||
|| opt_key == "internal_solid_filament_id"
|
||||
|| opt_key == "top_surface_filament_id"
|
||||
|| opt_key == "bottom_surface_filament_id"
|
||||
|| opt_key == "sparse_infill_line_width"
|
||||
|| opt_key == "skin_infill_line_width"
|
||||
|| opt_key == "skeleton_infill_line_width"
|
||||
@@ -1277,7 +1308,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "ensure_vertical_shell_thickness"
|
||||
|| opt_key == "bridge_angle"
|
||||
|| opt_key == "internal_bridge_angle" // ORCA: Internal bridge angle override
|
||||
|| opt_key == "relative_bridge_angle" // ORCA: Relative bridge angle
|
||||
//BBS
|
||||
|| opt_key == "bridge_line_width"
|
||||
|| opt_key == "bridge_density"
|
||||
|| opt_key == "internal_bridge_density") {
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
@@ -1302,6 +1335,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_shift_step"
|
||||
|| opt_key == "sparse_infill_rotate_template"
|
||||
|| opt_key == "solid_infill_rotate_template"
|
||||
|| opt_key == "lightning_overhang_angle"
|
||||
|| opt_key == "lightning_prune_angle"
|
||||
|| opt_key == "lightning_straightening_angle"
|
||||
|| opt_key == "skeleton_infill_density"
|
||||
|| opt_key == "skin_infill_density"
|
||||
|| opt_key == "infill_lock_depth"
|
||||
@@ -1324,7 +1360,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
} else if (
|
||||
opt_key == "outer_wall_line_width"
|
||||
|| opt_key == "wall_filament"
|
||||
|| opt_key == "outer_wall_filament_id"
|
||||
|| opt_key == "inner_wall_filament_id"
|
||||
|| opt_key == "fuzzy_skin"
|
||||
|| opt_key == "fuzzy_skin_thickness"
|
||||
|| opt_key == "fuzzy_skin_point_distance"
|
||||
@@ -1705,30 +1742,46 @@ void PrintObject::detect_surfaces_type()
|
||||
// Only iterate to the second-to-last layer, since we look at layer i+1.
|
||||
if( (this->config().enable_extra_bridge_layer.value == eblApplyToAll) || (this->config().enable_extra_bridge_layer.value == eblExternalBridgeOnly)){
|
||||
const size_t last = (m_layers.empty() ? 0 : m_layers.size() - 1);
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, region_id](const tbb::blocked_range<size_t> &range) {
|
||||
|
||||
// ORCA: Two-phase split (collect-then-apply) to eliminate a data race in the
|
||||
// original single-phase parallel_for, where iteration `i` rewrote
|
||||
// m_layers[i+1]->slices.surfaces via std::move while iteration `i+1` (running
|
||||
// on an adjacent TBB block on another worker thread) was iterating that same
|
||||
// Surfaces vector as its bot_surfs. Splitting into a read-only collect pass
|
||||
// followed by a write-only apply pass removes the cross-iteration aliasing.
|
||||
//
|
||||
// Phase 1: read-only pass — collect each layer's stBottomBridge polygons into a
|
||||
// per-layer cache. No surfaces are mutated, so concurrent reads are safe.
|
||||
std::vector<Polygons> bridge_polys_per_layer(last);
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, last), [this, region_id, &bridge_polys_per_layer](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
m_print->throw_if_canceled();
|
||||
|
||||
// Step 1: Find bridge polygons
|
||||
// Current layer (i): Search for stBottomBridge polygons.
|
||||
const Surfaces &bot_surfs = m_layers[i]->m_regions[region_id]->slices.surfaces;
|
||||
// Next layer (i+1): The layer where stInternal polygons may be re-classified.
|
||||
Surfaces &top_surfs = m_layers[i + 1]->m_regions[region_id]->slices.surfaces;
|
||||
|
||||
// Step 2: Collect the bridge polygons in the current layer region
|
||||
Polygons polygons_bridge;
|
||||
for (const Surface &sbot : bot_surfs) {
|
||||
if (sbot.surface_type == stBottomBridge) {
|
||||
polygons_append(polygons_bridge, to_polygons(sbot));
|
||||
polygons_append(bridge_polys_per_layer[i], to_polygons(sbot));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Phase 2: write pass — each iteration mutates only m_layers[i+1]->slices.surfaces
|
||||
// and reads its bridge polygons from the precomputed cache. Different iterations
|
||||
// never share a write target, so there is no aliasing between worker threads.
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, region_id, &bridge_polys_per_layer](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
m_print->throw_if_canceled();
|
||||
|
||||
// Step 1 + 2: pull the precomputed bridge polygons for the current source layer.
|
||||
const Polygons &polygons_bridge = bridge_polys_per_layer[i];
|
||||
|
||||
// Step 3: Early termination of loop if no meaningfull bridge found
|
||||
// No bridge polygons found, continue to the next layer
|
||||
if (polygons_bridge.empty())
|
||||
continue;
|
||||
|
||||
// Step 4: Bottom bridge polygons found - scan and create layer+1 bridge polygon
|
||||
Surfaces &top_surfs = m_layers[i + 1]->m_regions[region_id]->slices.surfaces;
|
||||
Surfaces new_surfaces;
|
||||
new_surfaces.reserve(top_surfs.size());
|
||||
|
||||
@@ -1742,7 +1795,50 @@ void PrintObject::detect_surfaces_type()
|
||||
// This would also skip generation of very short dual bridge layers (that are shorter than N perimeters), but these are unecessary as the bridge distance is
|
||||
// We could reduce this slightly to account for innacurcies in the clipping operation.
|
||||
// TODO: Monitor GitHub issues to check whether second bridge layers are ommited where they should be generated. If yes, reduce the filtering distance
|
||||
|
||||
|
||||
// ORCA: Same-layer-top guard.
|
||||
//
|
||||
// Collect every stTop polygon present at layer i+1 (this region) and
|
||||
// expand it by the same `offset_distance` used by the bridge filter
|
||||
// above. Note that `offset_distance` here is the full wall-band
|
||||
// distance for the region (external wall width + all configured
|
||||
// internal wall widths, i.e. external + (wall_loops - 1) × internal),
|
||||
// not a single perimeter width. Any candidate second-bridge area that
|
||||
// falls under this expanded mask will be subtracted out below.
|
||||
//
|
||||
// Why this exists: detect_surfaces_type() classifies a layer's "top"
|
||||
// surfaces as the geometry that is not covered by the layer above. Those
|
||||
// stTop regions often have small stInternal islands embedded in them.
|
||||
// The pre-existing wall-band filter (shrink_ex/offset_ex by
|
||||
// offset_distance) is supposed to throw those tiny islands away, but
|
||||
// its result is sensitive to Clipper's floating-point order of
|
||||
// operations: on macOS ARM the filter eats them, on Windows/Intel it
|
||||
// doesn't. Visible bridges then show up scattered across the printed
|
||||
// top surface.
|
||||
//
|
||||
// Expanding stTop by offset_distance and subtracting it from the
|
||||
// overlap makes the decision platform-independent: an island fully
|
||||
// surrounded by stTop disappears regardless of which Clipper happens
|
||||
// to be doing the math, while large stInternal regions away from the
|
||||
// top survive intact (the expansion only nibbles the wall-band depth
|
||||
// inward).
|
||||
//
|
||||
// Keep ExPolygons throughout so that any holes inside an stTop surface
|
||||
// are offset with the correct sign (positive offset shrinks holes /
|
||||
// grows the solid region). Using Polygons + expand() would treat the
|
||||
// contour and each hole as independent polygons and could distort the
|
||||
// mask.
|
||||
ExPolygons same_layer_top_expanded;
|
||||
{
|
||||
ExPolygons same_layer_top;
|
||||
for (const Surface &s : top_surfs) {
|
||||
if (s.surface_type == stTop)
|
||||
same_layer_top.push_back(s.expolygon);
|
||||
}
|
||||
if (! same_layer_top.empty())
|
||||
same_layer_top_expanded = offset_ex(same_layer_top, offset_distance);
|
||||
}
|
||||
|
||||
// For each surface in the layer above
|
||||
for (Surface &s_up : top_surfs) {
|
||||
// Only reclassify stInternal polygons (i.e. what will become later solid and sparse infill)
|
||||
@@ -1757,7 +1853,13 @@ void PrintObject::detect_surfaces_type()
|
||||
// Filter out the resulting candidate bridges based on size. First perform a shrink operation...
|
||||
// ...followed by an expand operation to bring them back to the original size (positive offset)
|
||||
overlap = offset_ex(shrink_ex(overlap, offset_distance), offset_distance);
|
||||
|
||||
|
||||
// ORCA: subtract the expanded same-layer stTop mask (see comment above
|
||||
// the mask construction). Drops stInternal islands fully surrounded by
|
||||
// stTop at i+1 without affecting bridges that lie away from the top.
|
||||
if (! same_layer_top_expanded.empty() && ! overlap.empty())
|
||||
overlap = diff_ex(overlap, same_layer_top_expanded, ApplySafetyOffset::Yes);
|
||||
|
||||
// Now subtract the filtered new bridge layer from the remaining internal surfaces to create the new internal surface
|
||||
ExPolygons remainder = diff_ex(p_up, overlap, ApplySafetyOffset::Yes);
|
||||
|
||||
@@ -2502,29 +2604,76 @@ void PrintObject::bridge_over_infill()
|
||||
backup_surfaces[lidx] = {};
|
||||
}
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &backup_surfaces,
|
||||
&surfaces_by_layer](tbb::blocked_range<size_t> r) {
|
||||
// ORCA: Two-phase split (collect-then-apply) to eliminate a data race in
|
||||
// the original single-phase parallel_for, where iteration `lidx` read
|
||||
// m_layers[lidx-1]->regions()->fill_surfaces (its lower_layer) to compute
|
||||
// `lightning_fill`, while iteration `lidx-1`, on an adjacent TBB block,
|
||||
// was concurrently std::move-ing / emplace_back-ing into that same
|
||||
// SurfaceCollection.
|
||||
//
|
||||
// Semantic choice — read ORIGINAL surfaces in Phase 1:
|
||||
// The lower_layer that iteration `lidx` looks at is the *current* layer
|
||||
// for iteration `lidx-1`, which Phase 2 will modify. We therefore have to
|
||||
// pick whether Phase 1 sees that layer's pre-modification or
|
||||
// post-modification state. We deliberately use the original (pre-modification)
|
||||
// state, for two reasons:
|
||||
// 1. The gate is asking "does the layer below use lightning sparse
|
||||
// infill?" — that's a property of the layer's configuration plus its
|
||||
// original sparse-infill classification. Phase 2's edits only carve
|
||||
// a small overhang-aligned slice of sparse into solid; they do not
|
||||
// change whether the layer is using lightning. The realistic gate
|
||||
// answer is the same either way.
|
||||
// 2. Each layer's solid expansion is meant to give its OWN lower_layer
|
||||
// something to anchor lightning lines onto. Cascading the gate
|
||||
// across layers ("skip mine because the layer below already did
|
||||
// some") would invert that intent and force a serial Phase 2.
|
||||
// The original racy code didn't actually implement either choice
|
||||
// consistently — it returned whichever bytes happened to be in the
|
||||
// vector when the thread arrived. This split makes the behaviour
|
||||
// defined, deterministic across runs and platforms, and equivalent to
|
||||
// a clean sequential implementation that gathered all gates first and
|
||||
// then applied modifications.
|
||||
//
|
||||
// Phase 1: read-only — for each layer, determine whether its lower_layer
|
||||
// has any stInternal area inside a lightning-infill region. That's the
|
||||
// sole purpose of `lightning_fill` in the original code: a gate. Capture
|
||||
// it once into a per-layer bool, derived from the original (unmodified)
|
||||
// surfaces, so the gate is platform-independent and order-independent.
|
||||
std::vector<char> needs_lightning_expansion(this->layers().size(), 0);
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &surfaces_by_layer,
|
||||
&needs_lightning_expansion](tbb::blocked_range<size_t> r) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
|
||||
if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end())
|
||||
continue;
|
||||
|
||||
Layer *layer = po->get_layer(lidx);
|
||||
const Layer *layer = po->get_layer(lidx);
|
||||
const Layer *lower_layer = layer->lower_layer;
|
||||
if (lower_layer == nullptr)
|
||||
continue;
|
||||
|
||||
Polygons lightning_fill;
|
||||
for (const LayerRegion *region : lower_layer->regions()) {
|
||||
if (region->region().config().sparse_infill_pattern == ipLightning) {
|
||||
Polygons lf = to_polygons(region->fill_surfaces.filter_by_type(stInternal));
|
||||
lightning_fill.insert(lightning_fill.end(), lf.begin(), lf.end());
|
||||
if (region->region().config().sparse_infill_pattern == ipLightning
|
||||
&& ! region->fill_surfaces.filter_by_type(stInternal).empty()) {
|
||||
needs_lightning_expansion[lidx] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (lightning_fill.empty())
|
||||
// Phase 2: write-only — each iteration mutates only m_layers[lidx]'s
|
||||
// fill_surfaces and never reads any other layer's surfaces. Different
|
||||
// iterations write to disjoint LayerRegion::fill_surfaces vectors, so
|
||||
// there is no aliasing between worker threads.
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &backup_surfaces,
|
||||
&surfaces_by_layer,
|
||||
&needs_lightning_expansion](tbb::blocked_range<size_t> r) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
|
||||
if (! needs_lightning_expansion[lidx])
|
||||
continue;
|
||||
|
||||
Layer *layer = po->get_layer(lidx);
|
||||
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
backup_surfaces[lidx][region] = std::move(
|
||||
region->fill_surfaces); // Make backup copy by move!! so that pointers in candidate surfaces stay valid
|
||||
@@ -3104,8 +3253,19 @@ void PrintObject::bridge_over_infill()
|
||||
}
|
||||
|
||||
// ORCA: Internal bridge angle override
|
||||
if (candidate.region->region().config().internal_bridge_angle > 0)
|
||||
bridging_angle = candidate.region->region().config().internal_bridge_angle.value * PI / 180.0; // Convert degrees to radians
|
||||
if (candidate.region->region().config().internal_bridge_angle.value > 0) {
|
||||
const auto ®ion_config = candidate.region->region().config();
|
||||
const double custom_angle_rad = Geometry::deg2rad(region_config.internal_bridge_angle.value);
|
||||
if (region_config.relative_bridge_angle.value)
|
||||
bridging_angle += custom_angle_rad;
|
||||
else {
|
||||
bridging_angle = custom_angle_rad;
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
auto m = po->trafo().matrix();
|
||||
bridging_angle += std::atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end());
|
||||
if (!lightning_area.empty() && !intersection(area_to_be_bridge, lightning_area).empty()) {
|
||||
@@ -3234,53 +3394,75 @@ void PrintObject::bridge_over_infill()
|
||||
// === ORCA: Create a second internal bridge layer above the first bridge layer. ========================================================
|
||||
// ======================================================================================================================================
|
||||
if ( this->m_config.enable_extra_bridge_layer == eblApplyToAll || this->m_config.enable_extra_bridge_layer == eblInternalBridgeOnly) {
|
||||
// Process layers in parallel up to second-to-last
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, this->layers().size() - 1), [this](const tbb::blocked_range<size_t>& r) {
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); ++lidx)
|
||||
{
|
||||
// ORCA: Two-phase to eliminate the same data race as the external-bridge
|
||||
// pass in detect_surfaces_type().
|
||||
//
|
||||
// Phase 1: read-only — for each layer, collect its stInternalBridge polygons and
|
||||
// the matching bridge angle into a per-layer cache.
|
||||
struct LayerBridgeCache {
|
||||
ExPolygons polys;
|
||||
double angle = 0.0;
|
||||
float offset_distance = 0.0f;
|
||||
bool has_bridge = false;
|
||||
};
|
||||
// Guard against size_t underflow when the object has 0 or 1 layers — there is
|
||||
// no "layer above" to receive an extra bridge, so the whole pass is a no-op.
|
||||
const size_t last = (this->layers().size() < 2) ? 0 : this->layers().size() - 1;
|
||||
std::vector<LayerBridgeCache> caches(this->layers().size());
|
||||
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, &caches](const tbb::blocked_range<size_t>& r) {
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); ++lidx) {
|
||||
Layer* layer = this->get_layer(lidx);
|
||||
|
||||
// (A) Gather internal bridging surfaces in the current layer
|
||||
ExPolygons bridging_current_layer;
|
||||
double bridging_angle_current = 0.0;
|
||||
|
||||
bool found_any_bridge = false;
|
||||
float offset_distance = 0.0f;
|
||||
|
||||
// Pick a region from which to retrieve the flow width
|
||||
LayerBridgeCache &cache = caches[lidx];
|
||||
|
||||
if (!layer->regions().empty())
|
||||
offset_distance = layer->regions().front()->flow(frSolidInfill).scaled_width();
|
||||
|
||||
cache.offset_distance = layer->regions().front()->flow(frSolidInfill).scaled_width();
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
for (const Surface &surf : region->fill_surfaces.surfaces) {
|
||||
if (surf.surface_type == stInternalBridge) {
|
||||
bridging_current_layer.push_back(surf.expolygon);
|
||||
bridging_angle_current = surf.bridge_angle; // Store the last bridging angle of the current print object
|
||||
found_any_bridge = true;
|
||||
cache.polys.push_back(surf.expolygon);
|
||||
cache.angle = surf.bridge_angle; // last bridge angle on this layer wins, matching prior behaviour
|
||||
cache.has_bridge = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no bridging in this layer, continue with the next
|
||||
if (!found_any_bridge || bridging_current_layer.empty())
|
||||
|
||||
if (!cache.has_bridge || cache.polys.empty()) {
|
||||
cache.has_bridge = false;
|
||||
continue;
|
||||
|
||||
// (B) Shrink-expand to remove trivial bridging areas
|
||||
bridging_current_layer = offset_ex( shrink_ex(bridging_current_layer, offset_distance), offset_distance );
|
||||
|
||||
if (bridging_current_layer.empty())
|
||||
continue; // all bridging was trivial, continue with the next layer
|
||||
|
||||
}
|
||||
|
||||
// Shrink-expand to remove trivial bridging areas
|
||||
cache.polys = offset_ex(shrink_ex(cache.polys, cache.offset_distance), cache.offset_distance);
|
||||
if (cache.polys.empty())
|
||||
cache.has_bridge = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Phase 2: write — each iteration mutates only m_layers[lidx+1]->fill_surfaces and
|
||||
// pulls its bridge polygons from the precomputed cache. Different iterations never
|
||||
// touch the same fill_surfaces vector, so there is no aliasing between workers.
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, &caches](const tbb::blocked_range<size_t>& r) {
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); ++lidx)
|
||||
{
|
||||
const LayerBridgeCache &cache = caches[lidx];
|
||||
|
||||
// If no bridging in this layer, continue with the next
|
||||
if (!cache.has_bridge || cache.polys.empty())
|
||||
continue;
|
||||
|
||||
// (C) If there is a next layer, identify overlapping stInternal & stInternalSolid areas and convert the overlap to stSecondInternalBridge
|
||||
if (lidx + 1 < this->layers().size()) {
|
||||
Layer* next_layer = this->get_layer(lidx + 1);
|
||||
|
||||
// second bridging angle is 90 degrees offset
|
||||
double bridging_angle_second = bridging_angle_current + M_PI / 2.0;
|
||||
|
||||
double bridging_angle_second = cache.angle + M_PI / 2.0;
|
||||
// Union the bridging polygons
|
||||
ExPolygons bridging_union = union_safety_offset_ex(bridging_current_layer);
|
||||
|
||||
ExPolygons bridging_union = union_safety_offset_ex(cache.polys);
|
||||
const float offset_distance = cache.offset_distance;
|
||||
|
||||
for (LayerRegion *next_region : next_layer->regions()) {
|
||||
Surfaces next_new_surfaces;
|
||||
Surfaces keep_surfaces;
|
||||
@@ -3379,6 +3561,12 @@ static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders
|
||||
opt.value = 1;
|
||||
}
|
||||
|
||||
static void clamp_feature_filament_to_valid(ConfigOptionInt &opt, size_t num_extruders)
|
||||
{
|
||||
if (opt.value <= 0 || opt.value > (int)num_extruders)
|
||||
opt.value = 1;
|
||||
}
|
||||
|
||||
PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders)
|
||||
{
|
||||
PrintObjectConfig config = default_object_config;
|
||||
@@ -3394,63 +3582,124 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject
|
||||
}
|
||||
|
||||
const std::string key_extruder { "extruder" };
|
||||
static constexpr const std::initializer_list<const std::string_view> keys_extruders { "sparse_infill_filament"sv, "solid_infill_filament"sv, "wall_filament"sv };
|
||||
static constexpr const std::initializer_list<const std::string_view> keys_extruders {
|
||||
"sparse_infill_filament_id"sv,
|
||||
"internal_solid_filament_id"sv,
|
||||
"top_surface_filament_id"sv,
|
||||
"bottom_surface_filament_id"sv,
|
||||
"outer_wall_filament_id"sv,
|
||||
"inner_wall_filament_id"sv
|
||||
};
|
||||
|
||||
static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in)
|
||||
struct FeatureFilamentOverrideMask
|
||||
{
|
||||
// 1) Map legacy "extruder" to feature filament keys as a fallback only.
|
||||
// If any feature-specific filament is explicitly set, keep those values.
|
||||
bool sparse_infill_filament_id = false;
|
||||
bool internal_solid_filament_id = false;
|
||||
bool top_surface_filament_id = false;
|
||||
bool bottom_surface_filament_id = false;
|
||||
bool outer_wall_filament_id = false;
|
||||
bool inner_wall_filament_id = false;
|
||||
};
|
||||
|
||||
static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in, FeatureFilamentOverrideMask &feature_overrides)
|
||||
{
|
||||
// 1) Explicit feature filament values take precedence over base extruder fallback.
|
||||
auto *opt_extruder = in.opt<ConfigOptionInt>(key_extruder);
|
||||
auto *opt_sparse_infill_filament = in.opt<ConfigOptionInt>("sparse_infill_filament");
|
||||
auto *opt_solid_infill_filament = in.opt<ConfigOptionInt>("solid_infill_filament");
|
||||
auto *opt_wall_filament = in.opt<ConfigOptionInt>("wall_filament");
|
||||
const bool has_feature_filament_override =
|
||||
(opt_sparse_infill_filament != nullptr && opt_sparse_infill_filament->value > 0) ||
|
||||
(opt_solid_infill_filament != nullptr && opt_solid_infill_filament->value > 0) ||
|
||||
(opt_wall_filament != nullptr && opt_wall_filament->value > 0);
|
||||
if (opt_extruder)
|
||||
if (int extruder = opt_extruder->value; extruder > 1 && ! has_feature_filament_override) {
|
||||
// Not a default extruder.
|
||||
out.sparse_infill_filament.value = extruder;
|
||||
out.solid_infill_filament.value = extruder;
|
||||
out.wall_filament.value = extruder;
|
||||
}
|
||||
int base_extruder = (opt_extruder != nullptr) ? opt_extruder->value : 0;
|
||||
|
||||
// 2) Copy the rest of the values.
|
||||
for (auto it = in.cbegin(); it != in.cend(); ++ it)
|
||||
if (it->first != key_extruder)
|
||||
if (ConfigOption* my_opt = out.option(it->first, false); my_opt != nullptr) {
|
||||
if (one_of(it->first, keys_extruders)) {
|
||||
// Ignore "default" extruders.
|
||||
// "Default" (0) clears explicit override for this scope and lets fallback apply.
|
||||
int extruder = static_cast<const ConfigOptionInt*>(it->second.get())->value;
|
||||
if (extruder > 0)
|
||||
if (extruder > 0) {
|
||||
my_opt->setInt(extruder);
|
||||
if (it->first == "sparse_infill_filament_id")
|
||||
feature_overrides.sparse_infill_filament_id = true;
|
||||
else if (it->first == "internal_solid_filament_id")
|
||||
feature_overrides.internal_solid_filament_id = true;
|
||||
else if (it->first == "top_surface_filament_id")
|
||||
feature_overrides.top_surface_filament_id = true;
|
||||
else if (it->first == "bottom_surface_filament_id")
|
||||
feature_overrides.bottom_surface_filament_id = true;
|
||||
else if (it->first == "outer_wall_filament_id")
|
||||
feature_overrides.outer_wall_filament_id = true;
|
||||
else if (it->first == "inner_wall_filament_id")
|
||||
feature_overrides.inner_wall_filament_id = true;
|
||||
} else {
|
||||
if (it->first == "sparse_infill_filament_id")
|
||||
feature_overrides.sparse_infill_filament_id = false;
|
||||
else if (it->first == "internal_solid_filament_id")
|
||||
feature_overrides.internal_solid_filament_id = false;
|
||||
else if (it->first == "top_surface_filament_id")
|
||||
feature_overrides.top_surface_filament_id = false;
|
||||
else if (it->first == "bottom_surface_filament_id")
|
||||
feature_overrides.bottom_surface_filament_id = false;
|
||||
else if (it->first == "outer_wall_filament_id")
|
||||
feature_overrides.outer_wall_filament_id = false;
|
||||
else if (it->first == "inner_wall_filament_id")
|
||||
feature_overrides.inner_wall_filament_id = false;
|
||||
}
|
||||
} else
|
||||
my_opt->set(it->second.get());
|
||||
}
|
||||
|
||||
// 3) Apply base extruder only to features that were not explicitly overridden.
|
||||
if (base_extruder > 0) {
|
||||
if (!feature_overrides.sparse_infill_filament_id)
|
||||
out.sparse_infill_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.internal_solid_filament_id)
|
||||
out.internal_solid_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.top_surface_filament_id)
|
||||
out.top_surface_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.bottom_surface_filament_id)
|
||||
out.bottom_surface_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.outer_wall_filament_id)
|
||||
out.outer_wall_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.inner_wall_filament_id)
|
||||
out.inner_wall_filament_id.value = base_extruder;
|
||||
}
|
||||
}
|
||||
|
||||
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 config = default_or_parent_region_config;
|
||||
FeatureFilamentOverrideMask feature_overrides;
|
||||
|
||||
// For model parts, non-zero values coming from the print defaults should stay explicit.
|
||||
if (volume.is_model_part()) {
|
||||
feature_overrides.sparse_infill_filament_id = (config.sparse_infill_filament_id.value > 0);
|
||||
feature_overrides.internal_solid_filament_id = (config.internal_solid_filament_id.value > 0);
|
||||
feature_overrides.top_surface_filament_id = (config.top_surface_filament_id.value > 0);
|
||||
feature_overrides.bottom_surface_filament_id = (config.bottom_surface_filament_id.value > 0);
|
||||
feature_overrides.outer_wall_filament_id = (config.outer_wall_filament_id.value > 0);
|
||||
feature_overrides.inner_wall_filament_id = (config.inner_wall_filament_id.value > 0);
|
||||
}
|
||||
|
||||
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());
|
||||
apply_to_print_region_config(config, volume.get_object()->config.get(), feature_overrides);
|
||||
} 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());
|
||||
apply_to_print_region_config(config, volume.config.get(), feature_overrides);
|
||||
if (! volume.material_id().empty())
|
||||
apply_to_print_region_config(config, volume.material()->config.get());
|
||||
apply_to_print_region_config(config, volume.material()->config.get(), feature_overrides);
|
||||
if (layer_range_config != nullptr) {
|
||||
// Not applicable to modifiers.
|
||||
assert(volume.is_model_part());
|
||||
apply_to_print_region_config(config, *layer_range_config);
|
||||
apply_to_print_region_config(config, *layer_range_config, feature_overrides);
|
||||
}
|
||||
// Clamp invalid extruders to the default extruder (with index 1).
|
||||
clamp_exturder_to_default(config.sparse_infill_filament, num_extruders);
|
||||
clamp_exturder_to_default(config.wall_filament, num_extruders);
|
||||
clamp_exturder_to_default(config.solid_infill_filament, num_extruders);
|
||||
// Resolve feature defaults and clamp invalid extruders to index 1.
|
||||
clamp_feature_filament_to_valid(config.sparse_infill_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.outer_wall_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.inner_wall_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.internal_solid_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.top_surface_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.bottom_surface_filament_id, num_extruders);
|
||||
if (config.sparse_infill_density.value < 0.00011f)
|
||||
// Switch of infill for very low infill rates, also avoid division by zero in infill generator for these very low rates.
|
||||
// See GH issue #5910.
|
||||
@@ -3513,9 +3762,12 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
|
||||
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)
|
||||
if (range_and_config.second.has("wall_filament") ||
|
||||
range_and_config.second.has("sparse_infill_filament") ||
|
||||
range_and_config.second.has("solid_infill_filament"))
|
||||
if (range_and_config.second.has("outer_wall_filament_id") ||
|
||||
range_and_config.second.has("inner_wall_filament_id") ||
|
||||
range_and_config.second.has("sparse_infill_filament_id") ||
|
||||
range_and_config.second.has("internal_solid_filament_id") ||
|
||||
range_and_config.second.has("top_surface_filament_id") ||
|
||||
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),
|
||||
@@ -3932,8 +4184,8 @@ void PrintObject::combine_infill()
|
||||
// Limit the number of combined layers to the maximum height allowed by this regions' nozzle.
|
||||
//FIXME limit the layer height to max_layer_height
|
||||
double nozzle_diameter = std::min(
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament.value - 1),
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().solid_infill_filament.value - 1));
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament_id.value - 1),
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().internal_solid_filament_id.value - 1));
|
||||
|
||||
//Orca: Limit combination of infill to up to infill_combination_max_layer_height
|
||||
const double infill_combination_max_layer_height = region.config().infill_combination_max_layer_height.get_abs_value(nozzle_diameter);
|
||||
|
||||
@@ -359,7 +359,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
||||
bool rhs_empty = rhs.region_id < 0 || rhs.expolygons.empty();
|
||||
// Sort the empty items to the end of the list.
|
||||
// Sort by region_id & volume_id lexicographically.
|
||||
return ! this_empty && (rhs_empty || (this->region_id < rhs.region_id || (this->region_id == rhs.region_id && volume_id < volume_id)));
|
||||
return ! this_empty && (rhs_empty || (this->region_id < rhs.region_id || (this->region_id == rhs.region_id && volume_id < rhs.volume_id)));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -7,12 +7,16 @@ namespace Slic3r {
|
||||
unsigned int PrintRegion::extruder(FlowRole role) const
|
||||
{
|
||||
size_t extruder = 0;
|
||||
if (role == frPerimeter || role == frExternalPerimeter)
|
||||
extruder = m_config.wall_filament;
|
||||
if (role == frPerimeter)
|
||||
extruder = m_config.inner_wall_filament_id;
|
||||
else if (role == frExternalPerimeter)
|
||||
extruder = m_config.outer_wall_filament_id;
|
||||
else if (role == frInfill)
|
||||
extruder = m_config.sparse_infill_filament;
|
||||
else if (role == frSolidInfill || role == frTopSolidInfill)
|
||||
extruder = m_config.solid_infill_filament;
|
||||
extruder = m_config.sparse_infill_filament_id;
|
||||
else if (role == frSolidInfill)
|
||||
extruder = m_config.internal_solid_filament_id;
|
||||
else if (role == frTopSolidInfill)
|
||||
extruder = m_config.top_surface_filament_id;
|
||||
else
|
||||
throw Slic3r::InvalidArgument("Unknown role");
|
||||
return extruder;
|
||||
@@ -51,9 +55,12 @@ Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_he
|
||||
|
||||
coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const
|
||||
{
|
||||
return (print_config.nozzle_diameter.get_at(m_config.wall_filament.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.sparse_infill_filament.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.solid_infill_filament.value - 1)) / 3.;
|
||||
return (print_config.nozzle_diameter.get_at(m_config.outer_wall_filament_id.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.inner_wall_filament_id.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.sparse_infill_filament_id.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.internal_solid_filament_id.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.top_surface_filament_id.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.bottom_surface_filament_id.value - 1)) / 6.;
|
||||
}
|
||||
|
||||
coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const
|
||||
@@ -70,12 +77,19 @@ void PrintRegion::collect_object_printing_extruders(const PrintConfig &print_con
|
||||
int i = std::max(0, extruder_id - 1);
|
||||
object_extruders.emplace_back((i >= num_extruders) ? 0 : i);
|
||||
};
|
||||
if (region_config.wall_loops.value > 0 || has_brim)
|
||||
emplace_extruder(region_config.wall_filament);
|
||||
if (region_config.wall_loops.value > 0 || has_brim) {
|
||||
emplace_extruder(region_config.outer_wall_filament_id);
|
||||
if (region_config.wall_loops.value > 1)
|
||||
emplace_extruder(region_config.inner_wall_filament_id);
|
||||
}
|
||||
if (region_config.sparse_infill_density.value > 0)
|
||||
emplace_extruder(region_config.sparse_infill_filament);
|
||||
if (region_config.top_shell_layers.value > 0 || region_config.bottom_shell_layers.value > 0)
|
||||
emplace_extruder(region_config.solid_infill_filament);
|
||||
emplace_extruder(region_config.sparse_infill_filament_id);
|
||||
if (region_config.sparse_infill_density.value > 0 || region_config.top_shell_layers.value > 0 || region_config.bottom_shell_layers.value > 0)
|
||||
emplace_extruder(region_config.internal_solid_filament_id);
|
||||
if (region_config.top_shell_layers.value > 0)
|
||||
emplace_extruder(region_config.top_surface_filament_id);
|
||||
if (region_config.bottom_shell_layers.value > 0)
|
||||
emplace_extruder(region_config.bottom_surface_filament_id);
|
||||
}
|
||||
|
||||
void PrintRegion::collect_object_printing_extruders(const Print &print, std::vector<unsigned int> &object_extruders) const
|
||||
@@ -85,9 +99,12 @@ void PrintRegion::collect_object_printing_extruders(const Print &print, std::vec
|
||||
#ifndef NDEBUG
|
||||
// BBS
|
||||
auto num_extruders = int(print.config().filament_diameter.size());
|
||||
assert(this->config().wall_filament <= num_extruders);
|
||||
assert(this->config().sparse_infill_filament <= num_extruders);
|
||||
assert(this->config().solid_infill_filament <= num_extruders);
|
||||
assert(this->config().outer_wall_filament_id <= num_extruders);
|
||||
assert(this->config().inner_wall_filament_id <= num_extruders);
|
||||
assert(this->config().sparse_infill_filament_id <= num_extruders);
|
||||
assert(this->config().internal_solid_filament_id <= num_extruders);
|
||||
assert(this->config().top_surface_filament_id <= num_extruders);
|
||||
assert(this->config().bottom_surface_filament_id <= num_extruders);
|
||||
#endif
|
||||
collect_object_printing_extruders(print.config(), this->config(), print.has_brim(), object_extruders);
|
||||
}
|
||||
|
||||
@@ -613,7 +613,7 @@ std::string SLAPrint::output_filename(const std::string &filename_base) const
|
||||
return this->PrintBase::output_filename(m_print_config.filename_format.value, ".sl1", filename_base, &config);
|
||||
}
|
||||
|
||||
StringObjectException SLAPrint::validate(StringObjectException *exception, Polygons *collison_polygons, std::vector<std::pair<Polygon, float>> *height_polygons) const
|
||||
StringObjectException SLAPrint::validate(std::vector<StringObjectException> *warnings, Polygons *collison_polygons, std::vector<std::pair<Polygon, float>> *height_polygons) const
|
||||
{
|
||||
for(SLAPrintObject * po : m_objects) {
|
||||
|
||||
|
||||
@@ -488,7 +488,7 @@ public:
|
||||
|
||||
const SLAPrintStatistics& print_statistics() const { return m_print_statistics; }
|
||||
|
||||
StringObjectException validate(StringObjectException * warning = nullptr,
|
||||
StringObjectException validate(std::vector<StringObjectException> * warnings = nullptr,
|
||||
Polygons * collison_polygons = nullptr,
|
||||
std::vector<std::pair<Polygon, float>> *height_polygons = nullptr) const override;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <libslic3r/MeshBoolean.hpp>
|
||||
#include <libslic3r/TriangleMeshSlicer.hpp>
|
||||
|
||||
// Need the cylinder method for the the drainholes in hollowing step
|
||||
// Need the cylinder method for the drainholes in hollowing step
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "clipper.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "MutablePriorityQueue.hpp"
|
||||
#include "Print.hpp"
|
||||
@@ -15,6 +16,30 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Orca: Some support entities may contain empty nested paths, which cannot be reordered safely.
|
||||
static bool extrusion_entity_has_endpoints(const ExtrusionEntity *entity)
|
||||
{
|
||||
auto paths_have_endpoints = [](const ExtrusionPaths &paths) {
|
||||
return !paths.empty() &&
|
||||
!paths.front().polyline.points.empty() &&
|
||||
!paths.back().polyline.points.empty();
|
||||
};
|
||||
|
||||
if (entity == nullptr)
|
||||
return false;
|
||||
if (const auto *collection = dynamic_cast<const ExtrusionEntityCollection *>(entity))
|
||||
return !collection->entities.empty() &&
|
||||
extrusion_entity_has_endpoints(collection->entities.front()) &&
|
||||
extrusion_entity_has_endpoints(collection->entities.back());
|
||||
if (const auto *path = dynamic_cast<const ExtrusionPath *>(entity))
|
||||
return !path->polyline.points.empty();
|
||||
if (const auto *multipath = dynamic_cast<const ExtrusionMultiPath *>(entity))
|
||||
return paths_have_endpoints(multipath->paths);
|
||||
if (const auto *loop = dynamic_cast<const ExtrusionLoop *>(entity))
|
||||
return paths_have_endpoints(loop->paths);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Naive implementation of the Traveling Salesman Problem, it works by always taking the next closest neighbor.
|
||||
// This implementation will always produce valid result even if some segments cannot reverse.
|
||||
template<typename EndPointType, typename KDTreeType, typename CouldReverseFunc>
|
||||
@@ -1035,8 +1060,10 @@ void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entitie
|
||||
|
||||
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
|
||||
{
|
||||
// this function crashes if there are empty elements in entities
|
||||
entities.erase(std::remove_if(entities.begin(), entities.end(), [](ExtrusionEntity *entity) { return static_cast<ExtrusionEntityCollection *>(entity)->empty(); }),
|
||||
// Orca: Reordering queries first_point() / last_point(); drop entities that cannot provide valid endpoints.
|
||||
entities.erase(std::remove_if(entities.begin(), entities.end(), [](ExtrusionEntity *entity) {
|
||||
return !extrusion_entity_has_endpoints(entity);
|
||||
}),
|
||||
entities.end());
|
||||
reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near));
|
||||
}
|
||||
|
||||
@@ -1733,8 +1733,8 @@ void generate_support_toolpaths(
|
||||
interface_as_base ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, interface_flow);
|
||||
}
|
||||
};
|
||||
const bool top_interfaces = config.support_interface_top_layers.value != 0;
|
||||
const bool bottom_interfaces = top_interfaces && config.support_interface_bottom_layers != 0;
|
||||
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);
|
||||
|
||||
@@ -1226,7 +1226,7 @@ namespace SupportMaterialInternal {
|
||||
// Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported.
|
||||
Polygons lower_grown_slices = expand(lower_layer_polygons,
|
||||
//FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width.
|
||||
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().wall_filament-1))),
|
||||
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().outer_wall_filament_id-1))),
|
||||
SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
// Collect perimeters of this layer.
|
||||
//FIXME split_at_first_point() could split a bridge mid-way
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
#include "../Flow.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
inline int number_of_support_interface_bottom_layers(const PrintObjectConfig& object_config)
|
||||
{
|
||||
return object_config.support_interface_bottom_layers.value < 0 ?
|
||||
object_config.support_interface_top_layers.value :
|
||||
object_config.support_interface_bottom_layers.value;
|
||||
}
|
||||
|
||||
struct SupportParameters {
|
||||
SupportParameters() = delete;
|
||||
SupportParameters(const PrintObject& object)
|
||||
@@ -26,8 +34,7 @@ struct SupportParameters {
|
||||
|
||||
{
|
||||
this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value);
|
||||
this->num_bottom_interface_layers = object_config.support_interface_bottom_layers < 0 ?
|
||||
num_top_interface_layers : object_config.support_interface_bottom_layers;
|
||||
this->num_bottom_interface_layers = 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
|
||||
|
||||
@@ -1332,7 +1332,12 @@ static void make_perimeter_and_infill(ExtrusionEntitiesPtr& dst, const ExPolygon
|
||||
dst = std::move(loops_entities);
|
||||
}
|
||||
}
|
||||
dst.erase(std::remove_if(dst.begin(), dst.end(), [](ExtrusionEntity *entity) { return static_cast<ExtrusionEntityCollection *>(entity)->empty(); }), dst.end());
|
||||
|
||||
// Orca: Some entities are direct paths, so check the type before testing for an empty collection.
|
||||
dst.erase(std::remove_if(dst.begin(), dst.end(), [](ExtrusionEntity *entity) {
|
||||
return entity != nullptr && entity->is_collection() && static_cast<ExtrusionEntityCollection *>(entity)->empty();
|
||||
}), dst.end());
|
||||
|
||||
if (infill_first) {
|
||||
// sort regions to reduce travel
|
||||
Points ordering_points;
|
||||
@@ -1610,80 +1615,71 @@ void TreeSupport::generate_toolpaths()
|
||||
filler_support->angle = Geometry::deg2rad(object_config.support_angle.value);
|
||||
|
||||
Polygons loops = to_polygons(poly);
|
||||
//ORCA: Group base per area as no_sort to keep outline->fill together.
|
||||
std::unique_ptr<ExtrusionEntityCollection> base_eec = std::make_unique<ExtrusionEntityCollection>();
|
||||
base_eec->no_sort = true;
|
||||
ExtrusionEntitiesPtr &base_dst = base_eec->entities;
|
||||
if (layer_id == 0) {
|
||||
float density = float(m_object_config->raft_first_layer_density.value * 0.01);
|
||||
fill_expolygons_with_sheath_generate_paths(ts_layer->support_fills.entities, loops, filler_support.get(), density, erSupportMaterial, flow,
|
||||
fill_expolygons_with_sheath_generate_paths(base_dst, loops, filler_support.get(), density, erSupportMaterial, flow,
|
||||
m_support_params, true, false);
|
||||
}
|
||||
else {
|
||||
//ORCA: Force base walls before infill to keep outline->fill order.
|
||||
if (need_infill && m_support_params.base_fill_pattern != ipLightning) {
|
||||
// allow infill-only mode if support is thick enough (so min_wall_count is 0);
|
||||
// otherwise must draw 1 wall
|
||||
// Don't need extra walls if we have infill. Extra walls may overlap with the infills.
|
||||
size_t min_wall_count = offset(poly, -scale_(support_spacing * 1.5)).empty() ? 1 : 0;
|
||||
make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::max(min_wall_count, wall_count), flow,
|
||||
erSupportMaterial, filler_support.get(), support_density);
|
||||
make_perimeter_and_infill(base_dst, poly, std::max(min_wall_count, wall_count), flow,
|
||||
erSupportMaterial, filler_support.get(), support_density, false);
|
||||
}
|
||||
else {
|
||||
SupportParameters support_params = m_support_params;
|
||||
if (area_group.need_extra_wall && object_config.tree_support_wall_count.value == 0)
|
||||
support_params.tree_branch_diameter_double_wall_area_scaled = 0.1;
|
||||
tree_supports_generate_paths(ts_layer->support_fills.entities, loops, flow, support_params);
|
||||
tree_supports_generate_paths(base_dst, loops, flow, support_params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_support_params.base_fill_pattern == ipLightning)
|
||||
{
|
||||
double print_z = ts_layer->print_z;
|
||||
if (printZ_to_lightninglayer.find(print_z) == printZ_to_lightninglayer.end())
|
||||
continue;
|
||||
//TODO:
|
||||
//1.the second parameter of convertToLines seems to decide how long the lightning should be trimmed from its root, so that the root wont overlap/detach the support contour.
|
||||
// whether current value works correctly remained to be tested
|
||||
//2.related to previous one, that lightning roots need to be trimed more when support has multiple walls
|
||||
//3.function connect_infill() and variable 'params' helps create connection pattern along contours between two lightning roots,
|
||||
// strengthen lightnings while it may make support harder. decide to enable it or not. if yes, proper values for params are remained to be tested
|
||||
auto& lightning_layer = generator->getTreesForLayer(printZ_to_lightninglayer[print_z]);
|
||||
|
||||
Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_support_params.first_layer_flow : support_flow;
|
||||
ExPolygons areas = offset_ex(ts_layer->base_areas, -flow.scaled_spacing());
|
||||
|
||||
for (auto& area : areas)
|
||||
{
|
||||
Polylines polylines = lightning_layer.convertToLines(to_polygons(area), 0);
|
||||
for (auto itr = polylines.begin(); itr != polylines.end();)
|
||||
{
|
||||
if (itr->length() < scale_(1.0))
|
||||
itr = polylines.erase(itr);
|
||||
else
|
||||
itr++;
|
||||
}
|
||||
Polylines opt_polylines;
|
||||
#if 1
|
||||
//this wont create connection patterns along contours
|
||||
append(opt_polylines, chain_polylines(std::move(polylines)));
|
||||
#else
|
||||
//this will create connection patterns along contours
|
||||
FillParams params;
|
||||
params.anchor_length = float(Fill::infill_anchor * 0.01 * flow.spacing());
|
||||
params.anchor_length_max = Fill::infill_anchor_max;
|
||||
params.anchor_length = std::min(params.anchor_length, params.anchor_length_max);
|
||||
Fill::connect_infill(std::move(polylines), area, opt_polylines, flow.spacing(), params);
|
||||
#endif
|
||||
extrusion_entities_append_paths(ts_layer->support_fills.entities, opt_polylines, erSupportMaterial,
|
||||
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
|
||||
|
||||
//ORCA: Emit lightning infill per base area to avoid interleaving across islands.
|
||||
if (m_support_params.base_fill_pattern == ipLightning) {
|
||||
double print_z = ts_layer->print_z;
|
||||
auto lightning_layer_mapping = printZ_to_lightninglayer.find(print_z);
|
||||
if (lightning_layer_mapping != printZ_to_lightninglayer.end()) {
|
||||
auto &lightning_layer = generator->getTreesForLayer(lightning_layer_mapping->second);
|
||||
ExPolygons areas;
|
||||
areas.emplace_back(poly);
|
||||
areas = offset_ex(areas, -flow.scaled_spacing());
|
||||
for (auto &area : areas) {
|
||||
Polylines polylines = lightning_layer.convertToLines(to_polygons(area), 0);
|
||||
for (auto itr = polylines.begin(); itr != polylines.end();) {
|
||||
if (itr->length() < scale_(1.0))
|
||||
itr = polylines.erase(itr);
|
||||
else
|
||||
itr++;
|
||||
}
|
||||
Polylines opt_polylines;
|
||||
append(opt_polylines, chain_polylines(std::move(polylines)));
|
||||
extrusion_entities_append_paths(base_dst, opt_polylines, erSupportMaterial,
|
||||
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
std::string name = debug_out_path("trees_polyline_%.2f.svg", ts_layer->print_z);
|
||||
BoundingBox bbox = get_extents(ts_layer->base_areas);
|
||||
SVG svg(name, bbox);
|
||||
if (svg.is_opened()) {
|
||||
svg.draw(ts_layer->base_areas, "blue");
|
||||
svg.draw(generator->Overhangs()[printZ_to_lightninglayer[print_z]], "red");
|
||||
for (auto &line : opt_polylines) svg.draw(line, "yellow");
|
||||
}
|
||||
std::string name = debug_out_path("trees_polyline_%.2f.svg", ts_layer->print_z);
|
||||
BoundingBox bbox = get_extents(ts_layer->base_areas);
|
||||
SVG svg(name, bbox);
|
||||
if (svg.is_opened()) {
|
||||
svg.draw(ts_layer->base_areas, "blue");
|
||||
svg.draw(generator->Overhangs()[lightning_layer_mapping->second], "red");
|
||||
for (auto &line : opt_polylines) svg.draw(line, "yellow");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//ORCA: Keep per-area base paths grouped for outline->fill preservation.
|
||||
if (!base_eec->empty())
|
||||
ts_layer->support_fills.entities.push_back(base_eec.release());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1696,13 +1692,6 @@ void TreeSupport::generate_toolpaths()
|
||||
);
|
||||
}
|
||||
|
||||
void deleteDirectoryContents(const std::filesystem::path& dir)
|
||||
{
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir))
|
||||
std::filesystem::remove_all(entry.path());
|
||||
}
|
||||
|
||||
|
||||
void TreeSupport::move_bounds_to_contact_nodes(std::vector<TreeSupport3D::SupportElements> &move_bounds,
|
||||
PrintObject &print_object,
|
||||
const TreeSupport3D::TreeSupportSettings &config)
|
||||
@@ -2024,8 +2013,8 @@ void TreeSupport::draw_circles()
|
||||
|
||||
// generate areas
|
||||
const coordf_t layer_height = config.layer_height.value;
|
||||
const size_t top_interface_layers = config.support_interface_top_layers.value;
|
||||
const size_t bottom_interface_layers = config.support_interface_bottom_layers.value < 0 ? top_interface_layers : config.support_interface_bottom_layers.value;
|
||||
const size_t top_interface_layers = m_support_params.num_top_interface_layers;
|
||||
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);
|
||||
const coordf_t line_width_scaled = scale_(line_width);
|
||||
@@ -2155,13 +2144,9 @@ void TreeSupport::draw_circles()
|
||||
if (!area.empty()) has_circle_node = true;
|
||||
if (node.need_extra_wall) need_extra_wall = true;
|
||||
|
||||
// Merge the overhang into the roof area so tree tips can still produce
|
||||
// a continuous support interface. Suppressing this for build-plate-only
|
||||
// support drops the roof polygons entirely in valid tree branches.
|
||||
// ORCA: Only keep top interface polygons that fully fit in the mm height cap.
|
||||
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 &&
|
||||
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON &&
|
||||
!node.is_sharp_tail) {
|
||||
// merge overhang to get a smoother interface surface
|
||||
// Do not merge when buildplate_only is on, because some underneath nodes may have been deleted.
|
||||
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !on_buildplate_only && !node.is_sharp_tail) {
|
||||
ExPolygons overhang_expanded;
|
||||
if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1)
|
||||
overhang_expanded.emplace_back(node.overhang);
|
||||
@@ -2207,16 +2192,6 @@ void TreeSupport::draw_circles()
|
||||
roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer)));
|
||||
roof_1st_layer = intersection_ex(roof_1st_layer, m_machine_border);
|
||||
|
||||
// Build-plate-only pruning can collapse the roof stack down to a single
|
||||
// printable layer. In that case we still need to emit an interface layer
|
||||
// instead of downgrading the last roof-adjacent layer to base support.
|
||||
if (on_buildplate_only && top_interface_layers > 0 && roof_areas.empty() && !roof_1st_layer.empty()) {
|
||||
append(roof_areas, roof_1st_layer);
|
||||
roof_1st_layer.clear();
|
||||
max_layers_above_roof = std::max(max_layers_above_roof, max_layers_above_roof1);
|
||||
max_layers_above_roof1 = 0;
|
||||
}
|
||||
|
||||
ExPolygons roofs; append(roofs, roof_1st_layer); append(roofs, roof_areas);append(roofs, roof_gap_areas);
|
||||
base_areas = diff_ex(base_areas, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roofs, get_extents(base_areas)));
|
||||
base_areas = intersection_ex(base_areas, m_machine_border);
|
||||
@@ -2374,6 +2349,15 @@ void TreeSupport::draw_circles()
|
||||
ts_layer->base_areas = std::move(expanded_base_areas);
|
||||
}
|
||||
|
||||
// Orca: Final tree base polygons may be too close above model surfaces.
|
||||
// Enforce bottom Z clearance for non-contact support layers as well.
|
||||
if (!ts_layer->base_areas.empty()) {
|
||||
const Polygons trimming = get_trim_support_regions(
|
||||
*m_object, ts_layer, 0., m_slicing_params.gap_object_support, 0);
|
||||
if (!trimming.empty())
|
||||
ts_layer->base_areas = diff_ex(ts_layer->base_areas, trimming);
|
||||
}
|
||||
|
||||
auto &area_groups = ts_layer->area_groups;
|
||||
|
||||
for (auto& expoly : ts_layer->base_areas) {
|
||||
@@ -2678,8 +2662,7 @@ void TreeSupport::drop_nodes()
|
||||
const size_t tip_layers = base_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle.
|
||||
const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution;
|
||||
const bool support_on_buildplate_only = config.support_on_build_plate_only.value;
|
||||
const size_t top_interface_layers = config.support_interface_top_layers.value;
|
||||
const size_t bottom_interface_layers = config.support_interface_bottom_layers.value < 0 ? top_interface_layers : config.support_interface_bottom_layers.value;
|
||||
const size_t bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||
SupportNode::diameter_angle_scale_factor = diameter_angle_scale_factor;
|
||||
float DO_NOT_MOVER_UNDER_MM = is_slim ? 0 : 5; // do not move contact points under 5mm
|
||||
|
||||
@@ -3576,7 +3559,14 @@ void TreeSupport::generate_contact_points()
|
||||
}
|
||||
|
||||
// add supports along contours
|
||||
libnest2d::placers::EdgeCache<ExPolygon> edge_cache(overhang);
|
||||
ExPolygon closed_overhang = overhang; // make a copy to add closing point for edge cache
|
||||
if (closed_overhang.contour.points.size() > 1)
|
||||
closed_overhang.contour.points.emplace_back(closed_overhang.contour.points.front());
|
||||
for (Polygon &hole : closed_overhang.holes)
|
||||
if (hole.points.size() > 1)
|
||||
hole.points.emplace_back(hole.points.front());
|
||||
|
||||
libnest2d::placers::EdgeCache<ExPolygon> edge_cache(closed_overhang);
|
||||
for (size_t i = 0; i < edge_cache.holeCount() + 1; i++) {
|
||||
double step = point_spread / (i == 0 ? edge_cache.circumference() : edge_cache.circumference(i - 1));
|
||||
double distance = 0;
|
||||
|
||||
@@ -124,6 +124,9 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
|
||||
{
|
||||
std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> grouped_meshes;
|
||||
|
||||
// Orca: Recompute static mesh-group state for this support generation pass.
|
||||
TreeSupportSettings::zero_top_z_gap = false;
|
||||
|
||||
//FIXME this is ugly, it does not belong here.
|
||||
for (size_t object_id : print_object_ids) {
|
||||
const PrintObject &print_object = *print.get_object(object_id);
|
||||
@@ -1194,7 +1197,7 @@ void sample_overhang_area(
|
||||
point_count += poly.size();
|
||||
const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), coord_t(total_length(overhang_area) / connect_length)));
|
||||
if (point_count <= min_support_points) {
|
||||
// add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the the support line width.
|
||||
// add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the support line width.
|
||||
// I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them
|
||||
// will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60 degrees so there is a fallback,
|
||||
// as some support is better than none.
|
||||
@@ -1375,7 +1378,7 @@ static void generate_initial_areas(
|
||||
}
|
||||
#if 0
|
||||
// If the xy distance overrides the z distance, some support needs to be inserted further down.
|
||||
//=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the the setting description, it would not produce reasonable results. )
|
||||
//=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the setting description, it would not produce reasonable results. )
|
||||
if (! min_xy_dist) {
|
||||
LineInformations overhang_lines;
|
||||
{
|
||||
@@ -1600,13 +1603,19 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di
|
||||
if (settings.increase_radius)
|
||||
current_elem.effective_radius_height += 1;
|
||||
coord_t radius = support_element_collision_radius(config, current_elem);
|
||||
|
||||
const auto _tiny_area_threshold = tiny_area_threshold();
|
||||
if (settings.move) {
|
||||
increased = relevant_offset;
|
||||
if (overspeed > 0) {
|
||||
const coord_t safe_movement_distance =
|
||||
coord_t safe_movement_distance =
|
||||
(current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) +
|
||||
(std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0);
|
||||
// Orca:
|
||||
// safe_movement_distance is used as the safe_offset_inc() step, so keep it non-zero
|
||||
// to preserve branch movement with zero-clearance support settings.
|
||||
if (safe_movement_distance == 0)
|
||||
safe_movement_distance = scaled<coord_t>(0.1);
|
||||
// The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later.
|
||||
// The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later.
|
||||
increased = safe_offset_inc(increased, overspeed, volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist),
|
||||
@@ -1817,9 +1826,15 @@ static void increase_areas_one_layer(
|
||||
* layer z-1:dddddxxxxxxxxxx
|
||||
* For more detailed visualisation see calculateWallRestrictions
|
||||
*/
|
||||
const coord_t safe_movement_distance =
|
||||
coord_t safe_movement_distance =
|
||||
(elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) +
|
||||
(std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0);
|
||||
|
||||
// safe_movement_distance is used as a divisor and as the safe_offset_inc() step,
|
||||
// so keep it non-zero to avoid division by zero and preserve branch movement.
|
||||
if (safe_movement_distance == 0)
|
||||
safe_movement_distance = scaled<coord_t>(0.1);
|
||||
|
||||
if (ceiled_parent_radius == volumes.ceilRadius(projected_radius_increased, parent.state.use_min_xy_dist) ||
|
||||
projected_radius_increased < config.increase_radius_until_radius)
|
||||
// If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of the slower moving wall
|
||||
@@ -2902,6 +2917,7 @@ static std::pair<float, float> extrude_branch(
|
||||
const TreeSupportSettings &config,
|
||||
const SlicingParameters &slicing_params,
|
||||
const std::vector<SupportElements> &move_bounds,
|
||||
bool has_root,
|
||||
indexed_triangle_set &result)
|
||||
{
|
||||
Vec3d p1, p2, p3;
|
||||
@@ -2923,24 +2939,38 @@ static std::pair<float, float> extrude_branch(
|
||||
v1 = (p2 - p1).normalized();
|
||||
if (ipath == 1) {
|
||||
nprev = v1;
|
||||
// Extrude the bottom half sphere.
|
||||
float radius = unscaled<float>(support_element_radius(config, prev));
|
||||
float angle_step = 2. * acos(1. - eps / radius);
|
||||
auto nsteps = int(ceil(M_PI / (2. * angle_step)));
|
||||
angle_step = M_PI / (2. * nsteps);
|
||||
int ifan = int(result.vertices.size());
|
||||
result.vertices.emplace_back((p1 - nprev * radius).cast<float>());
|
||||
zmin = result.vertices.back().z();
|
||||
float angle = angle_step;
|
||||
for (int i = 1; i < nsteps; ++ i, angle += angle_step) {
|
||||
std::pair<int, int> strip = discretize_circle((p1 - nprev * radius * cos(angle)).cast<float>(), nprev.cast<float>(), radius * sin(angle), eps, result.vertices);
|
||||
if (i == 1)
|
||||
triangulate_fan<false>(result, ifan, strip.first, strip.second);
|
||||
else
|
||||
triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second);
|
||||
// sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun);
|
||||
// its_write_obj(result, fname);
|
||||
prev_strip = strip;
|
||||
if (has_root && prev.state.layer_idx == 0) {
|
||||
// Orca: Buildplate roots need a flat foot. A rounded cap can extend far
|
||||
// below the bed and make the first layer slice cut unrelated trunk geometry.
|
||||
const Vec3f normal(0.f, 0.f, 1.f);
|
||||
const Vec3f bottom_center(float(p1.x()), float(p1.y()), 0.f);
|
||||
const Vec3f top_center(float(p1.x()), float(p1.y()), float(p1.z()));
|
||||
int ifan = int(result.vertices.size());
|
||||
result.vertices.emplace_back(bottom_center);
|
||||
std::pair<int, int> bottom_strip = discretize_circle(bottom_center, normal, radius, eps, result.vertices);
|
||||
triangulate_fan<false>(result, ifan, bottom_strip.first, bottom_strip.second);
|
||||
prev_strip = discretize_circle(top_center, normal, radius, eps, result.vertices);
|
||||
triangulate_strip(result, bottom_strip.first, bottom_strip.second, prev_strip.first, prev_strip.second);
|
||||
zmin = 0.f;
|
||||
} else {
|
||||
// Extrude the bottom half sphere.
|
||||
float angle_step = 2. * acos(1. - eps / radius);
|
||||
auto nsteps = int(ceil(M_PI / (2. * angle_step)));
|
||||
angle_step = M_PI / (2. * nsteps);
|
||||
int ifan = int(result.vertices.size());
|
||||
result.vertices.emplace_back((p1 - nprev * radius).cast<float>());
|
||||
zmin = result.vertices.back().z();
|
||||
float angle = angle_step;
|
||||
for (int i = 1; i < nsteps; ++ i, angle += angle_step) {
|
||||
std::pair<int, int> strip = discretize_circle((p1 - nprev * radius * cos(angle)).cast<float>(), nprev.cast<float>(), radius * sin(angle), eps, result.vertices);
|
||||
if (i == 1)
|
||||
triangulate_fan<false>(result, ifan, strip.first, strip.second);
|
||||
else
|
||||
triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second);
|
||||
|
||||
prev_strip = strip;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ipath + 1 == path.size()) {
|
||||
@@ -3123,13 +3153,60 @@ static void organic_smooth_branches_avoid_collisions(
|
||||
static constexpr const double max_nudge_collision_avoidance = 0.5;
|
||||
static constexpr const double max_nudge_smoothing = 0.2;
|
||||
static constexpr const size_t num_iter = 100; // 1000;
|
||||
|
||||
// Orca:
|
||||
// Collision and Laplacian smoothing run iteratively; keep each candidate reachable from linked upper/lower layers to avoid accumulated drift.
|
||||
auto limit_candidate_to_linked_layers = [&collision_spheres, &linear_data_layers, &config](const size_t collision_sphere_id, Vec2d candidate) {
|
||||
auto constrain_to_anchor = [](Vec2d candidate, const Vec2d ¤t_pos, const Vec2d &anchor, double allowed_shift) {
|
||||
const Vec2d delta = candidate - anchor;
|
||||
const double candidate_dist = delta.norm();
|
||||
const double current_dist = (current_pos - anchor).norm();
|
||||
allowed_shift = std::max(allowed_shift, current_dist);
|
||||
return candidate_dist > allowed_shift && candidate_dist > EPSILON ?
|
||||
anchor + delta * (allowed_shift / candidate_dist) :
|
||||
candidate;
|
||||
};
|
||||
|
||||
const CollisionSphere &sphere = collision_spheres[collision_sphere_id];
|
||||
const LayerIndex layer_idx = sphere.element.state.layer_idx;
|
||||
const Vec2d current_pos = to_2d(sphere.position).cast<double>();
|
||||
const double current_radius = double(support_element_radius(config, sphere.element));
|
||||
const double maximum_move_distance_slow = double(config.maximum_move_distance_slow);
|
||||
|
||||
if (sphere.element_below_id != -1 && layer_idx > 0) {
|
||||
const size_t lower_id = linear_data_layers[layer_idx - 1] + size_t(sphere.element_below_id);
|
||||
if (lower_id < collision_spheres.size()) {
|
||||
const CollisionSphere &lower = collision_spheres[lower_id];
|
||||
const double lower_radius = double(support_element_radius(config, lower.element));
|
||||
const double allowed_shift = unscaled<double>(std::max(0., lower_radius - current_radius) + maximum_move_distance_slow);
|
||||
candidate = constrain_to_anchor(candidate, current_pos, to_2d(lower.prev_position).cast<double>(), allowed_shift);
|
||||
}
|
||||
}
|
||||
|
||||
const LayerIndex upper_layer_idx = layer_idx + 1;
|
||||
if (!sphere.element.parents.empty() && upper_layer_idx < LayerIndex(linear_data_layers.size())) {
|
||||
const size_t upper_offset = linear_data_layers[upper_layer_idx];
|
||||
for (int32_t parent_idx : sphere.element.parents) {
|
||||
const size_t upper_id = upper_offset + size_t(parent_idx);
|
||||
if (upper_id >= collision_spheres.size())
|
||||
continue;
|
||||
const CollisionSphere &upper = collision_spheres[upper_id];
|
||||
const double upper_radius = double(support_element_radius(config, upper.element));
|
||||
const double allowed_shift = unscaled<double>(std::max(0., current_radius - upper_radius) + maximum_move_distance_slow);
|
||||
candidate = constrain_to_anchor(candidate, current_pos, to_2d(upper.prev_position).cast<double>(), allowed_shift);
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
};
|
||||
|
||||
for (size_t iter = 0; iter < num_iter; ++ iter) {
|
||||
// Back up prev position before Laplacian smoothing.
|
||||
for (CollisionSphere &collision_sphere : collision_spheres)
|
||||
collision_sphere.prev_position = collision_sphere.position;
|
||||
std::atomic<size_t> num_moved{ 0 };
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, collision_spheres.size()),
|
||||
[&collision_spheres, &layer_collision_cache, &slicing_params, &config, &linear_data_layers, &num_moved, &throw_on_cancel](const tbb::blocked_range<size_t> range) {
|
||||
[&collision_spheres, &layer_collision_cache, &slicing_params, &config, &linear_data_layers, &num_moved, &throw_on_cancel, &limit_candidate_to_linked_layers](const tbb::blocked_range<size_t> range) {
|
||||
for (size_t collision_sphere_id = range.begin(); collision_sphere_id < range.end(); ++ collision_sphere_id)
|
||||
if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) {
|
||||
// Calculate collision of multiple 2D layers against a collision sphere.
|
||||
@@ -3158,10 +3235,12 @@ static void organic_smooth_branches_avoid_collisions(
|
||||
if (collision_sphere.last_collision_depth > EPSILON)
|
||||
// a little bit of hysteresis to detect end of
|
||||
++ num_moved;
|
||||
// Shift by maximum 2mm.
|
||||
// Limit collision-avoidance nudge per iteration.
|
||||
double nudge_dist = std::min(std::max(0., collision_sphere.last_collision_depth + collision_extra_gap), max_nudge_collision_avoidance);
|
||||
Vec2d nudge_vector = (to_2d(collision_sphere.position) - to_2d(collision_sphere.last_collision)).cast<double>().normalized() * nudge_dist;
|
||||
collision_sphere.position.head<2>() += (nudge_vector * nudge_dist).cast<float>();
|
||||
Vec2d candidate = to_2d(collision_sphere.position).cast<double>() + nudge_vector * nudge_dist;
|
||||
candidate = limit_candidate_to_linked_layers(collision_sphere_id, candidate);
|
||||
collision_sphere.position.head<2>() = candidate.cast<float>();
|
||||
}
|
||||
// Laplacian smoothing
|
||||
Vec2d avg{ 0, 0 };
|
||||
@@ -3185,9 +3264,13 @@ static void organic_smooth_branches_avoid_collisions(
|
||||
Vec2d new_pos = (1. - smoothing_factor) * old_pos + smoothing_factor * avg;
|
||||
Vec2d shift = new_pos - old_pos;
|
||||
double nudge_dist_max = shift.norm();
|
||||
// Shift by maximum 1mm, less than the collision avoidance factor.
|
||||
// Limit Laplacian smoothing nudge per iteration.
|
||||
double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_smoothing);
|
||||
collision_sphere.position.head<2>() += (shift.normalized() * nudge_dist).cast<float>();
|
||||
if (nudge_dist > 0.) {
|
||||
Vec2d candidate = old_pos + shift * (nudge_dist / nudge_dist_max);
|
||||
candidate = limit_candidate_to_linked_layers(collision_sphere_id, candidate);
|
||||
collision_sphere.position.head<2>() = candidate.cast<float>();
|
||||
}
|
||||
|
||||
throw_on_cancel();
|
||||
}
|
||||
@@ -3454,6 +3537,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
|
||||
|
||||
// value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas
|
||||
std::vector<SupportElements> move_bounds(num_support_layers);
|
||||
|
||||
// ### Place tips of the support tree
|
||||
for (size_t mesh_idx : processing.second)
|
||||
generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs,
|
||||
@@ -3764,6 +3848,7 @@ void organic_draw_branches(
|
||||
// ++ ielement;
|
||||
}
|
||||
}
|
||||
|
||||
const SlicingParameters &slicing_params = print_object.slicing_parameters();
|
||||
MeshSlicingParams mesh_slicing_params;
|
||||
mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive;
|
||||
@@ -3778,7 +3863,7 @@ void organic_draw_branches(
|
||||
for (const Branch &branch : tree.branches) {
|
||||
// Triangulate the tube.
|
||||
partial_mesh.clear();
|
||||
std::pair<float, float> zspan = extrude_branch(branch.path, config, slicing_params, move_bounds, partial_mesh);
|
||||
std::pair<float, float> zspan = extrude_branch(branch.path, config, slicing_params, move_bounds, branch.has_root, partial_mesh);
|
||||
LayerIndex layer_begin = branch.has_root ?
|
||||
branch.path.front()->state.layer_idx :
|
||||
std::min(branch.path.front()->state.layer_idx, layer_idx_ceil(slicing_params, config, zspan.first));
|
||||
@@ -3945,19 +4030,7 @@ void organic_draw_branches(
|
||||
}
|
||||
// ORCA: bottom contacts provide the footprint; interface layers are built later.
|
||||
|
||||
#if 0
|
||||
//FIXME branch.has_tip seems to not be reliable.
|
||||
if (branch.has_tip && interface_placer.support_parameters.has_top_contacts)
|
||||
// Add top slices to top contacts / interfaces / base interfaces.
|
||||
for (int i = int(branch.path.size()) - 1; i >= 0; -- i) {
|
||||
const SupportElement &el = *branch.path[i];
|
||||
if (el.state.missing_roof_layers == 0)
|
||||
break;
|
||||
//FIXME Move or not?
|
||||
interface_placer.add_roof(std::move(slices[int(slices.size()) - i - 1]), el.state.layer_idx,
|
||||
interface_placer.support_parameters.num_top_interface_layers + 1 - el.state.missing_roof_layers);
|
||||
}
|
||||
#endif
|
||||
recover_pending_branch_roofs(interface_placer, branch.path, layer_begin, slices);
|
||||
|
||||
while (! slices.empty() && slices.back().empty()) {
|
||||
slices.pop_back();
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "../Polygon.hpp"
|
||||
#include "SupportCommon.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
|
||||
namespace Slic3r
|
||||
@@ -60,12 +61,10 @@ struct TreeSupportMeshGroupSettings {
|
||||
this->support_angle = 0.5 * M_PI - std::clamp<double>((config.support_threshold_angle + 1) * M_PI / 180., 0., 0.5 * M_PI);
|
||||
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();
|
||||
//FIXME add it to SlicingParameters and reuse in both tree and normal supports?
|
||||
this->support_bottom_enable = config.support_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value != 0;
|
||||
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_height = this->support_bottom_enable ?
|
||||
(config.support_interface_bottom_layers.value > 0 ?
|
||||
config.support_interface_bottom_layers.value :
|
||||
config.support_interface_top_layers.value) * this->layer_height :
|
||||
bottom_interface_layers * this->layer_height :
|
||||
0;
|
||||
this->support_material_buildplate_only = config.support_on_build_plate_only;
|
||||
this->support_xy_distance = scaled<coord_t>(config.support_object_xy_distance.value);
|
||||
@@ -76,8 +75,8 @@ struct TreeSupportMeshGroupSettings {
|
||||
this->support_bottom_distance = scaled<coord_t>(slicing_params.gap_object_support);
|
||||
this->support_roof_enable = config.support_interface_top_layers.value > 0;
|
||||
this->support_roof_layers = config.support_interface_top_layers.value;
|
||||
this->support_floor_enable = config.support_interface_bottom_layers.value > 0;
|
||||
this->support_floor_layers = config.support_interface_bottom_layers.value;
|
||||
this->support_floor_enable = bottom_interface_layers > 0;
|
||||
this->support_floor_layers = bottom_interface_layers;
|
||||
this->support_roof_pattern = config.support_interface_pattern;
|
||||
this->support_pattern = config.support_base_pattern;
|
||||
this->support_line_spacing = scaled<coord_t>(config.support_base_pattern_spacing.value);
|
||||
@@ -614,19 +613,35 @@ inline double layer_z(const SlicingParameters &slicing_params, const TreeSupport
|
||||
slicing_params.object_print_z_min + slicing_params.first_object_layer_height + (layer_idx - config.raft_layers.size()) * slicing_params.layer_height :
|
||||
config.raft_layers[layer_idx];
|
||||
}
|
||||
|
||||
inline double first_object_support_layer_z(const SlicingParameters &slicing_params)
|
||||
{
|
||||
return slicing_params.object_print_z_min + slicing_params.first_object_layer_height;
|
||||
}
|
||||
|
||||
// Orca: Reverse layer_z() for support layers below the first object layer.
|
||||
// config.raft_layers may include raft/contact/intermediate support Zs, so do not collapse them to the first object support layer.
|
||||
// Lowest collision layer
|
||||
inline LayerIndex layer_idx_ceil(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z)
|
||||
{
|
||||
return
|
||||
LayerIndex(config.raft_layers.size()) +
|
||||
std::max<LayerIndex>(0, ceil((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height));
|
||||
const double first_object_z = first_object_support_layer_z(slicing_params);
|
||||
if (!config.raft_layers.empty() && z < first_object_z - EPSILON) {
|
||||
auto it = std::lower_bound(config.raft_layers.begin(), config.raft_layers.end(), z - EPSILON);
|
||||
return LayerIndex(it == config.raft_layers.end() ? config.raft_layers.size() : std::distance(config.raft_layers.begin(), it));
|
||||
}
|
||||
return LayerIndex(config.raft_layers.size()) +
|
||||
std::max<LayerIndex>(0, LayerIndex(std::ceil((z - first_object_z) / slicing_params.layer_height)));
|
||||
}
|
||||
// Highest collision layer
|
||||
inline LayerIndex layer_idx_floor(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z)
|
||||
{
|
||||
return
|
||||
LayerIndex(config.raft_layers.size()) +
|
||||
std::max<LayerIndex>(0, floor((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height));
|
||||
const double first_object_z = first_object_support_layer_z(slicing_params);
|
||||
if (!config.raft_layers.empty() && z < first_object_z - EPSILON) {
|
||||
auto it = std::upper_bound(config.raft_layers.begin(), config.raft_layers.end(), z + EPSILON);
|
||||
return LayerIndex(it == config.raft_layers.begin() ? 0 : std::distance(config.raft_layers.begin(), it) - 1);
|
||||
}
|
||||
return LayerIndex(config.raft_layers.size()) +
|
||||
std::max<LayerIndex>(0, LayerIndex(std::floor((z - first_object_z) / slicing_params.layer_height)));
|
||||
}
|
||||
|
||||
inline SupportGeneratorLayer& layer_initialize(
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
|
||||
// Enable rendering of objects using environment map
|
||||
#define ENABLE_ENVIRONMENT_MAP 0
|
||||
// Enable smoothing of objects normals
|
||||
#define ENABLE_SMOOTH_NORMALS 0
|
||||
|
||||
// Enable rendering markers for options in preview as fixed screen size points
|
||||
#define ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS 1
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ namespace boost { namespace filesystem { class directory_entry; }}
|
||||
namespace Slic3r {
|
||||
|
||||
extern void set_logging_level(unsigned int level);
|
||||
extern void set_logging_file(const std::string &file);
|
||||
extern unsigned int level_string_to_boost(std::string level);
|
||||
extern std::string get_string_logging_level(unsigned level);
|
||||
extern unsigned get_logging_level();
|
||||
@@ -193,6 +194,7 @@ std::string debug_out_path(const char *name, ...);
|
||||
// smaller level means less log. level=5 means saving all logs.
|
||||
void set_log_path_and_level(const std::string& file, unsigned int level);
|
||||
void flush_logs();
|
||||
boost::filesystem::path get_log_file_name();
|
||||
|
||||
// A special type for strings encoded in the local Windows 8-bit code page.
|
||||
// This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded.
|
||||
|
||||
@@ -92,7 +92,7 @@ std::string MZ_Archive::get_errorstr(mz_zip_error mz_err)
|
||||
case MZ_ZIP_TOO_MANY_FILES:
|
||||
return L("too many files");
|
||||
case MZ_ZIP_FILE_TOO_LARGE:
|
||||
return L("file too large");
|
||||
return L("File too large");
|
||||
case MZ_ZIP_UNSUPPORTED_METHOD:
|
||||
return L("unsupported method");
|
||||
case MZ_ZIP_UNSUPPORTED_ENCRYPTION:
|
||||
@@ -106,7 +106,7 @@ std::string MZ_Archive::get_errorstr(mz_zip_error mz_err)
|
||||
case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED:
|
||||
return L("invalid header or corrupted");
|
||||
case MZ_ZIP_UNSUPPORTED_MULTIDISK:
|
||||
return L("unsupported multidisk");
|
||||
return L("Saving to RAID is not supported.");
|
||||
case MZ_ZIP_DECOMPRESSION_FAILED:
|
||||
return L("decompression failed");
|
||||
case MZ_ZIP_COMPRESSION_FAILED:
|
||||
@@ -138,13 +138,13 @@ std::string MZ_Archive::get_errorstr(mz_zip_error mz_err)
|
||||
case MZ_ZIP_INVALID_FILENAME:
|
||||
return L("invalid filename");
|
||||
case MZ_ZIP_BUF_TOO_SMALL:
|
||||
return L("buffer too small");
|
||||
return L("Buffer too small");
|
||||
case MZ_ZIP_INTERNAL_ERROR:
|
||||
return L("internal error");
|
||||
case MZ_ZIP_FILE_NOT_FOUND:
|
||||
return L("file not found");
|
||||
case MZ_ZIP_ARCHIVE_TOO_LARGE:
|
||||
return L("archive too large");
|
||||
return L("Archive too large");
|
||||
case MZ_ZIP_VALIDATION_FAILED:
|
||||
return L("validation failed");
|
||||
case MZ_ZIP_WRITE_CALLBACK_FAILED:
|
||||
|
||||
@@ -129,6 +129,11 @@ void set_logging_level(unsigned int level)
|
||||
);
|
||||
}
|
||||
|
||||
void set_logging_file(const std::string &file)
|
||||
{
|
||||
boost::log::add_file_log(file);
|
||||
}
|
||||
|
||||
unsigned int level_string_to_boost(std::string level)
|
||||
{
|
||||
std::map<std::string, int> Control_Param;
|
||||
@@ -387,6 +392,14 @@ void flush_logs()
|
||||
return;
|
||||
}
|
||||
|
||||
// ORCA
|
||||
boost::filesystem::path get_log_file_name()
|
||||
{
|
||||
if (g_log_sink)
|
||||
return g_log_sink->locked_backend()->get_current_file_name();
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// The following helpers are borrowed from the LLVM project https://github.com/llvm
|
||||
namespace WindowsSupport
|
||||
|
||||
@@ -56,6 +56,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/BitmapComboBox.hpp
|
||||
GUI/BonjourDialog.cpp
|
||||
GUI/BonjourDialog.hpp
|
||||
GUI/CrealityDiscoveryDialog.cpp
|
||||
GUI/CrealityDiscoveryDialog.hpp
|
||||
GUI/calib_dlg.cpp
|
||||
GUI/calib_dlg.hpp
|
||||
GUI/Calibration.cpp
|
||||
@@ -466,6 +468,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/TextLines.hpp
|
||||
GUI/TickCode.cpp
|
||||
GUI/TickCode.hpp
|
||||
GUI/TroubleshootDialog.cpp
|
||||
GUI/TroubleshootDialog.hpp
|
||||
GUI/UnsavedChangesDialog.cpp
|
||||
GUI/UnsavedChangesDialog.hpp
|
||||
GUI/UpdateDialogs.cpp
|
||||
@@ -580,6 +584,10 @@ set(SLIC3R_GUI_SOURCES
|
||||
Utils/ColorSpaceConvert.hpp
|
||||
Utils/CrealityPrint.cpp
|
||||
Utils/CrealityPrint.hpp
|
||||
Utils/CrealityPrintAgent.cpp
|
||||
Utils/CrealityPrintAgent.hpp
|
||||
Utils/CrealityHostDiscovery.cpp
|
||||
Utils/CrealityHostDiscovery.hpp
|
||||
Utils/Duet.cpp
|
||||
Utils/Duet.hpp
|
||||
Utils/ElegooLink.cpp
|
||||
@@ -626,6 +634,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
Utils/SnapmakerPrinterAgent.hpp
|
||||
Utils/MoonrakerPrinterAgent.cpp
|
||||
Utils/MoonrakerPrinterAgent.hpp
|
||||
Utils/Moonraker.cpp
|
||||
Utils/Moonraker.hpp
|
||||
Utils/BBLCloudServiceAgent.cpp
|
||||
Utils/BBLCloudServiceAgent.hpp
|
||||
Utils/BBLPrinterAgent.cpp
|
||||
@@ -659,6 +669,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
Utils/UndoRedo.cpp
|
||||
Utils/UndoRedo.hpp
|
||||
Utils/WebSocketClient.hpp
|
||||
Utils/3DPrinterOS.hpp
|
||||
Utils/3DPrinterOS.cpp
|
||||
Utils/WxFontUtils.cpp
|
||||
Utils/WxFontUtils.hpp
|
||||
Utils/FileTransferUtils.cpp
|
||||
@@ -752,7 +764,7 @@ else()
|
||||
set(_opengl_link_lib OpenGL::GL)
|
||||
endif()
|
||||
|
||||
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise)
|
||||
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise)
|
||||
|
||||
|
||||
if (MSVC)
|
||||
|
||||
+48
-20
@@ -758,13 +758,9 @@ int GLVolumeCollection::load_object_volume(
|
||||
GLVolume& v = *this->volumes.back();
|
||||
v.set_color(color_from_model_volume(*model_volume));
|
||||
v.name = model_volume->name;
|
||||
|
||||
#if ENABLE_SMOOTH_NORMALS
|
||||
v.model.init_from(mesh, true);
|
||||
#else
|
||||
|
||||
v.model.init_from(*mesh);
|
||||
if (need_raycaster) { v.mesh_raycaster = std::make_unique<GUI::MeshRaycaster>(mesh); }
|
||||
#endif // ENABLE_SMOOTH_NORMALS
|
||||
v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx);
|
||||
|
||||
if (model_volume->is_model_part())
|
||||
@@ -815,13 +811,9 @@ void GLVolumeCollection::load_object_auxiliary(
|
||||
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
|
||||
this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
|
||||
GLVolume& v = *this->volumes.back();
|
||||
#if ENABLE_SMOOTH_NORMALS
|
||||
v.model.init_from(mesh, true);
|
||||
#else
|
||||
v.model.init_from(mesh);
|
||||
v.model.set_color((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR);
|
||||
v.mesh_raycaster = std::make_unique<GUI::MeshRaycaster>(std::make_shared<const TriangleMesh>(mesh));
|
||||
#endif // ENABLE_SMOOTH_NORMALS
|
||||
v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first);
|
||||
v.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id);
|
||||
// Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance.
|
||||
@@ -978,12 +970,51 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo
|
||||
return list;
|
||||
}
|
||||
|
||||
int GLVolumeCollection::get_selection_support_threshold_angle(bool &enable_support) const
|
||||
// ORCA: Compute slope.normal_z for 3D overhang highlight directly from support settings.
|
||||
// If support_threshold_angle is 0, use tree fallback angle (30 deg) for tree supports,
|
||||
// and derive an equivalent angle from threshold overlap for normal supports.
|
||||
float GLVolumeCollection::get_selection_support_normal_z() const
|
||||
{
|
||||
const DynamicPrintConfig& glb_cfg = GUI::wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
enable_support = glb_cfg.opt_bool("enable_support");
|
||||
int support_threshold_angle = glb_cfg.opt_int("support_threshold_angle");
|
||||
return support_threshold_angle ;
|
||||
const DynamicPrintConfig& glb_cfg = GUI::wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
const auto& full_cfg = GUI::wxGetApp().preset_bundle->full_config();
|
||||
const auto support_type = glb_cfg.opt_enum<SupportType>("support_type");
|
||||
const int support_threshold_angle = glb_cfg.opt_int("support_threshold_angle");
|
||||
double angle_rad;
|
||||
|
||||
if (support_threshold_angle > 0) {
|
||||
// Match support generation: explicit threshold angles are treated as inclusive.
|
||||
const int effective_support_threshold_angle = std::min(support_threshold_angle + 1, 89);
|
||||
angle_rad = Geometry::deg2rad(static_cast<double>(effective_support_threshold_angle));
|
||||
} else if (is_tree(support_type)) {
|
||||
angle_rad = Geometry::deg2rad(30.0); // fallback value for tree supports
|
||||
} else { // For normal supports, if the angle is set to 0, calculate normal_z from overlap.
|
||||
const double layer_height = full_cfg.opt_float("layer_height");
|
||||
const auto* nozzle_diameter_opt = full_cfg.option<ConfigOptionFloats>("nozzle_diameter");
|
||||
const int wall_filament_id = full_cfg.opt_int("outer_wall_filament_id");
|
||||
const size_t nozzle_count = nozzle_diameter_opt->values.size();
|
||||
const size_t wall_extruder_idx = (wall_filament_id > 0 && wall_filament_id <= static_cast<int>(nozzle_count))
|
||||
? static_cast<size_t>(wall_filament_id - 1)
|
||||
: 0; // Invalid extruder index falls back to extruder 1.
|
||||
|
||||
// Use wall extruder's nozzle diameter for better estimation of external perimeter width,
|
||||
// which is more relevant to overhang printing than the default nozzle diameter.
|
||||
const double nozzle_diameter = nozzle_diameter_opt->values[wall_extruder_idx];
|
||||
|
||||
double external_perimeter_width = full_cfg.get_abs_value("outer_wall_line_width", nozzle_diameter);
|
||||
if (external_perimeter_width <= 0.0) {
|
||||
external_perimeter_width = full_cfg.get_abs_value("line_width", nozzle_diameter);
|
||||
|
||||
if (external_perimeter_width <= 0.0)
|
||||
external_perimeter_width = nozzle_diameter;
|
||||
}
|
||||
|
||||
const double overlap_width = full_cfg.get_abs_value("support_threshold_overlap", external_perimeter_width);
|
||||
const double lower_layer_offset = std::max(0.0, external_perimeter_width - overlap_width);
|
||||
|
||||
angle_rad = lower_layer_offset <= EPSILON ? Geometry::deg2rad(89.0) : std::atan(layer_height / lower_layer_offset);
|
||||
}
|
||||
|
||||
return static_cast<float>(-std::cos(std::clamp(angle_rad, 0.0, Geometry::deg2rad(89.0))));
|
||||
}
|
||||
|
||||
//BBS: add outline drawing logic
|
||||
@@ -1019,6 +1050,8 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
|
||||
if (disable_cullface)
|
||||
glsafe(::glDisable(GL_CULL_FACE));
|
||||
|
||||
const float support_normal_z = get_selection_support_normal_z();
|
||||
|
||||
for (GLVolumeWithIdAndZ& volume : to_render) {
|
||||
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
||||
if (type == ERenderType::Transparent) {
|
||||
@@ -1075,16 +1108,11 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
|
||||
//use -1 ad a invalid type
|
||||
shader->set_uniform("print_volume.type", -1);
|
||||
}
|
||||
|
||||
bool enable_support;
|
||||
int support_threshold_angle = get_selection_support_threshold_angle(enable_support);
|
||||
|
||||
float normal_z = -::cos(Geometry::deg2rad((float) support_threshold_angle));
|
||||
|
||||
shader->set_uniform("volume_world_matrix", volume.first->world_matrix());
|
||||
shader->set_uniform("slope.actived", m_slope.isGlobalActive && !volume.first->is_modifier && !volume.first->is_wipe_tower);
|
||||
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
|
||||
shader->set_uniform("slope.normal_z", normal_z);
|
||||
shader->set_uniform("slope.normal_z", support_normal_z);
|
||||
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
unsigned int environment_texture_id = GUI::wxGetApp().plater()->get_environment_texture_id();
|
||||
|
||||
@@ -488,7 +488,7 @@ public:
|
||||
GLVolume* new_toolpath_volume(const ColorRGBA& rgba);
|
||||
GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba);
|
||||
|
||||
int get_selection_support_threshold_angle(bool&) const;
|
||||
float get_selection_support_normal_z() const;
|
||||
// Render the volumes by OpenGL.
|
||||
//BBS: add outline drawing logic
|
||||
void render(ERenderType type,
|
||||
|
||||
@@ -863,7 +863,15 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
|
||||
std::set<std::string> filament_id_set;
|
||||
PresetBundle * preset_bundle = wxGetApp().preset_bundle;
|
||||
std::ostringstream stream;
|
||||
stream << std::fixed << std::setprecision(1) << obj->GetExtderSystem()->GetNozzleDiameter(0);
|
||||
// Defensive: this dialog is opened only from StatusPanel (BBL-only) today, so the fallback fires
|
||||
// only during the brief BBL startup window before firmware reports nozzle info. Without this,
|
||||
// the "0.0" lookup string returns an empty set and the filament dropdown goes blank.
|
||||
float machine_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0);
|
||||
if (machine_diameter == 0.0f && preset_bundle) {
|
||||
const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("nozzle_diameter");
|
||||
if (opt) machine_diameter = static_cast<const ConfigOptionFloats *>(opt)->values[0];
|
||||
}
|
||||
stream << std::fixed << std::setprecision(1) << machine_diameter;
|
||||
std::string nozzle_diameter_str = stream.str();
|
||||
std::set<std::string> printer_names = preset_bundle->get_printer_names_by_printer_type_and_nozzle(DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str);
|
||||
|
||||
@@ -1101,8 +1109,17 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
if (preset_bundle) {
|
||||
std::ostringstream stream;
|
||||
if (obj)
|
||||
stream << std::fixed << std::setprecision(1) << obj->GetExtderSystem()->GetNozzleDiameter(0);
|
||||
if (obj) {
|
||||
// Defensive: this dialog is opened only from StatusPanel (BBL-only) today, so the fallback fires
|
||||
// only during the brief BBL startup window before firmware reports nozzle info. Without this,
|
||||
// the "0.0" lookup string returns an empty set and filament lookup yields no results.
|
||||
float machine_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0);
|
||||
if (machine_diameter == 0.0f) {
|
||||
const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("nozzle_diameter");
|
||||
if (opt) machine_diameter = static_cast<const ConfigOptionFloats *>(opt)->values[0];
|
||||
}
|
||||
stream << std::fixed << std::setprecision(1) << machine_diameter;
|
||||
}
|
||||
std::string nozzle_diameter_str = stream.str();
|
||||
std::set<std::string> printer_names = preset_bundle->get_printer_names_by_printer_type_and_nozzle(DevPrinterConfigUtil::get_printer_display_name(obj->printer_type),
|
||||
nozzle_diameter_str);
|
||||
|
||||
@@ -69,7 +69,7 @@ void AMSSetting::create()
|
||||
|
||||
// tip line1
|
||||
m_tip_Insert_material_line1 = new Label(m_panel_Insert_material,
|
||||
_L("The AMS will automatically read the filament information when inserting a new Bambu Lab filament. This takes about 20 seconds.")
|
||||
_L("The AMS will automatically read the filament information when inserting a new Bambu Lab filament spool. This takes about 20 seconds.")
|
||||
);
|
||||
m_tip_Insert_material_line1->SetFont(::Label::Body_13);
|
||||
m_tip_Insert_material_line1->SetForegroundColour(AMS_SETTING_GREY700);
|
||||
@@ -112,7 +112,7 @@ void AMSSetting::create()
|
||||
m_checkbox_starting_auto_read->Bind(wxEVT_TOGGLEBUTTON, &AMSSetting::on_starting_read, this);
|
||||
m_sizer_starting->Add(m_checkbox_starting_auto_read, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_sizer_starting->Add(0, 0, 0, wxLEFT, 12);
|
||||
m_title_starting_auto_read = new wxStaticText(m_panel_body, wxID_ANY, _L("Power on update"), wxDefaultPosition,wxDefaultSize, 0);
|
||||
m_title_starting_auto_read = new wxStaticText(m_panel_body, wxID_ANY, _L("Update on startup"), wxDefaultPosition,wxDefaultSize, 0);
|
||||
m_title_starting_auto_read->SetFont(::Label::Head_13);
|
||||
m_title_starting_auto_read->SetForegroundColour(AMS_SETTING_GREY800);
|
||||
m_title_starting_auto_read->Wrap(AMS_SETTING_BODY_WIDTH);
|
||||
@@ -125,7 +125,7 @@ void AMSSetting::create()
|
||||
m_sizer_starting_tip_inline = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_tip_starting_line1 = new Label(m_panel_body,
|
||||
_L("The AMS will automatically read the information of inserted filament on start-up. It will take about 1 minute. The reading process will roll the filament spools.")
|
||||
_L("The AMS will automatically read the information of inserted filament on start-up. It will take about 1 minute. The reading process will rotate the filament spools.")
|
||||
);
|
||||
m_tip_starting_line1->SetFont(::Label::Body_13);
|
||||
m_tip_starting_line1->SetForegroundColour(AMS_SETTING_GREY700);
|
||||
|
||||
@@ -44,7 +44,7 @@ void AboutDialogLogo::onRepaint(wxEvent &event)
|
||||
CopyrightsDialog::CopyrightsDialog()
|
||||
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, from_u8((boost::format("%1% - %2%")
|
||||
% (wxGetApp().is_editor() ? SLIC3R_APP_FULL_NAME : GCODEVIEWER_APP_NAME)
|
||||
% _utf8(L("Portions copyright"))).str()),
|
||||
% _utf8(L("License Info"))).str()),
|
||||
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
{
|
||||
this->SetFont(wxGetApp().normal_font());
|
||||
@@ -337,7 +337,7 @@ AboutDialog::AboutDialog()
|
||||
m_html->Bind(wxEVT_HTML_LINK_CLICKED, &AboutDialog::onLinkClicked, this);
|
||||
}
|
||||
//Add "Portions copyright" button
|
||||
Button* button_portions = new Button(this,_L("Portions copyright"));
|
||||
Button* button_portions = new Button(this,_L("License Info"));
|
||||
button_portions->SetStyle(ButtonStyle::Regular, ButtonType::Window);
|
||||
|
||||
wxBoxSizer *copyright_button_ver = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -1686,7 +1686,7 @@ AmsMapingTipPopup::AmsMapingTipPopup(wxWindow *parent)
|
||||
m_title_enable_ams->Wrap(-1);
|
||||
sizer_enable_ams->Add(m_title_enable_ams, 0, 0, 0);
|
||||
|
||||
m_tip_enable_ams = new wxStaticText(m_panel_enable_ams, wxID_ANY, _L("Print with filaments in the AMS"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_tip_enable_ams = new wxStaticText(m_panel_enable_ams, wxID_ANY, _L("Print with filament in the AMS"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_tip_enable_ams->SetMinSize(wxSize(FromDIP(200), FromDIP(50)));
|
||||
m_tip_enable_ams->Wrap(FromDIP(200));
|
||||
m_tip_enable_ams->SetForegroundColour(*wxBLACK);
|
||||
@@ -1722,7 +1722,7 @@ AmsMapingTipPopup::AmsMapingTipPopup(wxWindow *parent)
|
||||
m_title_disable_ams->Wrap(-1);
|
||||
sizer_disable_ams->Add(m_title_disable_ams, 0, 0, 0);
|
||||
|
||||
m_tip_disable_ams = new wxStaticText(m_panel_disable_ams, wxID_ANY, _L("Print with the filament mounted on the back of chassis"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_tip_disable_ams = new wxStaticText(m_panel_disable_ams, wxID_ANY, _L("Print with filament on external spool"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_tip_disable_ams->SetMinSize(wxSize(FromDIP(200), FromDIP(50)));
|
||||
m_tip_disable_ams->Wrap(FromDIP(200));
|
||||
m_tip_disable_ams->SetForegroundColour(*wxBLACK);
|
||||
@@ -1783,9 +1783,7 @@ AmsHumidityTipPopup::AmsHumidityTipPopup(wxWindow* parent)
|
||||
humidity_level_list = new AmsHumidityLevelList(this);
|
||||
curr_humidity_img = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("hum_level1_light", this, 132), wxDefaultPosition, wxSize(FromDIP(132), FromDIP(132)), 0);
|
||||
|
||||
m_staticText_note = new Label(this, _L("Please change the desiccant when it is too wet. "
|
||||
"The indicator may not represent accurately in following cases: when the lid is open or the desiccant pack is changed. "
|
||||
"It take hours to absorb the moisture, and low temperatures also slow down the process."));
|
||||
m_staticText_note = new Label(this, _L("Please change the desiccant when it is too wet. The indicator may not represent accurately in following cases: when the lid is open or the desiccant pack is changed. It takes a few hours to absorb the moisture, and low temperatures also slow down the process."));
|
||||
m_staticText_note->SetMinSize(wxSize(FromDIP(680), -1));
|
||||
m_staticText_note->SetMaxSize(wxSize(FromDIP(680), -1));
|
||||
m_staticText_note->Wrap(FromDIP(680));
|
||||
@@ -2021,7 +2019,7 @@ AmsIntroducePopup::AmsIntroducePopup(wxWindow* parent)
|
||||
m_staticText_top->Wrap(-1);
|
||||
bSizer4->Add(m_staticText_top, 0, wxALL, 5);
|
||||
|
||||
m_staticText_bottom = new Label(this, _L("Print using materials mounted on the back of the case"));
|
||||
m_staticText_bottom = new Label(this, _L("Print using filament on external spool."));
|
||||
m_staticText_bottom->Wrap(-1);
|
||||
m_staticText_bottom->SetFont(::Label::Body_13);
|
||||
m_staticText_bottom->SetForegroundColour(wxColour("#6B6B6B"));
|
||||
@@ -2057,13 +2055,13 @@ void AmsIntroducePopup::set_mode(bool enable_ams)
|
||||
{
|
||||
if (enable_ams) {
|
||||
m_staticText_top->SetLabelText(_L("Enable AMS"));
|
||||
m_staticText_bottom->SetLabelText(_L("Print with filaments in AMS"));
|
||||
m_staticText_bottom->SetLabelText(_L("Print with filament in AMS"));
|
||||
m_img_enable_ams->Show();
|
||||
m_img_disable_ams->Hide();
|
||||
}
|
||||
else {
|
||||
m_staticText_top->SetLabelText(_L("Do not Enable AMS"));
|
||||
m_staticText_bottom->SetLabelText(_L("Print with filaments mounted on the back of the chassis"));
|
||||
m_staticText_bottom->SetLabelText(_L("Print with filament on external spool"));
|
||||
m_staticText_bottom->SetMinSize(wxSize(FromDIP(180), -1));
|
||||
m_staticText_bottom->Wrap(FromDIP(180));
|
||||
m_img_enable_ams->Hide();
|
||||
@@ -2437,7 +2435,7 @@ void AmsReplaceMaterialDialog::update_to_nozzle(int nozzle_id)
|
||||
}
|
||||
else if (!m_obj->GetFilaSystem()->IsAutoRefillEnabled())
|
||||
{
|
||||
label_txt->SetLabelText(_L("AMS filament backup is not enabled, please enable it in the AMS settings."));
|
||||
label_txt->SetLabelText(_L("AMS filament backup is not enabled; please enable it in the AMS settings."));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -380,17 +380,17 @@ void AuFile::on_input_enter(wxCommandEvent &evt)
|
||||
}
|
||||
|
||||
if (m_valid_type == Valid && new_file_name.empty()) {
|
||||
info_line = _L("The name is not allowed to be empty.");
|
||||
info_line = _L("The name field is not allowed to be empty.");
|
||||
m_valid_type = NoValid;
|
||||
}
|
||||
|
||||
if (m_valid_type == Valid && new_file_name.find_first_of(' ') == 0) {
|
||||
info_line = _L("The name is not allowed to start with space character.");
|
||||
info_line = _L("The name is not allowed to start with a space.");
|
||||
m_valid_type = NoValid;
|
||||
}
|
||||
|
||||
if (m_valid_type == Valid && new_file_name.find_last_of(' ') == new_file_name.length() - 1) {
|
||||
info_line = _L("The name is not allowed to end with space character.");
|
||||
info_line = _L("The name is not allowed to end with a space.");
|
||||
m_valid_type = NoValid;
|
||||
}
|
||||
|
||||
|
||||
@@ -807,6 +807,8 @@ wxAuiToolBarItem* BBLTopbar::FindToolByCurrentPosition()
|
||||
}
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#include <windowsx.h>
|
||||
|
||||
WXLRESULT CenteredTitle::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
|
||||
{
|
||||
switch (nMsg) {
|
||||
@@ -823,6 +825,8 @@ WXLRESULT BBLTopbar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam
|
||||
{
|
||||
switch (nMsg) {
|
||||
case WM_NCHITTEST: {
|
||||
m_last_mouse_position = ScreenToClient({GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)});
|
||||
|
||||
wxAuiToolBarItem* item = this->FindToolByCurrentPosition();
|
||||
if (item != NULL && item->GetWindow() != m_title_ctrl) {
|
||||
break;
|
||||
|
||||
@@ -77,12 +77,11 @@ std::pair<std::string, std::vector<size_t>> SlicingProcessCompletedEvent::format
|
||||
try {
|
||||
this->rethrow_exception();
|
||||
} catch (const std::bad_alloc &ex) {
|
||||
wxString errmsg = GUI::from_u8(boost::format(_utf8(L("A error occurred. Maybe memory of system is not enough or it's a bug "
|
||||
"of the program"))).str());
|
||||
wxString errmsg = GUI::from_u8(boost::format(_utf8(L("An error occurred. The system may have run out of memory, or a bug may have occurred."))).str());
|
||||
error = std::string(errmsg.ToUTF8()) + "\n" + std::string(ex.what());
|
||||
} catch (const HardCrash &ex) {
|
||||
error = GUI::format(_u8L("A fatal error occurred: \"%1%\""), ex.what()) + "\n" +
|
||||
_u8L("Please save project and restart the program.");
|
||||
_u8L("Please save your project and restart the application.");
|
||||
} catch (PlaceholderParserError &ex) {
|
||||
error = ex.what();
|
||||
monospace = 1;
|
||||
@@ -200,7 +199,7 @@ void BackgroundSlicingProcess::process_fff()
|
||||
//BBS: add the logic to process from an existed gcode file
|
||||
if (m_print->finished()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: skip slicing, to process previous gcode file")%__LINE__;
|
||||
m_fff_print->set_status(80, _utf8(L("Processing G-code from Previous file...")));
|
||||
m_fff_print->set_status(80, _utf8(L("Processing G-Code from previous file\u2026")));
|
||||
wxCommandEvent evt(m_event_slicing_completed_id);
|
||||
// Post the Slicing Finished message for the G-code viewer to update.
|
||||
// Passing the timestamp
|
||||
@@ -675,13 +674,13 @@ bool BackgroundSlicingProcess::empty() const
|
||||
return m_print->empty();
|
||||
}
|
||||
|
||||
StringObjectException BackgroundSlicingProcess::validate(StringObjectException *warning, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons)
|
||||
StringObjectException BackgroundSlicingProcess::validate(std::vector<StringObjectException> *warnings, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons)
|
||||
{
|
||||
assert(m_print != nullptr);
|
||||
assert(m_print == m_fff_print);
|
||||
|
||||
m_fff_print->is_BBL_printer() = wxGetApp().preset_bundle->is_bbl_vendor();
|
||||
return m_print->validate(warning, collison_polygons, height_polygons);
|
||||
return m_print->validate(warnings, collison_polygons, height_polygons);
|
||||
}
|
||||
|
||||
// Apply config over the print. Returns false, if the new config values caused any of the already
|
||||
@@ -869,7 +868,7 @@ void BackgroundSlicingProcess::export_gcode()
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
throw Slic3r::ExportError(_utf8(L("Unknown error when exporting G-code.")));
|
||||
throw Slic3r::ExportError(_utf8(L("Unknown error with G-code export")));
|
||||
}
|
||||
switch (copy_ret_val) {
|
||||
case CopyFileResult::SUCCESS: break; // no error
|
||||
@@ -920,7 +919,7 @@ void BackgroundSlicingProcess::prepare_upload()
|
||||
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
|
||||
std::string error_message;
|
||||
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS)
|
||||
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
|
||||
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed.")));
|
||||
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
// Orca: skip post-processing scripts for BBL printers as we have run them already in finalize_gcode()
|
||||
// todo: do we need to copy the file?
|
||||
|
||||
@@ -145,7 +145,7 @@ public:
|
||||
bool empty() const;
|
||||
// Validate the print. Returns an empty string if valid, returns an error message if invalid.
|
||||
// Call validate before calling start().
|
||||
StringObjectException validate(StringObjectException *warning = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr);
|
||||
StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr);
|
||||
|
||||
// Set the export path of the G-code.
|
||||
// Once the path is set, the G-code
|
||||
|
||||
@@ -566,7 +566,7 @@ void BedShapePanel::load_stl()
|
||||
model = Model::read_from_file(file_name);
|
||||
}
|
||||
catch (std::exception &) {
|
||||
show_error(this, _L("Error! Invalid model"));
|
||||
show_error(this, _L("Error: invalid model"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -578,7 +578,7 @@ void BedShapePanel::load_stl()
|
||||
return;
|
||||
}
|
||||
if (expolygons.size() > 1) {
|
||||
show_error(this, _L("The selected file contains several disjoint areas. This is not supported."));
|
||||
show_error(this, _L("The selected file contains several disjointed areas. This is not supported."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ wxString get_fail_reason(int code)
|
||||
return _L("Failed to publish login request");
|
||||
|
||||
else if (code == BAMBU_NETWORK_ERR_BIND_GET_PRINTER_TICKET_TIMEOUT)
|
||||
return _L("Get ticket from device timeout");
|
||||
return _L("Timeout getting ticket from device");
|
||||
|
||||
else if (code == BAMBU_NETWORK_ERR_BIND_GET_CLOUD_TICKET_TIMEOUT)
|
||||
return _L("Get ticket from server timeout");
|
||||
return _L("Timeout getting ticket from server");
|
||||
|
||||
else if (code == BAMBU_NETWORK_ERR_BIND_POST_TICKET_TO_CLOUD_FAILED)
|
||||
return _L("Failed to post ticket to server");
|
||||
|
||||
@@ -304,7 +304,7 @@ void HistoryWindow::reqeust_history_result(MachineObject* obj)
|
||||
cali_info.use_nozzle_volume_type = false;
|
||||
cali_info.use_extruder_id = false;
|
||||
CalibUtils::emit_get_PA_calib_infos(cali_info);
|
||||
m_tips->SetLabel(_L("Refreshing the historical Flow Dynamics Calibration records"));
|
||||
m_tips->SetLabel(_L("Refreshing the previous Flow Dynamics Calibration records"));
|
||||
BOOST_LOG_TRIVIAL(info) << "request calib history";
|
||||
}
|
||||
}
|
||||
@@ -476,7 +476,7 @@ int HistoryWindow::get_extruder_id()
|
||||
void HistoryWindow::on_click_new_button(wxCommandEvent& event)
|
||||
{
|
||||
if (curr_obj && curr_obj->get_printer_series() == PrinterSeries::SERIES_P1P && m_calib_results_history.size() >= 16) {
|
||||
MessageDialog msg_dlg(nullptr, wxString::Format(_L("This machine type can only hold %d history results per nozzle."), 16), wxEmptyString, wxICON_WARNING | wxOK);
|
||||
MessageDialog msg_dlg(nullptr, wxString::Format(_L("This machine type can only hold %d historical results per nozzle."), 16), wxEmptyString, wxICON_WARNING | wxOK);
|
||||
msg_dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
@@ -644,7 +644,14 @@ wxArrayString NewCalibrationHistoryDialog::get_all_filaments(const MachineObject
|
||||
std::set<std::string> filament_id_set;
|
||||
std::set<std::string> printer_names;
|
||||
std::ostringstream stream;
|
||||
stream << std::fixed << std::setprecision(1) << obj->GetExtderSystem()->GetNozzleDiameter(0);
|
||||
// If the machine didn't report a nozzle diameter (0.0 = unknown), fall back to the currently
|
||||
// selected printer preset so the filament list isn't empty.
|
||||
float machine_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0);
|
||||
if (machine_diameter == 0.0f && preset_bundle) {
|
||||
const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("nozzle_diameter");
|
||||
if (opt) machine_diameter = static_cast<const ConfigOptionFloats *>(opt)->values[0];
|
||||
}
|
||||
stream << std::fixed << std::setprecision(1) << machine_diameter;
|
||||
std::string nozzle_diameter_str = stream.str();
|
||||
|
||||
for (auto printer_it = preset_bundle->printers.begin(); printer_it != preset_bundle->printers.end(); printer_it++) {
|
||||
@@ -906,8 +913,7 @@ void NewCalibrationHistoryDialog::on_ok(wxCommandEvent &event)
|
||||
|
||||
if (iter != m_history_results.end()) {
|
||||
|
||||
wxString duplicate_name_info = wxString::Format(_L("There is already a historical calibration result with the same name: %s. Only one of the results with the same name "
|
||||
"is saved. Are you sure you want to override the historical result?"), m_new_result.name);
|
||||
wxString duplicate_name_info = wxString::Format(_L("There is already a previous calibration result with the same name: %s. Only one result with a name is saved. Are you sure you want to overwrite the previous result\?"), m_new_result.name);
|
||||
|
||||
duplicate_name_info = wxString::Format(_L("Within the same extruder, the name(%s) must be unique when the filament type, nozzle diameter, and nozzle flow are the same.\n"
|
||||
"Are you sure you want to override the historical result?"), m_new_result.name);
|
||||
|
||||
@@ -761,6 +761,7 @@ void PressureAdvanceWizard::on_cali_start()
|
||||
calib_info.extruder_id = preset_page->get_extruder_id(calib_info.ams_id);
|
||||
calib_info.extruder_type = preset_page->get_extruder_type(calib_info.extruder_id);
|
||||
calib_info.nozzle_volume_type = preset_page->get_nozzle_volume_type(calib_info.extruder_id);
|
||||
calib_info.nozzle_diameter = preset_page->get_nozzle_diameter(calib_info.extruder_id);
|
||||
calib_info.select_ams = std::to_string(selected_tray_id);
|
||||
Preset *preset = selected_filaments.begin()->second;
|
||||
Preset * temp_filament_preset = new Preset(preset->type, preset->name + "_temp");
|
||||
@@ -804,10 +805,7 @@ void PressureAdvanceWizard::on_cali_start()
|
||||
curr_obj->manual_pa_cali_method = pa_cali_method;
|
||||
|
||||
if (curr_obj->get_printer_series() != PrinterSeries::SERIES_X1 && curr_obj->pa_calib_tab.size() >= MAX_PA_HISTORY_RESULTS_NUMS) {
|
||||
MessageDialog msg_dlg(nullptr, wxString::Format(_L("This machine type can only hold 16 history results per nozzle. "
|
||||
"You can delete the existing historical results and then start calibration. "
|
||||
"Or you can continue the calibration, but you cannot create new calibration historical results.\n"
|
||||
"Do you still want to continue the calibration?"), MAX_PA_HISTORY_RESULTS_NUMS), wxEmptyString, wxICON_WARNING | wxYES | wxCANCEL);
|
||||
MessageDialog msg_dlg(nullptr, wxString::Format(_L("This machine type can only hold 16 historical results per nozzle. You can delete the existing historical results and then start calibration. Or you can continue the calibration, but you cannot create new calibration historical results.\nDo you still want to continue the calibration\?"), MAX_PA_HISTORY_RESULTS_NUMS), wxEmptyString, wxICON_WARNING | wxYES | wxCANCEL);
|
||||
if (msg_dlg.ShowModal() != wxID_YES) {
|
||||
return;
|
||||
}
|
||||
@@ -929,8 +927,7 @@ bool PressureAdvanceWizard::can_save_cali_result(const std::vector<PACalibResult
|
||||
|
||||
if (!same_pa_names.empty()) {
|
||||
same_pa_names.erase(same_pa_names.size() - 2);
|
||||
wxString duplicate_name_info = wxString::Format(_L("There is already a historical calibration result with the same name: %s. Only one of the results with the same name "
|
||||
"is saved. Are you sure you want to override the historical result?"), same_pa_names);
|
||||
wxString duplicate_name_info = wxString::Format(_L("There is already a previous calibration result with the same name: %s. Only one result with a name is saved. Are you sure you want to overwrite the previous result\?"), same_pa_names);
|
||||
|
||||
if (curr_obj->is_multi_extruders())
|
||||
duplicate_name_info = wxString::Format(_L("Within the same extruder, the name(%s) must be unique when the filament type, nozzle diameter, and nozzle flow are the same.\n"
|
||||
@@ -942,7 +939,7 @@ bool PressureAdvanceWizard::can_save_cali_result(const std::vector<PACalibResult
|
||||
}
|
||||
|
||||
if (curr_obj->get_printer_series() != PrinterSeries::SERIES_X1 && curr_obj->pa_calib_tab.size() >= MAX_PA_HISTORY_RESULTS_NUMS) {
|
||||
MessageDialog msg_dlg(nullptr, wxString::Format(_L("This machine type can only hold %d history results per nozzle. This result will not be saved."), MAX_PA_HISTORY_RESULTS_NUMS),
|
||||
MessageDialog msg_dlg(nullptr, wxString::Format(_L("This machine type can only hold %d historical results per nozzle. This result will not be saved."), MAX_PA_HISTORY_RESULTS_NUMS),
|
||||
wxEmptyString, wxICON_WARNING | wxOK);
|
||||
msg_dlg.ShowModal();
|
||||
return false;
|
||||
@@ -1293,6 +1290,7 @@ void FlowRateWizard::on_cali_start(CaliPresetStage stage, float cali_value, Flow
|
||||
calib_info.extruder_id = preset_page->get_extruder_id(calib_info.ams_id);
|
||||
calib_info.extruder_type = preset_page->get_extruder_type(calib_info.extruder_id);
|
||||
calib_info.nozzle_volume_type = preset_page->get_nozzle_volume_type(calib_info.extruder_id);
|
||||
calib_info.nozzle_diameter = preset_page->get_nozzle_diameter(calib_info.extruder_id);
|
||||
Preset* preset = selected_filaments.begin()->second;
|
||||
temp_filament_preset = new Preset(preset->type, preset->name + "_temp");
|
||||
temp_filament_preset->config = preset->config;
|
||||
@@ -1708,6 +1706,7 @@ void MaxVolumetricSpeedWizard::on_cali_start()
|
||||
calib_info.extruder_id = preset_page->get_extruder_id(calib_info.ams_id);
|
||||
calib_info.extruder_type = preset_page->get_extruder_type(calib_info.extruder_id);
|
||||
calib_info.nozzle_volume_type = preset_page->get_nozzle_volume_type(calib_info.extruder_id);
|
||||
calib_info.nozzle_diameter = preset_page->get_nozzle_diameter(calib_info.extruder_id);
|
||||
calib_info.filament_prest = selected_filaments.begin()->second;
|
||||
std::map<int, DynamicPrintConfig> filament_list = preset_page->get_filament_ams_list();
|
||||
calib_info.filament_color = filament_list[selected_filaments.begin()->first].opt_string("filament_colour", 0u);
|
||||
|
||||
@@ -326,7 +326,7 @@ void CalibrationCaliPage::update_subtask(MachineObject* obj)
|
||||
prepare_text = wxString::Format(_L("Cloud Slicing..."));
|
||||
}
|
||||
else {
|
||||
prepare_text = wxString::Format(_L("In Cloud Slicing Queue, there are %s tasks ahead."), std::to_string(obj->queue_number));
|
||||
prepare_text = wxString::Format(_L("In Cloud Slicing Queue, there are %s tasks ahead of you."), std::to_string(obj->queue_number));
|
||||
show_percent = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,7 +601,7 @@ PAPageHelpPanel::PAPageHelpPanel(wxWindow* parent, bool ground_panel, wxWindowID
|
||||
wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
top_sizer->AddSpacer(FromDIP(10));
|
||||
|
||||
auto help_text_title = new Label(this, _L("How to use calibration result?"));
|
||||
auto help_text_title = new Label(this, _L("How can I use calibration results\?"));
|
||||
help_text_title->SetFont(Label::Head_14);
|
||||
top_sizer->Add(help_text_title, 0, wxLEFT | wxRIGHT, left_align_padding);
|
||||
|
||||
|
||||
@@ -1882,12 +1882,12 @@ void CalibrationPresetPage::show_status(CaliPresetPageStatus status)
|
||||
Enable_Send_Button(false);
|
||||
}
|
||||
else if (status == CaliPresetPageStatus::CaliPresetStatusNeedForceUpgrading) {
|
||||
wxString msg_text = _L("Cannot send the print job to a printer whose firmware is required to get updated.");
|
||||
wxString msg_text = _L("Cannot send the print job to a printer whose firmware must be updated.");
|
||||
update_print_status_msg(msg_text, true);
|
||||
Enable_Send_Button(false);
|
||||
}
|
||||
else if (status == CaliPresetPageStatus::CaliPresetStatusNeedConsistencyUpgrading) {
|
||||
wxString msg_text = _L("Cannot send the print job to a printer whose firmware is required to get updated.");
|
||||
wxString msg_text = _L("Cannot send the print job to a printer whose firmware must be updated.");
|
||||
update_print_status_msg(msg_text, true);
|
||||
Enable_Send_Button(false);
|
||||
}
|
||||
|
||||
@@ -116,10 +116,7 @@ void CalibrationPAStartPage::create_page(wxWindow* parent)
|
||||
|
||||
create_about(parent,
|
||||
_L("About this calibration"),
|
||||
_L("Please find the details of Flow Dynamics Calibration from our wiki.\
|
||||
\n\nUsually the calibration is unnecessary. When you start a single color/material print, with the \"flow dynamics calibration\" option checked in the print start menu, the printer will follow the old way, calibrate the filament before the print; When you start a multi color/material print, the printer will use the default compensation parameter for the filament during every filament switch which will have a good result in most cases.\
|
||||
\n\nPlease note that there are a few cases that can make the calibration results unreliable, such as insufficient adhesion on the build plate. Improving adhesion can be achieved by washing the build plate or applying glue. For more information on this topic, please refer to our Wiki.\
|
||||
\n\nThe calibration results have about 10 percent jitter in our test, which may cause the result not exactly the same in each calibration. We are still investigating the root cause to do improvements with new updates."));
|
||||
_L("Please find the details of Flow Dynamics Calibration from our wiki.\n\nUsually the calibration is unnecessary. When you start a single color/material print, with the \"flow dynamics calibration\" option checked in the print start menu, the printer will follow the old way, calibrate the filament before the print; When you start a multi color/material print, the printer will use the default compensation parameter for the filament during every filament switch which will have a good result in most cases.\n\nPlease note that there are a few cases that can make the calibration results unreliable, such as insufficient adhesion on the build plate. Improving adhesion can be achieved by washing the build plate or applying glue. For more information on this topic, please refer to our Wiki.\n\nThe calibration results have about 10 percent jitter in our test, which may cause the result not exactly the same in each calibration. We are still investigating the root cause to do improvements with new updates."));
|
||||
m_top_sizer->Add(m_about_title);
|
||||
m_top_sizer->Add(m_about_content);
|
||||
m_top_sizer->AddSpacer(PRESET_GAP);
|
||||
@@ -255,9 +252,7 @@ void CalibrationFlowRateStartPage::create_page(wxWindow* parent)
|
||||
auto_cali_title->SetMinSize({CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1});
|
||||
|
||||
auto auto_cali_content = new Label(this,
|
||||
_L("Auto Flow Rate Calibration utilizes Bambu Lab's Micro-Lidar technology, directly measuring the calibration patterns. However, please be advised that the efficacy and accuracy of this method may be compromised with specific types of materials. Particularly, filaments that are transparent or semi-transparent, sparkling-particled, or have a high-reflective finish may not be suitable for this calibration and can produce less-than-desirable results.\
|
||||
\n\nThe calibration results may vary between each calibration or filament. We are still improving the accuracy and compatibility of this calibration through firmware updates over time.\
|
||||
\n\nCaution: Flow Rate Calibration is an advanced process, to be attempted only by those who fully understand its purpose and implications. Incorrect usage can lead to sub-par prints or printer damage. Please make sure to carefully read and understand the process before doing it."));
|
||||
_L("Auto Flow Rate Calibration utilizes Bambu Lab\'s Micro-Lidar technology, directly measuring the calibration patterns. However, please be advised that the efficacy and accuracy of this method may be compromised with specific types of materials. Particularly, filaments that are transparent or semi-transparent, have sparkles, or have a highly-reflective finish may not be suitable for this calibration and can produce less-than-desirable results.\n\nThe calibration results may vary between each calibration or filament. We are still improving the accuracy and compatibility of this calibration through firmware updates over time.\n\nCaution: Flow Rate Calibration is an advanced process, to be attempted only by those who fully understand its purpose and implications. Incorrect usage can lead to sub-par prints or printer damage. Please make sure to carefully read and understand the process before doing it."));
|
||||
auto_cali_content->SetFont(Label::Body_14);
|
||||
auto_cali_content->Wrap(CALIBRATION_START_PAGE_TEXT_MAX_LENGTH);
|
||||
auto_cali_content->SetMinSize({CALIBRATION_START_PAGE_TEXT_MAX_LENGTH, -1});
|
||||
|
||||
@@ -9,13 +9,29 @@
|
||||
#include "libslic3r/MaterialType.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/GCode/AdaptivePAProcessor.hpp"
|
||||
#include "Plater.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string trim_copy(const std::string& text)
|
||||
{
|
||||
const auto first = text.find_first_not_of(" \t\r\n");
|
||||
if (first == std::string::npos)
|
||||
return {};
|
||||
|
||||
const auto last = text.find_last_not_of(" \t\r\n");
|
||||
return text.substr(first, last - first + 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ConfigManipulation::apply(DynamicPrintConfig* config, DynamicPrintConfig* new_config)
|
||||
{
|
||||
bool modified = false;
|
||||
@@ -109,8 +125,7 @@ void ConfigManipulation::check_nozzle_temperature_range(DynamicPrintConfig *conf
|
||||
|
||||
if (config->has("nozzle_temperature")) {
|
||||
if (config->opt_int("nozzle_temperature", 0) < temperature_range_low || config->opt_int("nozzle_temperature", 0) > temperature_range_high) {
|
||||
wxString msg_text = _(L("Nozzle may be blocked when the temperature is out of recommended range.\n"
|
||||
"Please make sure whether to use the temperature to print.\n\n"));
|
||||
wxString msg_text = _(L("The nozzle may become clogged when the temperature is out of the recommended range.\nPlease make sure whether to use this temperature to print.\n\n"));
|
||||
msg_text += wxString::Format(_L("The recommended nozzle temperature for this filament type is [%d, %d] degrees Celsius."), temperature_range_low, temperature_range_high);
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
||||
is_msg_dlg_already_exist = true;
|
||||
@@ -132,8 +147,7 @@ void ConfigManipulation::check_nozzle_temperature_initial_layer_range(DynamicPri
|
||||
if (config->opt_int("nozzle_temperature_initial_layer", 0) < temperature_range_low ||
|
||||
config->opt_int("nozzle_temperature_initial_layer", 0) > temperature_range_high)
|
||||
{
|
||||
wxString msg_text = _(L("Nozzle may be blocked when the temperature is out of recommended range.\n"
|
||||
"Please make sure whether to use the temperature to print.\n\n"));
|
||||
wxString msg_text = _(L("The nozzle may become clogged when the temperature is out of the recommended range.\nPlease make sure whether to use this temperature to print.\n\n"));
|
||||
msg_text += wxString::Format(_L("The recommended nozzle temperature for this filament type is [%d, %d] degrees Celsius."), temperature_range_low, temperature_range_high);
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
||||
is_msg_dlg_already_exist = true;
|
||||
@@ -143,6 +157,30 @@ void ConfigManipulation::check_nozzle_temperature_initial_layer_range(DynamicPri
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManipulation::check_adaptive_pressure_advance_model(DynamicPrintConfig* config)
|
||||
{
|
||||
if (is_msg_dlg_already_exist || !config->has("adaptive_pressure_advance_model"))
|
||||
return;
|
||||
|
||||
const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model");
|
||||
if (model == nullptr || model->values.empty())
|
||||
return;
|
||||
|
||||
std::string raw_model;
|
||||
for (const std::string& chunk : model->values)
|
||||
raw_model += chunk;
|
||||
|
||||
std::string error = AdaptivePAProcessor::validate_adaptive_pa_model(raw_model);
|
||||
if (!error.empty()) {
|
||||
wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n");
|
||||
msg_text += from_u8(error);
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
||||
is_msg_dlg_already_exist = true;
|
||||
dialog.ShowModal();
|
||||
is_msg_dlg_already_exist = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ConfigManipulation::check_filament_max_volumetric_speed(DynamicPrintConfig *config)
|
||||
{
|
||||
@@ -152,7 +190,7 @@ void ConfigManipulation::check_filament_max_volumetric_speed(DynamicPrintConfig
|
||||
float max_volumetric_speed = config->has("filament_max_volumetric_speed") ? config->opt_float("filament_max_volumetric_speed", (float) 0.5) : 0.5;
|
||||
// BBS: limite the min max_volumetric_speed
|
||||
if (max_volumetric_speed < 0.5) {
|
||||
const wxString msg_text = _(L("Too small max volumetric speed.\nReset to 0.5."));
|
||||
const wxString msg_text = _(L("Too small max volumetric speed.\nValue was reset to 0.5"));
|
||||
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
@@ -172,8 +210,7 @@ void ConfigManipulation::check_chamber_temperature(DynamicPrintConfig* config)
|
||||
int chamber_min_temp, chamber_max_temp;
|
||||
if (MaterialType::get_chamber_temperature_range(filament_type, chamber_min_temp, chamber_max_temp)) {
|
||||
if (chamber_max_temp < config->option<ConfigOptionInts>("chamber_temperature")->get_at(0)) {
|
||||
wxString msg_text = wxString::Format(_L("Current chamber temperature is higher than the material's safe temperature, this may result in material softening and clogging. "
|
||||
"The maximum safe temperature for the material is %d"), chamber_max_temp);
|
||||
wxString msg_text = wxString::Format(_L("Current chamber temperature is higher than the material\'s safe temperature; this may result in material softening and nozzle clogs. The maximum safe temperature for the material is %d"), chamber_max_temp);
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
||||
is_msg_dlg_already_exist = true;
|
||||
dialog.ShowModal();
|
||||
@@ -200,7 +237,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
auto gpreset = GUI::wxGetApp().preset_bundle->printers.get_edited_preset();
|
||||
if (layer_height < EPSILON)
|
||||
{
|
||||
const wxString msg_text = _(L("Too small layer height.\nReset to 0.2."));
|
||||
const wxString msg_text = _(L("Layer height too small\nIt has been reset to 0.2"));
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
@@ -227,7 +264,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
//BBS: ironing_spacing shouldn't be too small or equal to zero
|
||||
if (config->opt_float("ironing_spacing") < 0.05)
|
||||
{
|
||||
const wxString msg_text = _(L("Too small ironing spacing.\nReset to 0.1."));
|
||||
const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1"));
|
||||
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
@@ -238,7 +275,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
}
|
||||
if (config->opt_float("support_ironing_spacing") < 0.05)
|
||||
{
|
||||
const wxString msg_text = _(L("Too small ironing spacing.\nReset to 0.1."));
|
||||
const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1"));
|
||||
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
@@ -262,10 +299,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
|
||||
if (abs(config->option<ConfigOptionFloat>("xy_hole_compensation")->value) > 2)
|
||||
{
|
||||
const wxString msg_text = _(L("This setting is only used for model size tunning with small value in some cases.\n"
|
||||
"For example, when model size has small error and hard to be assembled.\n"
|
||||
"For large size tuning, please use model scale function.\n\n"
|
||||
"The value will be reset to 0."));
|
||||
const wxString msg_text = _(L("This setting is only used for tuning model size by small amounts.\nFor example, when the model size has small errors or when tolerances are incorrect. For large adjustments, please use the model scale function.\n\nThe value will be reset to 0."));
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
@@ -277,10 +311,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
|
||||
if (abs(config->option<ConfigOptionFloat>("xy_contour_compensation")->value) > 2)
|
||||
{
|
||||
const wxString msg_text = _(L("This setting is only used for model size tunning with small value in some cases.\n"
|
||||
"For example, when model size has small error and hard to be assembled.\n"
|
||||
"For large size tuning, please use model scale function.\n\n"
|
||||
"The value will be reset to 0."));
|
||||
const wxString msg_text = _(L("This setting is only used for tuning model size by small amounts.\nFor example, when the model size has small errors or when tolerances are incorrect. For large adjustments, please use the model scale function.\n\nThe value will be reset to 0."));
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
@@ -292,10 +323,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
|
||||
if (config->option<ConfigOptionFloat>("elefant_foot_compensation")->value > 1)
|
||||
{
|
||||
const wxString msg_text = _(L("Too large elephant foot compensation is unreasonable.\n"
|
||||
"If really have serious elephant foot effect, please check other settings.\n"
|
||||
"For example, whether bed temperature is too high.\n\n"
|
||||
"The value will be reset to 0."));
|
||||
const wxString msg_text = _(L("The elephant foot compensation value is too large.\nIf there are significant elephant foot issues, please check other settings.\nThe bed temperature may be too high, for example.\n\nThe value will be reset to 0."));
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
@@ -571,7 +599,11 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
{
|
||||
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
|
||||
|
||||
auto gcflavor = preset_bundle->printers.get_edited_preset().config.option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value;
|
||||
const GCodeFlavor gcflavor = preset_bundle->printers.get_edited_preset().config.option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value;
|
||||
|
||||
// Orca: use booleans to avoid repeated comparisons with enum values
|
||||
const bool gcf_is_marlin_firmware = gcflavor == GCodeFlavor::gcfMarlinFirmware;
|
||||
const bool gcf_is_klipper = gcflavor == GCodeFlavor::gcfKlipper;
|
||||
|
||||
bool have_volumetric_extrusion_rate_slope = config->option<ConfigOptionFloat>("max_volumetric_extrusion_rate_slope")->value > 0;
|
||||
float have_volumetric_extrusion_rate_slope_segment_length = config->option<ConfigOptionFloat>("max_volumetric_extrusion_rate_slope_segment_length")->value;
|
||||
@@ -593,9 +625,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_field(el, have_perimeters, variant_index);
|
||||
|
||||
bool have_infill = config->option<ConfigOptionPercent>("sparse_infill_density")->value > 0;
|
||||
// sparse_infill_filament uses the same logic as in Print::extruders()
|
||||
// sparse_infill_filament_id uses the same logic as in Print::extruders()
|
||||
for (auto el : { "sparse_infill_pattern", "infill_combination", "fill_multiline","infill_direction",
|
||||
"minimum_sparse_infill_area", "sparse_infill_filament", "infill_anchor", "infill_anchor_max","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"})
|
||||
"minimum_sparse_infill_area", "sparse_infill_filament_id", "infill_anchor", "infill_anchor_max","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"})
|
||||
toggle_line(el, have_infill);
|
||||
|
||||
bool have_combined_infill = config->opt_bool("infill_combination") && have_infill;
|
||||
@@ -656,8 +688,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_field("bottom_surface_density", has_bottom_shell);
|
||||
|
||||
for (auto el : { "infill_direction", "sparse_infill_line_width", "gap_fill_target","filter_out_gap_fill","infill_wall_overlap",
|
||||
"bridge_angle", "internal_bridge_angle",
|
||||
"solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "solid_infill_filament",
|
||||
"bridge_angle", "internal_bridge_angle", "relative_bridge_angle",
|
||||
"solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id",
|
||||
})
|
||||
toggle_field(el, have_infill || has_solid_infill);
|
||||
for (auto el : { "sparse_infill_speed", "bridge_speed", "internal_bridge_speed"})
|
||||
@@ -679,12 +711,12 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_field(el, have_default_acceleration, variant_index);
|
||||
|
||||
bool machine_supports_junction_deviation = false;
|
||||
if (gcflavor == gcfMarlinFirmware) {
|
||||
if (gcf_is_marlin_firmware) {
|
||||
if (const auto *machine_jd = preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("machine_max_junction_deviation")) {
|
||||
machine_supports_junction_deviation = !machine_jd->values.empty() && machine_jd->values.front() > 0.0;
|
||||
}
|
||||
}
|
||||
toggle_line("default_junction_deviation", gcflavor == gcfMarlinFirmware, variant_index);
|
||||
toggle_line("default_junction_deviation", gcf_is_marlin_firmware, variant_index);
|
||||
if (machine_supports_junction_deviation) {
|
||||
toggle_field("default_junction_deviation", true, variant_index);
|
||||
toggle_field("default_jerk", false, variant_index);
|
||||
@@ -714,8 +746,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
config->opt_enum<BrimType>("brim_type") != btPainted;
|
||||
toggle_field("brim_width", have_brim_width);
|
||||
toggle_field("brim_flow_ratio", have_brim);
|
||||
// wall_filament uses the same logic as in Print::extruders()
|
||||
toggle_field("wall_filament", have_perimeters || have_brim);
|
||||
// Wall filament selectors use the same logic as in Print::extruders().
|
||||
toggle_field("outer_wall_filament_id", have_perimeters || have_brim);
|
||||
toggle_field("inner_wall_filament_id", have_perimeters || have_brim);
|
||||
|
||||
bool have_brim_ear = (config->opt_enum<BrimType>("brim_type") == btEar);
|
||||
const auto brim_width = config->opt_float("brim_width");
|
||||
@@ -839,9 +872,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_line("enable_tower_interface_cooldown_during_tower",
|
||||
have_prime_tower && config->opt_bool("enable_tower_interface_features"));
|
||||
|
||||
for (auto el : {"wall_filament", "sparse_infill_filament", "solid_infill_filament", "wipe_tower_filament"})
|
||||
toggle_line(el, !bSEMM);
|
||||
|
||||
bool purge_in_primetower = preset_bundle->printers.get_edited_preset().config.opt_bool("purge_in_prime_tower");
|
||||
|
||||
for (auto el : {"wipe_tower_rotation_angle", "wipe_tower_cone_angle",
|
||||
@@ -910,8 +940,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_field("wipe_speed",!is_role_based_wipe_speed);
|
||||
|
||||
for (auto el : {"accel_to_decel_enable", "accel_to_decel_factor"})
|
||||
toggle_line(el, gcflavor == gcfKlipper);
|
||||
if(gcflavor == gcfKlipper)
|
||||
toggle_line(el, gcf_is_klipper);
|
||||
if(gcf_is_klipper)
|
||||
toggle_field("accel_to_decel_factor", config->opt_bool("accel_to_decel_enable"));
|
||||
|
||||
bool have_make_overhang_printable = config->opt_bool("make_overhang_printable");
|
||||
@@ -967,6 +997,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
bool lattice_options = config->opt_enum<InfillPattern>("sparse_infill_pattern") == InfillPattern::ipLateralLattice;
|
||||
for (auto el : { "lateral_lattice_angle_1", "lateral_lattice_angle_2"})
|
||||
toggle_line(el, lattice_options);
|
||||
|
||||
bool lightning_options = config->opt_enum<InfillPattern>("sparse_infill_pattern") == InfillPattern::ipLightning;
|
||||
for (auto el : { "lightning_overhang_angle", "lightning_prune_angle", "lightning_straightening_angle" })
|
||||
toggle_line(el, lightning_options);
|
||||
|
||||
// Adaptative Cubic and support cubic infill patterns do not support infill rotation.
|
||||
bool FillAdaptive = (pattern == InfillPattern::ipAdaptiveCubic || pattern == InfillPattern::ipSupportCubic);
|
||||
@@ -1066,9 +1100,7 @@ int ConfigManipulation::show_spiral_mode_settings_dialog(bool is_object_config)
|
||||
msg_text += _(L(" But machines with I3 structure will not generate timelapse videos."));
|
||||
}
|
||||
if (!is_object_config)
|
||||
msg_text += "\n\n" + _(L("Change these settings automatically?\n"
|
||||
"Yes - Change these settings and enable spiral mode automatically\n"
|
||||
"No - Give up using spiral mode this time"));
|
||||
msg_text += "\n\n" + _(L("Change these settings automatically\?\nYes - Change these settings and enable spiral/vase mode automatically\nNo - Cancel enabling spiral mode"));
|
||||
|
||||
MessageDialog dialog(wxGetApp().plater(), msg_text, "",
|
||||
wxICON_WARNING | (!is_object_config ? wxYES | wxNO : wxOK));
|
||||
|
||||
@@ -77,6 +77,7 @@ public:
|
||||
void check_nozzle_recommended_temperature_range(DynamicPrintConfig *config);
|
||||
void check_nozzle_temperature_range(DynamicPrintConfig* config);
|
||||
void check_nozzle_temperature_initial_layer_range(DynamicPrintConfig* config);
|
||||
void check_adaptive_pressure_advance_model(DynamicPrintConfig* config);
|
||||
void check_filament_max_volumetric_speed(DynamicPrintConfig *config);
|
||||
void check_chamber_temperature(DynamicPrintConfig* config);
|
||||
void set_is_BBL_Printer(bool is_bbl_printer) { is_BBL_Printer = is_bbl_printer; };
|
||||
|
||||
@@ -159,7 +159,7 @@ void ConnectPrinterDialog::on_button_confirm(wxCommandEvent &event)
|
||||
wxString code = m_textCtrl_code->GetTextCtrl()->GetValue();
|
||||
for (char c : code) {
|
||||
if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))) {
|
||||
show_error(this, _L("Invalid input."));
|
||||
show_error(this, _L("Invalid input"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
#include "CrealityDiscoveryDialog.hpp"
|
||||
#include "slic3r/Utils/CrealityHostDiscovery.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Widgets/DialogButtons.hpp"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/listctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/utils.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
CrealityDiscoveryDialog::CrealityDiscoveryDialog(wxWindow* parent)
|
||||
: wxDialog(parent, wxID_ANY, _L("Detect Creality K-series printer"),
|
||||
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
{
|
||||
const int em = wxGetApp().em_unit();
|
||||
|
||||
m_status = new wxStaticText(this, wxID_ANY, _L("Click Scan to look for K-series printers on your network."));
|
||||
|
||||
m_list = new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
|
||||
wxLC_REPORT | wxSIMPLE_BORDER | wxLC_SINGLE_SEL);
|
||||
m_list->SetMinSize(wxSize(50 * em, 18 * em));
|
||||
m_list->AppendColumn(_L("Model"), wxLIST_FORMAT_LEFT, 8 * em);
|
||||
m_list->AppendColumn(_L("Hostname"), wxLIST_FORMAT_LEFT, 14 * em);
|
||||
m_list->AppendColumn(_L("IP"), wxLIST_FORMAT_LEFT, 14 * em);
|
||||
|
||||
auto* dlg_btns = new DialogButtons(this, {"Scan", "OK", "Cancel"}, "", 1 /*left_aligned*/);
|
||||
auto* ok_btn = dlg_btns->GetOK();
|
||||
ok_btn->SetLabel(_L("Use Selected"));
|
||||
ok_btn->Disable();
|
||||
|
||||
auto* vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
vsizer->Add(m_status, 0, wxEXPAND | wxALL, em);
|
||||
vsizer->Add(m_list, 1, wxEXPAND | wxALL, em);
|
||||
vsizer->Add(dlg_btns, 0, wxEXPAND);
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
dlg_btns->GetFIRST()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { run_discovery(); });
|
||||
|
||||
m_list->Bind(wxEVT_LIST_ITEM_SELECTED, [ok_btn](wxListEvent&) { ok_btn->Enable(); });
|
||||
m_list->Bind(wxEVT_LIST_ITEM_DESELECTED, [ok_btn](wxListEvent&) { ok_btn->Disable(); });
|
||||
m_list->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent&) { on_ok(); EndModal(wxID_OK); });
|
||||
|
||||
ok_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { on_ok(); EndModal(wxID_OK); });
|
||||
dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { EndModal(wxID_CANCEL); });
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
// Run discovery synchronously from the ctor so results are ready by the
|
||||
// time the dialog is shown. Posting an async CallAfter from a ShowModal
|
||||
// override risked the event firing after the modal loop had exited -- the
|
||||
// captured `this` would then be a dangling stack pointer and subsequent
|
||||
// UI access could fault.
|
||||
run_discovery();
|
||||
}
|
||||
|
||||
void CrealityDiscoveryDialog::run_discovery()
|
||||
{
|
||||
m_status->SetLabel(_L("Scanning the LAN for K-series printers... this takes a few seconds."));
|
||||
m_list->DeleteAllItems();
|
||||
m_rows.clear();
|
||||
Layout();
|
||||
Update();
|
||||
|
||||
std::vector<CrealityHost> hosts;
|
||||
{
|
||||
wxBusyCursor cursor;
|
||||
wxWindowDisabler disabler(this);
|
||||
hosts = CrealityHostDiscovery::scan(/*probe_info=*/true);
|
||||
}
|
||||
|
||||
for (const auto& h : hosts) {
|
||||
Row row;
|
||||
row.ip = h.ip;
|
||||
row.hostname = h.hostname;
|
||||
if (!h.model_name.empty())
|
||||
row.model = h.model_name;
|
||||
else if (h.cfs_capable)
|
||||
row.model = "(unknown K-series)";
|
||||
else
|
||||
row.model = "Creality";
|
||||
m_rows.push_back(std::move(row));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_rows.size(); ++i) {
|
||||
long idx = m_list->InsertItem(i, wxString::FromUTF8(m_rows[i].model));
|
||||
m_list->SetItem(idx, 1, wxString::FromUTF8(m_rows[i].hostname));
|
||||
m_list->SetItem(idx, 2, wxString::FromUTF8(m_rows[i].ip));
|
||||
}
|
||||
|
||||
if (m_rows.empty()) {
|
||||
m_status->SetLabel(_L(
|
||||
"No K-series printers found. Make sure the printer is on the same "
|
||||
"network and not blocked by Wi-Fi client isolation, then click Scan again."));
|
||||
} else {
|
||||
m_status->SetLabel(wxString::Format(
|
||||
_L("Found %zu Creality printer(s). Select one and click Use Selected."),
|
||||
m_rows.size()));
|
||||
m_list->Select(0);
|
||||
}
|
||||
}
|
||||
|
||||
void CrealityDiscoveryDialog::on_ok()
|
||||
{
|
||||
auto sel = m_list->GetFirstSelected();
|
||||
if (sel >= 0 && sel < int(m_rows.size())) {
|
||||
m_selected_ip = m_rows[sel].ip;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef slic3r_CrealityDiscoveryDialog_hpp_
|
||||
#define slic3r_CrealityDiscoveryDialog_hpp_
|
||||
|
||||
#include <wx/dialog.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class wxListView;
|
||||
class wxStaticText;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
// Modal dialog that finds Creality K-series printers on the LAN via DNS-SD
|
||||
// mDNS (vendored mjansson/mdns + cxmdns wrapper) and lets the user pick one.
|
||||
//
|
||||
// Discovery is synchronous (~5 second mDNS listen + ~2-4 sec /info probe per
|
||||
// host) and runs during ShowModal() with a busy cursor. The user-facing busy
|
||||
// time is bounded by the mDNS listener's 5-second deadline plus any in-flight
|
||||
// probes; in practice 5-10 seconds total for a typical LAN with one K2.
|
||||
//
|
||||
// After ShowModal() returns wxID_OK, selected_ip() yields the chosen
|
||||
// printer's IPv4 address. Returns the empty string on Cancel or if no match
|
||||
// was found.
|
||||
class CrealityDiscoveryDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
CrealityDiscoveryDialog(wxWindow* parent);
|
||||
~CrealityDiscoveryDialog() override = default;
|
||||
|
||||
std::string selected_ip() const { return m_selected_ip; }
|
||||
|
||||
private:
|
||||
void run_discovery();
|
||||
void on_ok();
|
||||
|
||||
struct Row { std::string ip; std::string model; std::string hostname; };
|
||||
|
||||
wxListView* m_list = nullptr;
|
||||
wxStaticText* m_status = nullptr;
|
||||
std::vector<Row> m_rows;
|
||||
std::string m_selected_ip;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
@@ -138,7 +138,7 @@ static const std::unordered_map<std::string, std::vector<std::string>> printer_m
|
||||
"RatRig V-Core 4 IDEX 400", "RatRig V-Core 4 IDEX 400 COPY MODE", "RatRig V-Core 4 IDEX 400 MIRROR MODE", "RatRig V-Core 4 IDEX 500", "RatRig V-Core 4 IDEX 500 COPY MODE",
|
||||
"RatRig V-Core 4 IDEX 500 MIRROR MODE"}},
|
||||
{"re3D", {"re3D Gigabot 4", "re3D Gigabot 4 XLT", "re3D Terabot 4",
|
||||
"re3D Gigabot X2", "re3D Gigabot X2 XLT", "re3D Terabot X2"}},
|
||||
"re3D GigabotX 2", "re3D GigabotX 2 XLT", "re3D TerabotX 2"}},
|
||||
{"RolohaunDesign", {"Rook MK1 LDO"}},
|
||||
{"SecKit", {"SecKit SK-Tank", "Seckit Go3"}},
|
||||
{"Snapmaker", {"Snapmaker J1", "Snapmaker A250", "Snapmaker A350", "Snapmaker A250 Dual", "Snapmaker A350 Dual",
|
||||
@@ -1049,7 +1049,7 @@ wxWindow *CreateFilamentPresetDialog::create_dialog_buttons()
|
||||
|
||||
if (!m_can_not_find_vendor_checkbox->GetValue()) {
|
||||
if (_L("Select Vendor") == vendor_str) {
|
||||
MessageDialog dlg(this, _L("Vendor is not selected, please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
MessageDialog dlg(this, _L("Vendor is not selected; please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return;
|
||||
@@ -1058,7 +1058,7 @@ wxWindow *CreateFilamentPresetDialog::create_dialog_buttons()
|
||||
}
|
||||
} else {
|
||||
if (m_filament_custom_vendor_input->GetTextCtrl()->GetValue().empty()) {
|
||||
MessageDialog dlg(this, _L("Custom vendor is missing, please input custom vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
MessageDialog dlg(this, _L("Custom vendor missing; please input custom vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return;
|
||||
@@ -1087,7 +1087,7 @@ wxWindow *CreateFilamentPresetDialog::create_dialog_buttons()
|
||||
wxString serial_str = m_filament_serial_input->GetTextCtrl()->GetValue();
|
||||
std::string serial_name;
|
||||
if (serial_str.empty()) {
|
||||
MessageDialog dlg(this, _L("Filament serial is not entered, please enter serial."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
MessageDialog dlg(this, _L("Filament serial missing; please input serial."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return;
|
||||
@@ -1098,7 +1098,7 @@ wxWindow *CreateFilamentPresetDialog::create_dialog_buttons()
|
||||
serial_name = remove_special_key(serial_name);
|
||||
|
||||
if (vendor_name.empty() || serial_name.empty()) {
|
||||
MessageDialog dlg(this, _L("There may be escape characters in the vendor or serial input of filament. Please delete and re-enter."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
MessageDialog dlg(this, _L("There may be disallowed characters in the vendor or serial input of the filament. Please delete and re-enter."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return;
|
||||
@@ -1112,7 +1112,7 @@ wxWindow *CreateFilamentPresetDialog::create_dialog_buttons()
|
||||
return;
|
||||
}
|
||||
if (m_can_not_find_vendor_checkbox->GetValue() && str_is_all_digit(vendor_name)) {
|
||||
MessageDialog dlg(this, _L("The vendor cannot be a number. Please re-enter."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
MessageDialog dlg(this, _L("The vendor cannot be a number; please re-enter."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return;
|
||||
@@ -1129,8 +1129,7 @@ wxWindow *CreateFilamentPresetDialog::create_dialog_buttons()
|
||||
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
|
||||
if (preset_bundle->filaments.is_alias_exist(filament_preset_name)) {
|
||||
MessageDialog dlg(this,
|
||||
wxString::Format(_L("The Filament name %s you created already exists.\n"
|
||||
"If you continue, the preset created will be displayed with its full name. Do you want to continue?"),
|
||||
wxString::Format(_L("The Filament name %s you created already exists.\nIf you continue, the preset created will be displayed with its full name. Do you want to continue\?"),
|
||||
from_u8(filament_preset_name)),
|
||||
wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
if (wxID_YES != dlg.ShowModal()) { return; }
|
||||
@@ -1593,8 +1592,7 @@ CreatePrinterPresetDialog::CreatePrinterPresetDialog(wxWindow *parent)
|
||||
m_page1 = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_page1->SetBackgroundColour(*wxWHITE);
|
||||
m_page1->SetScrollRate(5, 5);
|
||||
m_page2 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);\
|
||||
m_page2->SetBackgroundColour(*wxWHITE);
|
||||
m_page2 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_page2->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
create_printer_page1(m_page1);
|
||||
create_printer_page2(m_page2);
|
||||
@@ -1756,7 +1754,7 @@ wxBoxSizer *CreatePrinterPresetDialog::create_printer_item(wxWindow *parent)
|
||||
m_select_model->SetLabelColor(*wxBLACK);
|
||||
}
|
||||
} else {
|
||||
MessageDialog dlg(this, _L("The model was not found, please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
MessageDialog dlg(this, _L("The model was not found; please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
}
|
||||
|
||||
@@ -2190,7 +2188,7 @@ bool CreatePrinterPresetDialog::load_system_and_user_presets_with_curr_model(Pre
|
||||
}
|
||||
if (m_printer_preset_vendor_selected.id.empty() || m_printer_preset_model_selected.id.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "selected id was not found";
|
||||
MessageDialog dlg(this, _L("Preset path was not found, please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
MessageDialog dlg(this, _L("Preset path was not found; please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return false;
|
||||
}
|
||||
@@ -2211,7 +2209,7 @@ bool CreatePrinterPresetDialog::load_system_and_user_presets_with_curr_model(Pre
|
||||
|
||||
if (preset_path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Preset path was not found";
|
||||
MessageDialog dlg(this, _L("Preset path was not found, please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
MessageDialog dlg(this, _L("Preset path was not found; please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return false;
|
||||
@@ -2248,7 +2246,7 @@ bool CreatePrinterPresetDialog::load_system_and_user_presets_with_curr_model(Pre
|
||||
varient = model_varient.substr(index_at + 3, index_nozzle - index_at - 4);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "get nozzle failed";
|
||||
MessageDialog dlg(this, _L("The nozzle diameter was not found, please reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
MessageDialog dlg(this, _L("The nozzle diameter was not found; please reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return false;
|
||||
}
|
||||
@@ -2259,7 +2257,7 @@ bool CreatePrinterPresetDialog::load_system_and_user_presets_with_curr_model(Pre
|
||||
if (temp_printer_preset) {
|
||||
m_printer_preset = new Preset(*temp_printer_preset);
|
||||
} else {
|
||||
MessageDialog dlg(this, _L("The printer preset was not found, please reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
MessageDialog dlg(this, _L("The printer preset was not found; please reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return false;
|
||||
}
|
||||
@@ -2275,7 +2273,7 @@ bool CreatePrinterPresetDialog::load_system_and_user_presets_with_curr_model(Pre
|
||||
}
|
||||
if (preset_path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Preset path was not found";
|
||||
MessageDialog dlg(this, _L("Preset path was not found, please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
MessageDialog dlg(this, _L("Preset path was not found; please reselect vendor."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return false;
|
||||
@@ -2762,7 +2760,7 @@ wxWindow *CreatePrinterPresetDialog::create_page2_dialog_buttons(wxWindow *paren
|
||||
}
|
||||
|
||||
if (!save_printable_area_config(m_printer_preset)) {
|
||||
MessageDialog dlg(this, _L("You have entered an illegal input in the printable area section on the first page. Please check before creating it."),
|
||||
MessageDialog dlg(this, _L("You have entered a disallowed character in the printable area section on the first page. Please use only numbers."),
|
||||
wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
show_page1();
|
||||
@@ -2783,9 +2781,7 @@ wxWindow *CreatePrinterPresetDialog::create_page2_dialog_buttons(wxWindow *paren
|
||||
// Confirm if the printer preset has a duplicate name
|
||||
if (!rewritten && preset_bundle->printers.find_preset(printer_preset_name)) {
|
||||
MessageDialog dlg(this,
|
||||
_L("The printer preset you created already has a preset with the same name. Do you want to overwrite it?\n\tYes: Overwrite the printer preset with the "
|
||||
"same name, and filament and process presets with the same preset name will be recreated \nand filament and process presets without the same preset name will be reserve.\n\tCancel: Do not create a preset, return to the "
|
||||
"creation interface."),
|
||||
_L("The printer preset you created already has a preset with the same name. Do you want to overwrite it\?\n\tYes: Overwrite the printer preset with the same name, and filament and process presets with the same preset name will be recreated \nand filament and process presets without the same preset name will be reserved.\n\tCancel: Do not create a preset; return to the creation interface."),
|
||||
wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES | wxCANCEL | wxYES_DEFAULT | wxCENTRE);
|
||||
int res = dlg.ShowModal();
|
||||
if (res == wxID_YES) {
|
||||
@@ -3044,7 +3040,7 @@ void CreatePrinterPresetDialog::on_select_printer_model(wxCommandEvent &e)
|
||||
return;
|
||||
}
|
||||
|
||||
MessageDialog dlg(this, _L("Vendor was not found, please reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
MessageDialog dlg(this, _L("Vendor was not found; please reselect."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
@@ -3317,7 +3313,7 @@ bool CreatePrinterPresetDialog::validate_input_valid()
|
||||
std::string vendor_name = get_printer_vendor();
|
||||
std::string model_name = get_printer_model();
|
||||
if ((vendor_name.empty() || model_name.empty())) {
|
||||
MessageDialog dlg(this, _L("You have not selected the vendor and model or entered the custom vendor and model."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
MessageDialog dlg(this, _L("You have not selected the vendor and model or input the custom vendor and model."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return false;
|
||||
@@ -3348,7 +3344,7 @@ bool CreatePrinterPresetDialog::validate_input_valid()
|
||||
} else if (curr_selected_printer_type == m_create_type.create_nozzle) {
|
||||
wxString printer_name = m_select_printer->GetStringSelection();
|
||||
if (printer_name.empty()) {
|
||||
MessageDialog dlg(this, _L("You have not yet selected the printer to replace the nozzle, please choose."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
MessageDialog dlg(this, _L("You have not yet selected the printer to replace the nozzle for; please choose a printer."), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
return false;
|
||||
@@ -3482,8 +3478,7 @@ CreatePresetSuccessfulDialog::CreatePresetSuccessfulDialog(wxWindow *parent, con
|
||||
break;
|
||||
case FILAMENT:
|
||||
success_text = new wxStaticText(this, wxID_ANY, _L("Filament Created"));
|
||||
wxString prompt_text = _L("Please go to filament setting to edit your presets if you need.\nPlease note that nozzle temperature, hot bed temperature, and maximum "
|
||||
"volumetric speed has a significant impact on printing quality. Please set them carefully.");
|
||||
wxString prompt_text = _L("Please go to filament settings to edit your presets if you need to.\nPlease note that nozzle temperature, hot bed temperature, and maximum volumetric speed each have a significant impact on printing quality. Please set them carefully.");
|
||||
wxString sync_text = sync_user_preset_need_enabled ? _L("\n\nOrca has detected that your user presets synchronization function is not enabled, "
|
||||
"which may result in unsuccessful Filament settings on the Device page.\n"
|
||||
"Click \"Sync user presets\" to enable the synchronization function.") : "";
|
||||
@@ -3691,8 +3686,7 @@ std::string ExportConfigsDialog::initial_file_name(const wxString &path, const s
|
||||
std::string export_path = into_u8(path);
|
||||
boost::filesystem::path printer_export_path = (boost::filesystem::path(export_path) / file_name).make_preferred();
|
||||
if (boost::filesystem::exists(printer_export_path)) {
|
||||
MessageDialog dlg(this, wxString::Format(_L("The '%s' folder already exists in the current directory. Do you want to clear it and rebuild it.\nIf not, a time suffix will be "
|
||||
"added, and you can modify the name after creation."), file_name), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
MessageDialog dlg(this, wxString::Format(_L("The \'%s\' folder already exists in the current directory. Do you want to clear it and rebuild it\?\nIf not, a time suffix will be added, and you can modify the name after creation."), file_name), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
int res = dlg.ShowModal();
|
||||
if (wxID_YES == res) {
|
||||
try {
|
||||
@@ -3864,7 +3858,7 @@ void ExportConfigsDialog::select_curr_radiobox(std::vector<std::pair<RadioBox *,
|
||||
wxString printer_name = wxString::FromUTF8(preset_name);
|
||||
m_preset_sizer->Add(create_checkbox(m_presets_window, preset.second, printer_name, m_preset), 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, FromDIP(5));
|
||||
}
|
||||
m_serial_text->SetLabel(_L("Only display printer names with changes to printer, filament, and process presets."));
|
||||
m_serial_text->SetLabel(_L("Only display printers with changes to printer, filament, and process presets are displayed."));
|
||||
}else if (export_type == m_exprot_type.filament_bundle) {
|
||||
for (std::pair<std::string, std::vector<std::pair<std::string, Preset*>>> filament_name_to_preset : m_filament_name_to_presets) {
|
||||
if (filament_name_to_preset.second.empty()) continue;
|
||||
@@ -4302,7 +4296,7 @@ wxBoxSizer *ExportConfigsDialog::create_select_printer(wxWindow *parent)
|
||||
wxBoxSizer *horizontal_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxBoxSizer * optionSizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_serial_text = new wxStaticText(parent, wxID_ANY, _L("Please select a type you want to export"), wxDefaultPosition, wxDefaultSize);
|
||||
m_serial_text = new wxStaticText(parent, wxID_ANY, _L("Please select a preset type you want to export"), wxDefaultPosition, wxDefaultSize);
|
||||
optionSizer->Add(m_serial_text, 0, wxEXPAND | wxALL, 0);
|
||||
optionSizer->SetMinSize(OPTION_SIZE);
|
||||
horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL, FromDIP(10));
|
||||
|
||||
@@ -202,7 +202,7 @@ void DailyTipsDataRenderer::render_text(const ImVec2& start_pos, const ImVec2& s
|
||||
|
||||
// wiki
|
||||
if (!m_data.wiki_url.empty()) {
|
||||
std::string tips_line = _u8L("For more information, please check out Wiki");
|
||||
std::string tips_line = _u8L("For more information, please check out our Wiki");
|
||||
std::string wiki_part_text = _u8L("Wiki");
|
||||
std::string first_part_text = tips_line.substr(0, tips_line.find(wiki_part_text));
|
||||
ImVec2 wiki_part_size = ImGui::CalcTextSize(wiki_part_text.c_str());
|
||||
|
||||
@@ -645,7 +645,7 @@ DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent)
|
||||
|
||||
|
||||
wxBoxSizer *btn_szr = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxButton *btn_perform = new wxButton(this, wxID_ANY, _L("Perform"));
|
||||
wxButton *btn_perform = new wxButton(this, wxID_ANY, _L("Apply"));
|
||||
btn_szr->Add(btn_perform, 0, wxALL, 10);
|
||||
|
||||
btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(); EndModal(wxID_ANY); });
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include "DevManager.h"
|
||||
#include "DevUtil.h"
|
||||
|
||||
@@ -151,11 +154,15 @@ namespace Slic3r
|
||||
{
|
||||
keep_alive();
|
||||
MachineObject* obj = this->get_selected_machine();
|
||||
if (!obj) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "DeviceManager::check_pushing selected machine not found";
|
||||
return;
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
|
||||
auto internal = std::chrono::duration_cast<std::chrono::milliseconds>(start - obj->last_update_time);
|
||||
|
||||
if (obj && !obj->is_support_mqtt_alive)
|
||||
if (!obj->is_support_mqtt_alive)
|
||||
{
|
||||
if (internal.count() > TIMEOUT_FOR_STRAT && internal.count() < 1000 * 60 * 60 * 300)
|
||||
{
|
||||
@@ -916,18 +923,33 @@ namespace Slic3r
|
||||
const auto cloud_provider = Slic3r::GUI::wxGetApp().get_printer_cloud_provider();
|
||||
if (Slic3r::GUI::wxGetApp().is_user_login(cloud_provider))
|
||||
{
|
||||
m_manager->check_pushing();
|
||||
try
|
||||
{
|
||||
agent->refresh_connection(cloud_provider);
|
||||
try {
|
||||
m_manager->check_pushing();
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer check_pushing exception="
|
||||
<< e.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer check_pushing unknown exception";
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
;
|
||||
|
||||
try {
|
||||
agent->refresh_connection(cloud_provider);
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer refresh_connection exception="
|
||||
<< e.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer refresh_connection unknown exception";
|
||||
}
|
||||
}
|
||||
|
||||
// certificate
|
||||
agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
|
||||
try {
|
||||
agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer install_device_cert exception="
|
||||
<< e.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "DeviceManagerRefresher::on_timer install_device_cert unknown exception";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@ void DeviceErrorDialog::init_button_list()
|
||||
init_button(FILAMENT_EXTRUDED, _L("Filament Extruded, Continue"));
|
||||
init_button(RETRY_FILAMENT_EXTRUDED, _L("Not Extruded Yet, Retry"));
|
||||
init_button(CONTINUE, _L("Finished, Continue"));
|
||||
init_button(LOAD_VIRTUAL_TRAY, _L("Load Filament"));
|
||||
init_button(LOAD_VIRTUAL_TRAY, _L("Load"));
|
||||
init_button(OK_BUTTON, _L("OK"));
|
||||
init_button(FILAMENT_LOAD_RESUME, _L("Filament Loaded, Resume"));
|
||||
init_button(JUMP_TO_LIVEVIEW, _L("View Liveview"));
|
||||
|
||||
@@ -38,7 +38,7 @@ DownloadProgressDialog::DownloadProgressDialog(wxString title)
|
||||
wxString install_failed_url = wxT("https://wiki.bambulab.com/en/software/bambu-studio/failed-to-get-network-plugin");
|
||||
|
||||
wxString download_failed_msg = _L("Failed to download the plug-in. Please check your firewall settings and VPN software and retry.");
|
||||
wxString install_failed_msg = _L("Failed to install the plug-in. The plug-in file may be in use. Please restart OrcaSlicer and try again. Also check whether it is blocked or deleted by anti-virus software.");
|
||||
wxString install_failed_msg = _L("Failed to install the plug-in. The plug-in file may be in use. Please restart OrcaSlicer and try again. Also check whether it is blocked or has been deleted by anti-virus software.");
|
||||
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
wxBoxSizer *m_sizer_main = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -86,7 +86,7 @@ void ExtrusionCalibration::create()
|
||||
select_sizer->Add(m_comboBox_filament, 0, wxEXPAND);
|
||||
select_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
|
||||
|
||||
auto bed_type_sel_text = new wxStaticText(m_step_1_panel, wxID_ANY, _L("Bed Type"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
auto bed_type_sel_text = new wxStaticText(m_step_1_panel, wxID_ANY, _L("Plate Type"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
select_sizer->Add(bed_type_sel_text, 0, wxALIGN_LEFT);
|
||||
select_sizer->AddSpacer(FromDIP(4));
|
||||
|
||||
@@ -170,7 +170,7 @@ void ExtrusionCalibration::create()
|
||||
m_error_text->SetForegroundColour(wxColour(208, 27, 27));
|
||||
m_error_text->Hide();
|
||||
|
||||
m_button_cali = new Button(m_step_1_panel, _L("Start calibration"));
|
||||
m_button_cali = new Button(m_step_1_panel, _L("Start"));
|
||||
m_button_cali->SetStyle(ButtonStyle::Confirm, ButtonType::Choice);
|
||||
m_button_cali->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_cali, this);
|
||||
|
||||
@@ -239,7 +239,7 @@ void ExtrusionCalibration::create()
|
||||
m_button_save_result->SetStyle(ButtonStyle::Confirm, ButtonType::Choice);
|
||||
m_button_save_result->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_save, this);
|
||||
|
||||
m_button_last_step = new Button(m_step_2_panel, _L("Last Step")); // Back for english
|
||||
m_button_last_step = new Button(m_step_2_panel, _L("Back")); // Back for english
|
||||
m_button_last_step->SetStyle(ButtonStyle::Regular, ButtonType::Choice);
|
||||
m_button_last_step->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_last, this);
|
||||
|
||||
@@ -712,12 +712,12 @@ void ExtrusionCalibration::post_select_event() {
|
||||
void ExtrusionCalibration::set_step(int step_index)
|
||||
{
|
||||
if (step_index == 2) {
|
||||
wxString title_text = wxString::Format("%s - %s 2/2", _L("Dynamic flow Calibration"), _L("Step"));
|
||||
wxString title_text = wxString::Format("%s - %s 2/2", _L("Dynamic flow calibration"), _L("Step"));
|
||||
SetTitle(title_text);
|
||||
m_step_1_panel->Hide();
|
||||
m_step_2_panel->Show();
|
||||
} else {
|
||||
wxString title_text = wxString::Format("%s - %s 1/2", _L("Dynamic flow Calibration"), _L("Step"));
|
||||
wxString title_text = wxString::Format("%s - %s 1/2", _L("Dynamic flow calibration"), _L("Step"));
|
||||
SetTitle(title_text);
|
||||
m_step_1_panel->Show();
|
||||
m_step_2_panel->Hide();
|
||||
|
||||
@@ -367,7 +367,7 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
|
||||
}
|
||||
|
||||
wxString label = m_opt.full_label.empty() ? _(m_opt.label) : _(m_opt.full_label);
|
||||
show_error(m_parent, from_u8((boost::format(_utf8(L("%s can't be a percentage"))) % into_u8(label)).str()));
|
||||
show_error(m_parent, from_u8((boost::format(_utf8(L("%s can\u2019t be a percentage"))) % into_u8(label)).str()));
|
||||
set_value(double_to_string(m_opt.min), true);
|
||||
m_value = double(m_opt.min);
|
||||
break;
|
||||
|
||||
@@ -190,6 +190,7 @@ public:
|
||||
/// Call the attached m_fn_edit_value method.
|
||||
void on_edit_value();
|
||||
|
||||
virtual void propagate_value(){}
|
||||
public:
|
||||
/// parent wx item, opportunity to refactor (probably not necessary - data duplication)
|
||||
wxWindow* m_parent {nullptr};
|
||||
@@ -317,7 +318,7 @@ public:
|
||||
void BUILD() override;
|
||||
bool value_was_changed();
|
||||
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
|
||||
void propagate_value();
|
||||
virtual void propagate_value() override;
|
||||
wxWindow* window {nullptr};
|
||||
|
||||
void set_value(const std::string& value, bool change_event = false) {
|
||||
|
||||
@@ -338,7 +338,21 @@ void FilamentGroupPopup::OnRadioBtn(int idx)
|
||||
}
|
||||
}
|
||||
|
||||
void FilamentGroupPopup::OnTimer(wxTimerEvent &event) { Dismiss(); }
|
||||
void FilamentGroupPopup::OnTimer(wxTimerEvent &event)
|
||||
{
|
||||
#if __APPLE__
|
||||
// On macOS, when moving cursor from slice button to this popup window,
|
||||
// the popup window entering event is triggered first, then the slice button
|
||||
// leaving event got triggered. So the timer is stopped first, then started
|
||||
// again, causing the popup being dismissed immediately.
|
||||
// To fix this, we check if cursor is still inside the popup window before
|
||||
// dismissing.
|
||||
wxPoint pos = this->ScreenToClient(wxGetMousePosition());
|
||||
if (this->GetClientRect().Contains(pos)) return;
|
||||
#endif
|
||||
|
||||
Dismiss();
|
||||
}
|
||||
|
||||
void FilamentGroupPopup::Dismiss() {
|
||||
m_active = false;
|
||||
|
||||
Loaded 100 of 2782 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user