Merge remote-tracking branch 'remotes/origin/master' into lh_adaptive_infill_hooks

This commit is contained in:
Vojtech Bubnik 2020-11-03 15:07:38 +01:00
commit 414fdaefc5
228 changed files with 30930 additions and 13115 deletions

View file

@ -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

View file

@ -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;
};

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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),

View file

@ -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 },

View file

@ -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();

View file

@ -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;

View file

@ -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> &regions,
const std::vector<MonotonicRegion> &regions,
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 &region_from, bool flipped_from, const MonotonousRegion &region_to, bool flipped_to)
AntPath& operator()(const MonotonicRegion &region_from, bool flipped_from, const MonotonicRegion &region_to, bool flipped_to)
{
int row = 2 * int(&region_from - m_regions.data()) + flipped_from;
int col = 2 * int(&region_to - m_regions.data()) + flipped_to;
@ -1490,16 +1614,16 @@ public:
return path;
}
AntPath& operator()(const MonotonousRegionLink &region_from, const MonotonousRegion &region_to, bool flipped_to)
AntPath& operator()(const MonotonicRegionLink &region_from, const MonotonicRegion &region_to, bool flipped_to)
{ return (*this)(*region_from.region, region_from.flipped, region_to, flipped_to); }
AntPath& operator()(const MonotonousRegion &region_from, bool flipped_from, const MonotonousRegionLink &region_to)
AntPath& operator()(const MonotonicRegion &region_from, bool flipped_from, const MonotonicRegionLink &region_to)
{ return (*this)(region_from, flipped_from, *region_to.region, region_to.flipped); }
AntPath& operator()(const MonotonousRegionLink &region_from, const MonotonousRegionLink &region_to)
AntPath& operator()(const MonotonicRegionLink &region_from, const MonotonicRegionLink &region_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 &region, bool dir, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs)
static float montonous_region_path_length(const MonotonicRegion &region, 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 &region, bool d
return unscale<float>(total_length);
}
static void connect_monotonous_regions(std::vector<MonotonousRegion> &regions, const ExPolygonWithOffset &poly_with_offset, std::vector<SegmentedIntersectionLine> &segs)
static void connect_monotonic_regions(std::vector<MonotonicRegion> &regions, 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 &region : regions) {
for (MonotonicRegion &region : regions) {
map_intersection_to_region_start.emplace_back(&segs[region.left.vline].intersections[region.left.low], &region);
map_intersection_to_region_end.emplace_back(&segs[region.right.vline].intersections[region.right.low], &region);
}
@ -1840,7 +1964,7 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> &regions, c
std::sort(map_intersection_to_region_end.begin(), map_intersection_to_region_end.end(), intersections_lower);
// Scatter links to neighboring regions.
for (MonotonousRegion &region : regions) {
for (MonotonicRegion &region : 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> &regions, 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 &region : regions) {
for (MonotonicRegion &region : regions) {
std::sort(region.left_neighbors.begin(), region.left_neighbors.end());
std::sort(region.right_neighbors.begin(), region.right_neighbors.end());
}
for (MonotonousRegion &region : regions) {
for (MonotonousRegion *neighbor : region.left_neighbors) {
for (MonotonicRegion &region : regions) {
for (MonotonicRegion *neighbor : region.left_neighbors) {
auto it = std::lower_bound(neighbor->right_neighbors.begin(), neighbor->right_neighbors.end(), &region);
if (it == neighbor->right_neighbors.end() || *it != &region)
neighbor->right_neighbors.insert(it, &region);
}
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(), &region);
if (it == neighbor->left_neighbors.end() || *it != &region)
neighbor->left_neighbors.insert(it, &region);
@ -1903,12 +2027,12 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> &regions, c
#ifndef NDEBUG
// Verify symmetry of the left_neighbors / right_neighbors.
for (MonotonousRegion &region : regions) {
for (MonotonousRegion *neighbor : region.left_neighbors) {
for (MonotonicRegion &region : 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(), &region) != 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(), &region) != neighbor->left_neighbors.end());
}
@ -1916,7 +2040,7 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> &regions, 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 &region : regions) {
for (MonotonicRegion &region : 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> &regions, 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> &regions, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs, std::mt19937_64 &rng)
static std::vector<MonotonicRegionLink> chain_monotonic_regions(
std::vector<MonotonicRegion> &regions, 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 &region : regions)
for (MonotonicRegion &region : regions)
if (region.left_neighbors.empty())
queue.emplace_back(&region);
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(
[&regions, &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 &region = regions[i];
const MonotonicRegion &region = 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 &region = *path_end.region;
MonotonicRegion &region = *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 &region = *path.back().region;
MonotonicRegion &region = *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 &region = *path_segment.region;
for (const MonotonicRegionLink &path_segment : path) {
MonotonicRegion &region = *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 &region_prev = *path_segment_prev.region;
const MonotonicRegionLink &path_segment_prev = *(&path_segment - 1);
const MonotonicRegion &region_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 &region = *path.back().region;
const MonotonicRegion &region = *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 &params)
Polylines FillMonotonic::fill_surface(const Surface *surface, const FillParams &params)
{
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;
}

View file

@ -20,11 +20,11 @@ protected:
bool fill_surface_by_lines(const Surface *surface, const FillParams &params, 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 &params);
virtual bool no_sort() const { return true; }
};

File diff suppressed because it is too large Load diff

View file

@ -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 &params);
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 &params, 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 &params);
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 &params);
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 &params);
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 &params);
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_

View file

@ -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 << "\" ";

View file

@ -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

View file

@ -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

View file

@ -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_ */

View file

@ -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 << ").";
}
}
}

View file

@ -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);

View file

@ -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);

View file

@ -1,4 +1,3 @@
#include "Analyzer.hpp"
#include "PreviewData.hpp"
#include <I18N.hpp>
#include "Utils.hpp"

View file

@ -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";

View file

@ -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

View file

@ -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_ */

View file

@ -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();

View file

@ -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>

View file

@ -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;

View file

@ -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);

View file

@ -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.

View file

@ -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;
}

View file

@ -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;}); }

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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");

View file

@ -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(); } }

View file

@ -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;

View file

@ -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);

View file

@ -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:

View file

@ -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

View file

@ -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 &params) override;
void process() override;

View file

@ -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 ?

View file

@ -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
View 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
View 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

View file

@ -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

View file

@ -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);

View file

@ -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();

View file

@ -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>

View file

@ -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 */

View file

@ -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