mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-23 08:41:11 -06:00
Merge remote-tracking branch 'remotes/origin/master' into lh_adaptive_infill_hooks
This commit is contained in:
commit
414fdaefc5
228 changed files with 30930 additions and 13115 deletions
|
@ -2,6 +2,7 @@
|
|||
#include "libslic3r/Utils.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Thread.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -37,56 +38,89 @@ void AppConfig::reset()
|
|||
// Override missing or keys with their defaults.
|
||||
void AppConfig::set_defaults()
|
||||
{
|
||||
// Reset the empty fields to defaults.
|
||||
if (get("autocenter").empty())
|
||||
set("autocenter", "0");
|
||||
// Disable background processing by default as it is not stable.
|
||||
if (get("background_processing").empty())
|
||||
set("background_processing", "0");
|
||||
// If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
|
||||
// By default, Prusa has the controller hidden.
|
||||
if (get("no_controller").empty())
|
||||
set("no_controller", "1");
|
||||
// If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available.
|
||||
if (get("no_defaults").empty())
|
||||
set("no_defaults", "1");
|
||||
if (get("show_incompatible_presets").empty())
|
||||
set("show_incompatible_presets", "0");
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
if (m_mode == EAppMode::Editor) {
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
// Reset the empty fields to defaults.
|
||||
if (get("autocenter").empty())
|
||||
set("autocenter", "0");
|
||||
// Disable background processing by default as it is not stable.
|
||||
if (get("background_processing").empty())
|
||||
set("background_processing", "0");
|
||||
// If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
|
||||
// By default, Prusa has the controller hidden.
|
||||
if (get("no_controller").empty())
|
||||
set("no_controller", "1");
|
||||
// If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available.
|
||||
if (get("no_defaults").empty())
|
||||
set("no_defaults", "1");
|
||||
if (get("show_incompatible_presets").empty())
|
||||
set("show_incompatible_presets", "0");
|
||||
|
||||
if (get("version_check").empty())
|
||||
set("version_check", "1");
|
||||
if (get("preset_update").empty())
|
||||
set("preset_update", "1");
|
||||
if (get("version_check").empty())
|
||||
set("version_check", "1");
|
||||
if (get("preset_update").empty())
|
||||
set("preset_update", "1");
|
||||
|
||||
if (get("export_sources_full_pathnames").empty())
|
||||
set("export_sources_full_pathnames", "0");
|
||||
if (get("export_sources_full_pathnames").empty())
|
||||
set("export_sources_full_pathnames", "0");
|
||||
|
||||
// remove old 'use_legacy_opengl' parameter from this config, if present
|
||||
if (!get("use_legacy_opengl").empty())
|
||||
erase("", "use_legacy_opengl");
|
||||
// remove old 'use_legacy_opengl' parameter from this config, if present
|
||||
if (!get("use_legacy_opengl").empty())
|
||||
erase("", "use_legacy_opengl");
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (get("use_retina_opengl").empty())
|
||||
set("use_retina_opengl", "1");
|
||||
if (get("use_retina_opengl").empty())
|
||||
set("use_retina_opengl", "1");
|
||||
#endif
|
||||
|
||||
if (get("single_instance").empty())
|
||||
set("single_instance", "0");
|
||||
if (get("single_instance").empty())
|
||||
set("single_instance",
|
||||
#ifdef __APPLE__
|
||||
"1"
|
||||
#else // __APPLE__
|
||||
"0"
|
||||
#endif // __APPLE__
|
||||
);
|
||||
|
||||
if (get("remember_output_path").empty())
|
||||
set("remember_output_path", "1");
|
||||
if (get("remember_output_path").empty())
|
||||
set("remember_output_path", "1");
|
||||
|
||||
if (get("remember_output_path_removable").empty())
|
||||
set("remember_output_path_removable", "1");
|
||||
if (get("remember_output_path_removable").empty())
|
||||
set("remember_output_path_removable", "1");
|
||||
|
||||
if (get("use_custom_toolbar_size").empty())
|
||||
set("use_custom_toolbar_size", "0");
|
||||
if (get("use_custom_toolbar_size").empty())
|
||||
set("use_custom_toolbar_size", "0");
|
||||
|
||||
if (get("custom_toolbar_size").empty())
|
||||
set("custom_toolbar_size", "100");
|
||||
if (get("custom_toolbar_size").empty())
|
||||
set("custom_toolbar_size", "100");
|
||||
|
||||
if (get("auto_toolbar_size").empty())
|
||||
set("auto_toolbar_size", "100");
|
||||
if (get("auto_toolbar_size").empty())
|
||||
set("auto_toolbar_size", "100");
|
||||
|
||||
#if !ENABLE_GCODE_VIEWER
|
||||
if (get("use_perspective_camera").empty())
|
||||
set("use_perspective_camera", "1");
|
||||
|
||||
if (get("use_free_camera").empty())
|
||||
set("use_free_camera", "0");
|
||||
|
||||
if (get("reverse_mouse_wheel_zoom").empty())
|
||||
set("reverse_mouse_wheel_zoom", "0");
|
||||
#endif // !ENABLE_GCODE_VIEWER
|
||||
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
if (get("use_environment_map").empty())
|
||||
set("use_environment_map", "0");
|
||||
#endif // ENABLE_ENVIRONMENT_MAP
|
||||
|
||||
if (get("use_inches").empty())
|
||||
set("use_inches", "0");
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
}
|
||||
|
||||
if (get("seq_top_layer_only").empty())
|
||||
set("seq_top_layer_only", "1");
|
||||
|
||||
if (get("use_perspective_camera").empty())
|
||||
set("use_perspective_camera", "1");
|
||||
|
@ -94,17 +128,19 @@ void AppConfig::set_defaults()
|
|||
if (get("use_free_camera").empty())
|
||||
set("use_free_camera", "0");
|
||||
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
if (get("use_environment_map").empty())
|
||||
set("use_environment_map", "0");
|
||||
#endif // ENABLE_ENVIRONMENT_MAP
|
||||
|
||||
if (get("use_inches").empty())
|
||||
set("use_inches", "0");
|
||||
if (get("reverse_mouse_wheel_zoom").empty())
|
||||
set("reverse_mouse_wheel_zoom", "0");
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
|
||||
if (get("show_splash_screen").empty())
|
||||
set("show_splash_screen", "1");
|
||||
|
||||
if (get("default_action_on_close_application").empty())
|
||||
set("default_action_on_close_application", "none"); // , "discard" or "save"
|
||||
|
||||
if (get("default_action_on_select_preset").empty())
|
||||
set("default_action_on_select_preset", "none"); // , "transfer", "discard" or "save"
|
||||
|
||||
// Remove legacy window positions/sizes
|
||||
erase("", "main_frame_maximized");
|
||||
erase("", "main_frame_pos");
|
||||
|
@ -175,6 +211,20 @@ std::string AppConfig::load()
|
|||
m_legacy_datadir = ini_ver < Semver(1, 40, 0);
|
||||
}
|
||||
|
||||
// Legacy conversion
|
||||
if (m_mode == EAppMode::Editor) {
|
||||
// Convert [extras] "physical_printer" to [presets] "physical_printer",
|
||||
// remove the [extras] section if it becomes empty.
|
||||
if (auto it_section = m_storage.find("extras"); it_section != m_storage.end()) {
|
||||
if (auto it_physical_printer = it_section->second.find("physical_printer"); it_physical_printer != it_section->second.end()) {
|
||||
m_storage["presets"]["physical_printer"] = it_physical_printer->second;
|
||||
it_section->second.erase(it_physical_printer);
|
||||
}
|
||||
if (it_section->second.empty())
|
||||
m_storage.erase(it_section);
|
||||
}
|
||||
}
|
||||
|
||||
// Override missing or keys with their defaults.
|
||||
this->set_defaults();
|
||||
m_dirty = false;
|
||||
|
@ -183,10 +233,12 @@ std::string AppConfig::load()
|
|||
|
||||
void AppConfig::save()
|
||||
{
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
if (!m_save_enabled)
|
||||
return;
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
{
|
||||
// Returns "undefined" if the thread naming functionality is not supported by the operating system.
|
||||
std::optional<std::string> current_thread_name = get_current_thread_name();
|
||||
if (current_thread_name && *current_thread_name != "slic3r_main")
|
||||
throw CriticalException("Calling AppConfig::save() from a worker thread!");
|
||||
}
|
||||
|
||||
// The config is first written to a file with a PID suffix and then moved
|
||||
// to avoid race conditions with multiple instances of Slic3r
|
||||
|
@ -195,7 +247,14 @@ void AppConfig::save()
|
|||
|
||||
boost::nowide::ofstream c;
|
||||
c.open(path_pid, std::ios::out | std::ios::trunc);
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
if (m_mode == EAppMode::Editor)
|
||||
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||
else
|
||||
c << "# " << Slic3r::header_gcodeviewer_generated() << std::endl;
|
||||
#else
|
||||
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
// Make sure the "no" category is written first.
|
||||
for (const std::pair<std::string, std::string> &kvp : m_storage[""])
|
||||
c << kvp.first << " = " << kvp.second << std::endl;
|
||||
|
@ -389,13 +448,22 @@ void AppConfig::reset_selections()
|
|||
it->second.erase("sla_print");
|
||||
it->second.erase("sla_material");
|
||||
it->second.erase("printer");
|
||||
it->second.erase("physical_printer");
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string AppConfig::config_path()
|
||||
{
|
||||
return (boost::filesystem::path(Slic3r::data_dir()) / (SLIC3R_APP_KEY ".ini")).make_preferred().string();
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
std::string path = (m_mode == EAppMode::Editor) ?
|
||||
(boost::filesystem::path(Slic3r::data_dir()) / (SLIC3R_APP_KEY ".ini")).make_preferred().string() :
|
||||
(boost::filesystem::path(Slic3r::data_dir()) / (GCODEVIEWER_APP_KEY ".ini")).make_preferred().string();
|
||||
|
||||
return path;
|
||||
#else
|
||||
return (boost::filesystem::path(Slic3r::data_dir()) / (SLIC3R_APP_KEY ".ini")).make_preferred().string();
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
}
|
||||
|
||||
std::string AppConfig::version_check_url() const
|
||||
|
@ -406,7 +474,11 @@ std::string AppConfig::version_check_url() const
|
|||
|
||||
bool AppConfig::exists()
|
||||
{
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
return boost::filesystem::exists(config_path());
|
||||
#else
|
||||
return boost::filesystem::exists(AppConfig::config_path());
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
|
|
@ -15,11 +15,21 @@ namespace Slic3r {
|
|||
class AppConfig
|
||||
{
|
||||
public:
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
enum class EAppMode : unsigned char
|
||||
{
|
||||
Editor,
|
||||
GCodeViewer
|
||||
};
|
||||
|
||||
explicit AppConfig(EAppMode mode) :
|
||||
#else
|
||||
AppConfig() :
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
m_dirty(false),
|
||||
m_orig_version(Semver::invalid()),
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
m_save_enabled(true),
|
||||
m_mode(mode),
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
m_legacy_datadir(false)
|
||||
{
|
||||
|
@ -125,22 +135,30 @@ public:
|
|||
void reset_selections();
|
||||
|
||||
// Get the default config path from Slic3r::data_dir().
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
std::string config_path();
|
||||
#else
|
||||
static std::string config_path();
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
|
||||
// Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating)
|
||||
bool legacy_datadir() const { return m_legacy_datadir; }
|
||||
void set_legacy_datadir(bool value) { m_legacy_datadir = value; }
|
||||
bool legacy_datadir() const { return m_legacy_datadir; }
|
||||
void set_legacy_datadir(bool value) { m_legacy_datadir = value; }
|
||||
|
||||
// Get the Slic3r version check url.
|
||||
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
|
||||
std::string version_check_url() const;
|
||||
std::string version_check_url() const;
|
||||
|
||||
// Returns the original Slic3r version found in the ini file before it was overwritten
|
||||
// by the current version
|
||||
Semver orig_version() const { return m_orig_version; }
|
||||
Semver orig_version() const { return m_orig_version; }
|
||||
|
||||
// Does the config file exist?
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
bool exists();
|
||||
#else
|
||||
static bool exists();
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
|
||||
std::vector<std::string> get_recent_projects() const;
|
||||
void set_recent_projects(const std::vector<std::string>& recent_projects);
|
||||
|
@ -160,10 +178,6 @@ public:
|
|||
bool get_mouse_device_swap_yz(const std::string& name, bool& swap) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "swap_yz", swap); }
|
||||
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
void enable_save(bool enable) { m_save_enabled = enable; }
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
|
||||
static const std::string SECTION_FILAMENTS;
|
||||
static const std::string SECTION_MATERIALS;
|
||||
|
||||
|
@ -182,6 +196,10 @@ private:
|
|||
return true;
|
||||
}
|
||||
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
EAppMode m_mode { EAppMode::Editor };
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
|
||||
// Map of section, name -> value
|
||||
std::map<std::string, std::map<std::string, std::string>> m_storage;
|
||||
// Map of enabled vendors / models / variants
|
||||
|
@ -190,10 +208,6 @@ private:
|
|||
bool m_dirty;
|
||||
// Original version found in the ini file before it was overwritten
|
||||
Semver m_orig_version;
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
// Whether or not calls to save() should take effect
|
||||
bool m_save_enabled;
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
// Whether the existing version is before system profiles & configuration updating
|
||||
bool m_legacy_datadir;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
project(libslic3r)
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
include(PrecompiledHeader)
|
||||
|
||||
|
@ -62,8 +62,6 @@ add_library(libslic3r STATIC
|
|||
Fill/FillRectilinear.hpp
|
||||
Fill/FillRectilinear2.cpp
|
||||
Fill/FillRectilinear2.hpp
|
||||
Fill/FillRectilinear3.cpp
|
||||
Fill/FillRectilinear3.hpp
|
||||
Flow.cpp
|
||||
Flow.hpp
|
||||
format.hpp
|
||||
|
@ -81,8 +79,6 @@ add_library(libslic3r STATIC
|
|||
Format/STL.hpp
|
||||
Format/SL1.hpp
|
||||
Format/SL1.cpp
|
||||
GCode/Analyzer.cpp
|
||||
GCode/Analyzer.hpp
|
||||
GCode/ThumbnailData.cpp
|
||||
GCode/ThumbnailData.hpp
|
||||
GCode/CoolingBuffer.cpp
|
||||
|
@ -111,8 +107,6 @@ add_library(libslic3r STATIC
|
|||
GCodeReader.hpp
|
||||
# GCodeSender.cpp
|
||||
# GCodeSender.hpp
|
||||
GCodeTimeEstimator.cpp
|
||||
GCodeTimeEstimator.hpp
|
||||
GCodeWriter.cpp
|
||||
GCodeWriter.hpp
|
||||
Geometry.cpp
|
||||
|
@ -201,6 +195,8 @@ add_library(libslic3r STATIC
|
|||
Utils.hpp
|
||||
Time.cpp
|
||||
Time.hpp
|
||||
Thread.cpp
|
||||
Thread.hpp
|
||||
TriangleSelector.cpp
|
||||
TriangleSelector.hpp
|
||||
MTUtils.hpp
|
||||
|
|
|
@ -1414,6 +1414,8 @@ public:
|
|||
bool multiline = false;
|
||||
// For text input: If true, the GUI text box spans the complete page width.
|
||||
bool full_width = false;
|
||||
// For text input: If true, the GUI formats text as code (fixed-width)
|
||||
bool is_code = false;
|
||||
// Not editable. Currently only used for the display of the number of threads.
|
||||
bool readonly = false;
|
||||
// Height of a multiline GUI text box.
|
||||
|
|
|
@ -19,6 +19,7 @@ SLIC3R_DERIVE_EXCEPTION(InvalidArgument, LogicError);
|
|||
SLIC3R_DERIVE_EXCEPTION(OutOfRange, LogicError);
|
||||
SLIC3R_DERIVE_EXCEPTION(IOError, CriticalException);
|
||||
SLIC3R_DERIVE_EXCEPTION(FileIOError, IOError);
|
||||
SLIC3R_DERIVE_EXCEPTION(HostNetworkError, IOError);
|
||||
// Runtime exception produced by Slicer. Such exception cancels the slicing process and it shall be shown in notifications.
|
||||
SLIC3R_DERIVE_EXCEPTION(SlicingError, Exception);
|
||||
#undef SLIC3R_DERIVE_EXCEPTION
|
||||
|
|
|
@ -331,7 +331,7 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
|
|||
return "";
|
||||
}
|
||||
|
||||
ExtrusionRole ExtrusionEntity::string_to_role(const std::string& role)
|
||||
ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role)
|
||||
{
|
||||
if (role == L("Perimeter"))
|
||||
return erPerimeter;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "Polyline.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -106,7 +107,7 @@ public:
|
|||
virtual double total_volume() const = 0;
|
||||
|
||||
static std::string role_to_string(ExtrusionRole role);
|
||||
static ExtrusionRole string_to_role(const std::string& role);
|
||||
static ExtrusionRole string_to_role(const std::string_view role);
|
||||
};
|
||||
|
||||
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;
|
||||
|
|
|
@ -535,7 +535,7 @@ void Layer::make_ironing()
|
|||
fill_params.density = 1.;
|
||||
// fill_params.dont_connect = true;
|
||||
fill_params.dont_connect = false;
|
||||
fill_params.monotonous = true;
|
||||
fill_params.monotonic = true;
|
||||
|
||||
for (size_t i = 0; i < by_extruder.size(); ++ i) {
|
||||
// Find span of regions equivalent to the ironing operation.
|
||||
|
@ -579,7 +579,7 @@ void Layer::make_ironing()
|
|||
// Save into layer.
|
||||
ExtrusionEntityCollection *eec = nullptr;
|
||||
ironing_params.layerm->fills.entities.push_back(eec = new ExtrusionEntityCollection());
|
||||
// Don't sort the ironing infill lines as they are monotonously ordered.
|
||||
// Don't sort the ironing infill lines as they are monotonicly ordered.
|
||||
eec->no_sort = true;
|
||||
extrusion_entities_append_paths(
|
||||
eec->entities, std::move(polylines),
|
||||
|
|
|
@ -228,7 +228,7 @@ static const std::array<Vec3d, 8> child_centers {
|
|||
};
|
||||
|
||||
// Traversal order of octree children cells for three infill directions,
|
||||
// so that a single line will be discretized in a strictly monotonous order.
|
||||
// so that a single line will be discretized in a strictly monotonic order.
|
||||
static constexpr std::array<std::array<int, 8>, 3> child_traversal_order {
|
||||
std::array<int, 8>{ 2, 3, 0, 1, 6, 7, 4, 5 },
|
||||
std::array<int, 8>{ 4, 0, 6, 2, 5, 1, 7, 3 },
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include "FillPlanePath.hpp"
|
||||
#include "FillRectilinear.hpp"
|
||||
#include "FillRectilinear2.hpp"
|
||||
#include "FillRectilinear3.hpp"
|
||||
#include "FillAdaptive.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -28,7 +27,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
|
|||
case ip3DHoneycomb: return new Fill3DHoneycomb();
|
||||
case ipGyroid: return new FillGyroid();
|
||||
case ipRectilinear: return new FillRectilinear2();
|
||||
case ipMonotonous: return new FillMonotonous();
|
||||
case ipMonotonic: return new FillMonotonic();
|
||||
case ipLine: return new FillLine();
|
||||
case ipGrid: return new FillGrid2();
|
||||
case ipTriangles: return new FillTriangles();
|
||||
|
|
|
@ -43,8 +43,8 @@ struct FillParams
|
|||
// Don't adjust spacing to fill the space evenly.
|
||||
bool dont_adjust { true };
|
||||
|
||||
// Monotonous infill - strictly left to right for better surface quality of top infills.
|
||||
bool monotonous { false };
|
||||
// Monotonic infill - strictly left to right for better surface quality of top infills.
|
||||
bool monotonic { false };
|
||||
|
||||
// For Honeycomb.
|
||||
// we were requested to complete each loop;
|
||||
|
|
|
@ -154,7 +154,9 @@ struct SegmentIntersection
|
|||
// Vertical link, up.
|
||||
Up,
|
||||
// Vertical link, down.
|
||||
Down
|
||||
Down,
|
||||
// Phony intersection point has no link.
|
||||
Phony,
|
||||
};
|
||||
|
||||
enum class LinkQuality : uint8_t {
|
||||
|
@ -353,6 +355,25 @@ struct SegmentedIntersectionLine
|
|||
std::vector<SegmentIntersection> intersections;
|
||||
};
|
||||
|
||||
static SegmentIntersection phony_outer_intersection(SegmentIntersection::SegmentIntersectionType type, coord_t pos)
|
||||
{
|
||||
assert(type == SegmentIntersection::OUTER_LOW || type == SegmentIntersection::OUTER_HIGH);
|
||||
SegmentIntersection out;
|
||||
// Invalid contour & segment.
|
||||
out.iContour = std::numeric_limits<size_t>::max();
|
||||
out.iSegment = std::numeric_limits<size_t>::max();
|
||||
out.pos_p = pos;
|
||||
out.type = type;
|
||||
// Invalid prev / next.
|
||||
out.prev_on_contour = -1;
|
||||
out.next_on_contour = -1;
|
||||
out.prev_on_contour_type = SegmentIntersection::LinkType::Phony;
|
||||
out.next_on_contour_type = SegmentIntersection::LinkType::Phony;
|
||||
out.prev_on_contour_quality = SegmentIntersection::LinkQuality::Invalid;
|
||||
out.next_on_contour_quality = SegmentIntersection::LinkQuality::Invalid;
|
||||
return out;
|
||||
}
|
||||
|
||||
// A container maintaining an expolygon with its inner offsetted polygon.
|
||||
// The purpose of the inner offsetted polygon is to provide segments to connect the infill lines.
|
||||
struct ExPolygonWithOffset
|
||||
|
@ -889,6 +910,60 @@ static std::vector<SegmentedIntersectionLine> slice_region_by_vertical_lines(con
|
|||
return segs;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool validate_segment_intersection_connectivity(const std::vector<SegmentedIntersectionLine> &segs)
|
||||
{
|
||||
// Validate the connectivity.
|
||||
for (size_t i_vline = 0; i_vline + 1 < segs.size(); ++ i_vline) {
|
||||
const SegmentedIntersectionLine &il_left = segs[i_vline];
|
||||
const SegmentedIntersectionLine &il_right = segs[i_vline + 1];
|
||||
for (const SegmentIntersection &it : il_left.intersections) {
|
||||
if (it.has_right_horizontal()) {
|
||||
const SegmentIntersection &it_right = il_right.intersections[it.right_horizontal()];
|
||||
// For a right link there is a symmetric left link.
|
||||
assert(it.iContour == it_right.iContour);
|
||||
assert(it.type == it_right.type);
|
||||
assert(it_right.has_left_horizontal());
|
||||
assert(it_right.left_horizontal() == int(&it - il_left.intersections.data()));
|
||||
}
|
||||
}
|
||||
for (const SegmentIntersection &it : il_right.intersections) {
|
||||
if (it.has_left_horizontal()) {
|
||||
const SegmentIntersection &it_left = il_left.intersections[it.left_horizontal()];
|
||||
// For a right link there is a symmetric left link.
|
||||
assert(it.iContour == it_left.iContour);
|
||||
assert(it.type == it_left.type);
|
||||
assert(it_left.has_right_horizontal());
|
||||
assert(it_left.right_horizontal() == int(&it - il_right.intersections.data()));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t i_vline = 0; i_vline < segs.size(); ++ i_vline) {
|
||||
const SegmentedIntersectionLine &il = segs[i_vline];
|
||||
for (const SegmentIntersection &it : il.intersections) {
|
||||
auto i_it = int(&it - il.intersections.data());
|
||||
if (it.has_left_vertical_up()) {
|
||||
assert(il.intersections[it.left_vertical_up()].left_vertical_down() == i_it);
|
||||
assert(il.intersections[it.left_vertical_up()].prev_on_contour_quality == it.prev_on_contour_quality);
|
||||
}
|
||||
if (it.has_left_vertical_down()) {
|
||||
assert(il.intersections[it.left_vertical_down()].left_vertical_up() == i_it);
|
||||
assert(il.intersections[it.left_vertical_down()].prev_on_contour_quality == it.prev_on_contour_quality);
|
||||
}
|
||||
if (it.has_right_vertical_up()) {
|
||||
assert(il.intersections[it.right_vertical_up()].right_vertical_down() == i_it);
|
||||
assert(il.intersections[it.right_vertical_up()].next_on_contour_quality == it.next_on_contour_quality);
|
||||
}
|
||||
if (it.has_right_vertical_down()) {
|
||||
assert(il.intersections[it.right_vertical_down()].right_vertical_up() == i_it);
|
||||
assert(il.intersections[it.right_vertical_down()].next_on_contour_quality == it.next_on_contour_quality);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
// Connect each contour / vertical line intersection point with another two contour / vertical line intersection points.
|
||||
// (fill in SegmentIntersection::{prev_on_contour, prev_on_contour_vertical, next_on_contour, next_on_contour_vertical}.
|
||||
// These contour points are either on the same vertical line, or on the vertical line left / right to the current one.
|
||||
|
@ -1055,55 +1130,104 @@ static void connect_segment_intersections_by_contours(
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Validate the connectivity.
|
||||
for (size_t i_vline = 0; i_vline + 1 < segs.size(); ++ i_vline) {
|
||||
const SegmentedIntersectionLine &il_left = segs[i_vline];
|
||||
const SegmentedIntersectionLine &il_right = segs[i_vline + 1];
|
||||
for (const SegmentIntersection &it : il_left.intersections) {
|
||||
if (it.has_right_horizontal()) {
|
||||
const SegmentIntersection &it_right = il_right.intersections[it.right_horizontal()];
|
||||
// For a right link there is a symmetric left link.
|
||||
assert(it.iContour == it_right.iContour);
|
||||
assert(it.type == it_right.type);
|
||||
assert(it_right.has_left_horizontal());
|
||||
assert(it_right.left_horizontal() == int(&it - il_left.intersections.data()));
|
||||
assert(validate_segment_intersection_connectivity(segs));
|
||||
}
|
||||
|
||||
static void pinch_contours_insert_phony_outer_intersections(std::vector<SegmentedIntersectionLine> &segs)
|
||||
{
|
||||
// Keep the vector outside the loops, so they will not be reallocated.
|
||||
// Where to insert new outer points.
|
||||
std::vector<size_t> insert_after;
|
||||
// Mapping of indices of current intersection line after inserting new outer points.
|
||||
std::vector<int32_t> map;
|
||||
std::vector<SegmentIntersection> temp_intersections;
|
||||
|
||||
for (size_t i_vline = 1; i_vline < segs.size(); ++ i_vline) {
|
||||
SegmentedIntersectionLine &il = segs[i_vline];
|
||||
assert(il.intersections.empty() || il.intersections.size() >= 2);
|
||||
if (! il.intersections.empty()) {
|
||||
assert(il.intersections.front().type == SegmentIntersection::OUTER_LOW);
|
||||
assert(il.intersections.back().type == SegmentIntersection::OUTER_HIGH);
|
||||
auto end = il.intersections.end() - 1;
|
||||
insert_after.clear();
|
||||
for (auto it = il.intersections.begin() + 1; it != end;) {
|
||||
if (it->type == SegmentIntersection::OUTER_HIGH) {
|
||||
++ it;
|
||||
assert(it->type == SegmentIntersection::OUTER_LOW);
|
||||
++ it;
|
||||
} else {
|
||||
auto lo = it;
|
||||
assert(lo->type == SegmentIntersection::INNER_LOW);
|
||||
auto hi = ++ it;
|
||||
assert(hi->type == SegmentIntersection::INNER_HIGH);
|
||||
auto lo2 = ++ it;
|
||||
if (lo2->type == SegmentIntersection::INNER_LOW) {
|
||||
// INNER_HIGH followed by INNER_LOW. The outer contour may have squeezed the inner contour into two separate loops.
|
||||
// In that case one shall insert a phony OUTER_HIGH / OUTER_LOW pair.
|
||||
int up = hi->vertical_up();
|
||||
int dn = lo2->vertical_down();
|
||||
#ifndef _NDEBUG
|
||||
assert(up == -1 || up > 0);
|
||||
assert(dn == -1 || dn >= 0);
|
||||
assert((up == -1 && dn == -1) || (dn + 1 == up));
|
||||
#endif // _NDEBUG
|
||||
bool pinched = dn + 1 != up;
|
||||
if (pinched) {
|
||||
// hi is not connected with its inner contour to lo2.
|
||||
// Insert a phony OUTER_HIGH / OUTER_LOW pair.
|
||||
#if 0
|
||||
static int pinch_idx = 0;
|
||||
printf("Pinched %d\n", pinch_idx++);
|
||||
#endif
|
||||
insert_after.emplace_back(hi - il.intersections.begin());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const SegmentIntersection &it : il_right.intersections) {
|
||||
if (it.has_left_horizontal()) {
|
||||
const SegmentIntersection &it_left = il_left.intersections[it.left_horizontal()];
|
||||
// For a right link there is a symmetric left link.
|
||||
assert(it.iContour == it_left.iContour);
|
||||
assert(it.type == it_left.type);
|
||||
assert(it_left.has_right_horizontal());
|
||||
assert(it_left.right_horizontal() == int(&it - il_right.intersections.data()));
|
||||
|
||||
if (! insert_after.empty()) {
|
||||
// Insert phony OUTER_HIGH / OUTER_LOW pairs, adjust indices pointing to intersection points on this contour.
|
||||
map.clear();
|
||||
{
|
||||
size_t i = 0;
|
||||
temp_intersections.clear();
|
||||
for (size_t idx_inset_after : insert_after) {
|
||||
for (; i <= idx_inset_after; ++ i) {
|
||||
map.emplace_back(temp_intersections.size());
|
||||
temp_intersections.emplace_back(il.intersections[i]);
|
||||
}
|
||||
coord_t pos = (temp_intersections.back().pos() + il.intersections[i].pos()) / 2;
|
||||
temp_intersections.emplace_back(phony_outer_intersection(SegmentIntersection::OUTER_HIGH, pos));
|
||||
temp_intersections.emplace_back(phony_outer_intersection(SegmentIntersection::OUTER_LOW, pos));
|
||||
}
|
||||
for (; i < il.intersections.size(); ++ i) {
|
||||
map.emplace_back(temp_intersections.size());
|
||||
temp_intersections.emplace_back(il.intersections[i]);
|
||||
}
|
||||
temp_intersections.swap(il.intersections);
|
||||
}
|
||||
// Reindex references on current intersection line.
|
||||
for (SegmentIntersection &ip : il.intersections) {
|
||||
if (ip.has_left_vertical())
|
||||
ip.prev_on_contour = map[ip.prev_on_contour];
|
||||
if (ip.has_right_vertical())
|
||||
ip.next_on_contour = map[ip.next_on_contour];
|
||||
}
|
||||
// Reindex references on previous intersection line.
|
||||
for (SegmentIntersection &ip : segs[i_vline - 1].intersections)
|
||||
if (ip.has_right_horizontal())
|
||||
ip.next_on_contour = map[ip.next_on_contour];
|
||||
if (i_vline < segs.size()) {
|
||||
// Reindex references on next intersection line.
|
||||
for (SegmentIntersection &ip : segs[i_vline + 1].intersections)
|
||||
if (ip.has_left_horizontal())
|
||||
ip.prev_on_contour = map[ip.prev_on_contour];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t i_vline = 0; i_vline < segs.size(); ++ i_vline) {
|
||||
const SegmentedIntersectionLine &il = segs[i_vline];
|
||||
for (const SegmentIntersection &it : il.intersections) {
|
||||
auto i_it = int(&it - il.intersections.data());
|
||||
if (it.has_left_vertical_up()) {
|
||||
assert(il.intersections[it.left_vertical_up()].left_vertical_down() == i_it);
|
||||
assert(il.intersections[it.left_vertical_up()].prev_on_contour_quality == it.prev_on_contour_quality);
|
||||
}
|
||||
if (it.has_left_vertical_down()) {
|
||||
assert(il.intersections[it.left_vertical_down()].left_vertical_up() == i_it);
|
||||
assert(il.intersections[it.left_vertical_down()].prev_on_contour_quality == it.prev_on_contour_quality);
|
||||
}
|
||||
if (it.has_right_vertical_up()) {
|
||||
assert(il.intersections[it.right_vertical_up()].right_vertical_down() == i_it);
|
||||
assert(il.intersections[it.right_vertical_up()].next_on_contour_quality == it.next_on_contour_quality);
|
||||
}
|
||||
if (it.has_right_vertical_down()) {
|
||||
assert(il.intersections[it.right_vertical_down()].right_vertical_up() == i_it);
|
||||
assert(il.intersections[it.right_vertical_down()].next_on_contour_quality == it.next_on_contour_quality);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
assert(validate_segment_intersection_connectivity(segs));
|
||||
}
|
||||
|
||||
// Find the last INNER_HIGH intersection starting with INNER_LOW, that is followed by OUTER_HIGH intersection.
|
||||
|
@ -1387,7 +1511,7 @@ static void traverse_graph_generate_polylines(
|
|||
}
|
||||
}
|
||||
|
||||
struct MonotonousRegion
|
||||
struct MonotonicRegion
|
||||
{
|
||||
struct Boundary {
|
||||
int vline;
|
||||
|
@ -1412,13 +1536,13 @@ struct MonotonousRegion
|
|||
|
||||
#if NDEBUG
|
||||
// Left regions are used to track whether all regions left to this one have already been printed.
|
||||
boost::container::small_vector<MonotonousRegion*, 4> left_neighbors;
|
||||
boost::container::small_vector<MonotonicRegion*, 4> left_neighbors;
|
||||
// Right regions are held to pick a next region to be extruded using the "Ant colony" heuristics.
|
||||
boost::container::small_vector<MonotonousRegion*, 4> right_neighbors;
|
||||
boost::container::small_vector<MonotonicRegion*, 4> right_neighbors;
|
||||
#else
|
||||
// For debugging, use the normal vector as it is better supported by debug visualizers.
|
||||
std::vector<MonotonousRegion*> left_neighbors;
|
||||
std::vector<MonotonousRegion*> right_neighbors;
|
||||
std::vector<MonotonicRegion*> left_neighbors;
|
||||
std::vector<MonotonicRegion*> right_neighbors;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -1429,9 +1553,9 @@ struct AntPath
|
|||
float pheromone { 0 }; // <0, 1>
|
||||
};
|
||||
|
||||
struct MonotonousRegionLink
|
||||
struct MonotonicRegionLink
|
||||
{
|
||||
MonotonousRegion *region;
|
||||
MonotonicRegion *region;
|
||||
bool flipped;
|
||||
// Distance of right side of this region to left side of the next region, if the "flipped" flag of this region and the next region
|
||||
// is applied as defined.
|
||||
|
@ -1447,7 +1571,7 @@ class AntPathMatrix
|
|||
{
|
||||
public:
|
||||
AntPathMatrix(
|
||||
const std::vector<MonotonousRegion> ®ions,
|
||||
const std::vector<MonotonicRegion> ®ions,
|
||||
const ExPolygonWithOffset &poly_with_offset,
|
||||
const std::vector<SegmentedIntersectionLine> &segs,
|
||||
const float initial_pheromone) :
|
||||
|
@ -1463,7 +1587,7 @@ public:
|
|||
ap.pheromone = initial_pheromone;
|
||||
}
|
||||
|
||||
AntPath& operator()(const MonotonousRegion ®ion_from, bool flipped_from, const MonotonousRegion ®ion_to, bool flipped_to)
|
||||
AntPath& operator()(const MonotonicRegion ®ion_from, bool flipped_from, const MonotonicRegion ®ion_to, bool flipped_to)
|
||||
{
|
||||
int row = 2 * int(®ion_from - m_regions.data()) + flipped_from;
|
||||
int col = 2 * int(®ion_to - m_regions.data()) + flipped_to;
|
||||
|
@ -1490,16 +1614,16 @@ public:
|
|||
return path;
|
||||
}
|
||||
|
||||
AntPath& operator()(const MonotonousRegionLink ®ion_from, const MonotonousRegion ®ion_to, bool flipped_to)
|
||||
AntPath& operator()(const MonotonicRegionLink ®ion_from, const MonotonicRegion ®ion_to, bool flipped_to)
|
||||
{ return (*this)(*region_from.region, region_from.flipped, region_to, flipped_to); }
|
||||
AntPath& operator()(const MonotonousRegion ®ion_from, bool flipped_from, const MonotonousRegionLink ®ion_to)
|
||||
AntPath& operator()(const MonotonicRegion ®ion_from, bool flipped_from, const MonotonicRegionLink ®ion_to)
|
||||
{ return (*this)(region_from, flipped_from, *region_to.region, region_to.flipped); }
|
||||
AntPath& operator()(const MonotonousRegionLink ®ion_from, const MonotonousRegionLink ®ion_to)
|
||||
AntPath& operator()(const MonotonicRegionLink ®ion_from, const MonotonicRegionLink ®ion_to)
|
||||
{ return (*this)(*region_from.region, region_from.flipped, *region_to.region, region_to.flipped); }
|
||||
|
||||
private:
|
||||
// Source regions, used for addressing and updating m_matrix.
|
||||
const std::vector<MonotonousRegion> &m_regions;
|
||||
const std::vector<MonotonicRegion> &m_regions;
|
||||
// To calculate the intersection points and contour lengths.
|
||||
const ExPolygonWithOffset &m_poly_with_offset;
|
||||
const std::vector<SegmentedIntersectionLine> &m_segs;
|
||||
|
@ -1652,9 +1776,9 @@ static std::pair<SegmentIntersection*, SegmentIntersection*> right_overlap(std::
|
|||
return start_end.first == nullptr ? start_end : right_overlap(*start_end.first, *start_end.second, vline_this, vline_right);
|
||||
}
|
||||
|
||||
static std::vector<MonotonousRegion> generate_montonous_regions(std::vector<SegmentedIntersectionLine> &segs)
|
||||
static std::vector<MonotonicRegion> generate_montonous_regions(std::vector<SegmentedIntersectionLine> &segs)
|
||||
{
|
||||
std::vector<MonotonousRegion> monotonous_regions;
|
||||
std::vector<MonotonicRegion> monotonic_regions;
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define SLIC3R_DEBUG_MONOTONOUS_REGIONS
|
||||
|
@ -1685,11 +1809,11 @@ static std::vector<MonotonousRegion> generate_montonous_regions(std::vector<Segm
|
|||
SegmentIntersection *start = &vline_seed.intersections[i_intersection_seed];
|
||||
SegmentIntersection *end = &end_of_vertical_run(vline_seed, *start);
|
||||
if (! start->consumed_vertical_up) {
|
||||
// Draw a new monotonous region starting with this segment.
|
||||
// Draw a new monotonic region starting with this segment.
|
||||
// while there is only a single right neighbor
|
||||
int i_vline = i_vline_seed;
|
||||
std::pair<SegmentIntersection*, SegmentIntersection*> left(start, end);
|
||||
MonotonousRegion region;
|
||||
MonotonicRegion region;
|
||||
region.left.vline = i_vline;
|
||||
region.left.low = int(left.first - vline_seed.intersections.data());
|
||||
region.left.high = int(left.second - vline_seed.intersections.data());
|
||||
|
@ -1722,19 +1846,19 @@ static std::vector<MonotonousRegion> generate_montonous_regions(std::vector<Segm
|
|||
}
|
||||
// Even number of lines makes the infill zig-zag to exit on the other side of the region than where it starts.
|
||||
region.flips = (num_lines & 1) != 0;
|
||||
monotonous_regions.emplace_back(region);
|
||||
monotonic_regions.emplace_back(region);
|
||||
}
|
||||
i_intersection_seed = int(end - vline_seed.intersections.data()) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return monotonous_regions;
|
||||
return monotonic_regions;
|
||||
}
|
||||
|
||||
// Traverse path, calculate length of the draw for the purpose of optimization.
|
||||
// This function is very similar to polylines_from_paths() in the way how it traverses the path, but
|
||||
// polylines_from_paths() emits a path, while this function just calculates the path length.
|
||||
static float montonous_region_path_length(const MonotonousRegion ®ion, bool dir, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs)
|
||||
static float montonous_region_path_length(const MonotonicRegion ®ion, bool dir, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs)
|
||||
{
|
||||
// From the initial point (i_vline, i_intersection), follow a path.
|
||||
int i_intersection = region.left_intersection_point(dir);
|
||||
|
@ -1822,15 +1946,15 @@ static float montonous_region_path_length(const MonotonousRegion ®ion, bool d
|
|||
return unscale<float>(total_length);
|
||||
}
|
||||
|
||||
static void connect_monotonous_regions(std::vector<MonotonousRegion> ®ions, const ExPolygonWithOffset &poly_with_offset, std::vector<SegmentedIntersectionLine> &segs)
|
||||
static void connect_monotonic_regions(std::vector<MonotonicRegion> ®ions, const ExPolygonWithOffset &poly_with_offset, std::vector<SegmentedIntersectionLine> &segs)
|
||||
{
|
||||
// Map from low intersection to left / right side of a monotonous region.
|
||||
using MapType = std::pair<SegmentIntersection*, MonotonousRegion*>;
|
||||
// Map from low intersection to left / right side of a monotonic region.
|
||||
using MapType = std::pair<SegmentIntersection*, MonotonicRegion*>;
|
||||
std::vector<MapType> map_intersection_to_region_start;
|
||||
std::vector<MapType> map_intersection_to_region_end;
|
||||
map_intersection_to_region_start.reserve(regions.size());
|
||||
map_intersection_to_region_end.reserve(regions.size());
|
||||
for (MonotonousRegion ®ion : regions) {
|
||||
for (MonotonicRegion ®ion : regions) {
|
||||
map_intersection_to_region_start.emplace_back(&segs[region.left.vline].intersections[region.left.low], ®ion);
|
||||
map_intersection_to_region_end.emplace_back(&segs[region.right.vline].intersections[region.right.low], ®ion);
|
||||
}
|
||||
|
@ -1840,7 +1964,7 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> ®ions, c
|
|||
std::sort(map_intersection_to_region_end.begin(), map_intersection_to_region_end.end(), intersections_lower);
|
||||
|
||||
// Scatter links to neighboring regions.
|
||||
for (MonotonousRegion ®ion : regions) {
|
||||
for (MonotonicRegion ®ion : regions) {
|
||||
if (region.left.vline > 0) {
|
||||
auto &vline = segs[region.left.vline];
|
||||
auto &vline_left = segs[region.left.vline - 1];
|
||||
|
@ -1884,17 +2008,17 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> ®ions, c
|
|||
// Sometimes a segment may indicate that it connects to a segment on the other side while the other does not.
|
||||
// This may be a valid case if one side contains runs of OUTER_LOW, INNER_LOW, {INNER_HIGH, INNER_LOW}*, INNER_HIGH, OUTER_HIGH,
|
||||
// where the part in the middle does not connect to the other side, but it will be extruded through.
|
||||
for (MonotonousRegion ®ion : regions) {
|
||||
for (MonotonicRegion ®ion : regions) {
|
||||
std::sort(region.left_neighbors.begin(), region.left_neighbors.end());
|
||||
std::sort(region.right_neighbors.begin(), region.right_neighbors.end());
|
||||
}
|
||||
for (MonotonousRegion ®ion : regions) {
|
||||
for (MonotonousRegion *neighbor : region.left_neighbors) {
|
||||
for (MonotonicRegion ®ion : regions) {
|
||||
for (MonotonicRegion *neighbor : region.left_neighbors) {
|
||||
auto it = std::lower_bound(neighbor->right_neighbors.begin(), neighbor->right_neighbors.end(), ®ion);
|
||||
if (it == neighbor->right_neighbors.end() || *it != ®ion)
|
||||
neighbor->right_neighbors.insert(it, ®ion);
|
||||
}
|
||||
for (MonotonousRegion *neighbor : region.right_neighbors) {
|
||||
for (MonotonicRegion *neighbor : region.right_neighbors) {
|
||||
auto it = std::lower_bound(neighbor->left_neighbors.begin(), neighbor->left_neighbors.end(), ®ion);
|
||||
if (it == neighbor->left_neighbors.end() || *it != ®ion)
|
||||
neighbor->left_neighbors.insert(it, ®ion);
|
||||
|
@ -1903,12 +2027,12 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> ®ions, c
|
|||
|
||||
#ifndef NDEBUG
|
||||
// Verify symmetry of the left_neighbors / right_neighbors.
|
||||
for (MonotonousRegion ®ion : regions) {
|
||||
for (MonotonousRegion *neighbor : region.left_neighbors) {
|
||||
for (MonotonicRegion ®ion : regions) {
|
||||
for (MonotonicRegion *neighbor : region.left_neighbors) {
|
||||
assert(std::count(region.left_neighbors.begin(), region.left_neighbors.end(), neighbor) == 1);
|
||||
assert(std::find(neighbor->right_neighbors.begin(), neighbor->right_neighbors.end(), ®ion) != neighbor->right_neighbors.end());
|
||||
}
|
||||
for (MonotonousRegion *neighbor : region.right_neighbors) {
|
||||
for (MonotonicRegion *neighbor : region.right_neighbors) {
|
||||
assert(std::count(region.right_neighbors.begin(), region.right_neighbors.end(), neighbor) == 1);
|
||||
assert(std::find(neighbor->left_neighbors.begin(), neighbor->left_neighbors.end(), ®ion) != neighbor->left_neighbors.end());
|
||||
}
|
||||
|
@ -1916,7 +2040,7 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> ®ions, c
|
|||
#endif /* NDEBUG */
|
||||
|
||||
// Fill in sum length of connecting lines of a region. This length is used for optimizing the infill path for minimum length.
|
||||
for (MonotonousRegion ®ion : regions) {
|
||||
for (MonotonicRegion ®ion : regions) {
|
||||
region.len1 = montonous_region_path_length(region, false, poly_with_offset, segs);
|
||||
region.len2 = montonous_region_path_length(region, true, poly_with_offset, segs);
|
||||
// Subtract the smaller length from the longer one, so we will optimize just with the positive difference of the two.
|
||||
|
@ -1934,7 +2058,7 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> ®ions, c
|
|||
// https://www.chalmers.se/en/departments/math/research/research-groups/optimization/OptimizationMasterTheses/MScThesis-RaadSalman-final.pdf
|
||||
// Algorithm 6.1 Lexicographic Path Preserving 3-opt
|
||||
// Optimize path while maintaining the ordering constraints.
|
||||
void monotonous_3_opt(std::vector<MonotonousRegionLink> &path, const std::vector<SegmentedIntersectionLine> &segs)
|
||||
void monotonic_3_opt(std::vector<MonotonicRegionLink> &path, const std::vector<SegmentedIntersectionLine> &segs)
|
||||
{
|
||||
// When doing the 3-opt path preserving flips, one has to fulfill two constraints:
|
||||
//
|
||||
|
@ -1949,7 +2073,7 @@ void monotonous_3_opt(std::vector<MonotonousRegionLink> &path, const std::vector
|
|||
// then the precedence constraint verification is amortized inside the O(n^3) loop. Now which is better for our task?
|
||||
//
|
||||
// It is beneficial to also try flipping of the infill zig-zags, for which a prefix sum of both flipped and non-flipped paths over
|
||||
// MonotonousRegionLinks may be utilized, however updating the prefix sum has a linear complexity, the same complexity as doing the 3-opt
|
||||
// MonotonicRegionLinks may be utilized, however updating the prefix sum has a linear complexity, the same complexity as doing the 3-opt
|
||||
// exchange by copying the pieces.
|
||||
}
|
||||
|
||||
|
@ -1962,17 +2086,17 @@ inline void print_ant(const std::string& fmt, TArgs&&... args) {
|
|||
#endif
|
||||
}
|
||||
|
||||
// Find a run through monotonous infill blocks using an 'Ant colony" optimization method.
|
||||
// Find a run through monotonic infill blocks using an 'Ant colony" optimization method.
|
||||
// http://www.scholarpedia.org/article/Ant_colony_optimization
|
||||
static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
||||
std::vector<MonotonousRegion> ®ions, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs, std::mt19937_64 &rng)
|
||||
static std::vector<MonotonicRegionLink> chain_monotonic_regions(
|
||||
std::vector<MonotonicRegion> ®ions, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs, std::mt19937_64 &rng)
|
||||
{
|
||||
// Number of left neighbors (regions that this region depends on, this region cannot be printed before the regions left of it are printed) + self.
|
||||
std::vector<int32_t> left_neighbors_unprocessed(regions.size(), 1);
|
||||
// Queue of regions, which have their left neighbors already printed.
|
||||
std::vector<MonotonousRegion*> queue;
|
||||
std::vector<MonotonicRegion*> queue;
|
||||
queue.reserve(regions.size());
|
||||
for (MonotonousRegion ®ion : regions)
|
||||
for (MonotonicRegion ®ion : regions)
|
||||
if (region.left_neighbors.empty())
|
||||
queue.emplace_back(®ion);
|
||||
else
|
||||
|
@ -1981,13 +2105,13 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
auto left_neighbors_unprocessed_initial = left_neighbors_unprocessed;
|
||||
auto queue_initial = queue;
|
||||
|
||||
std::vector<MonotonousRegionLink> path, best_path;
|
||||
std::vector<MonotonicRegionLink> path, best_path;
|
||||
path.reserve(regions.size());
|
||||
best_path.reserve(regions.size());
|
||||
float best_path_length = std::numeric_limits<float>::max();
|
||||
|
||||
struct NextCandidate {
|
||||
MonotonousRegion *region;
|
||||
MonotonicRegion *region;
|
||||
AntPath *link;
|
||||
AntPath *link_flipped;
|
||||
float probability;
|
||||
|
@ -2002,22 +2126,22 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
[®ions, &left_neighbors_unprocessed, &path, &queue]() {
|
||||
std::vector<unsigned char> regions_processed(regions.size(), false);
|
||||
std::vector<unsigned char> regions_in_queue(regions.size(), false);
|
||||
for (const MonotonousRegion *region : queue) {
|
||||
for (const MonotonicRegion *region : queue) {
|
||||
// This region is not processed yet, his predecessors are processed.
|
||||
assert(left_neighbors_unprocessed[region - regions.data()] == 1);
|
||||
regions_in_queue[region - regions.data()] = true;
|
||||
}
|
||||
for (const MonotonousRegionLink &link : path) {
|
||||
for (const MonotonicRegionLink &link : path) {
|
||||
assert(left_neighbors_unprocessed[link.region - regions.data()] == 0);
|
||||
regions_processed[link.region - regions.data()] = true;
|
||||
}
|
||||
for (size_t i = 0; i < regions_processed.size(); ++ i) {
|
||||
assert(! regions_processed[i] || ! regions_in_queue[i]);
|
||||
const MonotonousRegion ®ion = regions[i];
|
||||
const MonotonicRegion ®ion = regions[i];
|
||||
if (regions_processed[i] || regions_in_queue[i]) {
|
||||
assert(left_neighbors_unprocessed[i] == (regions_in_queue[i] ? 1 : 0));
|
||||
// All left neighbors should be processed already.
|
||||
for (const MonotonousRegion *left : region.left_neighbors) {
|
||||
for (const MonotonicRegion *left : region.left_neighbors) {
|
||||
assert(regions_processed[left - regions.data()]);
|
||||
assert(left_neighbors_unprocessed[left - regions.data()] == 0);
|
||||
}
|
||||
|
@ -2026,7 +2150,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
assert(left_neighbors_unprocessed[i] > 1);
|
||||
size_t num_predecessors_unprocessed = 0;
|
||||
bool has_left_last_on_path = false;
|
||||
for (const MonotonousRegion* left : region.left_neighbors) {
|
||||
for (const MonotonicRegion* left : region.left_neighbors) {
|
||||
size_t iprev = left - regions.data();
|
||||
if (regions_processed[iprev]) {
|
||||
assert(left_neighbors_unprocessed[iprev] == 0);
|
||||
|
@ -2058,7 +2182,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
// After how many rounds without an improvement to exit?
|
||||
constexpr int num_rounds_no_change_exit = 8;
|
||||
// With how many ants each of the run will be performed?
|
||||
const int num_ants = std::min<int>(regions.size(), 10);
|
||||
const int num_ants = std::min(int(regions.size()), 10);
|
||||
// Base (initial) pheromone level. This value will be adjusted based on the length of the first greedy path found.
|
||||
float pheromone_initial_deposit = 0.5f;
|
||||
// Evaporation rate of pheromones.
|
||||
|
@ -2080,18 +2204,18 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
left_neighbors_unprocessed = left_neighbors_unprocessed_initial;
|
||||
assert(validate_unprocessed());
|
||||
// Pick the last of the queue.
|
||||
MonotonousRegionLink path_end { queue.back(), false };
|
||||
MonotonicRegionLink path_end { queue.back(), false };
|
||||
queue.pop_back();
|
||||
-- left_neighbors_unprocessed[path_end.region - regions.data()];
|
||||
|
||||
float total_length = path_end.region->length(false);
|
||||
while (! queue.empty() || ! path_end.region->right_neighbors.empty()) {
|
||||
// Chain.
|
||||
MonotonousRegion ®ion = *path_end.region;
|
||||
MonotonicRegion ®ion = *path_end.region;
|
||||
bool dir = path_end.flipped;
|
||||
NextCandidate next_candidate;
|
||||
next_candidate.probability = 0;
|
||||
for (MonotonousRegion *next : region.right_neighbors) {
|
||||
for (MonotonicRegion *next : region.right_neighbors) {
|
||||
int &unprocessed = left_neighbors_unprocessed[next - regions.data()];
|
||||
assert(unprocessed > 1);
|
||||
if (left_neighbors_unprocessed[next - regions.data()] == 2) {
|
||||
|
@ -2106,7 +2230,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
}
|
||||
bool from_queue = next_candidate.probability == 0;
|
||||
if (from_queue) {
|
||||
for (MonotonousRegion *next : queue) {
|
||||
for (MonotonicRegion *next : queue) {
|
||||
AntPath &path1 = path_matrix(region, dir, *next, false);
|
||||
AntPath &path2 = path_matrix(region, dir, *next, true);
|
||||
if (path1.visibility > next_candidate.probability)
|
||||
|
@ -2116,7 +2240,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
}
|
||||
}
|
||||
// Move the other right neighbors with satisified constraints to the queue.
|
||||
for (MonotonousRegion *next : region.right_neighbors)
|
||||
for (MonotonicRegion *next : region.right_neighbors)
|
||||
if (-- left_neighbors_unprocessed[next - regions.data()] == 1 && next_candidate.region != next)
|
||||
queue.emplace_back(next);
|
||||
if (from_queue) {
|
||||
|
@ -2127,7 +2251,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
queue.pop_back();
|
||||
}
|
||||
// Extend the path.
|
||||
MonotonousRegion *next_region = next_candidate.region;
|
||||
MonotonicRegion *next_region = next_candidate.region;
|
||||
bool next_dir = next_candidate.dir;
|
||||
total_length += next_region->length(next_dir) + path_matrix(*path_end.region, path_end.flipped, *next_region, next_dir).length;
|
||||
path_end = { next_region, next_dir };
|
||||
|
@ -2136,11 +2260,11 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
}
|
||||
|
||||
// Set an initial pheromone value to 10% of the greedy path's value.
|
||||
pheromone_initial_deposit = 0.1 / total_length;
|
||||
pheromone_initial_deposit = 0.1f / total_length;
|
||||
path_matrix.update_inital_pheromone(pheromone_initial_deposit);
|
||||
}
|
||||
|
||||
// Probability (unnormalized) of traversing a link between two monotonous regions.
|
||||
// Probability (unnormalized) of traversing a link between two monotonic regions.
|
||||
auto path_probability = [pheromone_alpha, pheromone_beta](AntPath &path) {
|
||||
return pow(path.pheromone, pheromone_alpha) * pow(path.visibility, pheromone_beta);
|
||||
};
|
||||
|
@ -2163,10 +2287,10 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
left_neighbors_unprocessed = left_neighbors_unprocessed_initial;
|
||||
assert(validate_unprocessed());
|
||||
// Pick randomly the first from the queue at random orientation.
|
||||
//FIXME picking the 1st monotonous region should likely be done based on accumulated pheromone level as well,
|
||||
// but the inefficiency caused by the random pick of the 1st monotonous region is likely insignificant.
|
||||
//FIXME picking the 1st monotonic region should likely be done based on accumulated pheromone level as well,
|
||||
// but the inefficiency caused by the random pick of the 1st monotonic region is likely insignificant.
|
||||
int first_idx = std::uniform_int_distribution<>(0, int(queue.size()) - 1)(rng);
|
||||
path.emplace_back(MonotonousRegionLink{ queue[first_idx], rng() > rng.max() / 2 });
|
||||
path.emplace_back(MonotonicRegionLink{ queue[first_idx], rng() > rng.max() / 2 });
|
||||
*(queue.begin() + first_idx) = std::move(queue.back());
|
||||
queue.pop_back();
|
||||
-- left_neighbors_unprocessed[path.back().region - regions.data()];
|
||||
|
@ -2182,12 +2306,12 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
|
||||
while (! queue.empty() || ! path.back().region->right_neighbors.empty()) {
|
||||
// Chain.
|
||||
MonotonousRegion ®ion = *path.back().region;
|
||||
MonotonicRegion ®ion = *path.back().region;
|
||||
bool dir = path.back().flipped;
|
||||
// Sort by distance to pt.
|
||||
next_candidates.clear();
|
||||
next_candidates.reserve(region.right_neighbors.size() * 2);
|
||||
for (MonotonousRegion *next : region.right_neighbors) {
|
||||
for (MonotonicRegion *next : region.right_neighbors) {
|
||||
int &unprocessed = left_neighbors_unprocessed[next - regions.data()];
|
||||
assert(unprocessed > 1);
|
||||
if (-- unprocessed == 1) {
|
||||
|
@ -2204,7 +2328,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
//FIXME add the queue items to the candidates? These are valid moves as well.
|
||||
if (num_direct_neighbors == 0) {
|
||||
// Add the queue candidates.
|
||||
for (MonotonousRegion *next : queue) {
|
||||
for (MonotonicRegion *next : queue) {
|
||||
assert(left_neighbors_unprocessed[next - regions.data()] == 1);
|
||||
AntPath &path1 = path_matrix(region, dir, *next, false);
|
||||
AntPath &path1_flipped = path_matrix(region, ! dir, *next, true);
|
||||
|
@ -2247,11 +2371,11 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
queue.pop_back();
|
||||
}
|
||||
// Extend the path.
|
||||
MonotonousRegion *next_region = take_path->region;
|
||||
MonotonicRegion *next_region = take_path->region;
|
||||
bool next_dir = take_path->dir;
|
||||
path.back().next = take_path->link;
|
||||
path.back().next_flipped = take_path->link_flipped;
|
||||
path.emplace_back(MonotonousRegionLink{ next_region, next_dir });
|
||||
path.emplace_back(MonotonicRegionLink{ next_region, next_dir });
|
||||
assert(left_neighbors_unprocessed[next_region - regions.data()] == 1);
|
||||
left_neighbors_unprocessed[next_region - regions.data()] = 0;
|
||||
print_ant("\tRegion (%1%:%2%,%3%) (%4%:%5%,%6%) length to prev %7%",
|
||||
|
@ -2279,14 +2403,14 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
}
|
||||
|
||||
// Perform 3-opt local optimization of the path.
|
||||
monotonous_3_opt(path, segs);
|
||||
monotonic_3_opt(path, segs);
|
||||
|
||||
// Measure path length.
|
||||
assert(! path.empty());
|
||||
float path_length = std::accumulate(path.begin(), path.end() - 1,
|
||||
path.back().region->length(path.back().flipped),
|
||||
[&path_matrix](const float l, const MonotonousRegionLink &r) {
|
||||
const MonotonousRegionLink &next = *(&r + 1);
|
||||
[&path_matrix](const float l, const MonotonicRegionLink &r) {
|
||||
const MonotonicRegionLink &next = *(&r + 1);
|
||||
return l + r.region->length(r.flipped) + path_matrix(*r.region, r.flipped, *next.region, next.flipped).length;
|
||||
});
|
||||
// Save the shortest path.
|
||||
|
@ -2309,7 +2433,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions(
|
|||
// Reinforce the path pheromones with the best path.
|
||||
float total_cost = best_path_length + float(EPSILON);
|
||||
for (size_t i = 0; i + 1 < path.size(); ++ i) {
|
||||
MonotonousRegionLink &link = path[i];
|
||||
MonotonicRegionLink &link = path[i];
|
||||
link.next->pheromone = (1.f - pheromone_evaporation) * link.next->pheromone + pheromone_evaporation / total_cost;
|
||||
}
|
||||
|
||||
|
@ -2324,7 +2448,7 @@ end:
|
|||
}
|
||||
|
||||
// Traverse path, produce polylines.
|
||||
static void polylines_from_paths(const std::vector<MonotonousRegionLink> &path, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs, Polylines &polylines_out)
|
||||
static void polylines_from_paths(const std::vector<MonotonicRegionLink> &path, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs, Polylines &polylines_out)
|
||||
{
|
||||
Polyline *polyline = nullptr;
|
||||
auto finish_polyline = [&polyline, &polylines_out]() {
|
||||
|
@ -2334,14 +2458,26 @@ static void polylines_from_paths(const std::vector<MonotonousRegionLink> &path,
|
|||
// Handle nearly zero length edges.
|
||||
if (polyline->points.size() <= 1 ||
|
||||
(polyline->points.size() == 2 &&
|
||||
std::abs(polyline->points.front()(0) - polyline->points.back()(0)) < SCALED_EPSILON &&
|
||||
std::abs(polyline->points.front()(1) - polyline->points.back()(1)) < SCALED_EPSILON))
|
||||
std::abs(polyline->points.front().x() - polyline->points.back().x()) < SCALED_EPSILON &&
|
||||
std::abs(polyline->points.front().y() - polyline->points.back().y()) < SCALED_EPSILON))
|
||||
polylines_out.pop_back();
|
||||
else if (polylines_out.size() >= 2) {
|
||||
assert(polyline->points.size() >= 2);
|
||||
// Merge the two last polylines. An extrusion may have been split by an introduction of phony outer points on intersection lines
|
||||
// to cope with pinching of inner offset contours.
|
||||
Polyline &pl_prev = polylines_out[polylines_out.size() - 2];
|
||||
if (std::abs(polyline->points.front().x() - pl_prev.points.back().x()) < SCALED_EPSILON &&
|
||||
std::abs(polyline->points.front().y() - pl_prev.points.back().y()) < SCALED_EPSILON) {
|
||||
pl_prev.points.back() = (pl_prev.points.back() + polyline->points.front()) / 2;
|
||||
pl_prev.points.insert(pl_prev.points.end(), polyline->points.begin() + 1, polyline->points.end());
|
||||
polylines_out.pop_back();
|
||||
}
|
||||
}
|
||||
polyline = nullptr;
|
||||
};
|
||||
|
||||
for (const MonotonousRegionLink &path_segment : path) {
|
||||
MonotonousRegion ®ion = *path_segment.region;
|
||||
for (const MonotonicRegionLink &path_segment : path) {
|
||||
MonotonicRegion ®ion = *path_segment.region;
|
||||
bool dir = path_segment.flipped;
|
||||
|
||||
// From the initial point (i_vline, i_intersection), follow a path.
|
||||
|
@ -2350,8 +2486,8 @@ static void polylines_from_paths(const std::vector<MonotonousRegionLink> &path,
|
|||
|
||||
if (polyline != nullptr && &path_segment != path.data()) {
|
||||
// Connect previous path segment with the new one.
|
||||
const MonotonousRegionLink &path_segment_prev = *(&path_segment - 1);
|
||||
const MonotonousRegion ®ion_prev = *path_segment_prev.region;
|
||||
const MonotonicRegionLink &path_segment_prev = *(&path_segment - 1);
|
||||
const MonotonicRegion ®ion_prev = *path_segment_prev.region;
|
||||
bool dir_prev = path_segment_prev.flipped;
|
||||
int i_vline_prev = region_prev.right.vline;
|
||||
const SegmentedIntersectionLine &vline_prev = segs[i_vline_prev];
|
||||
|
@ -2456,7 +2592,7 @@ static void polylines_from_paths(const std::vector<MonotonousRegionLink> &path,
|
|||
|
||||
if (polyline != nullptr) {
|
||||
// Finish the current vertical line,
|
||||
const MonotonousRegion ®ion = *path.back().region;
|
||||
const MonotonicRegion ®ion = *path.back().region;
|
||||
const SegmentedIntersectionLine &vline = segs[region.right.vline];
|
||||
const SegmentIntersection *ip = &vline.intersections[region.right_intersection_point(path.back().flipped)];
|
||||
assert(ip->is_inner());
|
||||
|
@ -2489,7 +2625,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP
|
|||
surface->expolygon,
|
||||
- rotate_vector.first,
|
||||
float(scale_(this->overlap - (0.5 - INFILL_OVERLAP_OVER_SPACING) * this->spacing)),
|
||||
float(scale_(this->overlap - 0.5 * this->spacing)));
|
||||
float(scale_(this->overlap - 0.5f * this->spacing)));
|
||||
if (poly_with_offset.n_contours_inner == 0) {
|
||||
// Not a single infill line fits.
|
||||
//FIXME maybe one shall trigger the gap fill here?
|
||||
|
@ -2558,14 +2694,18 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP
|
|||
svg.Close();
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
||||
//FIXME this is a hack to get the monotonous infill rolling. We likely want a smarter switch, likely based on user decison.
|
||||
bool monotonous_infill = params.monotonous; // || params.density > 0.99;
|
||||
if (monotonous_infill) {
|
||||
std::vector<MonotonousRegion> regions = generate_montonous_regions(segs);
|
||||
connect_monotonous_regions(regions, poly_with_offset, segs);
|
||||
//FIXME this is a hack to get the monotonic infill rolling. We likely want a smarter switch, likely based on user decison.
|
||||
bool monotonic_infill = params.monotonic; // || params.density > 0.99;
|
||||
if (monotonic_infill) {
|
||||
// Sometimes the outer contour pinches the inner contour from both sides along a single vertical line.
|
||||
// This situation is not handled correctly by generate_montonous_regions().
|
||||
// Insert phony OUTER_HIGH / OUTER_LOW pairs at the position where the contour is pinched.
|
||||
pinch_contours_insert_phony_outer_intersections(segs);
|
||||
std::vector<MonotonicRegion> regions = generate_montonous_regions(segs);
|
||||
connect_monotonic_regions(regions, poly_with_offset, segs);
|
||||
if (! regions.empty()) {
|
||||
std::mt19937_64 rng;
|
||||
std::vector<MonotonousRegionLink> path = chain_monotonous_regions(regions, poly_with_offset, segs, rng);
|
||||
std::vector<MonotonicRegionLink> path = chain_monotonic_regions(regions, poly_with_offset, segs, rng);
|
||||
polylines_from_paths(path, poly_with_offset, segs, polylines_out);
|
||||
}
|
||||
} else
|
||||
|
@ -2616,13 +2756,13 @@ Polylines FillRectilinear2::fill_surface(const Surface *surface, const FillParam
|
|||
return polylines_out;
|
||||
}
|
||||
|
||||
Polylines FillMonotonous::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
Polylines FillMonotonic::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
{
|
||||
FillParams params2 = params;
|
||||
params2.monotonous = true;
|
||||
params2.monotonic = true;
|
||||
Polylines polylines_out;
|
||||
if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out)) {
|
||||
printf("FillMonotonous::fill_surface() failed to fill a region.\n");
|
||||
printf("FillMonotonic::fill_surface() failed to fill a region.\n");
|
||||
}
|
||||
return polylines_out;
|
||||
}
|
||||
|
|
|
@ -20,11 +20,11 @@ protected:
|
|||
bool fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out);
|
||||
};
|
||||
|
||||
class FillMonotonous : public FillRectilinear2
|
||||
class FillMonotonic : public FillRectilinear2
|
||||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillMonotonous(*this); };
|
||||
virtual ~FillMonotonous() = default;
|
||||
virtual Fill* clone() const { return new FillMonotonic(*this); };
|
||||
virtual ~FillMonotonic() = default;
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
virtual bool no_sort() const { return true; }
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,83 +0,0 @@
|
|||
#ifndef slic3r_FillRectilinear3_hpp_
|
||||
#define slic3r_FillRectilinear3_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Surface;
|
||||
|
||||
class FillRectilinear3 : public Fill
|
||||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillRectilinear3(*this); };
|
||||
virtual ~FillRectilinear3() {}
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
|
||||
struct FillDirParams
|
||||
{
|
||||
FillDirParams(coordf_t spacing, double angle, coordf_t pattern_shift = 0.f) :
|
||||
spacing(spacing), angle(angle), pattern_shift(pattern_shift) {}
|
||||
coordf_t spacing;
|
||||
double angle;
|
||||
coordf_t pattern_shift;
|
||||
};
|
||||
|
||||
protected:
|
||||
bool fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, std::vector<FillDirParams> &fill_dir_params, Polylines &polylines_out);
|
||||
};
|
||||
|
||||
class FillGrid3 : public FillRectilinear3
|
||||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillGrid3(*this); };
|
||||
virtual ~FillGrid3() {}
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
|
||||
protected:
|
||||
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
|
||||
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
|
||||
};
|
||||
|
||||
class FillTriangles3 : public FillRectilinear3
|
||||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillTriangles3(*this); };
|
||||
virtual ~FillTriangles3() {}
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
|
||||
protected:
|
||||
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
|
||||
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
|
||||
};
|
||||
|
||||
class FillStars3 : public FillRectilinear3
|
||||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillStars3(*this); };
|
||||
virtual ~FillStars3() {}
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
|
||||
protected:
|
||||
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
|
||||
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
|
||||
};
|
||||
|
||||
class FillCubic3 : public FillRectilinear3
|
||||
{
|
||||
public:
|
||||
virtual Fill* clone() const { return new FillCubic3(*this); };
|
||||
virtual ~FillCubic3() {}
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
|
||||
protected:
|
||||
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
|
||||
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
|
||||
};
|
||||
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillRectilinear3_hpp_
|
|
@ -1899,9 +1899,9 @@ namespace Slic3r {
|
|||
assert(index < geometry.custom_supports.size());
|
||||
assert(index < geometry.custom_seam.size());
|
||||
if (! geometry.custom_supports[index].empty())
|
||||
volume->m_supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
|
||||
volume->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
|
||||
if (! geometry.custom_seam[index].empty())
|
||||
volume->m_seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
|
||||
volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
|
||||
}
|
||||
|
||||
|
||||
|
@ -2417,11 +2417,11 @@ namespace Slic3r {
|
|||
stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" ";
|
||||
}
|
||||
|
||||
std::string custom_supports_data_string = volume->m_supported_facets.get_triangle_as_string(i);
|
||||
std::string custom_supports_data_string = volume->supported_facets.get_triangle_as_string(i);
|
||||
if (! custom_supports_data_string.empty())
|
||||
stream << CUSTOM_SUPPORTS_ATTR << "=\"" << custom_supports_data_string << "\" ";
|
||||
|
||||
std::string custom_seam_data_string = volume->m_seam_facets.get_triangle_as_string(i);
|
||||
std::string custom_seam_data_string = volume->seam_facets.get_triangle_as_string(i);
|
||||
if (! custom_seam_data_string.empty())
|
||||
stream << CUSTOM_SEAM_ATTR << "=\"" << custom_seam_data_string << "\" ";
|
||||
|
||||
|
|
|
@ -383,6 +383,7 @@ void fill_slicerconf(ConfMap &m, const SLAPrint &print)
|
|||
static constexpr auto banned_keys = {
|
||||
"compatible_printers"sv,
|
||||
"compatible_prints"sv,
|
||||
//FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed.
|
||||
"print_host"sv,
|
||||
"printhost_apikey"sv,
|
||||
"printhost_cafile"sv
|
||||
|
|
|
@ -158,7 +158,7 @@ namespace Slic3r {
|
|||
polygons_per_layer[i * 2] = union_(polys);
|
||||
}
|
||||
});
|
||||
for (size_t i = 0; i < cnt / 2; ++i)
|
||||
for (size_t i = 1; i < cnt / 2; ++i)
|
||||
polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]);
|
||||
if (cnt & 1)
|
||||
polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]);
|
||||
|
@ -819,48 +819,54 @@ namespace DoExport {
|
|||
// this->print_machine_envelope(file, print);
|
||||
// shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor.
|
||||
if (config.gcode_flavor.value == gcfMarlin) {
|
||||
normal_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values[0]);
|
||||
normal_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values[0]);
|
||||
normal_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values[0]);
|
||||
normal_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values[0]);
|
||||
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values[0]);
|
||||
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values[0]);
|
||||
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values[0]);
|
||||
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values[0]);
|
||||
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values[0]);
|
||||
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values[0]);
|
||||
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values[0]);
|
||||
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values[0]);
|
||||
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values[0]);
|
||||
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values[0]);
|
||||
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values[0]);
|
||||
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values[0]);
|
||||
|
||||
if (config.machine_limits_usage.value != MachineLimitsUsage::Ignore) {
|
||||
normal_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values[0]);
|
||||
normal_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values[0]);
|
||||
normal_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values[0]);
|
||||
normal_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values[0]);
|
||||
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values[0]);
|
||||
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values[0]);
|
||||
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values[0]);
|
||||
normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values[0]);
|
||||
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values[0]);
|
||||
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values[0]);
|
||||
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values[0]);
|
||||
normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values[0]);
|
||||
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values[0]);
|
||||
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values[0]);
|
||||
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values[0]);
|
||||
normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values[0]);
|
||||
}
|
||||
|
||||
if (silent_time_estimator_enabled)
|
||||
{
|
||||
silent_time_estimator.reset();
|
||||
silent_time_estimator.set_dialect(config.gcode_flavor);
|
||||
silent_time_estimator.set_extrusion_axis(config.get_extrusion_axis()[0]);
|
||||
/* "Stealth mode" values can be just a copy of "normal mode" values
|
||||
* (when they aren't input for a printer preset).
|
||||
* Thus, use back value from values, instead of second one, which could be absent
|
||||
*/
|
||||
silent_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values.back());
|
||||
silent_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values.back());
|
||||
silent_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values.back());
|
||||
silent_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values.back());
|
||||
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values.back());
|
||||
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values.back());
|
||||
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values.back());
|
||||
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values.back());
|
||||
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values.back());
|
||||
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values.back());
|
||||
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values.back());
|
||||
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values.back());
|
||||
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values.back());
|
||||
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values.back());
|
||||
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values.back());
|
||||
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values.back());
|
||||
|
||||
if (config.machine_limits_usage.value != MachineLimitsUsage::Ignore) {
|
||||
/* "Stealth mode" values can be just a copy of "normal mode" values
|
||||
* (when they aren't input for a printer preset).
|
||||
* Thus, use back value from values, instead of second one, which could be absent
|
||||
*/
|
||||
silent_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values.back());
|
||||
silent_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values.back());
|
||||
silent_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values.back());
|
||||
silent_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values.back());
|
||||
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values.back());
|
||||
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values.back());
|
||||
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values.back());
|
||||
silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values.back());
|
||||
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values.back());
|
||||
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values.back());
|
||||
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values.back());
|
||||
silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values.back());
|
||||
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values.back());
|
||||
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values.back());
|
||||
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values.back());
|
||||
silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values.back());
|
||||
}
|
||||
|
||||
if (config.single_extruder_multi_material) {
|
||||
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
|
||||
// are considered to be active for the single extruder multi-material printers only.
|
||||
|
@ -1082,14 +1088,14 @@ namespace DoExport {
|
|||
++ dst.second;
|
||||
};
|
||||
print_statistics.filament_stats.insert(std::pair<size_t, float>{extruder.id(), (float)used_filament});
|
||||
append(out_filament_used_mm, "%.1lf", used_filament);
|
||||
append(out_filament_used_cm3, "%.1lf", extruded_volume * 0.001);
|
||||
append(out_filament_used_mm, "%.2lf", used_filament);
|
||||
append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001);
|
||||
if (filament_weight > 0.) {
|
||||
print_statistics.total_weight = print_statistics.total_weight + filament_weight;
|
||||
append(out_filament_used_g, "%.1lf", filament_weight);
|
||||
append(out_filament_used_g, "%.2lf", filament_weight);
|
||||
if (filament_cost > 0.) {
|
||||
print_statistics.total_cost = print_statistics.total_cost + filament_cost;
|
||||
append(out_filament_cost, "%.1lf", filament_cost);
|
||||
append(out_filament_cost, "%.2lf", filament_cost);
|
||||
}
|
||||
}
|
||||
print_statistics.total_used_filament += used_filament;
|
||||
|
@ -1598,8 +1604,8 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
|
|||
// Modifies
|
||||
print.m_print_statistics));
|
||||
_write(file, "\n");
|
||||
_write_format(file, "; total filament used [g] = %.1lf\n", print.m_print_statistics.total_weight);
|
||||
_write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
|
||||
_write_format(file, "; total filament used [g] = %.2lf\n", print.m_print_statistics.total_weight);
|
||||
_write_format(file, "; total filament cost = %.2lf\n", print.m_print_statistics.total_cost);
|
||||
if (print.m_print_statistics.total_toolchanges > 0)
|
||||
_write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
|
@ -1613,7 +1619,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
|
|||
// Append full config.
|
||||
_write(file, "\n");
|
||||
{
|
||||
std::string full_config = "";
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
_write(file, full_config);
|
||||
|
@ -1636,9 +1642,9 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std
|
|||
}
|
||||
}
|
||||
|
||||
// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code.
|
||||
// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the custom G-code.
|
||||
// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out.
|
||||
static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, int &temp_out)
|
||||
static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, const bool include_g10, int &temp_out)
|
||||
{
|
||||
temp_out = -1;
|
||||
if (gcode.empty())
|
||||
|
@ -1649,17 +1655,23 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
|
|||
while (*ptr != 0) {
|
||||
// Skip whitespaces.
|
||||
for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
|
||||
if (*ptr == 'M') {
|
||||
// Line starts with 'M'. It is a machine command.
|
||||
if (*ptr == 'M' || // Line starts with 'M'. It is a machine command.
|
||||
(*ptr == 'G' && include_g10)) { // Only check for G10 if requested
|
||||
bool is_gcode = *ptr == 'G';
|
||||
++ ptr;
|
||||
// Parse the M code value.
|
||||
// Parse the M or G code value.
|
||||
char *endptr = nullptr;
|
||||
int mcode = int(strtol(ptr, &endptr, 10));
|
||||
if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) {
|
||||
// M104/M109 or M140/M190 found.
|
||||
int mgcode = int(strtol(ptr, &endptr, 10));
|
||||
if (endptr != nullptr && endptr != ptr &&
|
||||
is_gcode ?
|
||||
// G10 found
|
||||
mgcode == 10 :
|
||||
// M104/M109 or M140/M190 found.
|
||||
(mgcode == mcode_set_temp_dont_wait || mgcode == mcode_set_temp_and_wait)) {
|
||||
ptr = endptr;
|
||||
// Let the caller know that the custom G-code sets the temperature.
|
||||
temp_set_by_gcode = true;
|
||||
if (! is_gcode)
|
||||
// Let the caller know that the custom M-code sets the temperature.
|
||||
temp_set_by_gcode = true;
|
||||
// Now try to parse the temperature value.
|
||||
// While not at the end of the line:
|
||||
while (strchr(";\r\n\0", *ptr) == nullptr) {
|
||||
|
@ -1674,6 +1686,10 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
|
|||
if (endptr > ptr) {
|
||||
ptr = endptr;
|
||||
temp_out = temp_parsed;
|
||||
// Let the caller know that the custom G-code sets the temperature
|
||||
// Only do this after successfully parsing temperature since G10
|
||||
// can be used for other reasons
|
||||
temp_set_by_gcode = true;
|
||||
}
|
||||
} else {
|
||||
// Skip this word.
|
||||
|
@ -1694,7 +1710,7 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
|
|||
// Do not process this piece of G-code by the time estimator, it already knows the values through another sources.
|
||||
void GCode::print_machine_envelope(FILE *file, Print &print)
|
||||
{
|
||||
if (print.config().gcode_flavor.value == gcfMarlin) {
|
||||
if (print.config().gcode_flavor.value == gcfMarlin && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) {
|
||||
fprintf(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n",
|
||||
int(print.config().machine_max_acceleration_x.values.front() + 0.5),
|
||||
int(print.config().machine_max_acceleration_y.values.front() + 0.5),
|
||||
|
@ -1730,7 +1746,7 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s
|
|||
int temp = print.config().first_layer_bed_temperature.get_at(first_printing_extruder_id);
|
||||
// Is the bed temperature set by the provided custom G-code?
|
||||
int temp_by_gcode = -1;
|
||||
bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode);
|
||||
bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, false, temp_by_gcode);
|
||||
if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000)
|
||||
temp = temp_by_gcode;
|
||||
// Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if
|
||||
|
@ -1744,11 +1760,13 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s
|
|||
// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
|
||||
// M104 - Set Extruder Temperature
|
||||
// M109 - Set Extruder Temperature and Wait
|
||||
// RepRapFirmware: G10 Sxx
|
||||
void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
|
||||
{
|
||||
// Is the bed temperature set by the provided custom G-code?
|
||||
int temp_by_gcode = -1;
|
||||
if (custom_gcode_sets_temperature(gcode, 104, 109, temp_by_gcode)) {
|
||||
int temp_by_gcode = -1;
|
||||
bool include_g10 = print.config().gcode_flavor == gcfRepRapFirmware;
|
||||
if (custom_gcode_sets_temperature(gcode, 104, 109, include_g10, temp_by_gcode)) {
|
||||
// Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code.
|
||||
int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
|
||||
if (temp_by_gcode >= 0 && temp_by_gcode < 1000)
|
||||
|
@ -2457,6 +2475,7 @@ void GCode::append_full_config(const Print &print, std::string &str)
|
|||
static constexpr auto banned_keys = {
|
||||
"compatible_printers"sv,
|
||||
"compatible_prints"sv,
|
||||
//FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed.
|
||||
"print_host"sv,
|
||||
"printhost_apikey"sv,
|
||||
"printhost_cafile"sv
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,309 +0,0 @@
|
|||
#ifndef slic3r_GCode_Analyzer_hpp_
|
||||
#define slic3r_GCode_Analyzer_hpp_
|
||||
|
||||
#if !ENABLE_GCODE_VIEWER
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
|
||||
#include "../Point.hpp"
|
||||
#include "../GCodeReader.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCodePreviewData;
|
||||
|
||||
class GCodeAnalyzer
|
||||
{
|
||||
public:
|
||||
static const std::string Extrusion_Role_Tag;
|
||||
static const std::string Mm3_Per_Mm_Tag;
|
||||
static const std::string Width_Tag;
|
||||
static const std::string Height_Tag;
|
||||
static const std::string Color_Change_Tag;
|
||||
static const std::string Pause_Print_Tag;
|
||||
static const std::string Custom_Code_Tag;
|
||||
static const std::string End_Pause_Print_Or_Custom_Code_Tag;
|
||||
|
||||
static const float Default_mm3_per_mm;
|
||||
static const float Default_Width;
|
||||
static const float Default_Height;
|
||||
|
||||
enum EUnits : unsigned char
|
||||
{
|
||||
Millimeters,
|
||||
Inches
|
||||
};
|
||||
|
||||
enum EAxis : unsigned char
|
||||
{
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
E,
|
||||
Num_Axis
|
||||
};
|
||||
|
||||
enum EPositioningType : unsigned char
|
||||
{
|
||||
Absolute,
|
||||
Relative
|
||||
};
|
||||
|
||||
struct Metadata
|
||||
{
|
||||
ExtrusionRole extrusion_role;
|
||||
unsigned int extruder_id;
|
||||
float mm3_per_mm;
|
||||
float width; // mm
|
||||
float height; // mm
|
||||
float feedrate; // mm/s
|
||||
float fan_speed; // percentage
|
||||
unsigned int cp_color_id;
|
||||
|
||||
Metadata();
|
||||
Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, float mm3_per_mm, float width, float height, float feedrate, float fan_speed, unsigned int cp_color_id = 0);
|
||||
|
||||
bool operator != (const Metadata& other) const;
|
||||
};
|
||||
|
||||
struct GCodeMove
|
||||
{
|
||||
enum EType : unsigned char
|
||||
{
|
||||
Noop,
|
||||
Retract,
|
||||
Unretract,
|
||||
Tool_change,
|
||||
Move,
|
||||
Extrude,
|
||||
Num_Types
|
||||
};
|
||||
|
||||
EType type;
|
||||
Metadata data;
|
||||
Vec3f start_position;
|
||||
Vec3f end_position;
|
||||
float delta_extruder;
|
||||
|
||||
GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, float mm3_per_mm, float width, float height, float feedrate, const Vec3f& start_position, const Vec3f& end_position, float delta_extruder, float fan_speed, unsigned int cp_color_id = 0);
|
||||
GCodeMove(EType type, const Metadata& data, const Vec3f& start_position, const Vec3f& end_position, float delta_extruder);
|
||||
};
|
||||
|
||||
typedef std::vector<GCodeMove> GCodeMovesList;
|
||||
typedef std::map<GCodeMove::EType, GCodeMovesList> TypeToMovesMap;
|
||||
typedef std::map<unsigned int, Vec2d> ExtruderOffsetsMap;
|
||||
typedef std::map<unsigned int, unsigned int> ExtruderToColorMap;
|
||||
|
||||
private:
|
||||
struct State
|
||||
{
|
||||
EUnits units;
|
||||
EPositioningType global_positioning_type;
|
||||
EPositioningType e_local_positioning_type;
|
||||
Metadata data;
|
||||
Vec3f start_position = Vec3f::Zero();
|
||||
float cached_position[5];
|
||||
float start_extrusion;
|
||||
float position[Num_Axis];
|
||||
float origin[Num_Axis];
|
||||
unsigned int cp_color_counter = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
State m_state;
|
||||
GCodeReader m_parser;
|
||||
TypeToMovesMap m_moves_map;
|
||||
ExtruderOffsetsMap m_extruder_offsets;
|
||||
unsigned int m_extruders_count;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
|
||||
ExtruderToColorMap m_extruder_color;
|
||||
|
||||
// The output of process_layer()
|
||||
std::string m_process_output;
|
||||
|
||||
public:
|
||||
GCodeAnalyzer() { reset(); }
|
||||
|
||||
void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets) { m_extruder_offsets = extruder_offsets; }
|
||||
void set_extruders_count(unsigned int count);
|
||||
|
||||
void set_extrusion_axis(char axis) { m_parser.set_extrusion_axis(axis); }
|
||||
|
||||
void set_gcode_flavor(const GCodeFlavor& flavor) { m_gcode_flavor = flavor; }
|
||||
|
||||
// Reinitialize the analyzer
|
||||
void reset();
|
||||
|
||||
// Adds the gcode contained in the given string to the analysis and returns it after removing the workcodes
|
||||
const std::string& process_gcode(const std::string& gcode);
|
||||
|
||||
// Calculates all data needed for gcode visualization
|
||||
// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
|
||||
void calc_gcode_preview_data(GCodePreviewData& preview_data, std::function<void()> cancel_callback = std::function<void()>());
|
||||
|
||||
// Return an estimate of the memory consumed by the time estimator.
|
||||
size_t memory_used() const;
|
||||
|
||||
static bool is_valid_extrusion_role(ExtrusionRole role);
|
||||
|
||||
private:
|
||||
// Processes the given gcode line
|
||||
void _process_gcode_line(GCodeReader& reader, const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Move
|
||||
void _processG1(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Retract
|
||||
void _processG10(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Unretract
|
||||
void _processG11(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Firmware controlled Retract
|
||||
void _processG22(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Firmware controlled Unretract
|
||||
void _processG23(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set to Absolute Positioning
|
||||
void _processG90(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set to Relative Positioning
|
||||
void _processG91(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set Position
|
||||
void _processG92(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder to absolute mode
|
||||
void _processM82(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder to relative mode
|
||||
void _processM83(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set fan speed
|
||||
void _processM106(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Disable fan
|
||||
void _processM107(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set tool (MakerWare and Sailfish flavor)
|
||||
void _processM108orM135(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Recall stored home offsets
|
||||
void _processM132(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Repetier: Store x, y and z position
|
||||
void _processM401(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Repetier: Go to stored position
|
||||
void _processM402(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Processes T line (Select Tool)
|
||||
void _processT(const std::string& command);
|
||||
void _processT(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Processes the tags
|
||||
// Returns true if any tag has been processed
|
||||
bool _process_tags(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Processes extrusion role tag
|
||||
void _process_extrusion_role_tag(const std::string& comment, size_t pos);
|
||||
|
||||
// Processes mm3_per_mm tag
|
||||
void _process_mm3_per_mm_tag(const std::string& comment, size_t pos);
|
||||
|
||||
// Processes width tag
|
||||
void _process_width_tag(const std::string& comment, size_t pos);
|
||||
|
||||
// Processes height tag
|
||||
void _process_height_tag(const std::string& comment, size_t pos);
|
||||
|
||||
// Processes color change tag
|
||||
void _process_color_change_tag(unsigned extruder);
|
||||
|
||||
// Processes pause print and custom gcode tag
|
||||
void _process_pause_print_or_custom_code_tag();
|
||||
|
||||
// Processes new layer tag
|
||||
void _process_end_pause_print_or_custom_code_tag();
|
||||
|
||||
void _set_units(EUnits units);
|
||||
EUnits _get_units() const;
|
||||
|
||||
void _set_global_positioning_type(EPositioningType type);
|
||||
EPositioningType _get_global_positioning_type() const;
|
||||
|
||||
void _set_e_local_positioning_type(EPositioningType type);
|
||||
EPositioningType _get_e_local_positioning_type() const;
|
||||
|
||||
void _set_extrusion_role(ExtrusionRole extrusion_role);
|
||||
ExtrusionRole _get_extrusion_role() const;
|
||||
|
||||
void _set_extruder_id(unsigned int id);
|
||||
unsigned int _get_extruder_id() const;
|
||||
|
||||
void _set_cp_color_id(unsigned int id);
|
||||
unsigned int _get_cp_color_id() const;
|
||||
|
||||
void _set_mm3_per_mm(float value);
|
||||
float _get_mm3_per_mm() const;
|
||||
|
||||
void _set_width(float width);
|
||||
float _get_width() const;
|
||||
|
||||
void _set_height(float height);
|
||||
float _get_height() const;
|
||||
|
||||
void _set_feedrate(float feedrate_mm_sec);
|
||||
float _get_feedrate() const;
|
||||
|
||||
void _set_fan_speed(float fan_speed_percentage);
|
||||
float _get_fan_speed() const;
|
||||
|
||||
void _set_axis_position(EAxis axis, float position);
|
||||
float _get_axis_position(EAxis axis) const;
|
||||
|
||||
void _set_axis_origin(EAxis axis, float position);
|
||||
float _get_axis_origin(EAxis axis) const;
|
||||
|
||||
// Sets axes position to zero
|
||||
void _reset_axes_position();
|
||||
// Sets origin position to zero
|
||||
void _reset_axes_origin();
|
||||
|
||||
void _set_start_position(const Vec3f& position);
|
||||
const Vec3f& _get_start_position() const;
|
||||
|
||||
void _set_cached_position(unsigned char axis, float position);
|
||||
float _get_cached_position(unsigned char axis) const;
|
||||
|
||||
void _reset_cached_position();
|
||||
|
||||
void _set_start_extrusion(float extrusion);
|
||||
float _get_start_extrusion() const;
|
||||
float _get_delta_extrusion() const;
|
||||
|
||||
// Returns current xyz position (from m_state.position[])
|
||||
Vec3f _get_end_position() const;
|
||||
|
||||
// Adds a new move with the given data
|
||||
void _store_move(GCodeMove::EType type);
|
||||
|
||||
// Checks if the given int is a valid extrusion role (contained into enum ExtrusionRole)
|
||||
bool _is_valid_extrusion_role(int value) const;
|
||||
|
||||
// All the following methods throw CanceledException through print->throw_if_canceled() (sent by the caller as callback).
|
||||
void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
|
||||
void _calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
|
||||
void _calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
|
||||
void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // !ENABLE_GCODE_VIEWER
|
||||
|
||||
#endif /* slic3r_GCode_Analyzer_hpp_ */
|
|
@ -10,6 +10,11 @@
|
|||
#include <float.h>
|
||||
#include <assert.h>
|
||||
|
||||
#if __has_include(<charconv>)
|
||||
#include <charconv>
|
||||
#include <utility>
|
||||
#endif
|
||||
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
#include <chrono>
|
||||
|
||||
|
@ -170,7 +175,7 @@ void GCodeProcessor::TimeMachine::reset()
|
|||
prev.reset();
|
||||
gcode_time.reset();
|
||||
blocks = std::vector<TimeBlock>();
|
||||
g1_times_cache = std::vector<float>();
|
||||
g1_times_cache = std::vector<G1LinesCacheItem>();
|
||||
std::fill(moves_time.begin(), moves_time.end(), 0.0f);
|
||||
std::fill(roles_time.begin(), roles_time.end(), 0.0f);
|
||||
layers_time = std::vector<float>();
|
||||
|
@ -292,7 +297,7 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks)
|
|||
}
|
||||
layers_time[block.layer_id - 1] += block_time;
|
||||
}
|
||||
g1_times_cache.push_back(time);
|
||||
g1_times_cache.push_back({ block.g1_line_id, time });
|
||||
}
|
||||
|
||||
if (keep_last_n_blocks)
|
||||
|
@ -358,7 +363,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
|
|||
|
||||
std::string ret;
|
||||
|
||||
if (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag) {
|
||||
if (export_remaining_time_enabled && (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag)) {
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
|
||||
const TimeMachine& machine = machines[i];
|
||||
if (machine.enabled) {
|
||||
|
@ -371,10 +376,11 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
|
|||
else if (line == Estimated_Printing_Time_Placeholder_Tag) {
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
|
||||
const TimeMachine& machine = machines[i];
|
||||
if (machine.enabled) {
|
||||
PrintEstimatedTimeStatistics::ETimeMode mode = static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i);
|
||||
if (mode == PrintEstimatedTimeStatistics::ETimeMode::Normal || machine.enabled) {
|
||||
char buf[128];
|
||||
sprintf(buf, "; estimated printing time (%s mode) = %s\n",
|
||||
(static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal) ? "normal" : "silent",
|
||||
(mode == PrintEstimatedTimeStatistics::ETimeMode::Normal) ? "normal" : "silent",
|
||||
get_time_dhms(machine.time).c_str());
|
||||
ret += buf;
|
||||
}
|
||||
|
@ -394,18 +400,30 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
|
|||
return false;
|
||||
};
|
||||
|
||||
// Iterators for the normal and silent cached time estimate entry recently processed, used by process_line_G1.
|
||||
auto g1_times_cache_it = Slic3r::reserve_vector<std::vector<TimeMachine::G1LinesCacheItem>::const_iterator>(machines.size());
|
||||
for (const auto& machine : machines)
|
||||
g1_times_cache_it.emplace_back(machine.g1_times_cache.begin());
|
||||
// add lines M73 to exported gcode
|
||||
auto process_line_G1 = [&]() {
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
|
||||
const TimeMachine& machine = machines[i];
|
||||
if (machine.enabled && g1_lines_counter < machine.g1_times_cache.size()) {
|
||||
float elapsed_time = machine.g1_times_cache[g1_lines_counter];
|
||||
std::pair<int, int> to_export = { int(::roundf(100.0f * elapsed_time / machine.time)),
|
||||
time_in_minutes(machine.time - elapsed_time) };
|
||||
if (last_exported[i] != to_export) {
|
||||
export_line += format_line_M73(machine.line_m73_mask.c_str(),
|
||||
to_export.first, to_export.second);
|
||||
last_exported[i] = to_export;
|
||||
if (export_remaining_time_enabled) {
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
|
||||
const TimeMachine& machine = machines[i];
|
||||
if (machine.enabled) {
|
||||
// Skip all machine.g1_times_cache below g1_lines_counter.
|
||||
auto& it = g1_times_cache_it[i];
|
||||
while (it != machine.g1_times_cache.end() && it->id < g1_lines_counter)
|
||||
++it;
|
||||
if (it != machine.g1_times_cache.end() && it->id == g1_lines_counter) {
|
||||
float elapsed_time = it->elapsed_time;
|
||||
std::pair<int, int> to_export = { int(100.0f * elapsed_time / machine.time),
|
||||
time_in_minutes(machine.time - elapsed_time) };
|
||||
if (last_exported[i] != to_export) {
|
||||
export_line += format_line_M73(machine.line_m73_mask.c_str(),
|
||||
to_export.first, to_export.second);
|
||||
last_exported[i] = to_export;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -466,6 +484,8 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
|
|||
|
||||
const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProcessor::Producers = {
|
||||
{ EProducer::PrusaSlicer, "PrusaSlicer" },
|
||||
{ EProducer::Slic3rPE, "Slic3r Prusa Edition" },
|
||||
{ EProducer::Slic3r, "Slic3r" },
|
||||
{ EProducer::Cura, "Cura_SteamEngine" },
|
||||
{ EProducer::Simplify3D, "Simplify3D" },
|
||||
{ EProducer::CraftWare, "CraftWare" },
|
||||
|
@ -505,7 +525,9 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
|||
m_filament_diameters[i] = static_cast<float>(config.filament_diameter.values[i]);
|
||||
}
|
||||
|
||||
m_time_processor.machine_limits = reinterpret_cast<const MachineEnvelopeConfig&>(config);
|
||||
if (config.machine_limits_usage.value != MachineLimitsUsage::Ignore)
|
||||
m_time_processor.machine_limits = reinterpret_cast<const MachineEnvelopeConfig&>(config);
|
||||
|
||||
// Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
|
||||
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
|
||||
// are considered to be active for the single extruder multi-material printers only.
|
||||
|
@ -687,7 +709,7 @@ void GCodeProcessor::reset()
|
|||
m_global_positioning_type = EPositioningType::Absolute;
|
||||
m_e_local_positioning_type = EPositioningType::Absolute;
|
||||
m_extruder_offsets = std::vector<Vec3f>(Min_Extruder_Count, Vec3f::Zero());
|
||||
m_flavor = gcfRepRap;
|
||||
m_flavor = gcfRepRapSprinter;
|
||||
|
||||
m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
m_end_position = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
|
@ -709,6 +731,7 @@ void GCodeProcessor::reset()
|
|||
|
||||
m_filament_diameters = std::vector<float>(Min_Extruder_Count, 1.75f);
|
||||
m_extruded_last_z = 0.0f;
|
||||
m_g1_line_id = 0;
|
||||
m_layer_id = 0;
|
||||
m_cp_color.reset();
|
||||
|
||||
|
@ -739,9 +762,9 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
|
|||
// parse the gcode file to detect its producer
|
||||
if (m_producers_enabled) {
|
||||
m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
|
||||
std::string cmd = line.cmd();
|
||||
const std::string_view cmd = line.cmd();
|
||||
if (cmd.length() == 0) {
|
||||
std::string comment = line.comment();
|
||||
const std::string_view comment = line.comment();
|
||||
if (comment.length() > 1 && detect_producer(comment))
|
||||
m_parser.quit_parsing_file();
|
||||
}
|
||||
|
@ -749,7 +772,7 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
|
|||
|
||||
// if the gcode was produced by PrusaSlicer,
|
||||
// extract the config from it
|
||||
if (m_producer == EProducer::PrusaSlicer) {
|
||||
if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) {
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config.load_from_gcode_file(filename);
|
||||
|
@ -785,8 +808,7 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
|
|||
update_estimated_times_stats();
|
||||
|
||||
// post-process to add M73 lines into the gcode
|
||||
if (m_time_processor.export_remaining_time_enabled)
|
||||
m_time_processor.post_process(filename);
|
||||
m_time_processor.post_process(filename);
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
std::cout << "\n";
|
||||
|
@ -865,7 +887,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line)
|
|||
// update start position
|
||||
m_start_position = m_end_position;
|
||||
|
||||
std::string cmd = line.cmd();
|
||||
const std::string_view cmd = line.cmd();
|
||||
if (cmd.length() > 1) {
|
||||
// process command lines
|
||||
switch (::toupper(cmd[0]))
|
||||
|
@ -923,122 +945,151 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line)
|
|||
}
|
||||
}
|
||||
else {
|
||||
std::string comment = line.comment();
|
||||
if (comment.length() > 1)
|
||||
// process tags embedded into comments
|
||||
process_tags(comment);
|
||||
const std::string &comment = line.raw();
|
||||
if (comment.length() > 2 && comment.front() == ';')
|
||||
// Process tags embedded into comments. Tag comments always start at the start of a line
|
||||
// with a comment and continue with a tag without any whitespace separator.
|
||||
process_tags(comment.substr(1));
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_tags(const std::string& comment)
|
||||
static inline bool starts_with(const std::string_view comment, const std::string_view tag)
|
||||
{
|
||||
// producers tags
|
||||
if (m_producers_enabled) {
|
||||
if (m_producer != EProducer::Unknown) {
|
||||
if (process_producers_tags(comment))
|
||||
return;
|
||||
size_t tag_len = tag.size();
|
||||
return comment.size() >= tag_len && comment.substr(0, tag_len) == tag;
|
||||
}
|
||||
|
||||
#if __has_include(<charconv>)
|
||||
template <typename T, typename = void>
|
||||
struct is_from_chars_convertible : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_from_chars_convertible<T, std::void_t<decltype(std::from_chars(std::declval<const char*>(), std::declval<const char*>(), std::declval<T&>()))>> : std::true_type {};
|
||||
#endif
|
||||
|
||||
// Returns true if the number was parsed correctly into out and the number spanned the whole input string.
|
||||
template<typename T>
|
||||
[[nodiscard]] static inline bool parse_number(const std::string_view sv, T &out)
|
||||
{
|
||||
// https://www.bfilipek.com/2019/07/detect-overload-from-chars.html#example-stdfromchars
|
||||
#if __has_include(<charconv>)
|
||||
// Visual Studio 19 supports from_chars all right.
|
||||
// OSX compiler that we use only implements std::from_chars just for ints.
|
||||
// GCC that we compile on does not provide <charconv> at all.
|
||||
if constexpr (is_from_chars_convertible<T>::value) {
|
||||
auto str_end = sv.data() + sv.size();
|
||||
auto [end_ptr, error_code] = std::from_chars(sv.data(), str_end, out);
|
||||
return error_code == std::errc() && end_ptr == str_end;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// Legacy conversion, which is costly due to having to make a copy of the string before conversion.
|
||||
try {
|
||||
assert(sv.size() < 1024);
|
||||
assert(sv.data() != nullptr);
|
||||
std::string str { sv };
|
||||
size_t read = 0;
|
||||
if constexpr (std::is_same_v<T, int>)
|
||||
out = std::stoi(str, &read);
|
||||
else if constexpr (std::is_same_v<T, long>)
|
||||
out = std::stol(str, &read);
|
||||
else if constexpr (std::is_same_v<T, float>)
|
||||
out = std::stof(str, &read);
|
||||
else if constexpr (std::is_same_v<T, double>)
|
||||
out = std::stod(str, &read);
|
||||
return str.size() == read;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_tags(const std::string_view comment)
|
||||
{
|
||||
// producers tags
|
||||
if (m_producers_enabled && process_producers_tags(comment))
|
||||
return;
|
||||
|
||||
// extrusion role tag
|
||||
size_t pos = comment.find(Extrusion_Role_Tag);
|
||||
if (pos != comment.npos) {
|
||||
m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(pos + Extrusion_Role_Tag.length()));
|
||||
if (starts_with(comment, Extrusion_Role_Tag)) {
|
||||
m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(Extrusion_Role_Tag.length()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_producers_enabled || m_producer == EProducer::PrusaSlicer) {
|
||||
if ((!m_producers_enabled || m_producer == EProducer::PrusaSlicer) &&
|
||||
starts_with(comment, Height_Tag)) {
|
||||
// height tag
|
||||
pos = comment.find(Height_Tag);
|
||||
if (pos != comment.npos) {
|
||||
try {
|
||||
m_height = std::stof(comment.substr(pos + Height_Tag.length()));
|
||||
}
|
||||
catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (! parse_number(comment.substr(Height_Tag.size()), m_height))
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
|
||||
return;
|
||||
}
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
// width tag
|
||||
pos = comment.find(Width_Tag);
|
||||
if (pos != comment.npos) {
|
||||
try {
|
||||
m_width_compare.last_tag_value = std::stof(comment.substr(pos + Width_Tag.length()));
|
||||
}
|
||||
catch (...) {
|
||||
if (starts_with(comment, Width_Tag)) {
|
||||
if (! parse_number(comment.substr(Width_Tag.size()), m_width_compare.last_tag_value))
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
// color change tag
|
||||
pos = comment.find(Color_Change_Tag);
|
||||
if (pos != comment.npos) {
|
||||
pos = comment.find_last_of(",T");
|
||||
try {
|
||||
unsigned char extruder_id = (pos == comment.npos) ? 0 : static_cast<unsigned char>(std::stoi(comment.substr(pos + 1)));
|
||||
|
||||
m_extruder_colors[extruder_id] = static_cast<unsigned char>(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview
|
||||
++m_cp_color.counter;
|
||||
if (m_cp_color.counter == UCHAR_MAX)
|
||||
m_cp_color.counter = 0;
|
||||
|
||||
if (m_extruder_id == extruder_id) {
|
||||
m_cp_color.current = m_extruder_colors[extruder_id];
|
||||
store_move_vertex(EMoveType::Color_change);
|
||||
if (starts_with(comment, Color_Change_Tag)) {
|
||||
unsigned char extruder_id = 0;
|
||||
if (starts_with(comment.substr(Color_Change_Tag.size()), ",T")) {
|
||||
int eid;
|
||||
if (! parse_number(comment.substr(Color_Change_Tag.size() + 2), eid) || eid < 0 || eid > 255) {
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ").";
|
||||
return;
|
||||
}
|
||||
extruder_id = static_cast<unsigned char>(eid);
|
||||
}
|
||||
|
||||
process_custom_gcode_time(CustomGCode::ColorChange);
|
||||
}
|
||||
catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ").";
|
||||
m_extruder_colors[extruder_id] = static_cast<unsigned char>(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview
|
||||
++m_cp_color.counter;
|
||||
if (m_cp_color.counter == UCHAR_MAX)
|
||||
m_cp_color.counter = 0;
|
||||
|
||||
if (m_extruder_id == extruder_id) {
|
||||
m_cp_color.current = m_extruder_colors[extruder_id];
|
||||
store_move_vertex(EMoveType::Color_change);
|
||||
}
|
||||
|
||||
process_custom_gcode_time(CustomGCode::ColorChange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// pause print tag
|
||||
pos = comment.find(Pause_Print_Tag);
|
||||
if (pos != comment.npos) {
|
||||
if (comment == Pause_Print_Tag) {
|
||||
store_move_vertex(EMoveType::Pause_Print);
|
||||
process_custom_gcode_time(CustomGCode::PausePrint);
|
||||
return;
|
||||
}
|
||||
|
||||
// custom code tag
|
||||
pos = comment.find(Custom_Code_Tag);
|
||||
if (pos != comment.npos) {
|
||||
if (comment == Custom_Code_Tag) {
|
||||
store_move_vertex(EMoveType::Custom_GCode);
|
||||
return;
|
||||
}
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
// mm3_per_mm print tag
|
||||
pos = comment.find(Mm3_Per_Mm_Tag);
|
||||
if (pos != comment.npos) {
|
||||
try {
|
||||
m_mm3_per_mm_compare.last_tag_value = std::stof(comment.substr(pos + Mm3_Per_Mm_Tag.length()));
|
||||
}
|
||||
catch (...) {
|
||||
if (starts_with(comment, Mm3_Per_Mm_Tag)) {
|
||||
if (! parse_number(comment.substr(Mm3_Per_Mm_Tag.size()), m_mm3_per_mm_compare.last_tag_value))
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ").";
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
// layer change tag
|
||||
pos = comment.find(Layer_Change_Tag);
|
||||
if (pos != comment.npos) {
|
||||
if (comment == Layer_Change_Tag) {
|
||||
++m_layer_id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool GCodeProcessor::process_producers_tags(const std::string& comment)
|
||||
bool GCodeProcessor::process_producers_tags(const std::string_view comment)
|
||||
{
|
||||
switch (m_producer)
|
||||
{
|
||||
|
@ -1051,18 +1102,18 @@ bool GCodeProcessor::process_producers_tags(const std::string& comment)
|
|||
}
|
||||
}
|
||||
|
||||
bool GCodeProcessor::process_prusaslicer_tags(const std::string& comment)
|
||||
bool GCodeProcessor::process_prusaslicer_tags(const std::string_view comment)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GCodeProcessor::process_cura_tags(const std::string& comment)
|
||||
bool GCodeProcessor::process_cura_tags(const std::string_view comment)
|
||||
{
|
||||
// TYPE -> extrusion role
|
||||
std::string tag = "TYPE:";
|
||||
size_t pos = comment.find(tag);
|
||||
if (pos != comment.npos) {
|
||||
std::string type = comment.substr(pos + tag.length());
|
||||
const std::string_view type = comment.substr(pos + tag.length());
|
||||
if (type == "SKIRT")
|
||||
m_extrusion_role = erSkirt;
|
||||
else if (type == "WALL-OUTER")
|
||||
|
@ -1091,7 +1142,7 @@ bool GCodeProcessor::process_cura_tags(const std::string& comment)
|
|||
tag = "FLAVOR:";
|
||||
pos = comment.find(tag);
|
||||
if (pos != comment.npos) {
|
||||
std::string flavor = comment.substr(pos + tag.length());
|
||||
const std::string_view flavor = comment.substr(pos + tag.length());
|
||||
if (flavor == "BFB")
|
||||
m_flavor = gcfMarlin; // << ???????????????????????
|
||||
else if (flavor == "Mach3")
|
||||
|
@ -1107,7 +1158,7 @@ bool GCodeProcessor::process_cura_tags(const std::string& comment)
|
|||
else if (flavor == "Repetier")
|
||||
m_flavor = gcfRepetier;
|
||||
else if (flavor == "RepRap")
|
||||
m_flavor = gcfRepRap;
|
||||
m_flavor = gcfRepRapFirmware;
|
||||
else if (flavor == "Marlin")
|
||||
m_flavor = gcfMarlin;
|
||||
else
|
||||
|
@ -1119,7 +1170,7 @@ bool GCodeProcessor::process_cura_tags(const std::string& comment)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool GCodeProcessor::process_simplify3d_tags(const std::string& comment)
|
||||
bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment)
|
||||
{
|
||||
// extrusion roles
|
||||
|
||||
|
@ -1207,7 +1258,7 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment)
|
|||
std::string tag = " tool";
|
||||
pos = comment.find(tag);
|
||||
if (pos == 0) {
|
||||
std::string data = comment.substr(pos + tag.length());
|
||||
const std::string_view data = comment.substr(pos + tag.length());
|
||||
std::string h_tag = "H";
|
||||
size_t h_start = data.find(h_tag);
|
||||
size_t h_end = data.find_first_of(' ', h_start);
|
||||
|
@ -1215,20 +1266,12 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment)
|
|||
size_t w_start = data.find(w_tag);
|
||||
size_t w_end = data.find_first_of(' ', w_start);
|
||||
if (h_start != data.npos) {
|
||||
try {
|
||||
m_height_compare.last_tag_value = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end));
|
||||
}
|
||||
catch (...) {
|
||||
if (! parse_number(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end), m_height_compare.last_tag_value))
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
|
||||
}
|
||||
}
|
||||
if (w_start != data.npos) {
|
||||
try {
|
||||
m_width_compare.last_tag_value = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end));
|
||||
}
|
||||
catch (...) {
|
||||
if (! parse_number(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end), m_width_compare.last_tag_value))
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1238,13 +1281,13 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool GCodeProcessor::process_craftware_tags(const std::string& comment)
|
||||
bool GCodeProcessor::process_craftware_tags(const std::string_view comment)
|
||||
{
|
||||
// segType -> extrusion role
|
||||
std::string tag = "segType:";
|
||||
size_t pos = comment.find(tag);
|
||||
if (pos != comment.npos) {
|
||||
std::string type = comment.substr(pos + tag.length());
|
||||
const std::string_view type = comment.substr(pos + tag.length());
|
||||
if (type == "Skirt")
|
||||
m_extrusion_role = erSkirt;
|
||||
else if (type == "Perimeter")
|
||||
|
@ -1278,13 +1321,13 @@ bool GCodeProcessor::process_craftware_tags(const std::string& comment)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool GCodeProcessor::process_ideamaker_tags(const std::string& comment)
|
||||
bool GCodeProcessor::process_ideamaker_tags(const std::string_view comment)
|
||||
{
|
||||
// TYPE -> extrusion role
|
||||
std::string tag = "TYPE:";
|
||||
size_t pos = comment.find(tag);
|
||||
if (pos != comment.npos) {
|
||||
std::string type = comment.substr(pos + tag.length());
|
||||
const std::string_view type = comment.substr(pos + tag.length());
|
||||
if (type == "RAFT")
|
||||
m_extrusion_role = erSkirt;
|
||||
else if (type == "WALL-OUTER")
|
||||
|
@ -1313,12 +1356,8 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment)
|
|||
tag = "WIDTH:";
|
||||
pos = comment.find(tag);
|
||||
if (pos != comment.npos) {
|
||||
try {
|
||||
m_width_compare.last_tag_value = std::stof(comment.substr(pos + tag.length()));
|
||||
}
|
||||
catch (...) {
|
||||
if (! parse_number(comment.substr(pos + tag.length()), m_width_compare.last_tag_value))
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ").";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1326,12 +1365,8 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment)
|
|||
tag = "HEIGHT:";
|
||||
pos = comment.find(tag);
|
||||
if (pos != comment.npos) {
|
||||
try {
|
||||
m_height_compare.last_tag_value = std::stof(comment.substr(pos + tag.length()));
|
||||
}
|
||||
catch (...) {
|
||||
if (! parse_number(comment.substr(pos + tag.length()), m_height_compare.last_tag_value))
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
@ -1339,7 +1374,7 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool GCodeProcessor::detect_producer(const std::string& comment)
|
||||
bool GCodeProcessor::detect_producer(const std::string_view comment)
|
||||
{
|
||||
for (const auto& [id, search_string] : Producers) {
|
||||
size_t pos = comment.find(search_string);
|
||||
|
@ -1391,6 +1426,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
|||
return type;
|
||||
};
|
||||
|
||||
++m_g1_line_id;
|
||||
|
||||
// enable processing of lines M201/M203/M204/M205
|
||||
m_time_processor.machine_envelope_processing_enabled = true;
|
||||
|
||||
|
@ -1454,7 +1491,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
|||
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height;
|
||||
|
||||
// clamp width to avoid artifacts which may arise from wrong values of m_height
|
||||
m_width = std::min(m_width, 4.0f * m_height);
|
||||
m_width = std::min(m_width, 1.0f);
|
||||
// m_width = std::min(m_width, 4.0f * m_height);
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_width_compare.update(m_width, m_extrusion_role);
|
||||
|
@ -1495,6 +1533,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
|||
block.move_type = type;
|
||||
block.role = m_extrusion_role;
|
||||
block.distance = distance;
|
||||
block.g1_line_id = m_g1_line_id;
|
||||
block.layer_id = m_layer_id;
|
||||
|
||||
// calculates block cruise feedrate
|
||||
|
@ -1798,7 +1837,7 @@ void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line)
|
|||
return;
|
||||
|
||||
// see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration
|
||||
float factor = (m_flavor != gcfRepRap && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
|
||||
float factor = ((m_flavor != gcfRepRapSprinter && m_flavor != gcfRepRapFirmware) && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
|
||||
if (line.has_x())
|
||||
|
@ -1991,11 +2030,14 @@ void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line)
|
|||
process_T(line.cmd());
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_T(const std::string& command)
|
||||
void GCodeProcessor::process_T(const std::string_view command)
|
||||
{
|
||||
if (command.length() > 1) {
|
||||
try {
|
||||
unsigned char id = static_cast<unsigned char>(std::stoi(command.substr(1)));
|
||||
int eid;
|
||||
if (! parse_number(command.substr(1), eid) || eid < 0 || eid > 255) {
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ").";
|
||||
} else {
|
||||
unsigned char id = static_cast<unsigned char>(eid);
|
||||
if (m_extruder_id != id) {
|
||||
unsigned char extruders_count = static_cast<unsigned char>(m_extruder_offsets.size());
|
||||
if (id >= extruders_count)
|
||||
|
@ -2017,9 +2059,6 @@ void GCodeProcessor::process_T(const std::string& command)
|
|||
store_move_vertex(EMoveType::Tool_change);
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ").";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <array>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -145,6 +146,7 @@ namespace Slic3r {
|
|||
|
||||
EMoveType move_type{ EMoveType::Noop };
|
||||
ExtrusionRole role{ erNone };
|
||||
unsigned int g1_line_id{ 0 };
|
||||
unsigned int layer_id{ 0 };
|
||||
float distance{ 0.0f }; // mm
|
||||
float acceleration{ 0.0f }; // mm/s^2
|
||||
|
@ -182,6 +184,12 @@ namespace Slic3r {
|
|||
void reset();
|
||||
};
|
||||
|
||||
struct G1LinesCacheItem
|
||||
{
|
||||
unsigned int id;
|
||||
float elapsed_time;
|
||||
};
|
||||
|
||||
bool enabled;
|
||||
float acceleration; // mm/s^2
|
||||
// hard limit for the acceleration, to which the firmware will clamp.
|
||||
|
@ -193,7 +201,7 @@ namespace Slic3r {
|
|||
State prev;
|
||||
CustomGCodeTime gcode_time;
|
||||
std::vector<TimeBlock> blocks;
|
||||
std::vector<float> g1_times_cache;
|
||||
std::vector<G1LinesCacheItem> g1_times_cache;
|
||||
std::array<float, static_cast<size_t>(EMoveType::Count)> moves_time;
|
||||
std::array<float, static_cast<size_t>(ExtrusionRole::erCount)> roles_time;
|
||||
std::vector<float> layers_time;
|
||||
|
@ -305,10 +313,12 @@ namespace Slic3r {
|
|||
{}
|
||||
|
||||
void update(float value, ExtrusionRole role) {
|
||||
++count;
|
||||
if (last_tag_value != 0.0f) {
|
||||
if (std::abs(value - last_tag_value) / last_tag_value > threshold)
|
||||
errors.push_back({ value, last_tag_value, role });
|
||||
if (role != erCustom) {
|
||||
++count;
|
||||
if (last_tag_value != 0.0f) {
|
||||
if (std::abs(value - last_tag_value) / last_tag_value > threshold)
|
||||
errors.push_back({ value, last_tag_value, role });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,6 +384,7 @@ namespace Slic3r {
|
|||
ExtruderColors m_extruder_colors;
|
||||
std::vector<float> m_filament_diameters;
|
||||
float m_extruded_last_z;
|
||||
unsigned int m_g1_line_id;
|
||||
unsigned int m_layer_id;
|
||||
CpColor m_cp_color;
|
||||
|
||||
|
@ -381,6 +392,8 @@ namespace Slic3r {
|
|||
{
|
||||
Unknown,
|
||||
PrusaSlicer,
|
||||
Slic3rPE,
|
||||
Slic3r,
|
||||
Cura,
|
||||
Simplify3D,
|
||||
CraftWare,
|
||||
|
@ -434,15 +447,15 @@ namespace Slic3r {
|
|||
void process_gcode_line(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Process tags embedded into comments
|
||||
void process_tags(const std::string& comment);
|
||||
bool process_producers_tags(const std::string& comment);
|
||||
bool process_prusaslicer_tags(const std::string& comment);
|
||||
bool process_cura_tags(const std::string& comment);
|
||||
bool process_simplify3d_tags(const std::string& comment);
|
||||
bool process_craftware_tags(const std::string& comment);
|
||||
bool process_ideamaker_tags(const std::string& comment);
|
||||
void process_tags(const std::string_view comment);
|
||||
bool process_producers_tags(const std::string_view comment);
|
||||
bool process_prusaslicer_tags(const std::string_view comment);
|
||||
bool process_cura_tags(const std::string_view comment);
|
||||
bool process_simplify3d_tags(const std::string_view comment);
|
||||
bool process_craftware_tags(const std::string_view comment);
|
||||
bool process_ideamaker_tags(const std::string_view comment);
|
||||
|
||||
bool detect_producer(const std::string& comment);
|
||||
bool detect_producer(const std::string_view comment);
|
||||
|
||||
// Move
|
||||
void process_G0(const GCodeReader::GCodeLine& line);
|
||||
|
@ -528,7 +541,7 @@ namespace Slic3r {
|
|||
|
||||
// Processes T line (Select Tool)
|
||||
void process_T(const GCodeReader::GCodeLine& line);
|
||||
void process_T(const std::string& command);
|
||||
void process_T(const std::string_view command);
|
||||
|
||||
void store_move_vertex(EMoveType type);
|
||||
|
||||
|
|
|
@ -165,9 +165,10 @@ static inline float parse_float(const char *&line)
|
|||
return result;
|
||||
};
|
||||
|
||||
#define EXTRUSION_ROLE_TAG ";_EXTRUSION_ROLE:"
|
||||
bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLine &buf)
|
||||
{
|
||||
static constexpr const char *EXTRUSION_ROLE_TAG = ";_EXTRUSION_ROLE:";
|
||||
|
||||
if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) {
|
||||
line += strlen(EXTRUSION_ROLE_TAG);
|
||||
int role = atoi(line);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#include "Analyzer.hpp"
|
||||
#include "PreviewData.hpp"
|
||||
#include <I18N.hpp>
|
||||
#include "Utils.hpp"
|
||||
|
|
|
@ -126,7 +126,7 @@ public:
|
|||
}
|
||||
|
||||
WipeTowerWriter& disable_linear_advance() {
|
||||
m_gcode += (m_gcode_flavor == gcfRepRap
|
||||
m_gcode += (m_gcode_flavor == gcfRepRapSprinter || m_gcode_flavor == gcfRepRapFirmware
|
||||
? (std::string("M572 D") + std::to_string(m_current_tool) + " S0\n")
|
||||
: std::string("M900 K0\n"));
|
||||
return *this;
|
||||
|
@ -386,7 +386,7 @@ public:
|
|||
// Set digital trimpot motor
|
||||
WipeTowerWriter& set_extruder_trimpot(int current)
|
||||
{
|
||||
if (m_gcode_flavor == gcfRepRap)
|
||||
if (m_gcode_flavor == gcfRepRapSprinter || m_gcode_flavor == gcfRepRapFirmware)
|
||||
m_gcode += "M906 E";
|
||||
else
|
||||
m_gcode += "M907 E";
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -17,13 +18,13 @@ public:
|
|||
GCodeLine() { reset(); }
|
||||
void reset() { m_mask = 0; memset(m_axis, 0, sizeof(m_axis)); m_raw.clear(); }
|
||||
|
||||
const std::string& raw() const { return m_raw; }
|
||||
const std::string cmd() const {
|
||||
const std::string& raw() const { return m_raw; }
|
||||
const std::string_view cmd() const {
|
||||
const char *cmd = GCodeReader::skip_whitespaces(m_raw.c_str());
|
||||
return std::string(cmd, GCodeReader::skip_word(cmd));
|
||||
return std::string_view(cmd, GCodeReader::skip_word(cmd) - cmd);
|
||||
}
|
||||
const std::string comment() const
|
||||
{ size_t pos = m_raw.find(';'); return (pos == std::string::npos) ? "" : m_raw.substr(pos + 1); }
|
||||
const std::string_view comment() const
|
||||
{ size_t pos = m_raw.find(';'); return (pos == std::string::npos) ? std::string_view() : std::string_view(m_raw).substr(pos + 1); }
|
||||
|
||||
bool has(Axis axis) const { return (m_mask & (1 << int(axis))) != 0; }
|
||||
float value(Axis axis) const { return m_axis[axis]; }
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,488 +0,0 @@
|
|||
#ifndef slic3r_GCodeTimeEstimator_hpp_
|
||||
#define slic3r_GCodeTimeEstimator_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "GCodeReader.hpp"
|
||||
#include "CustomGCode.hpp"
|
||||
|
||||
#if !ENABLE_GCODE_VIEWER
|
||||
|
||||
#define ENABLE_MOVE_STATS 0
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//
|
||||
// Some of the algorithms used by class GCodeTimeEstimator were inpired by
|
||||
// Cura Engine's class TimeEstimateCalculator
|
||||
// https://github.com/Ultimaker/CuraEngine/blob/master/src/timeEstimate.h
|
||||
//
|
||||
class GCodeTimeEstimator
|
||||
{
|
||||
public:
|
||||
static const std::string Normal_First_M73_Output_Placeholder_Tag;
|
||||
static const std::string Silent_First_M73_Output_Placeholder_Tag;
|
||||
static const std::string Normal_Last_M73_Output_Placeholder_Tag;
|
||||
static const std::string Silent_Last_M73_Output_Placeholder_Tag;
|
||||
|
||||
static const std::string Color_Change_Tag;
|
||||
static const std::string Pause_Print_Tag;
|
||||
|
||||
enum EMode : unsigned char
|
||||
{
|
||||
Normal,
|
||||
Silent
|
||||
};
|
||||
|
||||
enum EUnits : unsigned char
|
||||
{
|
||||
Millimeters,
|
||||
Inches
|
||||
};
|
||||
|
||||
enum EAxis : unsigned char
|
||||
{
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
E,
|
||||
Num_Axis
|
||||
};
|
||||
|
||||
enum EPositioningType : unsigned char
|
||||
{
|
||||
Absolute,
|
||||
Relative
|
||||
};
|
||||
|
||||
private:
|
||||
struct Axis
|
||||
{
|
||||
float position; // mm
|
||||
float origin; // mm
|
||||
float max_feedrate; // mm/s
|
||||
float max_acceleration; // mm/s^2
|
||||
float max_jerk; // mm/s
|
||||
};
|
||||
|
||||
struct Feedrates
|
||||
{
|
||||
float feedrate; // mm/s
|
||||
float axis_feedrate[Num_Axis]; // mm/s
|
||||
float abs_axis_feedrate[Num_Axis]; // mm/s
|
||||
float safe_feedrate; // mm/s
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
struct State
|
||||
{
|
||||
GCodeFlavor dialect;
|
||||
EUnits units;
|
||||
EPositioningType global_positioning_type;
|
||||
EPositioningType e_local_positioning_type;
|
||||
Axis axis[Num_Axis];
|
||||
float feedrate; // mm/s
|
||||
float acceleration; // mm/s^2
|
||||
// hard limit for the acceleration, to which the firmware will clamp.
|
||||
float max_acceleration; // mm/s^2
|
||||
float retract_acceleration; // mm/s^2
|
||||
float minimum_feedrate; // mm/s
|
||||
float minimum_travel_feedrate; // mm/s
|
||||
float extrude_factor_override_percentage;
|
||||
// Additional load / unload times for a filament exchange sequence.
|
||||
std::vector<float> filament_load_times;
|
||||
std::vector<float> filament_unload_times;
|
||||
unsigned int g1_line_id;
|
||||
// extruder_id is currently used to correctly calculate filament load / unload times
|
||||
// into the total print time. This is currently only really used by the MK3 MMU2:
|
||||
// Extruder id (-1) means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit.
|
||||
static const unsigned int extruder_id_unloaded = (unsigned int)-1;
|
||||
unsigned int extruder_id;
|
||||
};
|
||||
|
||||
public:
|
||||
struct Block
|
||||
{
|
||||
#if ENABLE_MOVE_STATS
|
||||
enum EMoveType : unsigned char
|
||||
{
|
||||
Noop,
|
||||
Retract,
|
||||
Unretract,
|
||||
Tool_change,
|
||||
Move,
|
||||
Extrude,
|
||||
Num_Types
|
||||
};
|
||||
#endif // ENABLE_MOVE_STATS
|
||||
|
||||
struct FeedrateProfile
|
||||
{
|
||||
float entry; // mm/s
|
||||
float cruise; // mm/s
|
||||
float exit; // mm/s
|
||||
};
|
||||
|
||||
struct Trapezoid
|
||||
{
|
||||
float accelerate_until; // mm
|
||||
float decelerate_after; // mm
|
||||
float cruise_feedrate; // mm/sec
|
||||
|
||||
float acceleration_time(float entry_feedrate, float acceleration) const;
|
||||
float cruise_time() const;
|
||||
float deceleration_time(float distance, float acceleration) const;
|
||||
float cruise_distance() const;
|
||||
|
||||
// This function gives the time needed to accelerate from an initial speed to reach a final distance.
|
||||
static float acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration);
|
||||
|
||||
// This function gives the final speed while accelerating at the given constant acceleration from the given initial speed along the given distance.
|
||||
static float speed_from_distance(float initial_feedrate, float distance, float acceleration);
|
||||
};
|
||||
|
||||
struct Flags
|
||||
{
|
||||
bool recalculate;
|
||||
bool nominal_length;
|
||||
};
|
||||
|
||||
#if ENABLE_MOVE_STATS
|
||||
EMoveType move_type;
|
||||
#endif // ENABLE_MOVE_STATS
|
||||
Flags flags;
|
||||
|
||||
float distance; // mm
|
||||
float acceleration; // mm/s^2
|
||||
float max_entry_speed; // mm/s
|
||||
float safe_feedrate; // mm/s
|
||||
|
||||
FeedrateProfile feedrate;
|
||||
Trapezoid trapezoid;
|
||||
|
||||
// Ordnary index of this G1 line in the file.
|
||||
int g1_line_id { -1 };
|
||||
|
||||
// Returns the time spent accelerating toward cruise speed, in seconds
|
||||
float acceleration_time() const;
|
||||
|
||||
// Returns the time spent at cruise speed, in seconds
|
||||
float cruise_time() const;
|
||||
|
||||
// Returns the time spent decelerating from cruise speed, in seconds
|
||||
float deceleration_time() const;
|
||||
|
||||
// Returns the distance covered at cruise speed, in mm
|
||||
float cruise_distance() const;
|
||||
|
||||
// Calculates this block's trapezoid
|
||||
void calculate_trapezoid();
|
||||
|
||||
// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the
|
||||
// acceleration within the allotted distance.
|
||||
static float max_allowable_speed(float acceleration, float target_velocity, float distance);
|
||||
|
||||
// Calculates the distance (not time) it takes to accelerate from initial_rate to target_rate using the given acceleration:
|
||||
static float estimate_acceleration_distance(float initial_rate, float target_rate, float acceleration);
|
||||
|
||||
// This function gives you the point at which you must start braking (at the rate of -acceleration) if
|
||||
// you started at speed initial_rate and accelerated until this point and want to end at the final_rate after
|
||||
// a total travel of distance. This can be used to compute the intersection point between acceleration and
|
||||
// deceleration in the cases where the trapezoid has no plateau (i.e. never reaches maximum speed)
|
||||
static float intersection_distance(float initial_rate, float final_rate, float acceleration, float distance);
|
||||
};
|
||||
|
||||
typedef std::vector<Block> BlocksList;
|
||||
|
||||
#if ENABLE_MOVE_STATS
|
||||
struct MoveStats
|
||||
{
|
||||
unsigned int count;
|
||||
float time;
|
||||
|
||||
MoveStats();
|
||||
};
|
||||
|
||||
typedef std::map<Block::EMoveType, MoveStats> MovesStatsMap;
|
||||
#endif // ENABLE_MOVE_STATS
|
||||
|
||||
public:
|
||||
typedef std::pair<int, float> G1LineIdTime;
|
||||
typedef std::vector<G1LineIdTime> G1LineIdsTimes;
|
||||
|
||||
struct PostProcessData
|
||||
{
|
||||
const G1LineIdsTimes& g1_times;
|
||||
float time;
|
||||
};
|
||||
|
||||
private:
|
||||
EMode m_mode;
|
||||
GCodeReader m_parser;
|
||||
State m_state;
|
||||
Feedrates m_curr;
|
||||
Feedrates m_prev;
|
||||
BlocksList m_blocks;
|
||||
// Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks.
|
||||
// Let's be conservative and plan for newer boards with more memory.
|
||||
static constexpr size_t planner_queue_size = 64;
|
||||
// The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added.
|
||||
// We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate.
|
||||
static constexpr size_t planner_refresh_if_larger = planner_queue_size * 4;
|
||||
// Map from g1 line id to its elapsed time from the start of the print.
|
||||
G1LineIdsTimes m_g1_times;
|
||||
float m_time; // s
|
||||
|
||||
// data to calculate custom code times
|
||||
bool m_needs_custom_gcode_times;
|
||||
std::vector<std::pair<CustomGCode::Type, float>> m_custom_gcode_times;
|
||||
float m_custom_gcode_time_cache;
|
||||
|
||||
#if ENABLE_MOVE_STATS
|
||||
MovesStatsMap _moves_stats;
|
||||
#endif // ENABLE_MOVE_STATS
|
||||
|
||||
public:
|
||||
explicit GCodeTimeEstimator(EMode mode);
|
||||
|
||||
// Adds the given gcode line
|
||||
void add_gcode_line(const std::string& gcode_line);
|
||||
|
||||
void add_gcode_block(const char *ptr);
|
||||
void add_gcode_block(const std::string &str) { this->add_gcode_block(str.c_str()); }
|
||||
|
||||
// Calculates the time estimate from the gcode lines added using add_gcode_line() or add_gcode_block()
|
||||
// start_from_beginning:
|
||||
// if set to true all blocks will be used to calculate the time estimate,
|
||||
// if set to false only the blocks not yet processed will be used and the calculated time will be added to the current calculated time
|
||||
void calculate_time(bool start_from_beginning);
|
||||
|
||||
// Calculates the time estimate from the given gcode in string format
|
||||
//void calculate_time_from_text(const std::string& gcode);
|
||||
|
||||
// Calculates the time estimate from the gcode contained in the file with the given filename
|
||||
//void calculate_time_from_file(const std::string& file);
|
||||
|
||||
// Calculates the time estimate from the gcode contained in given list of gcode lines
|
||||
//void calculate_time_from_lines(const std::vector<std::string>& gcode_lines);
|
||||
|
||||
// Process the gcode contained in the file with the given filename,
|
||||
// replacing placeholders with correspondent new lines M73
|
||||
// placing new lines M73 (containing the remaining time) where needed (in dependence of the given interval in seconds)
|
||||
// and removing working tags (as those used for color changes)
|
||||
// if normal_mode == nullptr no M73 line will be added for normal mode
|
||||
// if silent_mode == nullptr no M73 line will be added for silent mode
|
||||
static bool post_process(const std::string& filename, float interval_sec, const PostProcessData* const normal_mode, const PostProcessData* const silent_mode);
|
||||
|
||||
// Set current position on the given axis with the given value
|
||||
void set_axis_position(EAxis axis, float position);
|
||||
// Set current origin on the given axis with the given value
|
||||
void set_axis_origin(EAxis axis, float position);
|
||||
|
||||
void set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec);
|
||||
void set_axis_max_acceleration(EAxis axis, float acceleration);
|
||||
void set_axis_max_jerk(EAxis axis, float jerk);
|
||||
|
||||
// Returns current position on the given axis
|
||||
float get_axis_position(EAxis axis) const;
|
||||
// Returns current origin on the given axis
|
||||
float get_axis_origin(EAxis axis) const;
|
||||
|
||||
float get_axis_max_feedrate(EAxis axis) const;
|
||||
float get_axis_max_acceleration(EAxis axis) const;
|
||||
float get_axis_max_jerk(EAxis axis) const;
|
||||
|
||||
void set_feedrate(float feedrate_mm_sec);
|
||||
float get_feedrate() const;
|
||||
|
||||
void set_acceleration(float acceleration_mm_sec2);
|
||||
float get_acceleration() const;
|
||||
|
||||
// Maximum acceleration for the machine. The firmware simulator will clamp the M204 Sxxx to this maximum.
|
||||
void set_max_acceleration(float acceleration_mm_sec2);
|
||||
float get_max_acceleration() const;
|
||||
|
||||
void set_retract_acceleration(float acceleration_mm_sec2);
|
||||
float get_retract_acceleration() const;
|
||||
|
||||
void set_minimum_feedrate(float feedrate_mm_sec);
|
||||
float get_minimum_feedrate() const;
|
||||
|
||||
void set_minimum_travel_feedrate(float feedrate_mm_sec);
|
||||
float get_minimum_travel_feedrate() const;
|
||||
|
||||
void set_filament_load_times(const std::vector<double> &filament_load_times);
|
||||
void set_filament_unload_times(const std::vector<double> &filament_unload_times);
|
||||
float get_filament_load_time(unsigned int id_extruder);
|
||||
float get_filament_unload_time(unsigned int id_extruder);
|
||||
|
||||
void set_extrude_factor_override_percentage(float percentage);
|
||||
float get_extrude_factor_override_percentage() const;
|
||||
|
||||
void set_dialect(GCodeFlavor dialect);
|
||||
GCodeFlavor get_dialect() const;
|
||||
|
||||
void set_units(EUnits units);
|
||||
EUnits get_units() const;
|
||||
|
||||
void set_global_positioning_type(EPositioningType type);
|
||||
EPositioningType get_global_positioning_type() const;
|
||||
|
||||
void set_e_local_positioning_type(EPositioningType type);
|
||||
EPositioningType get_e_local_positioning_type() const;
|
||||
|
||||
int get_g1_line_id() const;
|
||||
void increment_g1_line_id();
|
||||
void reset_g1_line_id();
|
||||
|
||||
void set_extrusion_axis(char axis) { m_parser.set_extrusion_axis(axis); }
|
||||
|
||||
void set_extruder_id(unsigned int id);
|
||||
unsigned int get_extruder_id() const;
|
||||
void reset_extruder_id();
|
||||
|
||||
void set_default();
|
||||
|
||||
// Call this method before to start adding lines using add_gcode_line() when reusing an instance of GCodeTimeEstimator
|
||||
void reset();
|
||||
|
||||
// Returns the estimated time, in seconds
|
||||
float get_time() const;
|
||||
|
||||
// Returns the estimated time, in format DDd HHh MMm SSs
|
||||
std::string get_time_dhms() const;
|
||||
|
||||
// Returns the estimated time, in format DDd HHh MMm
|
||||
std::string get_time_dhm() const;
|
||||
|
||||
// Returns the estimated time, in minutes (integer)
|
||||
std::string get_time_minutes() const;
|
||||
|
||||
// Returns the estimated time, in seconds, for each custom gcode
|
||||
std::vector<std::pair<CustomGCode::Type, float>> get_custom_gcode_times() const;
|
||||
|
||||
// Returns the estimated time, in format DDd HHh MMm SSs, for each color
|
||||
// If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)"
|
||||
std::vector<std::string> get_color_times_dhms(bool include_remaining) const;
|
||||
|
||||
// Returns the estimated time, in minutes (integer), for each color
|
||||
// If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)"
|
||||
std::vector<std::string> get_color_times_minutes(bool include_remaining) const;
|
||||
|
||||
// Returns the estimated time, in format DDd HHh MMm, for each custom_gcode
|
||||
// If include_remaining==true the strings will be formatted as: "time for custom_gcode (remaining time at color start)"
|
||||
std::vector<std::pair<CustomGCode::Type, std::string>> get_custom_gcode_times_dhm(bool include_remaining) const;
|
||||
|
||||
// Return an estimate of the memory consumed by the time estimator.
|
||||
size_t memory_used() const;
|
||||
|
||||
PostProcessData get_post_process_data() const { return PostProcessData{ m_g1_times, m_time }; }
|
||||
|
||||
private:
|
||||
void _reset();
|
||||
void _reset_time();
|
||||
void _reset_blocks();
|
||||
|
||||
// Calculates the time estimate
|
||||
void _calculate_time(size_t keep_last_n_blocks);
|
||||
|
||||
// Processes the given gcode line
|
||||
void _process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Move
|
||||
void _processG1(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Dwell
|
||||
void _processG4(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set Units to Inches
|
||||
void _processG20(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set Units to Millimeters
|
||||
void _processG21(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Move to Origin (Home)
|
||||
void _processG28(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set to Absolute Positioning
|
||||
void _processG90(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set to Relative Positioning
|
||||
void _processG91(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set Position
|
||||
void _processG92(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Sleep or Conditional stop
|
||||
void _processM1(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder to absolute mode
|
||||
void _processM82(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder to relative mode
|
||||
void _processM83(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set Extruder Temperature and Wait
|
||||
void _processM109(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set max printing acceleration
|
||||
void _processM201(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set maximum feedrate
|
||||
void _processM203(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set default acceleration
|
||||
void _processM204(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Advanced settings
|
||||
void _processM205(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extrude factor override percentage
|
||||
void _processM221(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set allowable instantaneous speed change
|
||||
void _processM566(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Unload the current filament into the MK3 MMU2 unit at the end of print.
|
||||
void _processM702(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Processes T line (Select Tool)
|
||||
void _processT(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Processes the tags
|
||||
// Returns true if any tag has been processed
|
||||
bool _process_tags(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Processes ColorChangeTag and PausePrintTag
|
||||
void _process_custom_gcode_tag(CustomGCode::Type code);
|
||||
|
||||
// Simulates firmware st_synchronize() call
|
||||
void _simulate_st_synchronize(float additional_time);
|
||||
|
||||
void _forward_pass();
|
||||
void _reverse_pass();
|
||||
|
||||
void _planner_forward_pass_kernel(Block& prev, Block& curr);
|
||||
void _planner_reverse_pass_kernel(Block& curr, Block& next);
|
||||
|
||||
void _recalculate_trapezoids();
|
||||
|
||||
// Returns the given time is seconds in format DDd HHh MMm SSs
|
||||
static std::string _get_time_dhms(float time_in_secs);
|
||||
// Returns the given time is minutes in format DDd HHh MMm
|
||||
static std::string _get_time_dhm(float time_in_secs);
|
||||
|
||||
// Returns the given, in minutes (integer)
|
||||
static std::string _get_time_minutes(float time_in_secs);
|
||||
|
||||
#if ENABLE_MOVE_STATS
|
||||
void _log_moves_stats() const;
|
||||
#endif // ENABLE_MOVE_STATS
|
||||
};
|
||||
|
||||
} /* namespace Slic3r */
|
||||
|
||||
#endif // !ENABLE_GCODE_VIEWER
|
||||
|
||||
#endif /* slic3r_GCodeTimeEstimator_hpp_ */
|
|
@ -20,7 +20,7 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
|
|||
this->config.apply(print_config, true);
|
||||
m_extrusion_axis = this->config.get_extrusion_axis();
|
||||
m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
|
||||
m_max_acceleration = std::lrint((print_config.gcode_flavor.value == gcfMarlin) ?
|
||||
m_max_acceleration = std::lrint((print_config.gcode_flavor.value == gcfMarlin && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ?
|
||||
print_config.machine_max_acceleration_extruding.values.front() : 0);
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,13 @@ std::string GCodeWriter::preamble()
|
|||
gcode << "G21 ; set units to millimeters\n";
|
||||
gcode << "G90 ; use absolute coordinates\n";
|
||||
}
|
||||
if (FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfMarlin) || FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepetier) || FLAVOR_IS(gcfSmoothie)) {
|
||||
if (FLAVOR_IS(gcfRepRapSprinter) ||
|
||||
FLAVOR_IS(gcfRepRapFirmware) ||
|
||||
FLAVOR_IS(gcfMarlin) ||
|
||||
FLAVOR_IS(gcfTeacup) ||
|
||||
FLAVOR_IS(gcfRepetier) ||
|
||||
FLAVOR_IS(gcfSmoothie))
|
||||
{
|
||||
if (this->config.use_relative_e_distances) {
|
||||
gcode << "M83 ; use relative distances for extrusion\n";
|
||||
} else {
|
||||
|
@ -72,11 +78,15 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in
|
|||
return "";
|
||||
|
||||
std::string code, comment;
|
||||
if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
|
||||
if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) {
|
||||
code = "M109";
|
||||
comment = "set temperature and wait for it to be reached";
|
||||
} else {
|
||||
code = "M104";
|
||||
if (FLAVOR_IS(gcfRepRapFirmware)) { // M104 is deprecated on RepRapFirmware
|
||||
code = "G10";
|
||||
} else {
|
||||
code = "M104";
|
||||
}
|
||||
comment = "set temperature";
|
||||
}
|
||||
|
||||
|
@ -88,14 +98,17 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in
|
|||
gcode << "S";
|
||||
}
|
||||
gcode << temperature;
|
||||
if (tool != -1 &&
|
||||
( (this->multiple_extruders && ! m_single_extruder_multi_material) ||
|
||||
FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) ) {
|
||||
gcode << " T" << tool;
|
||||
bool multiple_tools = this->multiple_extruders && ! m_single_extruder_multi_material;
|
||||
if (tool != -1 && (multiple_tools || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) ) {
|
||||
if (FLAVOR_IS(gcfRepRapFirmware)) {
|
||||
gcode << " P" << tool;
|
||||
} else {
|
||||
gcode << " T" << tool;
|
||||
}
|
||||
}
|
||||
gcode << " ; " << comment << "\n";
|
||||
|
||||
if (FLAVOR_IS(gcfTeacup) && wait)
|
||||
if ((FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepRapFirmware)) && wait)
|
||||
gcode << "M116 ; wait for temperature to be reached\n";
|
||||
|
||||
return gcode.str();
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <CGAL/Exact_integer.h>
|
||||
#include <CGAL/Surface_mesh.h>
|
||||
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
|
||||
#include <CGAL/Polygon_mesh_processing/repair_polygon_soup.h>
|
||||
#include <CGAL/Polygon_mesh_processing/repair.h>
|
||||
#include <CGAL/Polygon_mesh_processing/remesh.h>
|
||||
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>
|
||||
|
|
|
@ -608,6 +608,7 @@ void ModelObject::assign_new_unique_ids_recursive()
|
|||
model_volume->assign_new_unique_ids_recursive();
|
||||
for (ModelInstance *model_instance : this->instances)
|
||||
model_instance->assign_new_unique_ids_recursive();
|
||||
this->layer_height_profile.set_new_unique_id();
|
||||
}
|
||||
|
||||
// Clone this ModelObject including its volumes and instances, keep the IDs of the copies equal to the original.
|
||||
|
@ -1041,14 +1042,15 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial
|
|||
int vol_idx = 0;
|
||||
for (ModelVolume* volume : volumes)
|
||||
{
|
||||
volume->m_supported_facets.clear();
|
||||
volume->m_seam_facets.clear();
|
||||
volume->supported_facets.clear();
|
||||
volume->seam_facets.clear();
|
||||
if (!volume->mesh().empty()) {
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
ModelVolume* vol = new_object->add_volume(mesh);
|
||||
vol->name = volume->name;
|
||||
vol->set_type(volume->type());
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
|
@ -1059,7 +1061,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial
|
|||
if (volume_idxs.empty() ||
|
||||
std::find(volume_idxs.begin(), volume_idxs.end(), vol_idx) != volume_idxs.end()) {
|
||||
vol->scale_geometry_after_creation(versor);
|
||||
vol->set_offset(versor.cwiseProduct(vol->get_offset()));
|
||||
vol->set_offset(versor.cwiseProduct(volume->get_offset()));
|
||||
}
|
||||
else
|
||||
vol->set_offset(volume->get_offset());
|
||||
|
@ -1147,8 +1149,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
for (ModelVolume *volume : volumes) {
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
volume->m_supported_facets.clear();
|
||||
volume->m_seam_facets.clear();
|
||||
volume->supported_facets.clear();
|
||||
volume->seam_facets.clear();
|
||||
|
||||
if (! volume->is_model_part()) {
|
||||
// Modifiers are not cut, but we still need to add the instance transformation
|
||||
|
@ -1727,6 +1729,14 @@ void ModelObject::scale_to_fit(const Vec3d &size)
|
|||
*/
|
||||
}
|
||||
|
||||
void ModelVolume::assign_new_unique_ids_recursive()
|
||||
{
|
||||
ObjectBase::set_new_unique_id();
|
||||
config.set_new_unique_id();
|
||||
supported_facets.set_new_unique_id();
|
||||
seam_facets.set_new_unique_id();
|
||||
}
|
||||
|
||||
void ModelVolume::rotate(double angle, Axis axis)
|
||||
{
|
||||
switch (axis)
|
||||
|
@ -2011,7 +2021,7 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject
|
|||
assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
|
||||
assert(mo.volumes.size() == mo_new.volumes.size());
|
||||
for (size_t i=0; i<mo.volumes.size(); ++i) {
|
||||
if (! mo_new.volumes[i]->m_supported_facets.timestamp_matches(mo.volumes[i]->m_supported_facets))
|
||||
if (! mo_new.volumes[i]->supported_facets.timestamp_matches(mo.volumes[i]->supported_facets))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -2021,7 +2031,7 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo
|
|||
assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
|
||||
assert(mo.volumes.size() == mo_new.volumes.size());
|
||||
for (size_t i=0; i<mo.volumes.size(); ++i) {
|
||||
if (! mo_new.volumes[i]->m_seam_facets.timestamp_matches(mo.volumes[i]->m_seam_facets))
|
||||
if (! mo_new.volumes[i]->seam_facets.timestamp_matches(mo.volumes[i]->seam_facets))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -55,14 +55,14 @@ private:
|
|||
|
||||
// Constructors to be only called by derived classes.
|
||||
// Default constructor to assign a unique ID.
|
||||
explicit ModelConfigObject() {}
|
||||
explicit ModelConfigObject() = default;
|
||||
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
|
||||
// by an existing ID copied from elsewhere.
|
||||
explicit ModelConfigObject(int) : ObjectBase(-1) {}
|
||||
// Copy constructor copies the ID.
|
||||
explicit ModelConfigObject(const ModelConfigObject &cfg) : ObjectBase(-1), ModelConfig(cfg) { this->copy_id(cfg); }
|
||||
explicit ModelConfigObject(const ModelConfigObject &cfg) = default;
|
||||
// Move constructor copies the ID.
|
||||
explicit ModelConfigObject(ModelConfigObject &&cfg) : ObjectBase(-1), ModelConfig(std::move(cfg)) { this->copy_id(cfg); }
|
||||
explicit ModelConfigObject(ModelConfigObject &&cfg) = default;
|
||||
|
||||
Timestamp timestamp() const throw() override { return this->ModelConfig::timestamp(); }
|
||||
bool object_id_and_timestamp_match(const ModelConfigObject &rhs) const throw() { return this->id() == rhs.id() && this->timestamp() == rhs.timestamp(); }
|
||||
|
@ -178,6 +178,10 @@ private:
|
|||
|
||||
class LayerHeightProfile final : public ObjectWithTimestamp {
|
||||
public:
|
||||
// Assign the content if the timestamp differs, don't assign an ObjectID.
|
||||
void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = rhs.m_data; this->copy_timestamp(rhs); } }
|
||||
void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
|
||||
|
||||
std::vector<coordf_t> get() const throw() { return m_data; }
|
||||
bool empty() const throw() { return m_data.empty(); }
|
||||
void set(const std::vector<coordf_t> &data) { if (m_data != data) { m_data = data; this->touch(); } }
|
||||
|
@ -190,7 +194,25 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
// Constructors to be only called by derived classes.
|
||||
// Default constructor to assign a unique ID.
|
||||
explicit LayerHeightProfile() = default;
|
||||
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
|
||||
// by an existing ID copied from elsewhere.
|
||||
explicit LayerHeightProfile(int) : ObjectWithTimestamp(-1) {}
|
||||
// Copy constructor copies the ID.
|
||||
explicit LayerHeightProfile(const LayerHeightProfile &rhs) = default;
|
||||
// Move constructor copies the ID.
|
||||
explicit LayerHeightProfile(LayerHeightProfile &&rhs) = default;
|
||||
|
||||
// called by ModelObject::assign_copy()
|
||||
LayerHeightProfile& operator=(const LayerHeightProfile &rhs) = default;
|
||||
LayerHeightProfile& operator=(LayerHeightProfile &&rhs) = default;
|
||||
|
||||
std::vector<coordf_t> m_data;
|
||||
|
||||
// to access set_new_unique_id() when copy / pasting an object
|
||||
friend class ModelObject;
|
||||
};
|
||||
|
||||
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
|
||||
|
@ -338,41 +360,85 @@ private:
|
|||
// This constructor assigns new ID to this ModelObject and its config.
|
||||
explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()),
|
||||
m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
|
||||
{ assert(this->id().valid()); }
|
||||
explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
|
||||
{ assert(this->id().invalid()); assert(this->config.id().invalid()); }
|
||||
{
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->layer_height_profile.id().valid());
|
||||
}
|
||||
explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
|
||||
{
|
||||
assert(this->id().invalid());
|
||||
assert(this->config.id().invalid());
|
||||
assert(this->layer_height_profile.id().invalid());
|
||||
}
|
||||
~ModelObject();
|
||||
void assign_new_unique_ids_recursive() override;
|
||||
|
||||
// To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision"
|
||||
// (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics).
|
||||
ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), m_model(rhs.m_model) {
|
||||
assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id());
|
||||
ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(rhs.m_model) {
|
||||
assert(this->id().invalid());
|
||||
assert(this->config.id().invalid());
|
||||
assert(this->layer_height_profile.id().invalid());
|
||||
assert(rhs.id() != rhs.config.id());
|
||||
assert(rhs.id() != rhs.layer_height_profile.id());
|
||||
this->assign_copy(rhs);
|
||||
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
|
||||
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->layer_height_profile.id().valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->layer_height_profile.id());
|
||||
assert(this->id() == rhs.id());
|
||||
assert(this->config.id() == rhs.config.id());
|
||||
assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
|
||||
}
|
||||
explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1) {
|
||||
assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id());
|
||||
explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1) {
|
||||
assert(this->id().invalid());
|
||||
assert(this->config.id().invalid());
|
||||
assert(this->layer_height_profile.id().invalid());
|
||||
assert(rhs.id() != rhs.config.id());
|
||||
assert(rhs.id() != rhs.layer_height_profile.id());
|
||||
this->assign_copy(std::move(rhs));
|
||||
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
|
||||
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->layer_height_profile.id().valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->layer_height_profile.id());
|
||||
assert(this->id() == rhs.id());
|
||||
assert(this->config.id() == rhs.config.id());
|
||||
assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
|
||||
}
|
||||
ModelObject& operator=(const ModelObject &rhs) {
|
||||
ModelObject& operator=(const ModelObject &rhs) {
|
||||
this->assign_copy(rhs);
|
||||
m_model = rhs.m_model;
|
||||
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
|
||||
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->layer_height_profile.id().valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->layer_height_profile.id());
|
||||
assert(this->id() == rhs.id());
|
||||
assert(this->config.id() == rhs.config.id());
|
||||
assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
|
||||
return *this;
|
||||
}
|
||||
ModelObject& operator=(ModelObject &&rhs) {
|
||||
ModelObject& operator=(ModelObject &&rhs) {
|
||||
this->assign_copy(std::move(rhs));
|
||||
m_model = rhs.m_model;
|
||||
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
|
||||
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->layer_height_profile.id().valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->layer_height_profile.id());
|
||||
assert(this->id() == rhs.id());
|
||||
assert(this->config.id() == rhs.config.id());
|
||||
assert(this->layer_height_profile.id() == rhs.layer_height_profile.id());
|
||||
return *this;
|
||||
}
|
||||
void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
|
||||
void set_new_unique_id() {
|
||||
ObjectBase::set_new_unique_id();
|
||||
this->config.set_new_unique_id();
|
||||
this->layer_height_profile.set_new_unique_id();
|
||||
}
|
||||
|
||||
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
|
||||
|
||||
|
@ -396,8 +462,12 @@ private:
|
|||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
// Used for deserialization -> Don't allocate any IDs for the ModelObject or its config.
|
||||
ModelObject() : ObjectBase(-1), config(-1), m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {
|
||||
assert(this->id().invalid()); assert(this->config.id().invalid());
|
||||
ModelObject() :
|
||||
ObjectBase(-1), config(-1), layer_height_profile(-1),
|
||||
m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {
|
||||
assert(this->id().invalid());
|
||||
assert(this->config.id().invalid());
|
||||
assert(this->layer_height_profile.id().invalid());
|
||||
}
|
||||
template<class Archive> void serialize(Archive &ar) {
|
||||
ar(cereal::base_class<ObjectBase>(this));
|
||||
|
@ -427,16 +497,33 @@ enum class EnforcerBlockerType : int8_t {
|
|||
|
||||
class FacetsAnnotation final : public ObjectWithTimestamp {
|
||||
public:
|
||||
void assign(const FacetsAnnotation &rhs) { if (! this->timestamp_matches(rhs)) this->m_data = rhs.m_data; }
|
||||
void assign(FacetsAnnotation &&rhs) { if (! this->timestamp_matches(rhs)) this->m_data = rhs.m_data; }
|
||||
// Assign the content if the timestamp differs, don't assign an ObjectID.
|
||||
void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = rhs.m_data; this->copy_timestamp(rhs); } }
|
||||
void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
|
||||
const std::map<int, std::vector<bool>>& get_data() const throw() { return m_data; }
|
||||
bool set(const TriangleSelector& selector);
|
||||
indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
|
||||
bool empty() const { return m_data.empty(); }
|
||||
void clear();
|
||||
std::string get_triangle_as_string(int i) const;
|
||||
void set_triangle_from_string(int triangle_id, const std::string& str);
|
||||
|
||||
private:
|
||||
// Constructors to be only called by derived classes.
|
||||
// Default constructor to assign a unique ID.
|
||||
explicit FacetsAnnotation() = default;
|
||||
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
|
||||
// by an existing ID copied from elsewhere.
|
||||
explicit FacetsAnnotation(int) : ObjectWithTimestamp(-1) {}
|
||||
// Copy constructor copies the ID.
|
||||
explicit FacetsAnnotation(const FacetsAnnotation &rhs) = default;
|
||||
// Move constructor copies the ID.
|
||||
explicit FacetsAnnotation(FacetsAnnotation &&rhs) = default;
|
||||
|
||||
// called by ModelVolume::assign_copy()
|
||||
FacetsAnnotation& operator=(const FacetsAnnotation &rhs) = default;
|
||||
FacetsAnnotation& operator=(FacetsAnnotation &&rhs) = default;
|
||||
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
|
||||
|
@ -446,6 +533,9 @@ private:
|
|||
}
|
||||
|
||||
std::map<int, std::vector<bool>> m_data;
|
||||
|
||||
// To access set_new_unique_id() when copy / pasting a ModelVolume.
|
||||
friend class ModelVolume;
|
||||
};
|
||||
|
||||
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
|
||||
|
@ -483,10 +573,10 @@ public:
|
|||
ModelConfigObject config;
|
||||
|
||||
// List of mesh facets to be supported/unsupported.
|
||||
FacetsAnnotation m_supported_facets;
|
||||
FacetsAnnotation supported_facets;
|
||||
|
||||
// List of seam enforcers/blockers.
|
||||
FacetsAnnotation m_seam_facets;
|
||||
FacetsAnnotation seam_facets;
|
||||
|
||||
// A parent object owning this modifier volume.
|
||||
ModelObject* get_object() const { return this->object; }
|
||||
|
@ -568,7 +658,12 @@ public:
|
|||
|
||||
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
|
||||
|
||||
void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
|
||||
void set_new_unique_id() {
|
||||
ObjectBase::set_new_unique_id();
|
||||
this->config.set_new_unique_id();
|
||||
this->supported_facets.set_new_unique_id();
|
||||
this->seam_facets.set_new_unique_id();
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class Print;
|
||||
|
@ -579,7 +674,7 @@ protected:
|
|||
// Copies IDs of both the ModelVolume and its config.
|
||||
explicit ModelVolume(const ModelVolume &rhs) = default;
|
||||
void set_model_object(ModelObject *model_object) { object = model_object; }
|
||||
void assign_new_unique_ids_recursive() override { ObjectBase::set_new_unique_id(); config.set_new_unique_id(); }
|
||||
void assign_new_unique_ids_recursive() override;
|
||||
void transform_this_mesh(const Transform3d& t, bool fix_left_handed);
|
||||
void transform_this_mesh(const Matrix3d& m, bool fix_left_handed);
|
||||
|
||||
|
@ -603,13 +698,25 @@ private:
|
|||
|
||||
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : m_mesh(new TriangleMesh(mesh)), m_type(ModelVolumeType::MODEL_PART), object(object)
|
||||
{
|
||||
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->supported_facets.id().valid());
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
if (mesh.stl.stats.number_of_facets > 1)
|
||||
calculate_convex_hull();
|
||||
}
|
||||
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) :
|
||||
m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) {
|
||||
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->supported_facets.id().valid());
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
}
|
||||
|
||||
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
|
||||
|
@ -617,26 +724,45 @@ private:
|
|||
ObjectBase(other),
|
||||
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
|
||||
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
|
||||
m_supported_facets(other.m_supported_facets), m_seam_facets(other.m_seam_facets)
|
||||
supported_facets(other.supported_facets), seam_facets(other.seam_facets)
|
||||
{
|
||||
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
|
||||
assert(this->id() == other.id() && this->config.id() == other.config.id());
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->supported_facets.id().valid());
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
assert(this->id() == other.id());
|
||||
assert(this->config.id() == other.config.id());
|
||||
assert(this->supported_facets.id() == other.supported_facets.id());
|
||||
assert(this->seam_facets.id() == other.seam_facets.id());
|
||||
this->set_material_id(other.material_id());
|
||||
}
|
||||
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
|
||||
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
|
||||
name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
|
||||
{
|
||||
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
|
||||
assert(this->id() != other.id() && this->config.id() == other.config.id());
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->supported_facets.id().valid());
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
assert(this->id() != other.id());
|
||||
assert(this->config.id() == other.config.id());
|
||||
this->set_material_id(other.material_id());
|
||||
this->config.set_new_unique_id();
|
||||
if (mesh.stl.stats.number_of_facets > 1)
|
||||
calculate_convex_hull();
|
||||
assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id());
|
||||
|
||||
m_supported_facets.clear();
|
||||
m_seam_facets.clear();
|
||||
assert(this->config.id().valid());
|
||||
assert(this->config.id() != other.config.id());
|
||||
assert(this->supported_facets.id() != other.supported_facets.id());
|
||||
assert(this->seam_facets.id() != other.seam_facets.id());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->supported_facets.empty());
|
||||
assert(this->seam_facets.empty());
|
||||
}
|
||||
|
||||
ModelVolume& operator=(ModelVolume &rhs) = delete;
|
||||
|
@ -644,14 +770,17 @@ private:
|
|||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
// Used for deserialization, therefore no IDs are allocated.
|
||||
ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) {
|
||||
assert(this->id().invalid()); assert(this->config.id().invalid());
|
||||
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), object(nullptr) {
|
||||
assert(this->id().invalid());
|
||||
assert(this->config.id().invalid());
|
||||
assert(this->supported_facets.id().invalid());
|
||||
assert(this->seam_facets.id().invalid());
|
||||
}
|
||||
template<class Archive> void load(Archive &ar) {
|
||||
bool has_convex_hull;
|
||||
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
|
||||
cereal::load_by_value(ar, m_supported_facets);
|
||||
cereal::load_by_value(ar, m_seam_facets);
|
||||
cereal::load_by_value(ar, supported_facets);
|
||||
cereal::load_by_value(ar, seam_facets);
|
||||
cereal::load_by_value(ar, config);
|
||||
assert(m_mesh);
|
||||
if (has_convex_hull) {
|
||||
|
@ -665,8 +794,8 @@ private:
|
|||
template<class Archive> void save(Archive &ar) const {
|
||||
bool has_convex_hull = m_convex_hull.get() != nullptr;
|
||||
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
|
||||
cereal::save_by_value(ar, m_supported_facets);
|
||||
cereal::save_by_value(ar, m_seam_facets);
|
||||
cereal::save_by_value(ar, supported_facets);
|
||||
cereal::save_by_value(ar, seam_facets);
|
||||
cereal::save_by_value(ar, config);
|
||||
if (has_convex_hull)
|
||||
cereal::save_optional(ar, m_convex_hull);
|
||||
|
|
|
@ -108,6 +108,8 @@ protected:
|
|||
// Resetting timestamp to 1 indicates the object is in its initial (cleared) state.
|
||||
// To be called by the derived class's clear() method.
|
||||
void reset_timestamp() { m_timestamp = 1; }
|
||||
// The timestamp uniquely identifies content of the derived class' data, therefore it makes sense to copy the timestamp if the content data was copied.
|
||||
void copy_timestamp(const ObjectWithTimestamp& rhs) { m_timestamp = rhs.m_timestamp; }
|
||||
|
||||
public:
|
||||
// Return an optional timestamp of this object.
|
||||
|
|
|
@ -454,27 +454,40 @@ const std::vector<std::string>& Preset::filament_options()
|
|||
return s_opts;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Preset::machine_limits_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"machine_max_acceleration_extruding", "machine_max_acceleration_retracting",
|
||||
"machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e",
|
||||
"machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e",
|
||||
"machine_min_extruding_rate", "machine_min_travel_rate",
|
||||
"machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e",
|
||||
};
|
||||
}
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Preset::printer_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"printer_technology",
|
||||
"bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed",
|
||||
"bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances",
|
||||
"use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
|
||||
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
|
||||
"host_type", "print_host", "printhost_apikey", "printhost_cafile",
|
||||
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
|
||||
"color_change_gcode", "pause_print_gcode", "template_custom_gcode",
|
||||
"between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
|
||||
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height",
|
||||
"default_print_profile", "inherits",
|
||||
"remaining_times", "silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting",
|
||||
"machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e",
|
||||
"machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e",
|
||||
"machine_min_extruding_rate", "machine_min_travel_rate",
|
||||
"machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e",
|
||||
"thumbnails"
|
||||
"remaining_times", "silent_mode",
|
||||
"machine_limits_usage", "thumbnails"
|
||||
};
|
||||
s_opts.insert(s_opts.end(), Preset::machine_limits_options().begin(), Preset::machine_limits_options().end());
|
||||
s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end());
|
||||
}
|
||||
return s_opts;
|
||||
|
@ -583,6 +596,7 @@ const std::vector<std::string>& Preset::sla_printer_options()
|
|||
"gamma_correction",
|
||||
"min_exposure_time", "max_exposure_time",
|
||||
"min_initial_exposure_time", "max_initial_exposure_time",
|
||||
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
|
||||
"print_host", "printhost_apikey", "printhost_cafile",
|
||||
"printer_notes",
|
||||
"inherits"
|
||||
|
@ -699,38 +713,6 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string
|
|||
return this->load_preset(path, name, std::move(cfg), select);
|
||||
}
|
||||
|
||||
enum class ProfileHostParams
|
||||
{
|
||||
Same,
|
||||
Different,
|
||||
Anonymized,
|
||||
};
|
||||
|
||||
static ProfileHostParams profile_host_params_same_or_anonymized(const DynamicPrintConfig &cfg_old, const DynamicPrintConfig &cfg_new)
|
||||
{
|
||||
auto opt_print_host_old = cfg_old.option<ConfigOptionString>("print_host");
|
||||
auto opt_printhost_apikey_old = cfg_old.option<ConfigOptionString>("printhost_apikey");
|
||||
auto opt_printhost_cafile_old = cfg_old.option<ConfigOptionString>("printhost_cafile");
|
||||
|
||||
auto opt_print_host_new = cfg_new.option<ConfigOptionString>("print_host");
|
||||
auto opt_printhost_apikey_new = cfg_new.option<ConfigOptionString>("printhost_apikey");
|
||||
auto opt_printhost_cafile_new = cfg_new.option<ConfigOptionString>("printhost_cafile");
|
||||
|
||||
// If the new print host data is undefined, use the old data.
|
||||
bool new_print_host_undefined = (opt_print_host_new == nullptr || opt_print_host_new ->empty()) &&
|
||||
(opt_printhost_apikey_new == nullptr || opt_printhost_apikey_new ->empty()) &&
|
||||
(opt_printhost_cafile_new == nullptr || opt_printhost_cafile_new ->empty());
|
||||
if (new_print_host_undefined)
|
||||
return ProfileHostParams::Anonymized;
|
||||
|
||||
auto opt_same = [](const ConfigOptionString *l, const ConfigOptionString *r) {
|
||||
return ((l == nullptr || l->empty()) && (r == nullptr || r->empty())) ||
|
||||
(l != nullptr && r != nullptr && l->value == r->value);
|
||||
};
|
||||
return (opt_same(opt_print_host_old, opt_print_host_new) && opt_same(opt_printhost_apikey_old, opt_printhost_apikey_new) &&
|
||||
opt_same(opt_printhost_cafile_old, opt_printhost_cafile_new)) ? ProfileHostParams::Same : ProfileHostParams::Different;
|
||||
}
|
||||
|
||||
static bool profile_print_params_same(const DynamicPrintConfig &cfg_old, const DynamicPrintConfig &cfg_new)
|
||||
{
|
||||
t_config_option_keys diff = cfg_old.diff(cfg_new);
|
||||
|
@ -740,10 +722,11 @@ static bool profile_print_params_same(const DynamicPrintConfig &cfg_old, const D
|
|||
"compatible_printers", "compatible_printers_condition", "inherits",
|
||||
"print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id",
|
||||
"printer_model", "printer_variant", "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile",
|
||||
//FIXME remove the print host keys?
|
||||
"print_host", "printhost_apikey", "printhost_cafile" })
|
||||
diff.erase(std::remove(diff.begin(), diff.end(), key), diff.end());
|
||||
// Preset with the same name as stored inside the config exists.
|
||||
return diff.empty() && profile_host_params_same_or_anonymized(cfg_old, cfg_new) != ProfileHostParams::Different;
|
||||
return diff.empty();
|
||||
}
|
||||
|
||||
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
|
||||
|
@ -772,25 +755,11 @@ Preset& PresetCollection::load_external_preset(
|
|||
it = this->find_preset_renamed(original_name);
|
||||
found = it != m_presets.end();
|
||||
}
|
||||
if (found) {
|
||||
if (profile_print_params_same(it->config, cfg)) {
|
||||
// The preset exists and it matches the values stored inside config.
|
||||
if (select)
|
||||
this->select_preset(it - m_presets.begin());
|
||||
return *it;
|
||||
}
|
||||
if (profile_host_params_same_or_anonymized(it->config, cfg) == ProfileHostParams::Anonymized) {
|
||||
// The project being loaded is anonymized. Replace the empty host keys of the loaded profile with the data from the original profile.
|
||||
// See "Octoprint Settings when Opening a .3MF file" GH issue #3244
|
||||
auto opt_update = [it, &cfg](const std::string &opt_key) {
|
||||
auto opt = it->config.option<ConfigOptionString>(opt_key);
|
||||
if (opt != nullptr)
|
||||
cfg.set_key_value(opt_key, opt->clone());
|
||||
};
|
||||
opt_update("print_host");
|
||||
opt_update("printhost_apikey");
|
||||
opt_update("printhost_cafile");
|
||||
}
|
||||
if (found && profile_print_params_same(it->config, cfg)) {
|
||||
// The preset exists and it matches the values stored inside config.
|
||||
if (select)
|
||||
this->select_preset(it - m_presets.begin());
|
||||
return *it;
|
||||
}
|
||||
// Update the "inherits" field.
|
||||
std::string &inherits = Preset::inherits(cfg);
|
||||
|
@ -1362,31 +1331,25 @@ const std::vector<std::string>& PhysicalPrinter::printer_options()
|
|||
"preset_name",
|
||||
"printer_technology",
|
||||
// "printer_model",
|
||||
"host_type",
|
||||
"print_host",
|
||||
"printhost_apikey",
|
||||
"host_type",
|
||||
"print_host",
|
||||
"printhost_apikey",
|
||||
"printhost_cafile",
|
||||
"printhost_port",
|
||||
"printhost_authorization_type",
|
||||
// HTTP digest authentization (RFC 2617)
|
||||
"printhost_user",
|
||||
"printhost_user",
|
||||
"printhost_password"
|
||||
};
|
||||
}
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& PhysicalPrinter::print_host_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"print_host",
|
||||
"printhost_apikey",
|
||||
"printhost_cafile"
|
||||
};
|
||||
}
|
||||
return s_opts;
|
||||
}
|
||||
static constexpr auto legacy_print_host_options = {
|
||||
"print_host",
|
||||
"printhost_apikey",
|
||||
"printhost_cafile",
|
||||
};
|
||||
|
||||
std::vector<std::string> PhysicalPrinter::presets_with_print_host_information(const PrinterPresetCollection& printer_presets)
|
||||
{
|
||||
|
@ -1400,7 +1363,7 @@ std::vector<std::string> PhysicalPrinter::presets_with_print_host_information(co
|
|||
|
||||
bool PhysicalPrinter::has_print_host_information(const DynamicPrintConfig& config)
|
||||
{
|
||||
for (const std::string& opt : print_host_options())
|
||||
for (const char *opt : legacy_print_host_options)
|
||||
if (!config.opt_string(opt).empty())
|
||||
return true;
|
||||
|
||||
|
@ -1417,6 +1380,7 @@ bool PhysicalPrinter::has_empty_config() const
|
|||
return config.opt_string("print_host" ).empty() &&
|
||||
config.opt_string("printhost_apikey" ).empty() &&
|
||||
config.opt_string("printhost_cafile" ).empty() &&
|
||||
config.opt_string("printhost_port" ).empty() &&
|
||||
config.opt_string("printhost_user" ).empty() &&
|
||||
config.opt_string("printhost_password").empty();
|
||||
}
|
||||
|
@ -1445,7 +1409,7 @@ void PhysicalPrinter::update_from_preset(const Preset& preset)
|
|||
{
|
||||
config.apply_only(preset.config, printer_options(), true);
|
||||
// add preset names to the options list
|
||||
auto ret = preset_names.emplace(preset.name);
|
||||
preset_names.emplace(preset.name);
|
||||
update_preset_names_in_config();
|
||||
}
|
||||
|
||||
|
@ -1602,9 +1566,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti
|
|||
int cnt=0;
|
||||
for (Preset& preset: printer_presets) {
|
||||
DynamicPrintConfig& config = preset.config;
|
||||
const std::vector<std::string>& options = PhysicalPrinter::print_host_options();
|
||||
|
||||
for(const std::string& option : options) {
|
||||
for(const char* option : legacy_print_host_options) {
|
||||
if (!config.opt_string(option).empty()) {
|
||||
// check if printer with those "Print Host upload" options already exist
|
||||
PhysicalPrinter* existed_printer = find_printer_with_same_config(config);
|
||||
|
@ -1623,7 +1585,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti
|
|||
}
|
||||
|
||||
// erase "Print Host upload" information from the preset
|
||||
for (const std::string& opt : options)
|
||||
for (const char *opt : legacy_print_host_options)
|
||||
config.opt_string(opt).clear();
|
||||
// save changes for preset
|
||||
preset.save();
|
||||
|
@ -1631,7 +1593,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti
|
|||
// update those changes for edited preset if it's equal to the preset
|
||||
Preset& edited = printer_presets.get_edited_preset();
|
||||
if (preset.name == edited.name) {
|
||||
for (const std::string& opt : options)
|
||||
for (const char *opt : legacy_print_host_options)
|
||||
edited.config.opt_string(opt).clear();
|
||||
}
|
||||
|
||||
|
@ -1664,7 +1626,7 @@ std::deque<PhysicalPrinter>::iterator PhysicalPrinterCollection::find_printer_in
|
|||
|
||||
std::string low_name = boost::to_lower_copy<std::string>(name);
|
||||
|
||||
int i = 0;
|
||||
size_t i = 0;
|
||||
for (const PhysicalPrinter& printer : m_printers) {
|
||||
if (boost::to_lower_copy<std::string>(printer.name) == low_name)
|
||||
break;
|
||||
|
@ -1680,7 +1642,7 @@ PhysicalPrinter* PhysicalPrinterCollection::find_printer_with_same_config(const
|
|||
{
|
||||
for (const PhysicalPrinter& printer :*this) {
|
||||
bool is_equal = true;
|
||||
for (const std::string& opt : PhysicalPrinter::print_host_options())
|
||||
for (const char *opt : legacy_print_host_options)
|
||||
if (is_equal && printer.config.opt_string(opt) != config.opt_string(opt))
|
||||
is_equal = false;
|
||||
|
||||
|
@ -1854,7 +1816,7 @@ void PhysicalPrinterCollection::unselect_printer()
|
|||
|
||||
bool PhysicalPrinterCollection::is_selected(PhysicalPrinterCollection::ConstIterator it, const std::string& preset_name) const
|
||||
{
|
||||
return m_idx_selected == it - m_printers.begin() &&
|
||||
return m_idx_selected == size_t(it - m_printers.begin()) &&
|
||||
m_selected_preset == preset_name;
|
||||
}
|
||||
|
||||
|
|
|
@ -218,6 +218,8 @@ public:
|
|||
static const std::vector<std::string>& printer_options();
|
||||
// Nozzle options of the printer options.
|
||||
static const std::vector<std::string>& nozzle_options();
|
||||
// Printer machine limits, those are contained in printer_options().
|
||||
static const std::vector<std::string>& machine_limits_options();
|
||||
|
||||
static const std::vector<std::string>& sla_printer_options();
|
||||
static const std::vector<std::string>& sla_material_options();
|
||||
|
@ -369,19 +371,28 @@ public:
|
|||
size_t i = m_default_suppressed ? m_num_default_presets : 0;
|
||||
size_t n = this->m_presets.size();
|
||||
size_t i_compatible = n;
|
||||
int match_quality = -1;
|
||||
for (; i < n; ++ i)
|
||||
// Since we use the filament selection from Wizard, it's needed to control the preset visibility too
|
||||
if (m_presets[i].is_compatible && m_presets[i].is_visible) {
|
||||
if (prefered_condition(m_presets[i].name))
|
||||
return i;
|
||||
if (i_compatible == n)
|
||||
// Store the first compatible profile into i_compatible.
|
||||
int this_match_quality = prefered_condition(m_presets[i]);
|
||||
if (this_match_quality > match_quality) {
|
||||
if (match_quality == std::numeric_limits<int>::max())
|
||||
// Better match will not be found.
|
||||
return i;
|
||||
// Store the first compatible profile with highest match quality into i_compatible.
|
||||
i_compatible = i;
|
||||
match_quality = this_match_quality;
|
||||
}
|
||||
}
|
||||
return (i_compatible == n) ? 0 : i_compatible;
|
||||
return (i_compatible == n) ?
|
||||
// No compatible preset found, return the default preset.
|
||||
0 :
|
||||
// Compatible preset found.
|
||||
i_compatible;
|
||||
}
|
||||
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
|
||||
size_t first_compatible_idx() const { return this->first_compatible_idx([](const std::string&){return true;}); }
|
||||
size_t first_compatible_idx() const { return this->first_compatible_idx([](const Preset&) -> int { return 0; }); }
|
||||
|
||||
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
|
||||
// Return the first visible preset. Certainly at least the '- default -' preset shall be visible.
|
||||
|
@ -405,7 +416,7 @@ public:
|
|||
this->select_preset(this->first_compatible_idx(prefered_condition));
|
||||
}
|
||||
void update_compatible(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, PresetSelectCompatibleType select_other_if_incompatible)
|
||||
{ this->update_compatible(active_printer, active_print, select_other_if_incompatible, [](const std::string&){return true;}); }
|
||||
{ this->update_compatible(active_printer, active_print, select_other_if_incompatible, [](const Preset&) -> int { return 0; }); }
|
||||
|
||||
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
|
||||
|
||||
|
|
|
@ -37,11 +37,11 @@ static std::vector<std::string> s_project_options {
|
|||
const char *PresetBundle::PRUSA_BUNDLE = "PrusaResearch";
|
||||
|
||||
PresetBundle::PresetBundle() :
|
||||
prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults())),
|
||||
filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults())),
|
||||
prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())),
|
||||
filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())),
|
||||
sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast<const SLAMaterialConfig&>(SLAFullPrintConfig::defaults())),
|
||||
sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast<const SLAPrintObjectConfig&>(SLAFullPrintConfig::defaults())),
|
||||
printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults()), "- default FFF -"),
|
||||
printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults()), "- default FFF -"),
|
||||
physical_printers(PhysicalPrinter::printer_options())
|
||||
{
|
||||
// The following keys are handled by the UI, they do not have a counterpart in any StaticPrintConfig derived classes,
|
||||
|
@ -77,11 +77,12 @@ PresetBundle::PresetBundle() :
|
|||
for (size_t i = 0; i < 2; ++ i) {
|
||||
// The following ugly switch is to avoid printers.preset(0) to return the edited instance, as the 0th default is the current one.
|
||||
Preset &preset = this->printers.default_preset(i);
|
||||
preset.config.optptr("printer_settings_id", true);
|
||||
preset.config.optptr("printer_vendor", true);
|
||||
preset.config.optptr("printer_model", true);
|
||||
preset.config.optptr("printer_variant", true);
|
||||
preset.config.optptr("thumbnails", true);
|
||||
for (const char *key : {
|
||||
"printer_settings_id", "printer_vendor", "printer_model", "printer_variant", "thumbnails",
|
||||
//FIXME the following keys are only created here for compatibility to be able to parse legacy Printer profiles.
|
||||
// These keys are converted to Physical Printer profile. After the conversion, they shall be removed.
|
||||
"host_type", "print_host", "printhost_apikey", "printhost_cafile"})
|
||||
preset.config.optptr(key, true);
|
||||
if (i == 0) {
|
||||
preset.config.optptr("default_print_profile", true);
|
||||
preset.config.option<ConfigOptionStrings>("default_filament_profile", true)->values = { "" };
|
||||
|
@ -458,7 +459,7 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr
|
|||
this->update_multi_material_filament_presets();
|
||||
|
||||
// Parse the initial physical printer name.
|
||||
std::string initial_physical_printer_name = remove_ini_suffix(config.get("extras", "physical_printer"));
|
||||
std::string initial_physical_printer_name = remove_ini_suffix(config.get("presets", "physical_printer"));
|
||||
|
||||
// Activate physical printer from the config
|
||||
if (!initial_physical_printer_name.empty())
|
||||
|
@ -482,8 +483,7 @@ void PresetBundle::export_selections(AppConfig &config)
|
|||
config.set("presets", "sla_print", sla_prints.get_selected_preset_name());
|
||||
config.set("presets", "sla_material", sla_materials.get_selected_preset_name());
|
||||
config.set("presets", "printer", printers.get_selected_preset_name());
|
||||
|
||||
config.set("extras", "physical_printer", physical_printers.get_selected_full_printer_name());
|
||||
config.set("presets", "physical_printer", physical_printers.get_selected_full_printer_name());
|
||||
}
|
||||
|
||||
DynamicPrintConfig PresetBundle::full_config() const
|
||||
|
@ -496,6 +496,7 @@ DynamicPrintConfig PresetBundle::full_config() const
|
|||
DynamicPrintConfig PresetBundle::full_config_secure() const
|
||||
{
|
||||
DynamicPrintConfig config = this->full_config();
|
||||
//FIXME legacy, the keys should not be there after conversion to a Physical Printer profile.
|
||||
config.erase("print_host");
|
||||
config.erase("printhost_apikey");
|
||||
config.erase("printhost_cafile");
|
||||
|
@ -1439,50 +1440,132 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri
|
|||
const Preset &printer_preset = this->printers.get_edited_preset();
|
||||
const PresetWithVendorProfile printer_preset_with_vendor_profile = this->printers.get_preset_with_vendor_profile(printer_preset);
|
||||
|
||||
class PreferedProfileMatch
|
||||
{
|
||||
public:
|
||||
PreferedProfileMatch(const std::string &prefered_alias, const std::string &prefered_name) :
|
||||
m_prefered_alias(prefered_alias), m_prefered_name(prefered_name) {}
|
||||
|
||||
int operator()(const Preset &preset) const
|
||||
{
|
||||
return (! m_prefered_alias.empty() && m_prefered_alias == preset.alias) ?
|
||||
// Matching an alias, always take this preset with priority.
|
||||
std::numeric_limits<int>::max() :
|
||||
// Otherwise take the prefered profile, or the first compatible.
|
||||
preset.name == m_prefered_name;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_prefered_alias;
|
||||
const std::string &m_prefered_name;
|
||||
};
|
||||
|
||||
// Matching by the layer height in addition.
|
||||
class PreferedPrintProfileMatch : public PreferedProfileMatch
|
||||
{
|
||||
public:
|
||||
PreferedPrintProfileMatch(const Preset &preset, const std::string &prefered_name) :
|
||||
PreferedProfileMatch(preset.alias, prefered_name), m_prefered_layer_height(preset.config.opt_float("layer_height")) {}
|
||||
|
||||
int operator()(const Preset &preset) const
|
||||
{
|
||||
int match_quality = PreferedProfileMatch::operator()(preset);
|
||||
if (match_quality < std::numeric_limits<int>::max()) {
|
||||
match_quality += 1;
|
||||
if (m_prefered_layer_height > 0. && std::abs(preset.config.opt_float("layer_height") - m_prefered_layer_height) < 0.0005)
|
||||
match_quality *= 10;
|
||||
}
|
||||
return match_quality;
|
||||
}
|
||||
|
||||
private:
|
||||
const double m_prefered_layer_height;
|
||||
};
|
||||
|
||||
// Matching by the layer height in addition.
|
||||
class PreferedFilamentProfileMatch : public PreferedProfileMatch
|
||||
{
|
||||
public:
|
||||
PreferedFilamentProfileMatch(const Preset *preset, const std::string &prefered_name) :
|
||||
PreferedProfileMatch(preset ? preset->alias : std::string(), prefered_name),
|
||||
m_prefered_filament_type(preset ? preset->config.opt_string("filament_type", 0) : std::string()) {}
|
||||
|
||||
int operator()(const Preset &preset) const
|
||||
{
|
||||
int match_quality = PreferedProfileMatch::operator()(preset);
|
||||
if (match_quality < std::numeric_limits<int>::max()) {
|
||||
match_quality += 1;
|
||||
if (! m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", 0))
|
||||
match_quality *= 10;
|
||||
}
|
||||
return match_quality;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_prefered_filament_type;
|
||||
};
|
||||
|
||||
// Matching by the layer height in addition.
|
||||
class PreferedFilamentsProfileMatch
|
||||
{
|
||||
public:
|
||||
PreferedFilamentsProfileMatch(const Preset &preset, const std::vector<std::string> &prefered_names) :
|
||||
m_prefered_alias(preset.alias),
|
||||
m_prefered_filament_type(preset.config.opt_string("filament_type", 0)),
|
||||
m_prefered_names(prefered_names)
|
||||
{}
|
||||
|
||||
int operator()(const Preset &preset) const
|
||||
{
|
||||
if (! m_prefered_alias.empty() && m_prefered_alias == preset.alias)
|
||||
// Matching an alias, always take this preset with priority.
|
||||
return std::numeric_limits<int>::max();
|
||||
int match_quality = (std::find(m_prefered_names.begin(), m_prefered_names.end(), preset.name) != m_prefered_names.end()) + 1;
|
||||
if (! m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", 0))
|
||||
match_quality *= 10;
|
||||
return match_quality;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_prefered_alias;
|
||||
const std::string m_prefered_filament_type;
|
||||
const std::vector<std::string> &m_prefered_names;
|
||||
};
|
||||
|
||||
switch (printer_preset.printer_technology()) {
|
||||
case ptFFF:
|
||||
{
|
||||
assert(printer_preset.config.has("default_print_profile"));
|
||||
assert(printer_preset.config.has("default_filament_profile"));
|
||||
const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile");
|
||||
const std::vector<std::string> &prefered_filament_profiles = printer_preset.config.option<ConfigOptionStrings>("default_filament_profile")->values;
|
||||
prefered_print_profile.empty() ?
|
||||
this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible) :
|
||||
this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible,
|
||||
[&prefered_print_profile](const std::string& profile_name) { return profile_name == prefered_print_profile; });
|
||||
this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible,
|
||||
PreferedPrintProfileMatch(this->prints.get_edited_preset(), printer_preset.config.opt_string("default_print_profile")));
|
||||
const PresetWithVendorProfile print_preset_with_vendor_profile = this->prints.get_edited_preset_with_vendor_profile();
|
||||
// Remember whether the filament profiles were compatible before updating the filament compatibility.
|
||||
std::vector<char> filament_preset_was_compatible(this->filament_presets.size(), false);
|
||||
for (size_t idx = 0; idx < this->filament_presets.size(); ++ idx) {
|
||||
std::string &filament_name = this->filament_presets[idx];
|
||||
Preset *preset = this->filaments.find_preset(filament_name, false);
|
||||
Preset *preset = this->filaments.find_preset(this->filament_presets[idx], false);
|
||||
filament_preset_was_compatible[idx] = preset != nullptr && preset->is_compatible;
|
||||
}
|
||||
prefered_filament_profiles.empty() ?
|
||||
this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible) :
|
||||
this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible,
|
||||
[&prefered_filament_profiles](const std::string& profile_name)
|
||||
{ return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); });
|
||||
// First select a first compatible profile for the preset editor.
|
||||
this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible,
|
||||
PreferedFilamentsProfileMatch(this->filaments.get_edited_preset(), prefered_filament_profiles));
|
||||
if (select_other_filament_if_incompatible != PresetSelectCompatibleType::Never) {
|
||||
// Verify validity of the current filament presets.
|
||||
const std::string prefered_filament_profile = prefered_filament_profiles.empty() ? std::string() : prefered_filament_profiles.front();
|
||||
if (this->filament_presets.size() == 1) {
|
||||
// The compatible profile should have been already selected for the preset editor. Just use it.
|
||||
if (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible.front())
|
||||
this->filament_presets.front() = this->filaments.get_edited_preset().name;
|
||||
} else {
|
||||
for (size_t idx = 0; idx < this->filament_presets.size(); ++ idx) {
|
||||
std::string &filament_name = this->filament_presets[idx];
|
||||
Preset *preset = this->filaments.find_preset(filament_name, false);
|
||||
if (preset == nullptr || (! preset->is_compatible && (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible[idx]))) {
|
||||
if (preset == nullptr || (! preset->is_compatible && (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible[idx])))
|
||||
// Pick a compatible profile. If there are prefered_filament_profiles, use them.
|
||||
if (prefered_filament_profiles.empty())
|
||||
filament_name = this->filaments.first_compatible().name;
|
||||
else {
|
||||
const std::string &preferred = (idx < prefered_filament_profiles.size()) ?
|
||||
prefered_filament_profiles[idx] : prefered_filament_profiles.front();
|
||||
filament_name = this->filaments.first_compatible(
|
||||
[&preferred](const std::string& profile_name) { return profile_name == preferred; }).name;
|
||||
}
|
||||
}
|
||||
filament_name = this->filaments.first_compatible(
|
||||
PreferedFilamentProfileMatch(preset,
|
||||
(idx < prefered_filament_profiles.size()) ? prefered_filament_profiles[idx] : prefered_filament_profile)).name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1492,17 +1575,11 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri
|
|||
{
|
||||
assert(printer_preset.config.has("default_sla_print_profile"));
|
||||
assert(printer_preset.config.has("default_sla_material_profile"));
|
||||
const PresetWithVendorProfile sla_print_preset_with_vendor_profile = this->sla_prints.get_edited_preset_with_vendor_profile();
|
||||
const std::string &prefered_sla_print_profile = printer_preset.config.opt_string("default_sla_print_profile");
|
||||
(prefered_sla_print_profile.empty()) ?
|
||||
this->sla_prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible) :
|
||||
this->sla_prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible,
|
||||
[&prefered_sla_print_profile](const std::string& profile_name){ return profile_name == prefered_sla_print_profile; });
|
||||
const std::string &prefered_sla_material_profile = printer_preset.config.opt_string("default_sla_material_profile");
|
||||
prefered_sla_material_profile.empty() ?
|
||||
this->sla_materials.update_compatible(printer_preset_with_vendor_profile, &sla_print_preset_with_vendor_profile, select_other_filament_if_incompatible) :
|
||||
this->sla_materials.update_compatible(printer_preset_with_vendor_profile, &sla_print_preset_with_vendor_profile, select_other_filament_if_incompatible,
|
||||
[&prefered_sla_material_profile](const std::string& profile_name){ return profile_name == prefered_sla_material_profile; });
|
||||
this->sla_prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible,
|
||||
PreferedPrintProfileMatch(this->sla_prints.get_edited_preset(), printer_preset.config.opt_string("default_sla_print_profile")));
|
||||
const PresetWithVendorProfile sla_print_preset_with_vendor_profile = this->sla_prints.get_edited_preset_with_vendor_profile();
|
||||
this->sla_materials.update_compatible(printer_preset_with_vendor_profile, &sla_print_preset_with_vendor_profile, select_other_filament_if_incompatible,
|
||||
PreferedProfileMatch(this->sla_materials.get_edited_preset().alias, printer_preset.config.opt_string("default_sla_material_profile")));
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "I18N.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "SupportMaterial.hpp"
|
||||
#include "Thread.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
@ -404,10 +405,10 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
|
|||
// Copy the ModelVolume data.
|
||||
mv_dst.name = mv_src.name;
|
||||
mv_dst.config.assign_config(mv_src.config);
|
||||
if (! mv_dst.m_supported_facets.timestamp_matches(mv_src.m_supported_facets))
|
||||
mv_dst.m_supported_facets = mv_src.m_supported_facets;
|
||||
if (! mv_dst.m_seam_facets.timestamp_matches(mv_src.m_seam_facets))
|
||||
mv_dst.m_seam_facets = mv_src.m_seam_facets;
|
||||
assert(mv_dst.supported_facets.id() == mv_src.supported_facets.id());
|
||||
mv_dst.supported_facets.assign(mv_src.supported_facets);
|
||||
assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id());
|
||||
mv_dst.seam_facets.assign(mv_src.seam_facets);
|
||||
//FIXME what to do with the materials?
|
||||
// mv_dst.m_material_id = mv_src.m_material_id;
|
||||
++ i_src;
|
||||
|
@ -577,6 +578,16 @@ void Print::config_diffs(
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<ObjectID> Print::print_object_ids() const
|
||||
{
|
||||
std::vector<ObjectID> out;
|
||||
// Reserve one more for the caller to append the ID of the Print itself.
|
||||
out.reserve(m_objects.size() + 1);
|
||||
for (const PrintObject *print_object : m_objects)
|
||||
out.emplace_back(print_object->id());
|
||||
return out;
|
||||
}
|
||||
|
||||
Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
|
@ -1274,8 +1285,9 @@ std::string Print::validate() const
|
|||
"and use filaments of the same diameter.");
|
||||
}
|
||||
|
||||
if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin)
|
||||
return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors.");
|
||||
if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware &&
|
||||
m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin)
|
||||
return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors.");
|
||||
if (! m_config.use_relative_e_distances)
|
||||
return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).");
|
||||
if (m_config.ooze_prevention)
|
||||
|
@ -1594,6 +1606,8 @@ void Print::auto_assign_extruders(ModelObject* model_object) const
|
|||
// Slicing process, running at a background thread.
|
||||
void Print::process()
|
||||
{
|
||||
name_tbb_thread_pool_threads();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info();
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->make_perimeters();
|
||||
|
|
|
@ -37,16 +37,18 @@ namespace FillAdaptive {
|
|||
};
|
||||
|
||||
// Print step IDs for keeping track of the print state.
|
||||
// The Print steps are applied in this order.
|
||||
enum PrintStep {
|
||||
psSkirt,
|
||||
psBrim,
|
||||
// Synonym for the last step before the Wipe Tower / Tool Ordering, for the G-code preview slider to understand that
|
||||
// all the extrusions are there for the layer slider to add color changes etc.
|
||||
psExtrusionPaths = psBrim,
|
||||
psWipeTower,
|
||||
// Ordering of the tools on PrintObjects for a multi-material print.
|
||||
// psToolOrdering is a synonym to psWipeTower, as the Wipe Tower calculates and modifies the ToolOrdering,
|
||||
// while if printing without the Wipe Tower, the ToolOrdering is calculated as well.
|
||||
psToolOrdering = psWipeTower,
|
||||
psSkirt,
|
||||
psBrim,
|
||||
// Last step before G-code export, after this step is finished, the initial extrusion path preview
|
||||
// should be refreshed.
|
||||
psSlicingFinished = psBrim,
|
||||
psGCodeExport,
|
||||
psCount,
|
||||
};
|
||||
|
@ -370,6 +372,8 @@ public:
|
|||
// a cancellation callback is executed to stop the background processing before the operation.
|
||||
void clear() override;
|
||||
bool empty() const override { return m_objects.empty(); }
|
||||
// List of existing PrintObject IDs, to remove notifications for non-existent IDs.
|
||||
std::vector<ObjectID> print_object_ids() const override;
|
||||
|
||||
ApplyStatus apply(const Model &model, DynamicPrintConfig config) override;
|
||||
|
||||
|
|
|
@ -348,6 +348,8 @@ public:
|
|||
// The Print is empty either after clear() or after apply() over an empty model,
|
||||
// or after apply() over a model, where no object is printable (all outside the print volume).
|
||||
virtual bool empty() const = 0;
|
||||
// List of existing PrintObject IDs, to remove notifications for non-existent IDs.
|
||||
virtual std::vector<ObjectID> print_object_ids() const = 0;
|
||||
|
||||
// Validate the print, return empty string if valid, return error if process() cannot (or should not) be started.
|
||||
virtual std::string validate() const { return std::string(); }
|
||||
|
@ -406,7 +408,7 @@ public:
|
|||
// set to an ObjectID of a Print or a PrintObject based on flags
|
||||
// (whether UPDATE_PRINT_STEP_WARNINGS or UPDATE_PRINT_OBJECT_STEP_WARNINGS is set).
|
||||
ObjectID warning_object_id;
|
||||
// For which Print or PrintObject step a new warning is beeing issued?
|
||||
// For which Print or PrintObject step a new warning is being issued?
|
||||
int warning_step { -1 };
|
||||
};
|
||||
typedef std::function<void(const SlicingStatus&)> status_callback_type;
|
||||
|
|
|
@ -39,11 +39,6 @@ void PrintConfigDef::init_common_params()
|
|||
{
|
||||
ConfigOptionDef* def;
|
||||
|
||||
def = this->add("single_instance", coBool);
|
||||
def->label = L("Single Instance");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("printer_technology", coEnum);
|
||||
def->label = L("Printer technology");
|
||||
def->tooltip = L("Printer technology");
|
||||
|
@ -113,7 +108,14 @@ void PrintConfigDef::init_common_params()
|
|||
"the API Key or the password required for authentication.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
|
||||
def = this->add("printhost_port", coString);
|
||||
def->label = L("Printer");
|
||||
def->tooltip = L("Name of the printer");
|
||||
def->gui_type = "select_open";
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("printhost_cafile", coString);
|
||||
def->label = L("HTTPS CA File");
|
||||
def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
|
||||
|
@ -457,20 +459,20 @@ void PrintConfigDef::init_fff_params()
|
|||
def->cli = "top-fill-pattern|external-fill-pattern|solid-fill-pattern";
|
||||
def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values();
|
||||
def->enum_values.push_back("rectilinear");
|
||||
def->enum_values.push_back("monotonous");
|
||||
def->enum_values.push_back("monotonic");
|
||||
def->enum_values.push_back("concentric");
|
||||
def->enum_values.push_back("hilbertcurve");
|
||||
def->enum_values.push_back("archimedeanchords");
|
||||
def->enum_values.push_back("octagramspiral");
|
||||
def->enum_labels.push_back(L("Rectilinear"));
|
||||
def->enum_labels.push_back(L("Monotonous"));
|
||||
def->enum_labels.push_back(L("Monotonic"));
|
||||
def->enum_labels.push_back(L("Concentric"));
|
||||
def->enum_labels.push_back(L("Hilbert Curve"));
|
||||
def->enum_labels.push_back(L("Archimedean Chords"));
|
||||
def->enum_labels.push_back(L("Octagram Spiral"));
|
||||
// solid_fill_pattern is an obsolete equivalent to top_fill_pattern/bottom_fill_pattern.
|
||||
def->aliases = { "solid_fill_pattern", "external_fill_pattern" };
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipMonotonous));
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipMonotonic));
|
||||
|
||||
def = this->add("bottom_fill_pattern", coEnum);
|
||||
def->label = L("Bottom fill pattern");
|
||||
|
@ -988,6 +990,7 @@ void PrintConfigDef::init_fff_params()
|
|||
"The \"No extrusion\" flavor prevents PrusaSlicer from exporting any extrusion value at all.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<GCodeFlavor>::get_enum_values();
|
||||
def->enum_values.push_back("reprap");
|
||||
def->enum_values.push_back("reprapfirmware");
|
||||
def->enum_values.push_back("repetier");
|
||||
def->enum_values.push_back("teacup");
|
||||
def->enum_values.push_back("makerware");
|
||||
|
@ -998,6 +1001,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_values.push_back("smoothie");
|
||||
def->enum_values.push_back("no-extrusion");
|
||||
def->enum_labels.push_back("RepRap/Sprinter");
|
||||
def->enum_labels.push_back("RepRapFirmware");
|
||||
def->enum_labels.push_back("Repetier");
|
||||
def->enum_labels.push_back("Teacup");
|
||||
def->enum_labels.push_back("MakerWare (MakerBot)");
|
||||
|
@ -1008,7 +1012,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_labels.push_back("Smoothie");
|
||||
def->enum_labels.push_back(L("No extrusion"));
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionEnum<GCodeFlavor>(gcfRepRap));
|
||||
def->set_default_value(new ConfigOptionEnum<GCodeFlavor>(gcfRepRapSprinter));
|
||||
|
||||
def = this->add("gcode_label_objects", coBool);
|
||||
def->label = L("Label objects");
|
||||
|
@ -1201,6 +1205,21 @@ void PrintConfigDef::init_fff_params()
|
|||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
def = this->add("machine_limits_usage", coEnum);
|
||||
def->label = L("How to apply");
|
||||
def->full_label = L("Purpose of Machine Limits");
|
||||
def->category = L("Machine limits");
|
||||
def->tooltip = L("How to apply the Machine Limits");
|
||||
def->enum_keys_map = &ConfigOptionEnum<MachineLimitsUsage>::get_enum_values();
|
||||
def->enum_values.push_back("emit_to_gcode");
|
||||
def->enum_values.push_back("time_estimate_only");
|
||||
def->enum_values.push_back("ignore");
|
||||
def->enum_labels.push_back("Emit to G-code");
|
||||
def->enum_labels.push_back("Use for time estimate");
|
||||
def->enum_labels.push_back("Ignore");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<MachineLimitsUsage>(MachineLimitsUsage::EmitToGCode));
|
||||
|
||||
{
|
||||
struct AxisDefault {
|
||||
std::string name;
|
||||
|
@ -1435,10 +1454,12 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_values.push_back("duet");
|
||||
def->enum_values.push_back("flashair");
|
||||
def->enum_values.push_back("astrobox");
|
||||
def->enum_values.push_back("repetier");
|
||||
def->enum_labels.push_back("OctoPrint");
|
||||
def->enum_labels.push_back("Duet");
|
||||
def->enum_labels.push_back("FlashAir");
|
||||
def->enum_labels.push_back("AstroBox");
|
||||
def->enum_labels.push_back("Repetier");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));
|
||||
|
||||
|
@ -1753,26 +1774,6 @@ void PrintConfigDef::init_fff_params()
|
|||
def->set_default_value(new ConfigOptionFloat(30));
|
||||
#endif
|
||||
|
||||
def = this->add("serial_port", coString);
|
||||
def->gui_type = "select_open";
|
||||
def->label = "";
|
||||
def->full_label = L("Serial port");
|
||||
def->tooltip = L("USB/serial port for printer connection.");
|
||||
def->width = 20;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("serial_speed", coInt);
|
||||
def->gui_type = "i_enum_open";
|
||||
def->label = L("Speed");
|
||||
def->full_label = L("Serial port speed");
|
||||
def->tooltip = L("Speed (baud) of USB/serial port for printer connection.");
|
||||
def->min = 1;
|
||||
def->max = 300000;
|
||||
def->enum_values.push_back("115200");
|
||||
def->enum_values.push_back("250000");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionInt(250000));
|
||||
|
||||
def = this->add("skirt_distance", coFloat);
|
||||
def->label = L("Distance from object");
|
||||
def->tooltip = L("Distance between skirt and object(s). Set this to zero to attach the skirt "
|
||||
|
@ -3164,10 +3165,15 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
|||
"seal_position", "vibration_limit", "bed_size",
|
||||
"print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe"
|
||||
#ifndef HAS_PRESSURE_EQUALIZER
|
||||
, "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative"
|
||||
, "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
|
||||
#endif /* HAS_PRESSURE_EQUALIZER */
|
||||
"serial_port", "serial_speed"
|
||||
};
|
||||
|
||||
// In PrusaSlicer 2.3.0-alpha0 the "monotonous" infill was introduced, which was later renamed to "monotonic".
|
||||
if (value == "monotonous" && (opt_key == "top_fill_pattern" || opt_key == "bottom_fill_pattern" || opt_key == "fill_pattern"))
|
||||
value = "monotonic";
|
||||
|
||||
if (ignore.find(opt_key) != ignore.end()) {
|
||||
opt_key = "";
|
||||
return;
|
||||
|
@ -3338,11 +3344,12 @@ std::string FullPrintConfig::validate()
|
|||
|
||||
if (this->use_firmware_retraction.value &&
|
||||
this->gcode_flavor.value != gcfSmoothie &&
|
||||
this->gcode_flavor.value != gcfRepRap &&
|
||||
this->gcode_flavor.value != gcfRepRapSprinter &&
|
||||
this->gcode_flavor.value != gcfRepRapFirmware &&
|
||||
this->gcode_flavor.value != gcfMarlin &&
|
||||
this->gcode_flavor.value != gcfMachinekit &&
|
||||
this->gcode_flavor.value != gcfRepetier)
|
||||
return "--use-firmware-retraction is only supported by Marlin, Smoothie, Repetier and Machinekit firmware";
|
||||
return "--use-firmware-retraction is only supported by Marlin, Smoothie, RepRapFirmware, Repetier and Machinekit firmware";
|
||||
|
||||
if (this->use_firmware_retraction.value)
|
||||
for (unsigned char wipe : this->wipe.values)
|
||||
|
@ -3481,7 +3488,6 @@ StaticPrintConfig::StaticCache<class Slic3r::PrintRegionConfig> PrintRegionConfi
|
|||
StaticPrintConfig::StaticCache<class Slic3r::MachineEnvelopeConfig> MachineEnvelopeConfig::s_cache_MachineEnvelopeConfig;
|
||||
StaticPrintConfig::StaticCache<class Slic3r::GCodeConfig> GCodeConfig::s_cache_GCodeConfig;
|
||||
StaticPrintConfig::StaticCache<class Slic3r::PrintConfig> PrintConfig::s_cache_PrintConfig;
|
||||
StaticPrintConfig::StaticCache<class Slic3r::HostConfig> HostConfig::s_cache_HostConfig;
|
||||
StaticPrintConfig::StaticCache<class Slic3r::FullPrintConfig> FullPrintConfig::s_cache_FullPrintConfig;
|
||||
|
||||
StaticPrintConfig::StaticCache<class Slic3r::SLAMaterialConfig> SLAMaterialConfig::s_cache_SLAMaterialConfig;
|
||||
|
@ -3679,6 +3685,12 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
|||
def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file).");
|
||||
def->cli = "output|o";
|
||||
|
||||
def = this->add("single_instance", coBool);
|
||||
def->label = L("Single Instance");
|
||||
def->tooltip = L("If enabled, the command line arguments are sent to an existing instance of GUI PrusaSlicer, "
|
||||
"or an existing PrusaSlicer window is activated. "
|
||||
"Overrides the \"single_instance\" configuration value from application preferences.");
|
||||
|
||||
/*
|
||||
def = this->add("autosave", coString);
|
||||
def->label = L("Autosave");
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
// PrintRegionConfig
|
||||
// PrintConfig
|
||||
// GCodeConfig
|
||||
// HostConfig
|
||||
//
|
||||
|
||||
#ifndef slic3r_PrintConfig_hpp_
|
||||
|
@ -25,12 +24,19 @@
|
|||
namespace Slic3r {
|
||||
|
||||
enum GCodeFlavor : unsigned char {
|
||||
gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit,
|
||||
gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit,
|
||||
gcfSmoothie, gcfNoExtrusion,
|
||||
};
|
||||
|
||||
enum class MachineLimitsUsage {
|
||||
EmitToGCode,
|
||||
TimeEstimateOnly,
|
||||
Ignore,
|
||||
Count,
|
||||
};
|
||||
|
||||
enum PrintHostType {
|
||||
htOctoPrint, htDuet, htFlashAir, htAstroBox
|
||||
htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier
|
||||
};
|
||||
|
||||
enum AuthorizationType {
|
||||
|
@ -38,7 +44,7 @@ enum AuthorizationType {
|
|||
};
|
||||
|
||||
enum InfillPattern : int {
|
||||
ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
|
||||
ipRectilinear, ipMonotonic, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
|
||||
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipCount,
|
||||
};
|
||||
|
||||
|
@ -88,7 +94,8 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<PrinterTechnology
|
|||
template<> inline const t_config_enum_values& ConfigOptionEnum<GCodeFlavor>::get_enum_values() {
|
||||
static t_config_enum_values keys_map;
|
||||
if (keys_map.empty()) {
|
||||
keys_map["reprap"] = gcfRepRap;
|
||||
keys_map["reprap"] = gcfRepRapSprinter;
|
||||
keys_map["reprapfirmware"] = gcfRepRapFirmware;
|
||||
keys_map["repetier"] = gcfRepetier;
|
||||
keys_map["teacup"] = gcfTeacup;
|
||||
keys_map["makerware"] = gcfMakerWare;
|
||||
|
@ -102,6 +109,16 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<GCodeFlavor>::get
|
|||
return keys_map;
|
||||
}
|
||||
|
||||
template<> inline const t_config_enum_values& ConfigOptionEnum<MachineLimitsUsage>::get_enum_values() {
|
||||
static t_config_enum_values keys_map;
|
||||
if (keys_map.empty()) {
|
||||
keys_map["emit_to_gcode"] = int(MachineLimitsUsage::EmitToGCode);
|
||||
keys_map["time_estimate_only"] = int(MachineLimitsUsage::TimeEstimateOnly);
|
||||
keys_map["ignore"] = int(MachineLimitsUsage::Ignore);
|
||||
}
|
||||
return keys_map;
|
||||
}
|
||||
|
||||
template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::get_enum_values() {
|
||||
static t_config_enum_values keys_map;
|
||||
if (keys_map.empty()) {
|
||||
|
@ -109,6 +126,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::g
|
|||
keys_map["duet"] = htDuet;
|
||||
keys_map["flashair"] = htFlashAir;
|
||||
keys_map["astrobox"] = htAstroBox;
|
||||
keys_map["repetier"] = htRepetier;
|
||||
}
|
||||
return keys_map;
|
||||
}
|
||||
|
@ -126,7 +144,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<InfillPattern>::g
|
|||
static t_config_enum_values keys_map;
|
||||
if (keys_map.empty()) {
|
||||
keys_map["rectilinear"] = ipRectilinear;
|
||||
keys_map["monotonous"] = ipMonotonous;
|
||||
keys_map["monotonic"] = ipMonotonic;
|
||||
keys_map["grid"] = ipGrid;
|
||||
keys_map["triangles"] = ipTriangles;
|
||||
keys_map["stars"] = ipStars;
|
||||
|
@ -597,6 +615,8 @@ class MachineEnvelopeConfig : public StaticPrintConfig
|
|||
{
|
||||
STATIC_PRINT_CONFIG_CACHE(MachineEnvelopeConfig)
|
||||
public:
|
||||
// Allowing the machine limits to be completely ignored or used just for time estimator.
|
||||
ConfigOptionEnum<MachineLimitsUsage> machine_limits_usage;
|
||||
// M201 X... Y... Z... E... [mm/sec^2]
|
||||
ConfigOptionFloats machine_max_acceleration_x;
|
||||
ConfigOptionFloats machine_max_acceleration_y;
|
||||
|
@ -624,6 +644,7 @@ public:
|
|||
protected:
|
||||
void initialize(StaticCacheBase &cache, const char *base_ptr)
|
||||
{
|
||||
OPT_PTR(machine_limits_usage);
|
||||
OPT_PTR(machine_max_acceleration_x);
|
||||
OPT_PTR(machine_max_acceleration_y);
|
||||
OPT_PTR(machine_max_acceleration_z);
|
||||
|
@ -941,38 +962,14 @@ protected:
|
|||
}
|
||||
};
|
||||
|
||||
class HostConfig : public StaticPrintConfig
|
||||
{
|
||||
STATIC_PRINT_CONFIG_CACHE(HostConfig)
|
||||
public:
|
||||
ConfigOptionEnum<PrintHostType> host_type;
|
||||
ConfigOptionString print_host;
|
||||
ConfigOptionString printhost_apikey;
|
||||
ConfigOptionString printhost_cafile;
|
||||
ConfigOptionString serial_port;
|
||||
ConfigOptionInt serial_speed;
|
||||
|
||||
protected:
|
||||
void initialize(StaticCacheBase &cache, const char *base_ptr)
|
||||
{
|
||||
OPT_PTR(host_type);
|
||||
OPT_PTR(print_host);
|
||||
OPT_PTR(printhost_apikey);
|
||||
OPT_PTR(printhost_cafile);
|
||||
OPT_PTR(serial_port);
|
||||
OPT_PTR(serial_speed);
|
||||
}
|
||||
};
|
||||
|
||||
// This object is mapped to Perl as Slic3r::Config::Full.
|
||||
class FullPrintConfig :
|
||||
public PrintObjectConfig,
|
||||
public PrintRegionConfig,
|
||||
public PrintConfig,
|
||||
public HostConfig
|
||||
public PrintConfig
|
||||
{
|
||||
STATIC_PRINT_CONFIG_CACHE_DERIVED(FullPrintConfig)
|
||||
FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) { initialize_cache(); *this = s_cache_FullPrintConfig.defaults(); }
|
||||
FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0) { initialize_cache(); *this = s_cache_FullPrintConfig.defaults(); }
|
||||
|
||||
public:
|
||||
// Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned.
|
||||
|
@ -980,13 +977,12 @@ public:
|
|||
|
||||
protected:
|
||||
// Protected constructor to be called to initialize ConfigCache::m_default.
|
||||
FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) {}
|
||||
FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0) {}
|
||||
void initialize(StaticCacheBase &cache, const char *base_ptr)
|
||||
{
|
||||
this->PrintObjectConfig::initialize(cache, base_ptr);
|
||||
this->PrintRegionConfig::initialize(cache, base_ptr);
|
||||
this->PrintConfig ::initialize(cache, base_ptr);
|
||||
this->HostConfig ::initialize(cache, base_ptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1385,8 +1381,6 @@ class ModelConfig
|
|||
public:
|
||||
void clear() { m_data.clear(); m_timestamp = 1; }
|
||||
|
||||
// Modification of the ModelConfig is not thread safe due to the global timestamp counter!
|
||||
// Don't call modification methods from the back-end!
|
||||
void assign_config(const ModelConfig &rhs) {
|
||||
if (m_timestamp != rhs.m_timestamp) {
|
||||
m_data = rhs.m_data;
|
||||
|
@ -1400,6 +1394,9 @@ public:
|
|||
rhs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Modification of the ModelConfig is not thread safe due to the global timestamp counter!
|
||||
// Don't call modification methods from the back-end!
|
||||
// Assign methods don't assign if src==dst to not having to bump the timestamp in case they are equal.
|
||||
void assign_config(const DynamicPrintConfig &rhs) { if (m_data != rhs) { m_data = rhs; this->touch(); } }
|
||||
void assign_config(DynamicPrintConfig &&rhs) { if (m_data != rhs) { m_data = std::move(rhs); this->touch(); } }
|
||||
|
|
|
@ -650,14 +650,14 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
|
|||
|
||||
// propagate to dependent steps
|
||||
if (step == posPerimeters) {
|
||||
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill });
|
||||
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirt, psBrim });
|
||||
} else if (step == posPrepareInfill) {
|
||||
invalidated |= this->invalidate_step(posInfill);
|
||||
invalidated |= this->invalidate_steps({ posInfill, posIroning });
|
||||
} else if (step == posInfill) {
|
||||
invalidated |= m_print->invalidate_steps({ psSkirt, psBrim });
|
||||
} else if (step == posSlice) {
|
||||
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posSupportMaterial });
|
||||
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportMaterial });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirt, psBrim });
|
||||
this->m_slicing_params.valid = false;
|
||||
} else if (step == posSupportMaterial) {
|
||||
|
@ -1688,12 +1688,6 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
|
|||
|
||||
m_typed_slices = false;
|
||||
|
||||
#ifdef SLIC3R_PROFILE
|
||||
// Disable parallelization so the Shiny profiler works
|
||||
static tbb::task_scheduler_init *tbb_init = nullptr;
|
||||
tbb_init = new tbb::task_scheduler_init(1);
|
||||
#endif
|
||||
|
||||
// 1) Initialize layers and their slice heights.
|
||||
std::vector<float> slice_zs;
|
||||
{
|
||||
|
@ -2706,7 +2700,7 @@ void PrintObject::combine_infill()
|
|||
// Because fill areas for rectilinear and honeycomb are grown
|
||||
// later to overlap perimeters, we need to counteract that too.
|
||||
((region->config().fill_pattern == ipRectilinear ||
|
||||
region->config().fill_pattern == ipMonotonous ||
|
||||
region->config().fill_pattern == ipMonotonic ||
|
||||
region->config().fill_pattern == ipGrid ||
|
||||
region->config().fill_pattern == ipLine ||
|
||||
region->config().fill_pattern == ipHoneycomb) ? 1.5f : 0.5f) *
|
||||
|
@ -2748,8 +2742,8 @@ void PrintObject::project_and_append_custom_facets(
|
|||
{
|
||||
for (const ModelVolume* mv : this->model_object()->volumes) {
|
||||
const indexed_triangle_set custom_facets = seam
|
||||
? mv->m_seam_facets.get_facets(*mv, type)
|
||||
: mv->m_supported_facets.get_facets(*mv, type);
|
||||
? mv->seam_facets.get_facets(*mv, type)
|
||||
: mv->supported_facets.get_facets(*mv, type);
|
||||
if (! mv->is_model_part() || custom_facets.indices.empty())
|
||||
continue;
|
||||
|
||||
|
|
|
@ -11,8 +11,12 @@
|
|||
#include "Point.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Tesselate.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include "libslic3r.h"
|
||||
|
||||
#include "libnest2d/backends/clipper/geometries.hpp"
|
||||
#include "libnest2d/utils/rotcalipers.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
|
||||
|
@ -300,32 +304,35 @@ void SupportPointGenerator::add_support_points(SupportPointGenerator::Structure
|
|||
|
||||
float tp = m_config.tear_pressure();
|
||||
float current = s.supports_force_total();
|
||||
static constexpr float SLOPE_DAMPING = .0015f;
|
||||
static constexpr float DANGL_DAMPING = .09f;
|
||||
static constexpr float DANGL_DAMPING = .5f;
|
||||
static constexpr float SLOPE_DAMPING = .1f;
|
||||
|
||||
if (s.islands_below.empty()) {
|
||||
// completely new island - needs support no doubt
|
||||
// deficit is full, there is nothing below that would hold this island
|
||||
uniformly_cover({ *s.polygon }, s, s.area * tp, grid3d, IslandCoverageFlags(icfIsNew | icfBoundaryOnly) );
|
||||
uniformly_cover({ *s.polygon }, s, s.area * tp, grid3d, IslandCoverageFlags(icfIsNew | icfWithBoundary) );
|
||||
return;
|
||||
}
|
||||
|
||||
if (! s.overhangs.empty()) {
|
||||
uniformly_cover(s.overhangs, s, s.overhangs_area * tp, grid3d);
|
||||
}
|
||||
|
||||
auto areafn = [](double sum, auto &p) { return sum + p.area() * SCALING_FACTOR * SCALING_FACTOR; };
|
||||
|
||||
current = s.supports_force_total();
|
||||
if (! s.dangling_areas.empty()) {
|
||||
// Let's see if there's anything that overlaps enough to need supports:
|
||||
// What we now have in polygons needs support, regardless of what the forces are, so we can add them.
|
||||
|
||||
double a = std::accumulate(s.dangling_areas.begin(), s.dangling_areas.end(), 0., areafn);
|
||||
uniformly_cover(s.dangling_areas, s, a * tp - current * DANGL_DAMPING * std::sqrt(1. - a / s.area), grid3d);
|
||||
uniformly_cover(s.dangling_areas, s, a * tp - a * current * s.area, grid3d, icfWithBoundary);
|
||||
}
|
||||
|
||||
current = s.supports_force_total();
|
||||
if (! s.overhangs_slopes.empty()) {
|
||||
double a = std::accumulate(s.overhangs_slopes.begin(), s.overhangs_slopes.end(), 0., areafn);
|
||||
uniformly_cover(s.overhangs_slopes, s, a * tp - current * SLOPE_DAMPING * std::sqrt(1. - a / s.area), grid3d);
|
||||
}
|
||||
|
||||
if (! s.overhangs.empty()) {
|
||||
uniformly_cover(s.overhangs, s, s.overhangs_area * tp, grid3d);
|
||||
uniformly_cover(s.overhangs_slopes, s, a * tp - a * current / s.area, grid3d, icfWithBoundary);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -357,13 +364,26 @@ std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_m
|
|||
double r = random_triangle(rng);
|
||||
size_t idx_triangle = std::min<size_t>(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3;
|
||||
// Select a random point on the triangle.
|
||||
double u = float(std::sqrt(random_float(rng)));
|
||||
double v = float(random_float(rng));
|
||||
const Vec2f &a = triangles[idx_triangle ++];
|
||||
const Vec2f &b = triangles[idx_triangle++];
|
||||
const Vec2f &c = triangles[idx_triangle];
|
||||
const Vec2f x = a * (1.f - u) + b * (u * (1.f - v)) + c * (v * u);
|
||||
out.emplace_back(x);
|
||||
#if 1
|
||||
// https://www.cs.princeton.edu/~funk/tog02.pdf
|
||||
// page 814, formula 1.
|
||||
double u = float(std::sqrt(random_float(rng)));
|
||||
double v = float(random_float(rng));
|
||||
out.emplace_back(a * (1.f - u) + b * (u * (1.f - v)) + c * (v * u));
|
||||
#else
|
||||
// Greg Turk, Graphics Gems
|
||||
// https://devsplorer.wordpress.com/2019/08/07/find-a-random-point-on-a-plane-using-barycentric-coordinates-in-unity/
|
||||
double u = float(random_float(rng));
|
||||
double v = float(random_float(rng));
|
||||
if (u + v >= 1.f) {
|
||||
u = 1.f - u;
|
||||
v = 1.f - v;
|
||||
}
|
||||
out.emplace_back(a + u * (b - a) + v * (c - a));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return out;
|
||||
|
@ -532,10 +552,14 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
|
|||
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
|
||||
|
||||
float support_force_deficit = deficit;
|
||||
auto bb = get_extents(islands);
|
||||
// auto bb = get_extents(islands);
|
||||
|
||||
if (flags & icfIsNew) {
|
||||
Vec2d bbdim = unscaled(Vec2crd{bb.max - bb.min});
|
||||
auto chull_ex = ExPolygonCollection{islands}.convex_hull();
|
||||
auto chull = Slic3rMultiPoint_to_ClipperPath(chull_ex);
|
||||
auto rotbox = libnest2d::minAreaBoundingBox(chull);
|
||||
Vec2d bbdim = {unscaled(rotbox.width()), unscaled(rotbox.height())};
|
||||
|
||||
if (bbdim.x() > bbdim.y()) std::swap(bbdim.x(), bbdim.y());
|
||||
double aspectr = bbdim.y() / bbdim.x();
|
||||
|
||||
|
@ -560,7 +584,7 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
|
|||
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
|
||||
|
||||
std::vector<Vec2f> raw_samples =
|
||||
flags & icfBoundaryOnly ?
|
||||
flags & icfWithBoundary ?
|
||||
sample_expolygon_with_boundary(islands, samples_per_mm2,
|
||||
5.f / poisson_radius, m_rng) :
|
||||
sample_expolygon(islands, samples_per_mm2, m_rng);
|
||||
|
|
|
@ -201,7 +201,7 @@ private:
|
|||
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
|
||||
|
||||
public:
|
||||
enum IslandCoverageFlags : uint8_t { icfNone = 0x0, icfIsNew = 0x1, icfBoundaryOnly = 0x2 };
|
||||
enum IslandCoverageFlags : uint8_t { icfNone = 0x0, icfIsNew = 0x1, icfWithBoundary = 0x2 };
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "ClipperUtils.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
#include "Thread.hpp"
|
||||
|
||||
#include <unordered_set>
|
||||
#include <numeric>
|
||||
|
@ -175,6 +176,16 @@ static std::vector<SLAPrintObject::Instance> sla_instances(const ModelObject &mo
|
|||
return instances;
|
||||
}
|
||||
|
||||
std::vector<ObjectID> SLAPrint::print_object_ids() const
|
||||
{
|
||||
std::vector<ObjectID> out;
|
||||
// Reserve one more for the caller to append the ID of the Print itself.
|
||||
out.reserve(m_objects.size() + 1);
|
||||
for (const SLAPrintObject *print_object : m_objects)
|
||||
out.emplace_back(print_object->id());
|
||||
return out;
|
||||
}
|
||||
|
||||
SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig config)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
|
@ -679,7 +690,10 @@ bool SLAPrint::invalidate_step(SLAPrintStep step)
|
|||
|
||||
void SLAPrint::process()
|
||||
{
|
||||
if(m_objects.empty()) return;
|
||||
if (m_objects.empty())
|
||||
return;
|
||||
|
||||
name_tbb_thread_pool_threads();
|
||||
|
||||
// Assumption: at this point the print objects should be populated only with
|
||||
// the model objects we have to process and the instances are also filtered
|
||||
|
|
|
@ -420,6 +420,8 @@ public:
|
|||
|
||||
void clear() override;
|
||||
bool empty() const override { return m_objects.empty(); }
|
||||
// List of existing PrintObject IDs, to remove notifications for non-existent IDs.
|
||||
std::vector<ObjectID> print_object_ids() const;
|
||||
ApplyStatus apply(const Model &model, DynamicPrintConfig config) override;
|
||||
void set_task(const TaskParams ¶ms) override;
|
||||
void process() override;
|
||||
|
|
|
@ -93,9 +93,10 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin
|
|||
coord_t clpr_offs = scaled(doffs);
|
||||
|
||||
faded_lyrs = std::min(po.m_slice_index.size(), faded_lyrs);
|
||||
size_t faded_lyrs_efc = std::max(size_t(1), faded_lyrs - 1);
|
||||
|
||||
auto efc = [start_efc, faded_lyrs](size_t pos) {
|
||||
return (faded_lyrs - 1 - pos) * start_efc / (faded_lyrs - 1);
|
||||
auto efc = [start_efc, faded_lyrs_efc](size_t pos) {
|
||||
return (faded_lyrs_efc - pos) * start_efc / faded_lyrs_efc;
|
||||
};
|
||||
|
||||
std::vector<ExPolygons> &slices = o == soModel ?
|
||||
|
|
|
@ -39,11 +39,8 @@
|
|||
//===================
|
||||
#define ENABLE_2_3_0_ALPHA1 1
|
||||
|
||||
// Enable rendering of objects colored by facets' slope
|
||||
#define ENABLE_SLOPE_RENDERING (1 && ENABLE_2_3_0_ALPHA1)
|
||||
|
||||
// Enable rendering of objects using environment map
|
||||
#define ENABLE_ENVIRONMENT_MAP (1 && ENABLE_2_3_0_ALPHA1)
|
||||
#define ENABLE_ENVIRONMENT_MAP (0 && ENABLE_2_3_0_ALPHA1)
|
||||
|
||||
// Enable smoothing of objects normals
|
||||
#define ENABLE_SMOOTH_NORMALS (0 && ENABLE_2_3_0_ALPHA1)
|
||||
|
@ -58,6 +55,13 @@
|
|||
#define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1)
|
||||
#define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER)
|
||||
#define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER)
|
||||
#define ENABLE_GCODE_VIEWER_TASKBAR_ICON (0 && ENABLE_GCODE_VIEWER)
|
||||
|
||||
|
||||
//===================
|
||||
// 2.3.0.alpha3 techs
|
||||
//===================
|
||||
#define ENABLE_2_3_0_ALPHA3 1
|
||||
|
||||
#define ENABLE_CTRL_M_ON_WINDOWS (0 && ENABLE_2_3_0_ALPHA3)
|
||||
|
||||
#endif // _prusaslicer_technologies_h_
|
||||
|
|
238
src/libslic3r/Thread.cpp
Normal file
238
src/libslic3r/Thread.cpp
Normal file
|
@ -0,0 +1,238 @@
|
|||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#else
|
||||
// any posix system
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/tbb_thread.h>
|
||||
#include <tbb/task_scheduler_init.h>
|
||||
|
||||
|
||||
#include "Thread.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef _WIN32
|
||||
// The new API is better than the old SEH style thread naming since the names also show up in crash dumpsand ETW traces.
|
||||
// Because the new API is only available on newer Windows 10, look it up dynamically.
|
||||
|
||||
typedef HRESULT(__stdcall* SetThreadDescriptionType)(HANDLE, PCWSTR);
|
||||
typedef HRESULT(__stdcall* GetThreadDescriptionType)(HANDLE, PWSTR*);
|
||||
|
||||
static bool s_SetGetThreadDescriptionInitialized = false;
|
||||
static HMODULE s_hKernel32 = nullptr;
|
||||
static SetThreadDescriptionType s_fnSetThreadDescription = nullptr;
|
||||
static GetThreadDescriptionType s_fnGetThreadDescription = nullptr;
|
||||
|
||||
static bool WindowsGetSetThreadNameAPIInitialize()
|
||||
{
|
||||
if (! s_SetGetThreadDescriptionInitialized) {
|
||||
// Not thread safe! It is therefore a good idea to name the main thread before spawning worker threads
|
||||
// to initialize
|
||||
s_hKernel32 = LoadLibraryW(L"Kernel32.dll");
|
||||
if (s_hKernel32) {
|
||||
s_fnSetThreadDescription = (SetThreadDescriptionType)::GetProcAddress(s_hKernel32, "SetThreadDescription");
|
||||
s_fnGetThreadDescription = (GetThreadDescriptionType)::GetProcAddress(s_hKernel32, "GetThreadDescription");
|
||||
}
|
||||
s_SetGetThreadDescriptionInitialized = true;
|
||||
}
|
||||
return s_fnSetThreadDescription && s_fnGetThreadDescription;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Use the old way by throwing an exception, so at least in Debug mode the thread names are shown by the debugger.
|
||||
static constexpr DWORD MSVC_SEH_EXCEPTION_NAME_THREAD = 0x406D1388;
|
||||
|
||||
#pragma pack(push,8)
|
||||
typedef struct tagTHREADNAME_INFO
|
||||
{
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
} THREADNAME_INFO;
|
||||
#pragma pack(pop)
|
||||
|
||||
static void WindowsSetThreadNameSEH(HANDLE hThread, const char* thread_name)
|
||||
{
|
||||
THREADNAME_INFO info;
|
||||
info.dwType = 0x1000;
|
||||
info.szName = thread_name;
|
||||
info.dwThreadID = ::GetThreadId(hThread);
|
||||
info.dwFlags = 0;
|
||||
__try {
|
||||
RaiseException(MSVC_SEH_EXCEPTION_NAME_THREAD, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
}
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
static bool WindowsSetThreadName(HANDLE hThread, const char *thread_name)
|
||||
{
|
||||
if (! WindowsGetSetThreadNameAPIInitialize()) {
|
||||
#ifdef NDEBUG
|
||||
return false;
|
||||
#else // NDEBUG
|
||||
// Running on Windows 7 or old Windows 7 in debug mode,
|
||||
// inform the debugger about the thread name by throwing an SEH.
|
||||
WindowsSetThreadNameSEH(hThread, thread_name);
|
||||
return true;
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
size_t len = strlen(thread_name);
|
||||
if (len < 1024) {
|
||||
// Allocate the temp string on stack.
|
||||
wchar_t buf[1024];
|
||||
s_fnSetThreadDescription(hThread, boost::nowide::widen(buf, 1024, thread_name));
|
||||
} else {
|
||||
// Allocate dynamically.
|
||||
s_fnSetThreadDescription(hThread, boost::nowide::widen(thread_name).c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool set_thread_name(std::thread &thread, const char *thread_name)
|
||||
{
|
||||
return WindowsSetThreadName(static_cast<HANDLE>(thread.native_handle()), thread_name);
|
||||
}
|
||||
|
||||
bool set_thread_name(boost::thread &thread, const char *thread_name)
|
||||
{
|
||||
return WindowsSetThreadName(static_cast<HANDLE>(thread.native_handle()), thread_name);
|
||||
}
|
||||
|
||||
bool set_current_thread_name(const char *thread_name)
|
||||
{
|
||||
return WindowsSetThreadName(::GetCurrentThread(), thread_name);
|
||||
}
|
||||
|
||||
std::optional<std::string> get_current_thread_name()
|
||||
{
|
||||
if (! WindowsGetSetThreadNameAPIInitialize())
|
||||
return std::nullopt;
|
||||
|
||||
wchar_t *ptr = nullptr;
|
||||
s_fnGetThreadDescription(::GetCurrentThread(), &ptr);
|
||||
return (ptr == nullptr) ? std::string() : boost::nowide::narrow(ptr);
|
||||
}
|
||||
|
||||
#else // _WIN32
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
// Appe screwed the Posix norm.
|
||||
bool set_thread_name(std::thread &thread, const char *thread_name)
|
||||
{
|
||||
// not supported
|
||||
// pthread_setname_np(thread.native_handle(), thread_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_thread_name(boost::thread &thread, const char *thread_name)
|
||||
{
|
||||
// not supported
|
||||
// pthread_setname_np(thread.native_handle(), thread_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_current_thread_name(const char *thread_name)
|
||||
{
|
||||
pthread_setname_np(thread_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::string> get_current_thread_name()
|
||||
{
|
||||
// not supported
|
||||
// char buf[16];
|
||||
// return std::string(thread_getname_np(buf, 16) == 0 ? buf : "");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// posix
|
||||
bool set_thread_name(std::thread &thread, const char *thread_name)
|
||||
{
|
||||
pthread_setname_np(thread.native_handle(), thread_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool set_thread_name(boost::thread &thread, const char *thread_name)
|
||||
{
|
||||
pthread_setname_np(thread.native_handle(), thread_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool set_current_thread_name(const char *thread_name)
|
||||
{
|
||||
pthread_setname_np(pthread_self(), thread_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::string> get_current_thread_name()
|
||||
{
|
||||
char buf[16];
|
||||
return std::string(pthread_getname_np(pthread_self(), buf, 16) == 0 ? buf : "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
// Spawn (n - 1) worker threads on Intel TBB thread pool and name them by an index and a system thread ID.
|
||||
void name_tbb_thread_pool_threads()
|
||||
{
|
||||
static bool initialized = false;
|
||||
if (initialized)
|
||||
return;
|
||||
initialized = true;
|
||||
|
||||
const size_t nthreads_hw = std::thread::hardware_concurrency();
|
||||
size_t nthreads = nthreads_hw;
|
||||
|
||||
#ifdef SLIC3R_PROFILE
|
||||
// Shiny profiler is not thread safe, thus disable parallelization.
|
||||
nthreads = 1;
|
||||
#endif
|
||||
|
||||
if (nthreads != nthreads_hw)
|
||||
new tbb::task_scheduler_init(int(nthreads));
|
||||
|
||||
std::atomic<size_t> nthreads_running(0);
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
auto master_thread_id = tbb::this_tbb_thread::get_id();
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, nthreads, 1),
|
||||
[&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range<size_t> &range) {
|
||||
assert(range.begin() + 1 == range.end());
|
||||
if (nthreads_running.fetch_add(1) + 1 == nthreads) {
|
||||
// All threads are spinning.
|
||||
// Wake them up.
|
||||
cv.notify_all();
|
||||
} else {
|
||||
// Wait for the last thread to wake the others.
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;});
|
||||
}
|
||||
auto thread_id = tbb::this_tbb_thread::get_id();
|
||||
if (thread_id == master_thread_id) {
|
||||
// The calling thread runs the 0'th task.
|
||||
assert(range.begin() == 0);
|
||||
} else {
|
||||
assert(range.begin() > 0);
|
||||
std::ostringstream name;
|
||||
name << "slic3r_tbb_" << range.begin();
|
||||
set_current_thread_name(name.str().c_str());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
57
src/libslic3r/Thread.hpp
Normal file
57
src/libslic3r/Thread.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#ifndef GUI_THREAD_HPP
|
||||
#define GUI_THREAD_HPP
|
||||
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Set / get thread name.
|
||||
// Returns false if the API is not supported.
|
||||
//
|
||||
// It is a good idea to name the main thread before spawning children threads, because dynamic linking is used on Windows 10
|
||||
// to initialize Get/SetThreadDescription functions, which is not thread safe.
|
||||
//
|
||||
// pthread_setname_np supports maximum 15 character thread names! (16th character is the null terminator)
|
||||
//
|
||||
// Methods taking the thread as an argument are not supported by OSX.
|
||||
// Naming threads is only supported on newer Windows 10.
|
||||
|
||||
bool set_thread_name(std::thread &thread, const char *thread_name);
|
||||
inline bool set_thread_name(std::thread &thread, const std::string &thread_name) { return set_thread_name(thread, thread_name.c_str()); }
|
||||
bool set_thread_name(boost::thread &thread, const char *thread_name);
|
||||
inline bool set_thread_name(boost::thread &thread, const std::string &thread_name) { return set_thread_name(thread, thread_name.c_str()); }
|
||||
bool set_current_thread_name(const char *thread_name);
|
||||
inline bool set_current_thread_name(const std::string &thread_name) { return set_current_thread_name(thread_name.c_str()); }
|
||||
|
||||
// Returns nullopt if not supported.
|
||||
// Not supported by OSX.
|
||||
// Naming threads is only supported on newer Windows 10.
|
||||
std::optional<std::string> get_current_thread_name();
|
||||
|
||||
// To be called somewhere before the TBB threads are spinned for the first time, to
|
||||
// give them names recognizible in the debugger.
|
||||
void name_tbb_thread_pool_threads();
|
||||
|
||||
template<class Fn>
|
||||
inline boost::thread create_thread(boost::thread::attributes &attrs, Fn &&fn)
|
||||
{
|
||||
// Duplicating the stack allocation size of Thread Building Block worker
|
||||
// threads of the thread pool: allocate 4MB on a 64bit system, allocate 2MB
|
||||
// on a 32bit system by default.
|
||||
|
||||
attrs.set_stack_size((sizeof(void*) == 4) ? (2048 * 1024) : (4096 * 1024));
|
||||
return boost::thread{attrs, std::forward<Fn>(fn)};
|
||||
}
|
||||
|
||||
template<class Fn> inline boost::thread create_thread(Fn &&fn)
|
||||
{
|
||||
boost::thread::attributes attrs;
|
||||
return create_thread(attrs, std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // GUI_THREAD_HPP
|
|
@ -34,16 +34,15 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si
|
|||
|
||||
|
||||
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
const Vec3f& source, const Vec3f& dir,
|
||||
float radius, CursorType cursor_type,
|
||||
EnforcerBlockerType new_state)
|
||||
const Vec3f& source, float radius,
|
||||
CursorType cursor_type, EnforcerBlockerType new_state,
|
||||
const Transform3d& trafo)
|
||||
{
|
||||
assert(facet_start < m_orig_size_indices);
|
||||
assert(is_approx(dir.norm(), 1.f));
|
||||
|
||||
// Save current cursor center, squared radius and camera direction,
|
||||
// so we don't have to pass it around.
|
||||
m_cursor = {hit, source, dir, radius*radius, cursor_type};
|
||||
// Save current cursor center, squared radius and camera direction, so we don't
|
||||
// have to pass it around.
|
||||
m_cursor = Cursor(hit, source, radius, cursor_type, trafo);
|
||||
|
||||
// In case user changed cursor size since last time, update triangle edge limit.
|
||||
if (m_old_cursor_radius != radius) {
|
||||
|
@ -176,10 +175,24 @@ void TriangleSelector::split_triangle(int facet_idx)
|
|||
const double limit_squared = m_edge_limit_sqr;
|
||||
|
||||
std::array<int, 3>& facet = tr->verts_idxs;
|
||||
const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v};
|
||||
double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(),
|
||||
(*pts[0]-*pts[2]).squaredNorm(),
|
||||
(*pts[1]-*pts[0]).squaredNorm() };
|
||||
std::array<const stl_vertex*, 3> pts = { &m_vertices[facet[0]].v,
|
||||
&m_vertices[facet[1]].v,
|
||||
&m_vertices[facet[2]].v};
|
||||
std::array<stl_vertex, 3> pts_transformed; // must stay in scope of pts !!!
|
||||
|
||||
// In case the object is non-uniformly scaled, transform the
|
||||
// points to world coords.
|
||||
if (! m_cursor.uniform_scaling) {
|
||||
for (size_t i=0; i<pts.size(); ++i) {
|
||||
pts_transformed[i] = m_cursor.trafo * (*pts[i]);
|
||||
pts[i] = &pts_transformed[i];
|
||||
}
|
||||
}
|
||||
|
||||
std::array<double, 3> sides;
|
||||
sides = { (*pts[2]-*pts[1]).squaredNorm(),
|
||||
(*pts[0]-*pts[2]).squaredNorm(),
|
||||
(*pts[1]-*pts[0]).squaredNorm() };
|
||||
|
||||
std::vector<int> sides_to_split;
|
||||
int side_to_keep = -1;
|
||||
|
@ -204,38 +217,14 @@ void TriangleSelector::split_triangle(int facet_idx)
|
|||
}
|
||||
|
||||
|
||||
// Calculate distance of a point from a line.
|
||||
bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const
|
||||
{
|
||||
Vec3f diff = m_cursor.center - point;
|
||||
|
||||
if (m_cursor.type == CIRCLE)
|
||||
return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr;
|
||||
else // SPHERE
|
||||
return diff.squaredNorm() < m_cursor.radius_sqr;
|
||||
}
|
||||
|
||||
|
||||
// Is pointer in a triangle?
|
||||
bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const
|
||||
{
|
||||
auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b,
|
||||
const Vec3f& c, const Vec3f& d) -> bool {
|
||||
return ((b-a).cross(c-a)).dot(d-a) > 0.;
|
||||
};
|
||||
|
||||
const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v;
|
||||
const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v;
|
||||
const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v;
|
||||
const Vec3f& q1 = m_cursor.center + m_cursor.dir;
|
||||
const Vec3f q2 = m_cursor.center - m_cursor.dir;
|
||||
|
||||
if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) {
|
||||
bool pos = signed_volume_sign(q1,q2,p1,p2);
|
||||
if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return m_cursor.is_pointer_in_triangle(p1, p2, p3);
|
||||
}
|
||||
|
||||
|
||||
|
@ -245,7 +234,13 @@ bool TriangleSelector::faces_camera(int facet) const
|
|||
{
|
||||
assert(facet < m_orig_size_indices);
|
||||
// The normal is cached in mesh->stl, use it.
|
||||
return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.);
|
||||
Vec3f normal = m_mesh->stl.facet_start[facet].normal;
|
||||
|
||||
if (! m_cursor.uniform_scaling) {
|
||||
// Transform the normal into world coords.
|
||||
normal = m_cursor.trafo_normal * normal;
|
||||
}
|
||||
return (normal.dot(m_cursor.dir) < 0.);
|
||||
}
|
||||
|
||||
|
||||
|
@ -254,7 +249,7 @@ int TriangleSelector::vertices_inside(int facet_idx) const
|
|||
{
|
||||
int inside = 0;
|
||||
for (size_t i=0; i<3; ++i) {
|
||||
if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v))
|
||||
if (m_cursor.is_mesh_point_inside(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v))
|
||||
++inside;
|
||||
}
|
||||
return inside;
|
||||
|
@ -264,9 +259,12 @@ int TriangleSelector::vertices_inside(int facet_idx) const
|
|||
// Is edge inside cursor?
|
||||
bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const
|
||||
{
|
||||
Vec3f pts[3];
|
||||
for (int i=0; i<3; ++i)
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i=0; i<3; ++i) {
|
||||
pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v;
|
||||
if (! m_cursor.uniform_scaling)
|
||||
pts[i] = m_cursor.trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f& p = m_cursor.center;
|
||||
|
||||
|
@ -690,6 +688,79 @@ void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data)
|
|||
}
|
||||
|
||||
|
||||
TriangleSelector::Cursor::Cursor(
|
||||
const Vec3f& center_, const Vec3f& source_, float radius_world,
|
||||
CursorType type_, const Transform3d& trafo_)
|
||||
: center{center_},
|
||||
source{source_},
|
||||
type{type_},
|
||||
trafo{trafo_.cast<float>()}
|
||||
{
|
||||
Vec3d sf = Geometry::Transformation(trafo_).get_scaling_factor();
|
||||
if (is_approx(sf(0), sf(1)) && is_approx(sf(1), sf(2))) {
|
||||
radius_sqr = std::pow(radius_world / sf(0), 2);
|
||||
uniform_scaling = true;
|
||||
}
|
||||
else {
|
||||
// In case that the transformation is non-uniform, all checks whether
|
||||
// something is inside the cursor should be done in world coords.
|
||||
// First transform center, source and dir in world coords and remember
|
||||
// that we did this.
|
||||
center = trafo * center;
|
||||
source = trafo * source;
|
||||
uniform_scaling = false;
|
||||
radius_sqr = radius_world * radius_world;
|
||||
trafo_normal = trafo.linear().inverse().transpose();
|
||||
}
|
||||
|
||||
// Calculate dir, in whatever coords is appropriate.
|
||||
dir = (center - source).normalized();
|
||||
}
|
||||
|
||||
|
||||
// Is a point (in mesh coords) inside a cursor?
|
||||
bool TriangleSelector::Cursor::is_mesh_point_inside(Vec3f point) const
|
||||
{
|
||||
if (! uniform_scaling)
|
||||
point = trafo * point;
|
||||
|
||||
Vec3f diff = center - point;
|
||||
|
||||
if (type == CIRCLE)
|
||||
return (diff - diff.dot(dir) * dir).squaredNorm() < radius_sqr;
|
||||
else // SPHERE
|
||||
return diff.squaredNorm() < radius_sqr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// p1, p2, p3 are in mesh coords!
|
||||
bool TriangleSelector::Cursor::is_pointer_in_triangle(const Vec3f& p1_,
|
||||
const Vec3f& p2_,
|
||||
const Vec3f& p3_) const
|
||||
{
|
||||
const Vec3f& q1 = center + dir;
|
||||
const Vec3f& q2 = center - dir;
|
||||
|
||||
auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b,
|
||||
const Vec3f& c, const Vec3f& d) -> bool {
|
||||
return ((b-a).cross(c-a)).dot(d-a) > 0.;
|
||||
};
|
||||
|
||||
// In case the object is non-uniformly scaled, do the check in world coords.
|
||||
const Vec3f& p1 = uniform_scaling ? p1_ : Vec3f(trafo * p1_);
|
||||
const Vec3f& p2 = uniform_scaling ? p2_ : Vec3f(trafo * p2_);
|
||||
const Vec3f& p3 = uniform_scaling ? p3_ : Vec3f(trafo * p3_);
|
||||
|
||||
if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) {
|
||||
bool pos = signed_volume_sign(q1,q2,p1,p2);
|
||||
if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -32,10 +32,10 @@ public:
|
|||
void select_patch(const Vec3f& hit, // point where to start
|
||||
int facet_start, // facet that point belongs to
|
||||
const Vec3f& source, // camera position (mesh coords)
|
||||
const Vec3f& dir, // direction of the ray (mesh coords)
|
||||
float radius, // radius of the cursor
|
||||
CursorType type, // current type of cursor
|
||||
EnforcerBlockerType new_state); // enforcer or blocker?
|
||||
EnforcerBlockerType new_state, // enforcer or blocker?
|
||||
const Transform3d& trafo); // matrix to get from mesh to world
|
||||
|
||||
// Get facets currently in the given state.
|
||||
indexed_triangle_set get_facets(EnforcerBlockerType state) const;
|
||||
|
@ -129,11 +129,20 @@ protected:
|
|||
|
||||
// Cache for cursor position, radius and direction.
|
||||
struct Cursor {
|
||||
Cursor() = default;
|
||||
Cursor(const Vec3f& center_, const Vec3f& source_, float radius_world,
|
||||
CursorType type_, const Transform3d& trafo_);
|
||||
bool is_mesh_point_inside(Vec3f pt) const;
|
||||
bool is_pointer_in_triangle(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) const;
|
||||
|
||||
Vec3f center;
|
||||
Vec3f source;
|
||||
Vec3f dir;
|
||||
float radius_sqr;
|
||||
CursorType type;
|
||||
Transform3f trafo;
|
||||
Transform3f trafo_normal;
|
||||
bool uniform_scaling;
|
||||
};
|
||||
|
||||
Cursor m_cursor;
|
||||
|
@ -142,7 +151,6 @@ protected:
|
|||
// Private functions:
|
||||
bool select_triangle(int facet_idx, EnforcerBlockerType type,
|
||||
bool recursive_call = false);
|
||||
bool is_point_inside_cursor(const Vec3f& point) const;
|
||||
int vertices_inside(int facet_idx) const;
|
||||
bool faces_camera(int facet) const;
|
||||
void undivide_triangle(int facet_idx);
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include <type_traits>
|
||||
#include <system_error>
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include "libslic3r.h"
|
||||
|
||||
namespace boost { namespace filesystem { class directory_entry; }}
|
||||
|
@ -73,11 +75,12 @@ enum CopyFileResult {
|
|||
FAIL_CHECK_TARGET_NOT_OPENED
|
||||
};
|
||||
// Copy a file, adjust the access attributes, so that the target is writable.
|
||||
CopyFileResult copy_file_inner(const std::string &from, const std::string &to);
|
||||
CopyFileResult copy_file_inner(const std::string &from, const std::string &to, std::string& error_message);
|
||||
// Copy file to a temp file first, then rename it to the final file name.
|
||||
// If with_check is true, then the content of the copied file is compared to the content
|
||||
// of the source file before renaming.
|
||||
extern CopyFileResult copy_file(const std::string &from, const std::string &to, const bool with_check = false);
|
||||
// Additional error info is passed in error message.
|
||||
extern CopyFileResult copy_file(const std::string &from, const std::string &to, std::string& error_message, const bool with_check = false);
|
||||
|
||||
// Compares two files if identical.
|
||||
extern CopyFileResult check_copy(const std::string& origin, const std::string& copy);
|
||||
|
@ -107,6 +110,12 @@ std::string string_printf(const char *format, ...);
|
|||
// to be placed at the top of Slic3r generated files.
|
||||
std::string header_slic3r_generated();
|
||||
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
// Standard "generated by PrusaGCodeViewer version xxx timestamp xxx" header string,
|
||||
// to be placed at the top of Slic3r generated files.
|
||||
std::string header_gcodeviewer_generated();
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
|
||||
// getpid platform wrapper
|
||||
extern unsigned get_current_pid();
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
#define _libslic3r_h_
|
||||
|
||||
#include "libslic3r_version.h"
|
||||
#define GCODEVIEWER_APP_NAME "PrusaSlicer G-code Viewer"
|
||||
#define GCODEVIEWER_APP_KEY "PrusaSlicerGcodeViewer"
|
||||
#define GCODEVIEWER_BUILD_ID std::string("PrusaSlicer G-code Viewer-") + std::string(SLIC3R_VERSION) + std::string("-UNKNOWN")
|
||||
|
||||
// this needs to be included early for MSVC (listing it in Build.PL is not enough)
|
||||
#include <memory>
|
||||
|
|
|
@ -6,7 +6,4 @@
|
|||
#define SLIC3R_VERSION "@SLIC3R_VERSION@"
|
||||
#define SLIC3R_BUILD_ID "@SLIC3R_BUILD_ID@"
|
||||
|
||||
#define GCODEVIEWER_APP_NAME "@GCODEVIEWER_APP_NAME@"
|
||||
#define GCODEVIEWER_BUILD_ID "@GCODEVIEWER_BUILD_ID@"
|
||||
|
||||
#endif /* __SLIC3R_VERSION_H */
|
||||
|
|
|
@ -417,7 +417,7 @@ std::error_code rename_file(const std::string &from, const std::string &to)
|
|||
#endif
|
||||
}
|
||||
|
||||
CopyFileResult copy_file_inner(const std::string& from, const std::string& to)
|
||||
CopyFileResult copy_file_inner(const std::string& from, const std::string& to, std::string& error_message)
|
||||
{
|
||||
const boost::filesystem::path source(from);
|
||||
const boost::filesystem::path target(to);
|
||||
|
@ -429,20 +429,31 @@ CopyFileResult copy_file_inner(const std::string& from, const std::string& to)
|
|||
// the copy_file() function will fail appropriately and we don't want the permission()
|
||||
// calls to cause needless failures on permissionless filesystems (ie. FATs on SD cards etc.)
|
||||
// or when the target file doesn't exist.
|
||||
|
||||
//This error code is ignored
|
||||
boost::system::error_code ec;
|
||||
|
||||
boost::filesystem::permissions(target, perms, ec);
|
||||
//if (ec)
|
||||
// BOOST_LOG_TRIVIAL(error) << "Copy file permisions before copy error message: " << ec.message();
|
||||
// This error code is passed up
|
||||
ec.clear();
|
||||
boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists, ec);
|
||||
if (ec) {
|
||||
error_message = ec.message();
|
||||
return FAIL_COPY_FILE;
|
||||
}
|
||||
//ec.clear();
|
||||
boost::filesystem::permissions(target, perms, ec);
|
||||
//if (ec)
|
||||
// BOOST_LOG_TRIVIAL(error) << "Copy file permisions after copy error message: " << ec.message();
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
CopyFileResult copy_file(const std::string &from, const std::string &to, const bool with_check)
|
||||
CopyFileResult copy_file(const std::string &from, const std::string &to, std::string& error_message, const bool with_check)
|
||||
{
|
||||
std::string to_temp = to + ".tmp";
|
||||
CopyFileResult ret_val = copy_file_inner(from,to_temp);
|
||||
CopyFileResult ret_val = copy_file_inner(from, to_temp, error_message);
|
||||
if(ret_val == SUCCESS)
|
||||
{
|
||||
if (with_check)
|
||||
|
@ -604,9 +615,16 @@ std::string string_printf(const char *format, ...)
|
|||
|
||||
std::string header_slic3r_generated()
|
||||
{
|
||||
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
|
||||
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
|
||||
}
|
||||
|
||||
#if ENABLE_GCODE_VIEWER
|
||||
std::string header_gcodeviewer_generated()
|
||||
{
|
||||
return std::string("generated by " GCODEVIEWER_APP_NAME " " SLIC3R_VERSION " on ") + Utils::utc_timestamp();
|
||||
}
|
||||
#endif // ENABLE_GCODE_VIEWER
|
||||
|
||||
unsigned get_current_pid()
|
||||
{
|
||||
#ifdef WIN32
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue