diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5ebda365d..dfc180c76 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -146,21 +146,33 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory.
# We pick it from environment if it is not defined in another way
if(WIN32)
- if(NOT DEFINED WIN10SDK_PATH)
- if(DEFINED ENV{WIN10SDK_PATH})
- set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}")
- endif()
+ if(NOT DEFINED WIN10SDK_PATH)
+ if(DEFINED ENV{WIN10SDK_PATH})
+ set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}")
endif()
- if(DEFINED WIN10SDK_PATH AND NOT EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h")
- message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}")
- message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found")
- message("STL fixing by the Netfabb service will not be compiled")
- unset(WIN10SDK_PATH)
+ endif()
+ if(DEFINED WIN10SDK_PATH)
+ if (EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h")
+ set(WIN10SDK_INCLUDE_PATH "${WIN10SDK_PATH}/Include")
+ else()
+ message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}")
+ message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found")
+ message("STL fixing by the Netfabb service will not be compiled")
+ unset(WIN10SDK_PATH)
endif()
- if(WIN10SDK_PATH)
+ else()
+ # Try to use the default Windows 10 SDK path.
+ set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}")
+ if (NOT EXISTS "${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h")
+ message("${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h was not found")
+ message("STL fixing by the Netfabb service will not be compiled")
+ unset(WIN10SDK_INCLUDE_PATH)
+ endif()
+ endif()
+ if(WIN10SDK_INCLUDE_PATH)
message("Building with Win10 Netfabb STL fixing service support")
add_definitions(-DHAS_WIN10SDK)
- include_directories("${WIN10SDK_PATH}/Include")
+ include_directories("${WIN10SDK_INCLUDE_PATH}")
else()
message("Building without Win10 Netfabb STL fixing service support")
endif()
diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake
index c7facc2c6..0c8eaca97 100644
--- a/deps/wxWidgets/wxWidgets.cmake
+++ b/deps/wxWidgets/wxWidgets.cmake
@@ -20,6 +20,7 @@ prusaslicer_add_cmake_project(wxWidgets
${_wx_toolkit}
"-DCMAKE_DEBUG_POSTFIX:STRING="
-DwxBUILD_DEBUG_LEVEL=0
+ -DwxUSE_MEDIACTRL=OFF
-DwxUSE_DETECT_SM=OFF
-DwxUSE_UNICODE=ON
-DwxUSE_OPENGL=ON
diff --git a/resources/icons/notification_cancel.svg b/resources/icons/notification_cancel.svg
new file mode 100644
index 000000000..d849e24c6
--- /dev/null
+++ b/resources/icons/notification_cancel.svg
@@ -0,0 +1,67 @@
+
+
diff --git a/resources/icons/notification_cancel_hover.svg b/resources/icons/notification_cancel_hover.svg
new file mode 100644
index 000000000..746d053e4
--- /dev/null
+++ b/resources/icons/notification_cancel_hover.svg
@@ -0,0 +1,67 @@
+
+
diff --git a/resources/localization/list.txt b/resources/localization/list.txt
index 64d50591a..a2618b44e 100644
--- a/resources/localization/list.txt
+++ b/resources/localization/list.txt
@@ -28,6 +28,7 @@ src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
src/slic3r/GUI/GUI.cpp
src/slic3r/GUI/GUI_App.cpp
src/slic3r/GUI/GUI_Init.cpp
+src/slic3r/GUI/GUI_Factories.cpp
src/slic3r/GUI/GUI_ObjectLayers.cpp
src/slic3r/GUI/GUI_ObjectList.cpp
src/slic3r/GUI/GUI_ObjectManipulation.cpp
diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h
index d52294acd..1ee719288 100644
--- a/src/imgui/imconfig.h
+++ b/src/imgui/imconfig.h
@@ -123,6 +123,8 @@ namespace ImGui
const char ErrorMarker = 0x11;
const char EjectButton = 0x12;
const char EjectHoverButton = 0x13;
+ const char CancelButton = 0x14;
+ const char CancelHoverButton = 0x15;
// void MyFunction(const char* name, const MyMatrix44& v);
}
diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp
index 8de28af5c..37483fc3e 100644
--- a/src/libslic3r/BoundingBox.hpp
+++ b/src/libslic3r/BoundingBox.hpp
@@ -21,22 +21,30 @@ public:
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
- BoundingBoxBase(const std::vector& points) : min(PointClass::Zero()), max(PointClass::Zero())
+
+ template >
+ BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero())
{
- if (points.empty()) {
+ if (from == to) {
this->defined = false;
// throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
} else {
- typename std::vector::const_iterator it = points.begin();
- this->min = *it;
- this->max = *it;
- for (++ it; it != points.end(); ++ it) {
- this->min = this->min.cwiseMin(*it);
- this->max = this->max.cwiseMax(*it);
+ auto it = from;
+ this->min = it->template cast();
+ this->max = this->min;
+ for (++ it; it != to; ++ it) {
+ auto vec = it->template cast();
+ this->min = this->min.cwiseMin(vec);
+ this->max = this->max.cwiseMax(vec);
}
this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1));
}
}
+
+ BoundingBoxBase(const std::vector &points)
+ : BoundingBoxBase(points.begin(), points.end())
+ {}
+
void reset() { this->defined = false; this->min = PointClass::Zero(); this->max = PointClass::Zero(); }
void merge(const PointClass &point);
void merge(const std::vector &points);
@@ -74,19 +82,27 @@ public:
{ if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; }
BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
BoundingBoxBase(p1, p1) { merge(p2); merge(p3); }
- BoundingBox3Base(const std::vector& points)
+
+ template > BoundingBox3Base(It from, It to)
{
- if (points.empty())
+ if (from == to)
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
- typename std::vector::const_iterator it = points.begin();
- this->min = *it;
- this->max = *it;
- for (++ it; it != points.end(); ++ it) {
- this->min = this->min.cwiseMin(*it);
- this->max = this->max.cwiseMax(*it);
+
+ auto it = from;
+ this->min = it->template cast();
+ this->max = this->min;
+ for (++ it; it != to; ++ it) {
+ auto vec = it->template cast();
+ this->min = this->min.cwiseMin(vec);
+ this->max = this->max.cwiseMax(vec);
}
this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2));
}
+
+ BoundingBox3Base(const std::vector &points)
+ : BoundingBox3Base(points.begin(), points.end())
+ {}
+
void merge(const PointClass &point);
void merge(const std::vector &points);
void merge(const BoundingBox3Base &bb);
@@ -188,9 +204,7 @@ public:
class BoundingBoxf3 : public BoundingBox3Base
{
public:
- BoundingBoxf3() : BoundingBox3Base() {}
- BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base(pmin, pmax) {}
- BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {}
+ using BoundingBox3Base::BoundingBox3Base;
BoundingBoxf3 transformed(const Transform3d& matrix) const;
};
diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp
index d5ec0d928..08bedc5c0 100644
--- a/src/libslic3r/Brim.cpp
+++ b/src/libslic3r/Brim.cpp
@@ -320,7 +320,7 @@ static void make_inner_brim(const Print &print, const ConstPrintObjectPtrs &top_
loops = union_pt_chained_outside_in(loops, false);
std::reverse(loops.begin(), loops.end());
extrusion_entities_append_loops(brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()),
- float(flow.width), float(print.skirt_first_layer_height()));
+ float(flow.width()), float(print.skirt_first_layer_height()));
}
// Produce brim lines around those objects, that have the brim enabled.
@@ -495,7 +495,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
if (i + 1 == j && first_path.size() > 3 && first_path.front().X == first_path.back().X && first_path.front().Y == first_path.back().Y) {
auto *loop = new ExtrusionLoop();
brim.entities.emplace_back(loop);
- loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height()));
+ loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
Points &points = loop->paths.front().polyline.points;
points.reserve(first_path.size());
for (const ClipperLib_Z::IntPoint &pt : first_path)
@@ -506,7 +506,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
ExtrusionEntityCollection this_loop_trimmed;
this_loop_trimmed.entities.reserve(j - i);
for (; i < j; ++ i) {
- this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height())));
+ this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())));
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
Points &points = static_cast(this_loop_trimmed.entities.back())->polyline.points;
points.reserve(path.size());
@@ -522,7 +522,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
}
}
} else {
- extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height()));
+ extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
}
make_inner_brim(print, top_level_objects_with_brim, brim);
diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp
index 9c9b5d348..80d34e821 100644
--- a/src/libslic3r/Config.hpp
+++ b/src/libslic3r/Config.hpp
@@ -1441,6 +1441,24 @@ private:
class ConfigOptionDef
{
public:
+ enum class GUIType {
+ undefined,
+ // Open enums, integer value could be one of the enumerated values or something else.
+ i_enum_open,
+ // Open enums, float value could be one of the enumerated values or something else.
+ f_enum_open,
+ // Color picker, string value.
+ color,
+ // ???
+ select_open,
+ // Currently unused.
+ slider,
+ // Static text
+ legend,
+ // Vector value, but edited as a single string.
+ one_string,
+ };
+
// Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map.
t_config_option_key opt_key;
// What type? bool, int, string etc.
@@ -1524,7 +1542,7 @@ public:
// Usually empty.
// Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection,
// "select_open" - to open a selection dialog (currently only a serial port selection).
- std::string gui_type;
+ GUIType gui_type { GUIType::undefined };
// Usually empty. Otherwise "serialized" or "show_value"
// The flags may be combined.
// "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon.
diff --git a/src/libslic3r/ElephantFootCompensation.cpp b/src/libslic3r/ElephantFootCompensation.cpp
index f28d88f7e..0895e16d6 100644
--- a/src/libslic3r/ElephantFootCompensation.cpp
+++ b/src/libslic3r/ElephantFootCompensation.cpp
@@ -621,7 +621,7 @@ ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, double min_c
ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &external_perimeter_flow, const double compensation)
{
// The contour shall be wide enough to apply the external perimeter plus compensation on both sides.
- double min_contour_width = double(external_perimeter_flow.width + external_perimeter_flow.spacing());
+ double min_contour_width = double(external_perimeter_flow.width() + external_perimeter_flow.spacing());
return elephant_foot_compensation(input, min_contour_width, compensation);
}
diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp
index 390d107f2..3284bc39e 100644
--- a/src/libslic3r/ExtrusionEntity.cpp
+++ b/src/libslic3r/ExtrusionEntity.cpp
@@ -52,7 +52,9 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale
{
// Instantiating the Flow class to get the line spacing.
// Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler.
- Flow flow(this->width, this->height, 0.f, is_bridge(this->role()));
+ bool bridge = is_bridge(this->role());
+ assert(! bridge || this->width == this->height);
+ auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f);
polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
}
diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp
index ee493ca9c..579259a5f 100644
--- a/src/libslic3r/Fill/Fill.cpp
+++ b/src/libslic3r/Fill/Fill.cpp
@@ -28,6 +28,8 @@ struct SurfaceFillParams
// coordf_t overlap = 0.;
// Angle as provided by the region config, in radians.
float angle = 0.f;
+ // Is bridging used for this fill? Bridging parameters may be used even if this->flow.bridge() is not set.
+ bool bridge;
// Non-negative for a bridge.
float bridge_angle = 0.f;
@@ -42,7 +44,7 @@ struct SurfaceFillParams
// width, height of extrusion, nozzle diameter, is bridge
// For the output, for fill generator.
- Flow flow = Flow(0.f, 0.f, 0.f, false);
+ Flow flow;
// For the output
ExtrusionRole extrusion_role = ExtrusionRole(0);
@@ -70,21 +72,22 @@ struct SurfaceFillParams
// RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust);
RETURN_COMPARE_NON_EQUAL(anchor_length);
RETURN_COMPARE_NON_EQUAL(anchor_length_max);
- RETURN_COMPARE_NON_EQUAL(flow.width);
- RETURN_COMPARE_NON_EQUAL(flow.height);
- RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter);
- RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, flow.bridge);
+ RETURN_COMPARE_NON_EQUAL(flow.width());
+ RETURN_COMPARE_NON_EQUAL(flow.height());
+ RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter());
+ RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, bridge);
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role);
return false;
}
bool operator==(const SurfaceFillParams &rhs) const {
return this->extruder == rhs.extruder &&
- this->pattern == rhs.pattern &&
this->pattern == rhs.pattern &&
this->spacing == rhs.spacing &&
// this->overlap == rhs.overlap &&
this->angle == rhs.angle &&
+ this->bridge == rhs.bridge &&
+// this->bridge_angle == rhs.bridge_angle &&
this->density == rhs.density &&
// this->dont_adjust == rhs.dont_adjust &&
this->anchor_length == rhs.anchor_length &&
@@ -128,6 +131,7 @@ std::vector group_fills(const Layer &layer)
if (surface.is_solid()) {
params.density = 100.f;
+ //FIXME for non-thick bridges, shall we allow a bottom surface pattern?
params.pattern = (surface.is_external() && ! is_bridge) ?
(surface.is_top() ? region_config.top_fill_pattern.value : region_config.bottom_fill_pattern.value) :
region_config.top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear;
@@ -143,17 +147,13 @@ std::vector group_fills(const Layer &layer)
params.bridge_angle = float(surface.bridge_angle);
params.angle = float(Geometry::deg2rad(region_config.fill_angle.value));
- // calculate the actual flow we'll be using for this infill
- params.flow = layerm.region()->flow(
- extrusion_role,
- (surface.thickness == -1) ? layer.height : surface.thickness, // extrusion height
- is_bridge || Fill::use_bridge_flow(params.pattern), // bridge flow?
- layer.id() == 0, // first layer?
- -1, // auto width
- *layer.object()
- );
-
- // Calculate flow spacing for infill pattern generation.
+ // Calculate the actual flow we'll be using for this infill.
+ params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
+ params.flow = params.bridge ?
+ layerm.bridging_flow(extrusion_role) :
+ layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
+
+ // Calculate flow spacing for infill pattern generation.
if (surface.is_solid() || is_bridge) {
params.spacing = params.flow.spacing();
// Don't limit anchor length for solid or bridging infill.
@@ -164,14 +164,7 @@ std::vector group_fills(const Layer &layer)
// for all layers, for avoiding the ugly effect of
// misaligned infill on first layer because of different extrusion width and
// layer height
- params.spacing = layerm.region()->flow(
- frInfill,
- layer.object()->config().layer_height.value, // TODO: handle infill_every_layers?
- false, // no bridge
- false, // no first layer
- -1, // auto width
- *layer.object()
- ).spacing();
+ params.spacing = layerm.flow(frInfill, layer.object()->config().layer_height).spacing();
// Anchor a sparse infill to inner perimeters with the following anchor length:
params.anchor_length = float(region_config.infill_anchor);
if (region_config.infill_anchor.percent)
@@ -278,7 +271,7 @@ std::vector group_fills(const Layer &layer)
region_id = region_some_infill;
const LayerRegion& layerm = *layer.regions()[region_id];
for (SurfaceFill &surface_fill : surface_fills)
- if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height) < EPSILON) {
+ if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) {
internal_solid_fill = &surface_fill;
break;
}
@@ -290,14 +283,7 @@ std::vector group_fills(const Layer &layer)
params.extrusion_role = erInternalInfill;
params.angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value));
// calculate the actual flow we'll be using for this infill
- params.flow = layerm.region()->flow(
- frSolidInfill,
- layer.height, // extrusion height
- false, // bridge flow?
- layer.id() == 0, // first layer?
- -1, // auto width
- *layer.object()
- );
+ params.flow = layerm.flow(frSolidInfill);
params.spacing = params.flow.spacing();
surface_fills.emplace_back(params);
surface_fills.back().surface.surface_type = stInternalSolid;
@@ -365,9 +351,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
// calculate flow spacing for infill pattern generation
- bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge;
+ bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge;
double link_max_length = 0.;
- if (! surface_fill.params.flow.bridge) {
+ if (! surface_fill.params.bridge) {
#if 0
link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
@@ -380,7 +366,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
// Maximum length of the perimeter segment linking two infill lines.
f->link_max_length = (coord_t)scale_(link_max_length);
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
- f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
+ f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
// apply half spacing using this flow's own spacing and generate infill
FillParams params;
@@ -402,15 +388,15 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
// calculate actual flow from spacing (which might have been adjusted by the infill
// pattern generator)
double flow_mm3_per_mm = surface_fill.params.flow.mm3_per_mm();
- double flow_width = surface_fill.params.flow.width;
+ double flow_width = surface_fill.params.flow.width();
if (using_internal_flow) {
// if we used the internal flow we're not doing a solid infill
// so we can safely ignore the slight variation that might have
// been applied to f->spacing
} else {
- Flow new_flow = Flow::new_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter, surface_fill.params.flow.height, surface_fill.params.flow.bridge);
+ Flow new_flow = surface_fill.params.flow.with_spacing(float(f->spacing));
flow_mm3_per_mm = new_flow.mm3_per_mm();
- flow_width = new_flow.width;
+ flow_width = new_flow.width();
}
// Save into layer.
ExtrusionEntityCollection* eec = nullptr;
@@ -420,7 +406,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
extrusion_entities_append_paths(
eec->entities, std::move(polylines),
surface_fill.params.extrusion_role,
- flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height);
+ flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height());
}
}
}
@@ -618,9 +604,9 @@ void Layer::make_ironing()
fill.spacing = ironing_params.line_spacing;
fill.angle = float(ironing_params.angle + 0.25 * M_PI);
fill.link_max_length = (coord_t)scale_(3. * fill.spacing);
- double height = ironing_params.height * fill.spacing / nozzle_dmr;
- Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height), false);
- double flow_mm3_per_mm = flow.mm3_per_mm();
+ double extrusion_height = ironing_params.height * fill.spacing / nozzle_dmr;
+ float extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(float(nozzle_dmr), float(extrusion_height));
+ double flow_mm3_per_mm = nozzle_dmr * extrusion_height;
Surface surface_fill(stTop, ExPolygon());
for (ExPolygon &expoly : ironing_areas) {
surface_fill.expolygon = std::move(expoly);
@@ -638,7 +624,7 @@ void Layer::make_ironing()
extrusion_entities_append_paths(
eec->entities, std::move(polylines),
erIroning,
- flow_mm3_per_mm, float(flow.width), float(height));
+ flow_mm3_per_mm, extrusion_width, float(extrusion_height));
}
}
}
diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp
index e5dcf0731..1645bf683 100644
--- a/src/libslic3r/Flow.cpp
+++ b/src/libslic3r/Flow.cpp
@@ -122,20 +122,13 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResol
// This constructor builds a Flow object from an extrusion width config setting
// and other context properties.
-Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio)
+Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height)
{
- // we need layer height unless it's a bridge
- if (height <= 0 && bridge_flow_ratio == 0)
+ if (height <= 0)
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
float w;
- if (bridge_flow_ratio > 0) {
- // If bridge flow was requested, calculate the bridge width.
- height = w = (bridge_flow_ratio == 1.) ?
- // optimization to avoid sqrt()
- nozzle_diameter :
- sqrt(bridge_flow_ratio) * nozzle_diameter;
- } else if (! width.percent && width.value == 0.) {
+ if (! width.percent && width.value == 0.) {
// If user left option to 0, calculate a sane default width.
w = auto_extrusion_width(role, nozzle_diameter);
} else {
@@ -143,71 +136,89 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent
w = float(width.get_abs_value(height));
}
- return Flow(w, height, nozzle_diameter, bridge_flow_ratio > 0);
+ return Flow(w, height, rounded_rectangle_extrusion_spacing(w, height), nozzle_diameter, false);
}
-// This constructor builds a Flow object from a given centerline spacing.
-Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge)
+// Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions.
+Flow Flow::with_spacing(float new_spacing) const
{
- // we need layer height unless it's a bridge
- if (height <= 0 && !bridge)
- throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()");
- // Calculate width from spacing.
- // For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions.
- // For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads.
- float width = float(bridge ?
- (spacing - BRIDGE_EXTRA_SPACING) :
-#ifdef HAS_PERIMETER_LINE_OVERLAP
- (spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * (1. - 0.25 * PI));
-#else
- (spacing + height * (1. - 0.25 * PI)));
-#endif
- return Flow(width, bridge ? width : height, nozzle_diameter, bridge);
+ Flow out = *this;
+ if (m_bridge) {
+ // Diameter of the rounded extrusion.
+ assert(m_width == m_height);
+ float gap = m_spacing - m_width;
+ auto new_diameter = new_spacing - gap;
+ out.m_width = out.m_height = new_diameter;
+ } else {
+ assert(m_width >= m_height);
+ out.m_width += new_spacing - m_spacing;
+ if (out.m_width < out.m_height)
+ throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing()");
+ }
+ out.m_spacing = new_spacing;
+ return out;
}
-// This method returns the centerline spacing between two adjacent extrusions
-// having the same extrusion width (and other properties).
-float Flow::spacing() const
+// Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing.
+Flow Flow::with_cross_section(float area_new) const
{
-#ifdef HAS_PERIMETER_LINE_OVERLAP
- if (this->bridge)
- return this->width + BRIDGE_EXTRA_SPACING;
- // rectangle with semicircles at the ends
- float min_flow_spacing = this->width - this->height * (1. - 0.25 * PI);
- float res = this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing);
-#else
- float res = float(this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1. - 0.25 * PI)));
-#endif
-// assert(res > 0.f);
- if (res <= 0.f)
- throw FlowErrorNegativeSpacing();
- return res;
+ assert(! m_bridge);
+ assert(m_width >= m_height);
+
+ // Adjust for bridge_flow_ratio, maintain the extrusion spacing.
+ float area = this->mm3_per_mm();
+ if (area_new > area + EPSILON) {
+ // Increasing the flow rate.
+ float new_full_spacing = area_new / m_height;
+ if (new_full_spacing > m_spacing) {
+ // Filling up the spacing without an air gap. Grow the extrusion in height.
+ float height = area_new / m_spacing;
+ return Flow(rounded_rectangle_extrusion_width_from_spacing(m_spacing, height), height, m_spacing, m_nozzle_diameter, false);
+ } else {
+ return this->with_width(rounded_rectangle_extrusion_width_from_spacing(area / m_height, m_height));
+ }
+ } else if (area_new < area - EPSILON) {
+ // Decreasing the flow rate.
+ float width_new = m_width - (area - area_new) / m_height;
+ assert(width_new > 0);
+ if (width_new > m_height) {
+ // Shrink the extrusion width.
+ return this->with_width(width_new);
+ } else {
+ // Create a rounded extrusion.
+ auto dmr = float(sqrt(area_new / M_PI));
+ return Flow(dmr, dmr, m_spacing, m_nozzle_diameter, false);
+ }
+ } else
+ return *this;
}
-// This method returns the centerline spacing between an extrusion using this
-// flow and another one using another flow.
-// this->spacing(other) shall return the same value as other.spacing(*this)
-float Flow::spacing(const Flow &other) const
+float Flow::rounded_rectangle_extrusion_spacing(float width, float height)
{
- assert(this->height == other.height);
- assert(this->bridge == other.bridge);
- float res = float(this->bridge ?
- 0.5 * this->width + 0.5 * other.width + BRIDGE_EXTRA_SPACING :
- 0.5 * this->spacing() + 0.5 * other.spacing());
-// assert(res > 0.f);
- if (res <= 0.f)
- throw FlowErrorNegativeSpacing();
- return res;
+ auto out = width - height * float(1. - 0.25 * PI);
+ if (out <= 0.f)
+ throw FlowErrorNegativeSpacing();
+ return out;
+}
+
+float Flow::rounded_rectangle_extrusion_width_from_spacing(float spacing, float height)
+{
+ return float(spacing + height * (1. - 0.25 * PI));
+}
+
+float Flow::bridge_extrusion_spacing(float dmr)
+{
+ return dmr + BRIDGE_EXTRA_SPACING;
}
// This method returns extrusion volume per head move unit.
-double Flow::mm3_per_mm() const
+double Flow::mm3_per_mm() const
{
- float res = this->bridge ?
+ float res = m_bridge ?
// Area of a circle with dmr of this->width.
- float((this->width * this->width) * 0.25 * PI) :
+ float((m_width * m_width) * 0.25 * PI) :
// Rectangle with semicircles at the ends. ~ h (w - 0.215 h)
- float(this->height * (this->width - this->height * (1. - 0.25 * PI)));
+ float(m_height * (m_width - m_height * (1. - 0.25 * PI)));
//assert(res > 0.);
if (res <= 0.)
throw FlowErrorNegativeFlow();
@@ -222,9 +233,7 @@ Flow support_material_flow(const PrintObject *object, float layer_height)
(object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width,
// if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)),
- (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value),
- // bridge_flow_ratio
- 0.f);
+ (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
}
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
@@ -235,9 +244,7 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(width.value > 0) ? width : object->config().extrusion_width,
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)),
- (layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)),
- // bridge_flow_ratio
- 0.f);
+ (layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)));
}
Flow support_material_interface_flow(const PrintObject *object, float layer_height)
@@ -248,9 +255,7 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig
(object->config().support_material_extrusion_width > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width,
// if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_interface_extruder-1)),
- (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value),
- // bridge_flow_ratio
- 0.f);
+ (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
}
}
diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp
index 9e57ce907..04ced3e13 100644
--- a/src/libslic3r/Flow.hpp
+++ b/src/libslic3r/Flow.hpp
@@ -13,11 +13,6 @@ class PrintObject;
// Extra spacing of bridge threads, in mm.
#define BRIDGE_EXTRA_SPACING 0.05
-// Overlap factor of perimeter lines. Currently no overlap.
-#ifdef HAS_PERIMETER_LINE_OVERLAP
- #define PERIMETER_LINE_OVERLAP_FACTOR 1.0
-#endif
-
enum FlowRole {
frExternalPerimeter,
frPerimeter,
@@ -56,26 +51,26 @@ public:
class Flow
{
public:
+ Flow() = default;
+ Flow(float width, float height, float nozzle_diameter) :
+ Flow(width, height, rounded_rectangle_extrusion_spacing(width, height), nozzle_diameter, false) {}
+
// Non bridging flow: Maximum width of an extrusion with semicircles at the ends.
// Bridging flow: Bridge thread diameter.
- float width;
+ float width() const { return m_width; }
+ coord_t scaled_width() const { return coord_t(scale_(m_width)); }
// Non bridging flow: Layer height.
// Bridging flow: Bridge thread diameter = layer height.
- float height;
+ float height() const { return m_height; }
+ // Spacing between the extrusion centerlines.
+ float spacing() const { return m_spacing; }
+ coord_t scaled_spacing() const { return coord_t(scale_(m_spacing)); }
// Nozzle diameter.
- float nozzle_diameter;
+ float nozzle_diameter() const { return m_nozzle_diameter; }
// Is it a bridge?
- bool bridge;
-
- Flow(float _w, float _h, float _nd, bool _bridge = false) :
- width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {}
-
- float spacing() const;
- float spacing(const Flow &other) const;
- double mm3_per_mm() const;
- coord_t scaled_width() const { return coord_t(scale_(this->width)); }
- coord_t scaled_spacing() const { return coord_t(scale_(this->spacing())); }
- coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); }
+ bool bridge() const { return m_bridge; }
+ // Cross section area of the extrusion.
+ double mm3_per_mm() const;
// Elephant foot compensation spacing to be used to detect narrow parts, where the elephant foot compensation cannot be applied.
// To be used on frExternalPerimeter only.
@@ -83,13 +78,32 @@ public:
// Here an overlap of 0.2x external perimeter spacing is allowed for by the elephant foot compensation.
coord_t scaled_elephant_foot_spacing() const { return coord_t(0.5f * float(this->scaled_width() + 0.6f * this->scaled_spacing())); }
- bool operator==(const Flow &rhs) const { return this->width == rhs.width && this->height == rhs.height && this->nozzle_diameter == rhs.nozzle_diameter && this->bridge == rhs.bridge; }
-
- static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio);
- // Create a flow from the spacing of extrusion lines.
- // This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale
- // to fit a region with integer number of lines.
- static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
+ bool operator==(const Flow &rhs) const { return m_width == rhs.m_width && m_height == rhs.m_height && m_nozzle_diameter == rhs.m_nozzle_diameter && m_bridge == rhs.m_bridge; }
+
+ Flow with_width (float width) const {
+ assert(! m_bridge);
+ return Flow(width, m_height, rounded_rectangle_extrusion_spacing(width, m_height), m_nozzle_diameter, m_bridge);
+ }
+ Flow with_height(float height) const {
+ assert(! m_bridge);
+ return Flow(m_width, height, rounded_rectangle_extrusion_spacing(m_width, height), m_nozzle_diameter, m_bridge);
+ }
+ // Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions.
+ Flow with_spacing(float spacing) const;
+ // Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing.
+ Flow with_cross_section(float area) const;
+ Flow with_flow_ratio(double ratio) const { return this->with_cross_section(this->mm3_per_mm() * ratio); }
+
+ static Flow bridging_flow(float dmr, float nozzle_diameter) { return Flow { dmr, dmr, bridge_extrusion_spacing(dmr), nozzle_diameter, true }; }
+
+ static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height);
+
+ // Spacing of extrusions with rounded extrusion model.
+ static float rounded_rectangle_extrusion_spacing(float width, float height);
+ // Width of extrusions with rounded extrusion model.
+ static float rounded_rectangle_extrusion_width_from_spacing(float spacing, float height);
+ // Spacing of round thread extrusions.
+ static float bridge_extrusion_spacing(float dmr);
// Sane extrusion width defautl based on nozzle diameter.
// The defaults were derived from manual Prusa MK3 profiles.
@@ -100,6 +114,20 @@ public:
// on active extruder etc. Therefore the value calculated by this function shall be used as a hint only.
static double extrusion_width(const std::string &opt_key, const ConfigOptionFloatOrPercent *opt, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0);
static double extrusion_width(const std::string &opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0);
+
+private:
+ Flow(float width, float height, float spacing, float nozzle_diameter, bool bridge) :
+ m_width(width), m_height(height), m_spacing(spacing), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge)
+ {
+ // Gap fill violates this condition.
+ //assert(width >= height);
+ }
+
+ float m_width { 0 };
+ float m_height { 0 };
+ float m_spacing { 0 };
+ float m_nozzle_diameter { 0 };
+ bool m_bridge { false };
};
extern Flow support_material_flow(const PrintObject *object, float layer_height = 0.f);
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
index e10b26f38..152d72079 100644
--- a/src/libslic3r/Format/3mf.cpp
+++ b/src/libslic3r/Format/3mf.cpp
@@ -2014,9 +2014,10 @@ namespace Slic3r {
typedef std::map IdToObjectDataMap;
bool m_fullpath_sources{ true };
+ bool m_zip64 { true };
public:
- bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr);
+ bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64);
private:
bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data);
@@ -2036,10 +2037,11 @@ namespace Slic3r {
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config);
};
- bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data)
+ bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64)
{
clear_errors();
m_fullpath_sources = fullpath_sources;
+ m_zip64 = zip64;
return _save_model_to_file(filename, model, config, thumbnail_data);
}
@@ -2233,9 +2235,13 @@ namespace Slic3r {
{
mz_zip_writer_staged_context context;
if (!mz_zip_writer_add_staged_open(&archive, &context, MODEL_FILE.c_str(),
- // Maximum expected and allowed 3MF file size is 16GiB.
- // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records.
- (uint64_t(1) << 30) * 16,
+ m_zip64 ?
+ // Maximum expected and allowed 3MF file size is 16GiB.
+ // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records.
+ (uint64_t(1) << 30) * 16 :
+ // Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see
+ // GH issue #6193.
+ (uint64_t(1) << 32) - 1,
nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0)) {
add_error("Unable to add model file to archive");
return false;
@@ -2926,13 +2932,13 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c
return res;
}
-bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data)
+bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64)
{
if (path == nullptr || model == nullptr)
return false;
_3MF_Exporter exporter;
- bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data);
+ bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data, zip64);
if (!res)
exporter.log_errors();
diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp
index ccfd9356d..a09a1b834 100644
--- a/src/libslic3r/Format/3mf.hpp
+++ b/src/libslic3r/Format/3mf.hpp
@@ -33,7 +33,7 @@ namespace Slic3r {
// Save the given model and the config data contained in the given Print into a 3mf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
- extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr);
+ extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr, bool zip64 = true);
} // namespace Slic3r
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index d7485313b..d7e72b4ad 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -15,6 +15,7 @@
#include
#include
+#include
#include
#include
@@ -1113,15 +1114,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
const double layer_height = first_object->config().layer_height.value;
const double first_layer_height = first_object->config().first_layer_height.get_abs_value(layer_height);
for (const PrintRegion* region : print.regions()) {
- _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
- _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, false, -1., *first_object).width);
- _write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(frInfill, layer_height, false, false, -1., *first_object).width);
- _write_format(file, "; solid infill extrusion width = %.2fmm\n", region->flow(frSolidInfill, layer_height, false, false, -1., *first_object).width);
- _write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(frTopSolidInfill, layer_height, false, false, -1., *first_object).width);
+ _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frExternalPerimeter, layer_height).width());
+ _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, layer_height).width());
+ _write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(*first_object, frInfill, layer_height).width());
+ _write_format(file, "; solid infill extrusion width = %.2fmm\n", region->flow(*first_object, frSolidInfill, layer_height).width());
+ _write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(*first_object, frTopSolidInfill, layer_height).width());
if (print.has_support_material())
- _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width);
+ _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width());
if (print.config().first_layer_extrusion_width.value > 0)
- _write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width);
+ _write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, first_layer_height, true).width());
_write_format(file, "\n");
}
print.throw_if_canceled();
@@ -1137,6 +1138,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
m_placeholder_parser = print.placeholder_parser();
m_placeholder_parser.update_timestamp();
+ m_placeholder_parser_context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count());
print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode");
// Get optimal tool ordering to minimize tool switches of a multi-exruder print.
@@ -1823,7 +1825,8 @@ namespace Skirt {
// Extrude skirt at the print_z of the raft layers and normal object layers
// not at the print_z of the interlaced support material layers.
std::map> skirt_loops_per_extruder_out;
- assert(skirt_done.empty());
+ //For sequential print, the following test may fail when extruding the 2nd and other objects.
+ // assert(skirt_done.empty());
if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt) {
skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
skirt_done.emplace_back(layer_tools.print_z);
@@ -2178,14 +2181,13 @@ void GCode::process_layer(
const std::pair loops = loops_it->second;
this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp();
- Flow layer_skirt_flow(print.skirt_flow());
- layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]));
+ Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2])));
double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
for (size_t i = loops.first; i < loops.second; ++i) {
// Adjust flow according to this layer's layer height.
ExtrusionLoop loop = *dynamic_cast(print.skirt().entities[i]);
for (ExtrusionPath &path : loop.paths) {
- path.height = layer_skirt_flow.height;
+ path.height = layer_skirt_flow.height();
path.mm3_per_mm = mm3_per_mm;
}
//FIXME using the support_material_speed of the 1st object printed.
diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp
index 9a3fe368d..8e2348530 100644
--- a/src/libslic3r/Layer.hpp
+++ b/src/libslic3r/Layer.hpp
@@ -59,7 +59,10 @@ public:
// (this collection contains only ExtrusionEntityCollection objects)
ExtrusionEntityCollection fills;
- Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
+ Flow flow(FlowRole role) const;
+ Flow flow(FlowRole role, double layer_height) const;
+ Flow bridging_flow(FlowRole role) const;
+
void slices_to_fill_surfaces_clipped();
void prepare_fill_surfaces();
void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces);
diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp
index 1a0bd341c..1bca95ca3 100644
--- a/src/libslic3r/LayerRegion.cpp
+++ b/src/libslic3r/LayerRegion.cpp
@@ -15,16 +15,31 @@
namespace Slic3r {
-Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const
+Flow LayerRegion::flow(FlowRole role) const
{
- return m_region->flow(
- role,
- m_layer->height,
- bridge,
- m_layer->id() == 0,
- width,
- *m_layer->object()
- );
+ return this->flow(role, m_layer->height);
+}
+
+Flow LayerRegion::flow(FlowRole role, double layer_height) const
+{
+ return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0);
+}
+
+Flow LayerRegion::bridging_flow(FlowRole role) const
+{
+ const PrintRegion ®ion = *this->region();
+ const PrintRegionConfig ®ion_config = region.config();
+ if (this->layer()->object()->config().thick_bridges) {
+ // The old Slic3r way (different from all other slicers): Use rounded extrusions.
+ // Get the configured nozzle_diameter for the extruder associated to the flow role requested.
+ // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
+ auto nozzle_diameter = float(region.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1));
+ // Applies default bridge spacing.
+ return Flow::bridging_flow(float(sqrt(region_config.bridge_flow_ratio)) * nozzle_diameter, nozzle_diameter);
+ } else {
+ // The same way as other slicers: Use normal extrusions. Apply bridge_flow_ratio while maintaining the original spacing.
+ return this->flow(role).with_flow_ratio(region_config.bridge_flow_ratio);
+ }
}
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
@@ -84,7 +99,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec
g.layer_id = (int)this->layer()->id();
g.ext_perimeter_flow = this->flow(frExternalPerimeter);
- g.overhang_flow = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object());
+ g.overhang_flow = this->bridging_flow(frPerimeter);
g.solid_infill_flow = this->flow(frSolidInfill);
g.process();
@@ -266,11 +281,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// would get merged into a single one while they need different directions
// also, supply the original expolygon instead of the grown one, because in case
// of very thin (but still working) anchors, the grown expolygon would go beyond them
- BridgeDetector bd(
- initial,
- lower_layer->lslices,
- this->flow(frInfill, true).scaled_width()
- );
+ BridgeDetector bd(initial, lower_layer->lslices, this->bridging_flow(frInfill).scaled_width());
#ifdef SLIC3R_DEBUG
printf("Processing bridge at layer %zu:\n", this->layer()->id());
#endif
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 5f806955e..d48443181 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -1302,52 +1302,54 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
void ModelObject::split(ModelObjectPtrs* new_objects)
{
- if (this->volumes.size() > 1) {
- // We can't split meshes if there's more than one volume, because
- // we can't group the resulting meshes by object afterwards
- new_objects->emplace_back(this);
- return;
- }
-
- ModelVolume* volume = this->volumes.front();
- TriangleMeshPtrs meshptrs = volume->mesh().split();
- size_t counter = 1;
- for (TriangleMesh *mesh : meshptrs) {
+ for (ModelVolume* volume : this->volumes) {
+ if (volume->type() != ModelVolumeType::MODEL_PART)
+ continue;
- // FIXME: crashes if not satisfied
- if (mesh->facets_count() < 3) continue;
+ TriangleMeshPtrs meshptrs = volume->mesh().split();
+ size_t counter = 1;
+ for (TriangleMesh* mesh : meshptrs) {
- mesh->repair();
-
- // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed?
- ModelObject* new_object = m_model->add_object();
- new_object->name = this->name + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : "");
+ // FIXME: crashes if not satisfied
+ if (mesh->facets_count() < 3) continue;
- // Don't copy the config's ID.
- new_object->config.assign_config(this->config);
- assert(new_object->config.id().valid());
- assert(new_object->config.id() != this->config.id());
- new_object->instances.reserve(this->instances.size());
- for (const ModelInstance *model_instance : this->instances)
- new_object->add_instance(*model_instance);
- ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh));
+ mesh->repair();
- for (ModelInstance* model_instance : new_object->instances)
- {
- Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset();
- model_instance->set_offset(model_instance->get_offset() + shift);
+ // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed?
+ ModelObject* new_object = m_model->add_object();
+ if (meshptrs.size() == 1) {
+ new_object->name = volume->name;
+ // Don't copy the config's ID.
+ new_object->config.assign_config(this->config.size() > 0 ? this->config : volume->config);
+ }
+ else {
+ new_object->name = this->name + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : "");
+ // Don't copy the config's ID.
+ new_object->config.assign_config(this->config);
+ }
+ assert(new_object->config.id().valid());
+ assert(new_object->config.id() != this->config.id());
+ new_object->instances.reserve(this->instances.size());
+ for (const ModelInstance* model_instance : this->instances)
+ new_object->add_instance(*model_instance);
+ ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh));
+
+ for (ModelInstance* model_instance : new_object->instances)
+ {
+ Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset();
+ model_instance->set_offset(model_instance->get_offset() + shift);
+ }
+
+ new_vol->set_offset(Vec3d::Zero());
+ // reset the source to disable reload from disk
+ new_vol->source = ModelVolume::Source();
+ new_objects->emplace_back(new_object);
+ delete mesh;
}
-
- new_vol->set_offset(Vec3d::Zero());
- // reset the source to disable reload from disk
- new_vol->source = ModelVolume::Source();
- new_objects->emplace_back(new_object);
- delete mesh;
}
-
- return;
}
+
void ModelObject::merge()
{
if (this->volumes.size() == 1) {
@@ -1738,6 +1740,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
this->object->volumes[ivolume]->translate(offset);
this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1);
this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter));
+ this->object->volumes[ivolume]->m_is_splittable = 0;
delete mesh;
++ idx;
}
diff --git a/src/libslic3r/MutablePolygon.cpp b/src/libslic3r/MutablePolygon.cpp
index f166ce701..104b4c6ee 100644
--- a/src/libslic3r/MutablePolygon.cpp
+++ b/src/libslic3r/MutablePolygon.cpp
@@ -69,7 +69,7 @@ static bool clip_narrow_corner(
Vec2i64 p2 = it2->cast();
Vec2i64 p02;
Vec2i64 p22;
- int64_t dist2_next;
+ int64_t dist2_next = 0;
// As long as there is at least a single triangle left in the polygon.
while (polygon.size() >= 3) {
diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp
index 0f5bfa157..e09fceed7 100644
--- a/src/libslic3r/OpenVDBUtils.cpp
+++ b/src/libslic3r/OpenVDBUtils.cpp
@@ -22,74 +22,54 @@ namespace Slic3r {
class TriangleMeshDataAdapter {
public:
const TriangleMesh &mesh;
-
+ float voxel_scale;
+
size_t polygonCount() const { return mesh.its.indices.size(); }
size_t pointCount() const { return mesh.its.vertices.size(); }
size_t vertexCount(size_t) const { return 3; }
-
+
// Return position pos in local grid index space for polygon n and vertex v
- void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
+ // The actual mesh will appear to openvdb as scaled uniformly by voxel_size
+ // And the voxel count per unit volume can be affected this way.
+ void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const
+ {
+ auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v)));
+ Slic3r::Vec3d p = mesh.its.vertices[vidx].cast() * voxel_scale;
+ pos = {p.x(), p.y(), p.z()};
+ }
+
+ TriangleMeshDataAdapter(const TriangleMesh &m, float voxel_sc = 1.f)
+ : mesh{m}, voxel_scale{voxel_sc} {};
};
-class Contour3DDataAdapter {
-public:
- const sla::Contour3D &mesh;
-
- size_t polygonCount() const { return mesh.faces3.size() + mesh.faces4.size(); }
- size_t pointCount() const { return mesh.points.size(); }
- size_t vertexCount(size_t n) const { return n < mesh.faces3.size() ? 3 : 4; }
-
- // Return position pos in local grid index space for polygon n and vertex v
- void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
-};
-
-void TriangleMeshDataAdapter::getIndexSpacePoint(size_t n,
- size_t v,
- openvdb::Vec3d &pos) const
-{
- auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v)));
- Slic3r::Vec3d p = mesh.its.vertices[vidx].cast();
- pos = {p.x(), p.y(), p.z()};
-}
-
-void Contour3DDataAdapter::getIndexSpacePoint(size_t n,
- size_t v,
- openvdb::Vec3d &pos) const
-{
- size_t vidx = 0;
- if (n < mesh.faces3.size()) vidx = size_t(mesh.faces3[n](Eigen::Index(v)));
- else vidx = size_t(mesh.faces4[n - mesh.faces3.size()](Eigen::Index(v)));
-
- Slic3r::Vec3d p = mesh.points[vidx];
- pos = {p.x(), p.y(), p.z()};
-}
-
-
// TODO: Do I need to call initialize? Seems to work without it as well but the
// docs say it should be called ones. It does a mutex lock-unlock sequence all
// even if was called previously.
-openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
+openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh,
const openvdb::math::Transform &tr,
- float exteriorBandWidth,
- float interiorBandWidth,
- int flags)
+ float voxel_scale,
+ float exteriorBandWidth,
+ float interiorBandWidth,
+ int flags)
{
openvdb::initialize();
- TriangleMeshPtrs meshparts = mesh.split();
+ TriangleMeshPtrs meshparts_raw = mesh.split();
+ auto meshparts = reserve_vector>(meshparts_raw.size());
+ for (auto *p : meshparts_raw)
+ meshparts.emplace_back(p);
- auto it = std::remove_if(meshparts.begin(), meshparts.end(),
- [](TriangleMesh *m){
- m->require_shared_vertices();
- return !m->is_manifold() || m->volume() < EPSILON;
- });
+ auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) {
+ m->require_shared_vertices();
+ return m->volume() < EPSILON;
+ });
meshparts.erase(it, meshparts.end());
openvdb::FloatGrid::Ptr grid;
- for (TriangleMesh *m : meshparts) {
+ for (auto &m : meshparts) {
auto subgrid = openvdb::tools::meshToVolume(
- TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth,
+ TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth,
interiorBandWidth, flags);
if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid);
@@ -106,19 +86,9 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
interiorBandWidth, flags);
}
- return grid;
-}
+ grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale));
-openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh,
- const openvdb::math::Transform &tr,
- float exteriorBandWidth,
- float interiorBandWidth,
- int flags)
-{
- openvdb::initialize();
- return openvdb::tools::meshToVolume(
- Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth,
- flags);
+ return grid;
}
template
@@ -128,20 +98,25 @@ sla::Contour3D _volumeToMesh(const Grid &grid,
bool relaxDisorientedTriangles)
{
openvdb::initialize();
-
+
std::vector points;
std::vector triangles;
std::vector quads;
-
+
openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue,
adaptivity, relaxDisorientedTriangles);
-
+
+ float scale = 1.;
+ try {
+ scale = grid.template metaValue("voxel_scale");
+ } catch (...) { }
+
sla::Contour3D ret;
ret.points.reserve(points.size());
ret.faces3.reserve(triangles.size());
ret.faces4.reserve(quads.size());
- for (auto &v : points) ret.points.emplace_back(to_vec3d(v));
+ for (auto &v : points) ret.points.emplace_back(to_vec3d(v) / scale);
for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v));
for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v));
@@ -166,9 +141,18 @@ sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
relaxDisorientedTriangles);
}
-openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, double iso, double er, double ir)
+openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
+ double iso,
+ double er,
+ double ir)
{
- return openvdb::tools::levelSetRebuild(grid, float(iso), float(er), float(ir));
+ auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso),
+ float(er), float(ir));
+
+ // Copies voxel_scale metadata, if it exists.
+ new_grid->insertMeta(*grid.deepCopyMeta());
+
+ return new_grid;
}
} // namespace Slic3r
diff --git a/src/libslic3r/OpenVDBUtils.hpp b/src/libslic3r/OpenVDBUtils.hpp
index aa4b5154a..5df816abb 100644
--- a/src/libslic3r/OpenVDBUtils.hpp
+++ b/src/libslic3r/OpenVDBUtils.hpp
@@ -21,14 +21,16 @@ inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast
inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; }
inline Vec4i to_vec4i(const openvdb::Vec4I &v) { return Vec4i{int(v[0]), int(v[1]), int(v[2]), int(v[3])}; }
+// Here voxel_scale defines the scaling of voxels which affects the voxel count.
+// 1.0 value means a voxel for every unit cube. 2 means the model is scaled to
+// be 2x larger and the voxel count is increased by the increment in the scaled
+// volume, thus 4 times. This kind a sampling accuracy selection is not
+// achievable through the Transform parameter. (TODO: or is it?)
+// The resulting grid will contain the voxel_scale in its metadata under the
+// "voxel_scale" key to be used in grid_to_mesh function.
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh,
const openvdb::math::Transform &tr = {},
- float exteriorBandWidth = 3.0f,
- float interiorBandWidth = 3.0f,
- int flags = 0);
-
-openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D & mesh,
- const openvdb::math::Transform &tr = {},
+ float voxel_scale = 1.f,
float exteriorBandWidth = 3.0f,
float interiorBandWidth = 3.0f,
int flags = 0);
diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp
index 7cfdc5847..6ec4dbf6b 100644
--- a/src/libslic3r/PerimeterGenerator.cpp
+++ b/src/libslic3r/PerimeterGenerator.cpp
@@ -8,7 +8,7 @@
namespace Slic3r {
-static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance)
+static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance)
{
ExtrusionPaths paths;
ExtrusionPath path(role);
@@ -62,15 +62,15 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi
path.polyline.append(line.b);
// Convert from spacing to extrusion width based on the extrusion model
// of a square extrusion ended with semi circles.
- flow.width = unscale(w) + flow.height * float(1. - 0.25 * PI);
+ Flow new_flow = flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI));
#ifdef SLIC3R_DEBUG
printf(" filling %f gap\n", flow.width);
#endif
- path.mm3_per_mm = flow.mm3_per_mm();
- path.width = flow.width;
- path.height = flow.height;
+ path.mm3_per_mm = new_flow.mm3_per_mm();
+ path.width = new_flow.width();
+ path.height = new_flow.height();
} else {
- thickness_delta = fabs(scale_(flow.width) - w);
+ thickness_delta = fabs(scale_(flow.width()) - w);
if (thickness_delta <= tolerance) {
// the width difference between this line and the current flow width is
// within the accepted tolerance
@@ -88,7 +88,7 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi
return paths;
}
-static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow, std::vector &out)
+static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, const Flow &flow, std::vector &out)
{
// This value determines granularity of adaptive width, as G-code does not allow
// variable extrusion within a single move; this value shall only affect the amount
@@ -205,8 +205,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
paths,
intersection_pl({ polygon }, perimeter_generator.lower_slices_polygons()),
role,
- is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(),
- is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width,
+ is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(),
+ is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(),
(float)perimeter_generator.layer_height);
// get overhang paths by checking what parts of this loop fall
@@ -217,8 +217,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
diff_pl({ polygon }, perimeter_generator.lower_slices_polygons()),
erOverhangPerimeter,
perimeter_generator.mm3_per_mm_overhang(),
- perimeter_generator.overhang_flow.width,
- perimeter_generator.overhang_flow.height);
+ perimeter_generator.overhang_flow.width(),
+ perimeter_generator.overhang_flow.height());
// Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
@@ -226,8 +226,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
} else {
ExtrusionPath path(role);
path.polyline = polygon.split_at_first_point();
- path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm();
- path.width = is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width;
+ path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm();
+ path.width = is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width();
path.height = (float)perimeter_generator.layer_height;
paths.push_back(path);
}
@@ -286,7 +286,7 @@ void PerimeterGenerator::process()
m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
- coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow);
+ coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing()));
// overhang perimeters
m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
@@ -346,7 +346,7 @@ void PerimeterGenerator::process()
if (this->config->thin_walls) {
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
// (actually, something larger than that still may exist due to mitering or other causes)
- coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter / 3));
+ coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3));
ExPolygons expp = offset2_ex(
// medial axis requires non-overlapping geometry
diff_ex(to_polygons(last),
diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp
index 67b1ebd4f..7dac34342 100644
--- a/src/libslic3r/Preset.cpp
+++ b/src/libslic3r/Preset.cpp
@@ -428,9 +428,10 @@ const std::vector& Preset::print_options()
"min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
"support_material_pattern", "support_material_with_sheath", "support_material_spacing",
- "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
- "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance",
- "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius",
+ "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers",
+ "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",
+ "support_material_contact_distance", "support_material_bottom_contact_distance",
+ "support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder",
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index c5babb248..e39fd8685 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -1326,7 +1326,8 @@ std::string Print::validate(std::string* warning) const
return L("The Wipe Tower is only supported for multiple objects if they have equal layer heights");
if (slicing_params.raft_layers() != slicing_params0.raft_layers())
return L("The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers");
- if (object->config().support_material_contact_distance != m_objects.front()->config().support_material_contact_distance)
+ if (slicing_params0.gap_object_support != slicing_params.gap_object_support ||
+ slicing_params0.gap_support_object != slicing_params.gap_support_object)
return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance");
if (! equal_layering(slicing_params, slicing_params0))
return L("The Wipe Tower is only supported for multiple objects if they are sliced equally.");
@@ -1577,9 +1578,7 @@ Flow Print::brim_flow() const
frPerimeter,
width,
(float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1),
- (float)this->skirt_first_layer_height(),
- 0
- );
+ (float)this->skirt_first_layer_height());
}
Flow Print::skirt_flow() const
@@ -1599,9 +1598,7 @@ Flow Print::skirt_flow() const
frPerimeter,
width,
(float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1),
- (float)this->skirt_first_layer_height(),
- 0
- );
+ (float)this->skirt_first_layer_height());
}
bool Print::has_support_material() const
@@ -1818,7 +1815,7 @@ void Print::_make_skirt()
ExtrusionPath(
erSkirt,
(float)mm3_per_mm, // this will be overridden at G-code export time
- flow.width,
+ flow.width(),
(float)first_layer_height // this will be overridden at G-code export time
)));
eloop.paths.back().polyline = loop.split_at_first_point();
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index 7311bdc79..91f86d010 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -65,7 +65,7 @@ public:
const PrintRegionConfig& config() const { return m_config; }
// 1-based extruder identifier for this region and role.
unsigned int extruder(FlowRole role) const;
- Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const;
+ Flow flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false) const;
// Average diameter of nozzles participating on extruding this region.
coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const;
// Average diameter of nozzles participating on extruding this region.
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index e534f140a..692b4c47a 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -1,4 +1,5 @@
#include "PrintConfig.hpp"
+#include "Config.hpp"
#include "I18N.hpp"
#include
@@ -66,7 +67,7 @@ void PrintConfigDef::init_common_params()
def->label = L("G-code thumbnails");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files, in the following format: \"XxY, XxY, ...\"");
def->mode = comExpert;
- def->gui_type = "one_string";
+ def->gui_type = ConfigOptionDef::GUIType::one_string;
def->set_default_value(new ConfigOptionPoints());
def = this->add("layer_height", coFloat);
@@ -116,7 +117,7 @@ void PrintConfigDef::init_common_params()
def = this->add("printhost_port", coString);
def->label = L("Printer");
def->tooltip = L("Name of the printer");
- def->gui_type = "select_open";
+ def->gui_type = ConfigOptionDef::GUIType::select_open;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
@@ -568,7 +569,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionBool(true));
def = this->add("extruder", coInt);
- def->gui_type = "i_enum_open";
+ def->gui_type = ConfigOptionDef::GUIType::i_enum_open;
def->label = L("Extruder");
def->category = L("Extruders");
def->tooltip = L("The extruder to use (unless more specific extruder settings are specified). "
@@ -606,7 +607,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("extruder_colour", coStrings);
def->label = L("Extruder Color");
def->tooltip = L("This is only used in the Slic3r interface as a visual help.");
- def->gui_type = "color";
+ def->gui_type = ConfigOptionDef::GUIType::color;
// Empty string means no color assigned yet.
def->set_default_value(new ConfigOptionStrings { "" });
@@ -668,7 +669,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("filament_colour", coStrings);
def->label = L("Color");
def->tooltip = L("This is only used in the Slic3r interface as a visual help.");
- def->gui_type = "color";
+ def->gui_type = ConfigOptionDef::GUIType::color;
def->set_default_value(new ConfigOptionStrings { "#29B2B2" });
def = this->add("filament_notes", coStrings);
@@ -812,7 +813,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("filament_type", coStrings);
def->label = L("Filament type");
def->tooltip = L("The filament material type for use in custom G-codes.");
- def->gui_type = "f_enum_open";
+ def->gui_type = ConfigOptionDef::GUIType::f_enum_open;
def->gui_flags = "show_value";
def->enum_values.push_back("PLA");
def->enum_values.push_back("PET");
@@ -881,7 +882,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(45));
def = this->add("fill_density", coPercent);
- def->gui_type = "f_enum_open";
+ def->gui_type = ConfigOptionDef::GUIType::f_enum_open;
def->gui_flags = "show_value";
def->label = L("Fill density");
def->category = L("Infill");
@@ -1168,7 +1169,7 @@ void PrintConfigDef::init_fff_params()
"Set this parameter to zero to disable anchoring perimeters connected to a single infill line.");
def->sidetext = L("mm or %");
def->ratio_over = "infill_extrusion_width";
- def->gui_type = "f_enum_open";
+ def->gui_type = ConfigOptionDef::GUIType::f_enum_open;
def->enum_values.push_back("0");
def->enum_values.push_back("1");
def->enum_values.push_back("2");
@@ -1950,7 +1951,7 @@ void PrintConfigDef::init_fff_params()
#if 0
def = this->add("seam_preferred_direction", coFloat);
-// def->gui_type = "slider";
+// def->gui_type = ConfigOptionDef::GUIType::slider;
def->label = L("Direction");
def->sidetext = L("°");
def->full_label = L("Preferred direction of the seam");
@@ -1960,7 +1961,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("seam_preferred_direction_jitter", coFloat);
-// def->gui_type = "slider";
+// def->gui_type = ConfigOptionDef::GUIType::slider;
def->label = L("Jitter");
def->sidetext = L("°");
def->full_label = L("Seam preferred direction jitter");
@@ -2234,8 +2235,8 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionBool(false));
def = this->add("support_material_contact_distance", coFloat);
- def->gui_type = "f_enum_open";
- def->label = L("Contact Z distance");
+ def->gui_type = ConfigOptionDef::GUIType::f_enum_open;
+ def->label = L("Top contact Z distance");
def->category = L("Support material");
def->tooltip = L("The vertical distance between object and support material interface. "
"Setting this to 0 will also prevent Slic3r from using bridge flow and speed "
@@ -2243,12 +2244,31 @@ void PrintConfigDef::init_fff_params()
def->sidetext = L("mm");
// def->min = 0;
def->enum_values.push_back("0");
+ def->enum_values.push_back("0.1");
def->enum_values.push_back("0.2");
def->enum_labels.push_back(L("0 (soluble)"));
+ def->enum_labels.push_back(L("0.1 (detachable)"));
def->enum_labels.push_back(L("0.2 (detachable)"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.2));
+ def = this->add("support_material_bottom_contact_distance", coFloat);
+ def->gui_type = ConfigOptionDef::GUIType::f_enum_open;
+ def->label = L("Bottom contact Z distance");
+ def->category = L("Support material");
+ def->tooltip = L("The vertical distance between the object top surface and the support material interface. "
+ "If set to zero, support_material_contact_distance will be used for both top and bottom contact Z distances.");
+ def->sidetext = L("mm");
+// def->min = 0;
+ def->enum_values.push_back("0");
+ def->enum_values.push_back("0.1");
+ def->enum_values.push_back("0.2");
+ def->enum_labels.push_back(L("same as top"));
+ def->enum_labels.push_back(L("0.1"));
+ def->enum_labels.push_back(L("0.2"));
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionFloat(0));
+
def = this->add("support_material_enforce_layers", coInt);
def->label = L("Enforce support for the first");
def->category = L("Support material");
@@ -2298,15 +2318,39 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(1));
- def = this->add("support_material_interface_layers", coInt);
- def->label = L("Interface layers");
+ auto support_material_interface_layers = def = this->add("support_material_interface_layers", coInt);
+ def->gui_type = ConfigOptionDef::GUIType::i_enum_open;
+ def->label = L("Top interface layers");
def->category = L("Support material");
def->tooltip = L("Number of interface layers to insert between the object(s) and support material.");
def->sidetext = L("layers");
def->min = 0;
+ def->enum_values.push_back("0");
+ def->enum_values.push_back("1");
+ def->enum_values.push_back("2");
+ def->enum_values.push_back("3");
+ def->enum_labels.push_back(L("0 (off)"));
+ def->enum_labels.push_back(L("1 (light)"));
+ def->enum_labels.push_back(L("2 (default)"));
+ def->enum_labels.push_back(L("3 (heavy)"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(3));
+ def = this->add("support_material_bottom_interface_layers", coInt);
+ def->gui_type = ConfigOptionDef::GUIType::i_enum_open;
+ def->label = L("Bottom interface layers");
+ def->category = L("Support material");
+ def->tooltip = L("Number of interface layers to insert between the object(s) and support material. "
+ "Set to -1 to use support_material_interface_layers");
+ def->sidetext = L("layers");
+ def->min = -1;
+ def->enum_values.push_back("-1");
+ append(def->enum_values, support_material_interface_layers->enum_values);
+ def->enum_labels.push_back(L("same as top"));
+ append(def->enum_labels, support_material_interface_layers->enum_labels);
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionInt(-1));
+
def = this->add("support_material_interface_spacing", coFloat);
def->label = L("Interface pattern spacing");
def->category = L("Support material");
@@ -2415,6 +2459,13 @@ void PrintConfigDef::init_fff_params()
def->max = max_temp;
def->set_default_value(new ConfigOptionInts { 200 });
+ def = this->add("thick_bridges", coBool);
+ def->label = L("Thick bridges");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("Print bridges with round extrusions.");
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionBool(true));
+
def = this->add("thin_walls", coBool);
def->label = L("Detect thin walls");
def->category = L("Layers and Perimeters");
@@ -2823,7 +2874,7 @@ void PrintConfigDef::init_sla_params()
def = this->add("material_type", coString);
def->label = L("SLA material type");
def->tooltip = L("SLA material type");
- def->gui_type = "f_enum_open"; // TODO: ???
+ def->gui_type = ConfigOptionDef::GUIType::f_enum_open; // TODO: ???
def->gui_flags = "show_value";
def->enum_values.push_back("Tough");
def->enum_values.push_back("Flexible");
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index 76085c941..abeb29d4b 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -503,12 +503,14 @@ public:
ConfigOptionFloat support_material_angle;
ConfigOptionBool support_material_buildplate_only;
ConfigOptionFloat support_material_contact_distance;
+ ConfigOptionFloat support_material_bottom_contact_distance;
ConfigOptionInt support_material_enforce_layers;
ConfigOptionInt support_material_extruder;
ConfigOptionFloatOrPercent support_material_extrusion_width;
ConfigOptionBool support_material_interface_contact_loops;
ConfigOptionInt support_material_interface_extruder;
ConfigOptionInt support_material_interface_layers;
+ ConfigOptionInt support_material_bottom_interface_layers;
// Spacing between interface lines (the hatching distance). Set zero to get a solid interface.
ConfigOptionFloat support_material_interface_spacing;
ConfigOptionFloatOrPercent support_material_interface_speed;
@@ -522,6 +524,7 @@ public:
ConfigOptionInt support_material_threshold;
ConfigOptionBool support_material_with_sheath;
ConfigOptionFloatOrPercent support_material_xy_spacing;
+ ConfigOptionBool thick_bridges;
ConfigOptionFloat xy_size_compensation;
ConfigOptionBool wipe_into_objects;
@@ -553,12 +556,14 @@ protected:
OPT_PTR(support_material_angle);
OPT_PTR(support_material_buildplate_only);
OPT_PTR(support_material_contact_distance);
+ OPT_PTR(support_material_bottom_contact_distance);
OPT_PTR(support_material_enforce_layers);
OPT_PTR(support_material_interface_contact_loops);
OPT_PTR(support_material_extruder);
OPT_PTR(support_material_extrusion_width);
OPT_PTR(support_material_interface_extruder);
OPT_PTR(support_material_interface_layers);
+ OPT_PTR(support_material_bottom_interface_layers);
OPT_PTR(support_material_interface_spacing);
OPT_PTR(support_material_interface_speed);
OPT_PTR(support_material_pattern);
@@ -569,6 +574,7 @@ protected:
OPT_PTR(support_material_xy_spacing);
OPT_PTR(support_material_threshold);
OPT_PTR(support_material_with_sheath);
+ OPT_PTR(thick_bridges);
OPT_PTR(xy_size_compensation);
OPT_PTR(wipe_into_objects);
}
diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp
index 52bdc87e7..e86eb2c9c 100644
--- a/src/libslic3r/PrintObject.cpp
+++ b/src/libslic3r/PrintObject.cpp
@@ -546,14 +546,9 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "extra_perimeters"
|| opt_key == "gap_fill_enabled"
|| opt_key == "gap_fill_speed"
- || opt_key == "overhangs"
|| opt_key == "first_layer_extrusion_width"
- || opt_key == "fuzzy_skin"
- || opt_key == "fuzzy_skin_thickness"
- || opt_key == "fuzzy_skin_point_dist"
|| opt_key == "perimeter_extrusion_width"
|| opt_key == "infill_overlap"
- || opt_key == "thin_walls"
|| opt_key == "external_perimeters_first") {
steps.emplace_back(posPerimeters);
} else if (
@@ -585,7 +580,9 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "support_material_enforce_layers"
|| opt_key == "support_material_extruder"
|| opt_key == "support_material_extrusion_width"
+ || opt_key == "support_material_bottom_contact_distance"
|| opt_key == "support_material_interface_layers"
+ || opt_key == "support_material_bottom_interface_layers"
|| opt_key == "support_material_interface_pattern"
|| opt_key == "support_material_interface_contact_loops"
|| opt_key == "support_material_interface_extruder"
@@ -652,7 +649,13 @@ bool PrintObject::invalidate_state_by_config_options(
steps.emplace_back(posPrepareInfill);
} else if (
opt_key == "external_perimeter_extrusion_width"
- || opt_key == "perimeter_extruder") {
+ || opt_key == "perimeter_extruder"
+ || opt_key == "fuzzy_skin"
+ || opt_key == "fuzzy_skin_thickness"
+ || opt_key == "fuzzy_skin_point_dist"
+ || opt_key == "overhangs"
+ || opt_key == "thin_walls"
+ || opt_key == "thick_bridges") {
steps.emplace_back(posPerimeters);
steps.emplace_back(posSupportMaterial);
} else if (opt_key == "bridge_flow_ratio") {
@@ -1456,26 +1459,18 @@ void PrintObject::bridge_over_infill()
const PrintRegion ®ion = *m_print->regions()[region_id];
// skip bridging in case there are no voids
- if (region.config().fill_density.value == 100) continue;
-
- // get bridge flow
- Flow bridge_flow = region.flow(
- frSolidInfill,
- -1, // layer height, not relevant for bridge flow
- true, // bridge
- false, // first layer
- -1, // custom width, not relevant for bridge flow
- *this
- );
-
+ if (region.config().fill_density.value == 100)
+ continue;
+
for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) {
// skip first layer
if (layer_it == m_layers.begin())
continue;
- Layer* layer = *layer_it;
- LayerRegion* layerm = layer->m_regions[region_id];
-
+ Layer *layer = *layer_it;
+ LayerRegion *layerm = layer->m_regions[region_id];
+ Flow bridge_flow = layerm->bridging_flow(frSolidInfill);
+
// extract the stInternalSolid surfaces that might be transformed into bridges
Polygons internal_solid;
layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid);
@@ -1488,7 +1483,7 @@ void PrintObject::bridge_over_infill()
Polygons to_bridge_pp = internal_solid;
// iterate through lower layers spanned by bridge_flow
- double bottom_z = layer->print_z - bridge_flow.height;
+ double bottom_z = layer->print_z - bridge_flow.height();
for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) {
const Layer* lower_layer = m_layers[i];
diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp
index 79eb647f6..837200984 100644
--- a/src/libslic3r/PrintRegion.cpp
+++ b/src/libslic3r/PrintRegion.cpp
@@ -18,31 +18,25 @@ unsigned int PrintRegion::extruder(FlowRole role) const
return extruder;
}
-Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const
+Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer) const
{
ConfigOptionFloatOrPercent config_width;
- if (width != -1) {
- // use the supplied custom width, if any
- config_width.value = width;
- config_width.percent = false;
+ // Get extrusion width from configuration.
+ // (might be an absolute value, or a percent value, or zero for auto)
+ if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) {
+ config_width = m_print->config().first_layer_extrusion_width;
+ } else if (role == frExternalPerimeter) {
+ config_width = m_config.external_perimeter_extrusion_width;
+ } else if (role == frPerimeter) {
+ config_width = m_config.perimeter_extrusion_width;
+ } else if (role == frInfill) {
+ config_width = m_config.infill_extrusion_width;
+ } else if (role == frSolidInfill) {
+ config_width = m_config.solid_infill_extrusion_width;
+ } else if (role == frTopSolidInfill) {
+ config_width = m_config.top_infill_extrusion_width;
} else {
- // otherwise, get extrusion width from configuration
- // (might be an absolute value, or a percent value, or zero for auto)
- if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) {
- config_width = m_print->config().first_layer_extrusion_width;
- } else if (role == frExternalPerimeter) {
- config_width = m_config.external_perimeter_extrusion_width;
- } else if (role == frPerimeter) {
- config_width = m_config.perimeter_extrusion_width;
- } else if (role == frInfill) {
- config_width = m_config.infill_extrusion_width;
- } else if (role == frSolidInfill) {
- config_width = m_config.solid_infill_extrusion_width;
- } else if (role == frTopSolidInfill) {
- config_width = m_config.top_infill_extrusion_width;
- } else {
- throw Slic3r::InvalidArgument("Unknown role");
- }
+ throw Slic3r::InvalidArgument("Unknown role");
}
if (config_width.value == 0)
@@ -50,8 +44,8 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir
// Get the configured nozzle_diameter for the extruder associated to the flow role requested.
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
- double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1);
- return Flow::new_from_config_width(role, config_width, (float)nozzle_diameter, (float)layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0f);
+ auto nozzle_diameter = float(m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1));
+ return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height));
}
coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const
diff --git a/src/libslic3r/SLA/Concurrency.hpp b/src/libslic3r/SLA/Concurrency.hpp
index b692914ac..8ff0ff809 100644
--- a/src/libslic3r/SLA/Concurrency.hpp
+++ b/src/libslic3r/SLA/Concurrency.hpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include
@@ -76,13 +77,18 @@ template<> struct _ccr
from, to, init, std::forward(mergefn),
[](typename I::value_type &i) { return i; }, granularity);
}
+
+ static size_t max_concurreny()
+ {
+ return tbb::this_task_arena::max_concurrency();
+ }
};
template<> struct _ccr
{
private:
struct _Mtx { inline void lock() {} inline void unlock() {} };
-
+
public:
using SpinningMutex = _Mtx;
using BlockingMutex = _Mtx;
@@ -133,6 +139,8 @@ public:
return reduce(from, to, init, std::forward(mergefn),
[](typename I::value_type &i) { return i; });
}
+
+ static size_t max_concurreny() { return 1; }
};
using ccr = _ccr;
diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp
index 6df752fd3..b38784521 100644
--- a/src/libslic3r/SLA/Hollowing.cpp
+++ b/src/libslic3r/SLA/Hollowing.cpp
@@ -26,64 +26,99 @@ inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); }
template>
inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; }
-static TriangleMesh _generate_interior(const TriangleMesh &mesh,
- const JobController &ctl,
- double min_thickness,
- double voxel_scale,
- double closing_dist)
+struct Interior {
+ TriangleMesh mesh;
+ openvdb::FloatGrid::Ptr gridptr;
+ mutable std::optional accessor;
+
+ double closing_distance = 0.;
+ double thickness = 0.;
+ double voxel_scale = 1.;
+ double nb_in = 3.; // narrow band width inwards
+ double nb_out = 3.; // narrow band width outwards
+ // Full narrow band is the sum of the two above values.
+
+ void reset_accessor() const // This resets the accessor and its cache
+ // Not a thread safe call!
+ {
+ if (gridptr)
+ accessor = gridptr->getConstAccessor();
+ }
+};
+
+void InteriorDeleter::operator()(Interior *p)
+{
+ delete p;
+}
+
+TriangleMesh &get_mesh(Interior &interior)
+{
+ return interior.mesh;
+}
+
+const TriangleMesh &get_mesh(const Interior &interior)
+{
+ return interior.mesh;
+}
+
+static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh,
+ const JobController &ctl,
+ double min_thickness,
+ double voxel_scale,
+ double closing_dist)
{
- TriangleMesh imesh{mesh};
-
- _scale(voxel_scale, imesh);
-
double offset = voxel_scale * min_thickness;
double D = voxel_scale * closing_dist;
float out_range = 0.1f * float(offset);
float in_range = 1.1f * float(offset + D);
-
+
if (ctl.stopcondition()) return {};
else ctl.statuscb(0, L("Hollowing"));
-
- auto gridptr = mesh_to_grid(imesh, {}, out_range, in_range);
-
+
+ auto gridptr = mesh_to_grid(mesh, {}, voxel_scale, out_range, in_range);
+
assert(gridptr);
-
+
if (!gridptr) {
BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL";
return {};
}
-
+
if (ctl.stopcondition()) return {};
else ctl.statuscb(30, L("Hollowing"));
-
- if (closing_dist > .0) {
- gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range));
- } else {
- D = -offset;
- }
-
+
+ double iso_surface = D;
+ auto narrowb = double(in_range);
+ gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb);
+
if (ctl.stopcondition()) return {};
else ctl.statuscb(70, L("Hollowing"));
-
- double iso_surface = D;
+
double adaptivity = 0.;
- auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
-
- _scale(1. / voxel_scale, omesh);
-
+ InteriorPtr interior = InteriorPtr{new Interior{}};
+
+ interior->mesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
+ interior->gridptr = gridptr;
+
if (ctl.stopcondition()) return {};
else ctl.statuscb(100, L("Hollowing"));
-
- return omesh;
+
+ interior->closing_distance = D;
+ interior->thickness = offset;
+ interior->voxel_scale = voxel_scale;
+ interior->nb_in = narrowb;
+ interior->nb_out = narrowb;
+
+ return interior;
}
-std::unique_ptr generate_interior(const TriangleMesh & mesh,
- const HollowingConfig &hc,
- const JobController & ctl)
+InteriorPtr generate_interior(const TriangleMesh & mesh,
+ const HollowingConfig &hc,
+ const JobController & ctl)
{
static const double MIN_OVERSAMPL = 3.;
static const double MAX_OVERSAMPL = 8.;
-
+
// I can't figure out how to increase the grid resolution through openvdb
// API so the model will be scaled up before conversion and the result
// scaled down. Voxels have a unit size. If I set voxelSize smaller, it
@@ -92,26 +127,29 @@ std::unique_ptr generate_interior(const TriangleMesh & mesh,
//
// max 8x upscale, min is native voxel size
auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality;
- auto meshptr = std::make_unique(
- _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale,
- hc.closing_distance));
-
- if (meshptr && !meshptr->empty()) {
-
+
+ InteriorPtr interior =
+ generate_interior_verbose(mesh, ctl, hc.min_thickness, voxel_scale,
+ hc.closing_distance);
+
+ if (interior && !interior->mesh.empty()) {
+
// This flips the normals to be outward facing...
- meshptr->require_shared_vertices();
- indexed_triangle_set its = std::move(meshptr->its);
-
+ interior->mesh.require_shared_vertices();
+ indexed_triangle_set its = std::move(interior->mesh.its);
+
Slic3r::simplify_mesh(its);
-
+
// flip normals back...
for (stl_triangle_vertex_indices &ind : its.indices)
std::swap(ind(0), ind(2));
-
- *meshptr = Slic3r::TriangleMesh{its};
+
+ interior->mesh = Slic3r::TriangleMesh{its};
+ interior->mesh.repaired = true;
+ interior->mesh.require_shared_vertices();
}
-
- return meshptr;
+
+ return interior;
}
Contour3D DrainHole::to_mesh() const
@@ -273,12 +311,264 @@ void cut_drainholes(std::vector & obj_slices,
obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]);
}
-void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg)
+void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags)
{
- std::unique_ptr inter_ptr =
- Slic3r::sla::generate_interior(mesh);
+ InteriorPtr interior = generate_interior(mesh, cfg, JobController{});
+ if (!interior) return;
- if (inter_ptr) mesh.merge(*inter_ptr);
+ hollow_mesh(mesh, *interior, flags);
+}
+
+void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags)
+{
+ if (mesh.empty() || interior.mesh.empty()) return;
+
+ if (flags & hfRemoveInsideTriangles && interior.gridptr)
+ remove_inside_triangles(mesh, interior);
+
+ mesh.merge(interior.mesh);
+ mesh.require_shared_vertices();
+}
+
+// Get the distance of p to the interior's zero iso_surface. Interior should
+// have its zero isosurface positioned at offset + closing_distance inwards form
+// the model surface.
+static double get_distance_raw(const Vec3f &p, const Interior &interior)
+{
+ assert(interior.gridptr);
+
+ if (!interior.accessor) interior.reset_accessor();
+
+ auto v = (p * interior.voxel_scale).cast();
+ auto grididx = interior.gridptr->transform().worldToIndexCellCentered(
+ {v.x(), v.y(), v.z()});
+
+ return interior.accessor->getValue(grididx) ;
+}
+
+struct TriangleBubble { Vec3f center; double R; };
+
+// Return the distance of bubble center to the interior boundary or NaN if the
+// triangle is too big to be measured.
+static double get_distance(const TriangleBubble &b, const Interior &interior)
+{
+ double R = b.R * interior.voxel_scale;
+ double D = get_distance_raw(b.center, interior);
+
+ return (D > 0. && R >= interior.nb_out) ||
+ (D < 0. && R >= interior.nb_in) ||
+ ((D - R) < 0. && 2 * R > interior.thickness) ?
+ std::nan("") :
+ // FIXME: Adding interior.voxel_scale is a compromise supposed
+ // to prevent the deletion of the triangles forming the interior
+ // itself. This has a side effect that a small portion of the
+ // bad triangles will still be visible.
+ D - interior.closing_distance /*+ 2 * interior.voxel_scale*/;
+}
+
+double get_distance(const Vec3f &p, const Interior &interior)
+{
+ double d = get_distance_raw(p, interior) - interior.closing_distance;
+ return d / interior.voxel_scale;
+}
+
+// A face that can be divided. Stores the indices into the original mesh if its
+// part of that mesh and the vertices it consists of.
+enum { NEW_FACE = -1};
+struct DivFace {
+ Vec3i indx;
+ std::array verts;
+ long faceid = NEW_FACE;
+ long parent = NEW_FACE;
+};
+
+// Divide a face recursively and call visitor on all the sub-faces.
+template
+void divide_triangle(const DivFace &face, Fn &&visitor)
+{
+ std::array edges = {(face.verts[0] - face.verts[1]),
+ (face.verts[1] - face.verts[2]),
+ (face.verts[2] - face.verts[0])};
+
+ std::array edgeidx = {0, 1, 2};
+
+ std::sort(edgeidx.begin(), edgeidx.end(), [&edges](size_t e1, size_t e2) {
+ return edges[e1].squaredNorm() > edges[e2].squaredNorm();
+ });
+
+ DivFace child1, child2;
+
+ child1.parent = face.faceid == NEW_FACE ? face.parent : face.faceid;
+ child1.indx(0) = -1;
+ child1.indx(1) = face.indx(edgeidx[1]);
+ child1.indx(2) = face.indx((edgeidx[1] + 1) % 3);
+ child1.verts[0] = (face.verts[edgeidx[0]] + face.verts[(edgeidx[0] + 1) % 3]) / 2.;
+ child1.verts[1] = face.verts[edgeidx[1]];
+ child1.verts[2] = face.verts[(edgeidx[1] + 1) % 3];
+
+ if (visitor(child1))
+ divide_triangle(child1, std::forward(visitor));
+
+ child2.parent = face.faceid == NEW_FACE ? face.parent : face.faceid;
+ child2.indx(0) = -1;
+ child2.indx(1) = face.indx(edgeidx[2]);
+ child2.indx(2) = face.indx((edgeidx[2] + 1) % 3);
+ child2.verts[0] = child1.verts[0];
+ child2.verts[1] = face.verts[edgeidx[2]];
+ child2.verts[2] = face.verts[(edgeidx[2] + 1) % 3];
+
+ if (visitor(child2))
+ divide_triangle(child2, std::forward(visitor));
+}
+
+void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
+ const std::vector &exclude_mask)
+{
+ enum TrPos { posInside, posTouch, posOutside };
+
+ auto &faces = mesh.its.indices;
+ auto &vertices = mesh.its.vertices;
+ auto bb = mesh.bounding_box();
+
+ bool use_exclude_mask = faces.size() == exclude_mask.size();
+ auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) {
+ return use_exclude_mask && exclude_mask[face_id];
+ };
+
+ // TODO: Parallel mode not working yet
+ using exec_policy = ccr_seq;
+
+ // Info about the needed modifications on the input mesh.
+ struct MeshMods {
+
+ // Just a thread safe wrapper for a vector of triangles.
+ struct {
+ std::vector> data;
+ exec_policy::SpinningMutex mutex;
+
+ void emplace_back(const std::array &pts)
+ {
+ std::lock_guard lk{mutex};
+ data.emplace_back(pts);
+ }
+
+ size_t size() const { return data.size(); }
+ const std::array& operator[](size_t idx) const
+ {
+ return data[idx];
+ }
+
+ } new_triangles;
+
+ // A vector of bool for all faces signaling if it needs to be removed
+ // or not.
+ std::vector to_remove;
+
+ MeshMods(const TriangleMesh &mesh):
+ to_remove(mesh.its.indices.size(), false) {}
+
+ // Number of triangles that need to be removed.
+ size_t to_remove_cnt() const
+ {
+ return std::accumulate(to_remove.begin(), to_remove.end(), size_t(0));
+ }
+
+ } mesh_mods{mesh};
+
+ // Must return true if further division of the face is needed.
+ auto divfn = [&interior, bb, &mesh_mods](const DivFace &f) {
+ BoundingBoxf3 facebb { f.verts.begin(), f.verts.end() };
+
+ // Face is certainly outside the cavity
+ if (! facebb.intersects(bb) && f.faceid != NEW_FACE) {
+ return false;
+ }
+
+ TriangleBubble bubble{facebb.center().cast(), facebb.radius()};
+
+ double D = get_distance(bubble, interior);
+ double R = bubble.R * interior.voxel_scale;
+
+ if (std::isnan(D)) // The distance cannot be measured, triangle too big
+ return true;
+
+ // Distance of the bubble wall to the interior wall. Negative if the
+ // bubble is overlapping with the interior
+ double bubble_distance = D - R;
+
+ // The face is crossing the interior or inside, it must be removed and
+ // parts of it re-added, that are outside the interior
+ if (bubble_distance < 0.) {
+ if (f.faceid != NEW_FACE)
+ mesh_mods.to_remove[f.faceid] = true;
+
+ if (f.parent != NEW_FACE) // Top parent needs to be removed as well
+ mesh_mods.to_remove[f.parent] = true;
+
+ // If the outside part is between the interior end the exterior
+ // (inside the wall being invisible), no further division is needed.
+ if ((R + D) < interior.thickness)
+ return false;
+
+ return true;
+ } else if (f.faceid == NEW_FACE) {
+ // New face completely outside needs to be re-added.
+ mesh_mods.new_triangles.emplace_back(f.verts);
+ }
+
+ return false;
+ };
+
+ interior.reset_accessor();
+
+ exec_policy::for_each(size_t(0), faces.size(), [&] (size_t face_idx) {
+ const Vec3i &face = faces[face_idx];
+
+ // If the triangle is excluded, we need to keep it.
+ if (is_excluded(face_idx))
+ return;
+
+ std::array pts =
+ { vertices[face(0)], vertices[face(1)], vertices[face(2)] };
+
+ BoundingBoxf3 facebb { pts.begin(), pts.end() };
+
+ // Face is certainly outside the cavity
+ if (! facebb.intersects(bb)) return;
+
+ DivFace df{face, pts, long(face_idx)};
+
+ if (divfn(df))
+ divide_triangle(df, divfn);
+
+ }, exec_policy::max_concurreny());
+
+ auto new_faces = reserve_vector(faces.size() +
+ mesh_mods.new_triangles.size());
+
+ for (size_t face_idx = 0; face_idx < faces.size(); ++face_idx) {
+ if (!mesh_mods.to_remove[face_idx])
+ new_faces.emplace_back(faces[face_idx]);
+ }
+
+ for(size_t i = 0; i < mesh_mods.new_triangles.size(); ++i) {
+ size_t o = vertices.size();
+ vertices.emplace_back(mesh_mods.new_triangles[i][0]);
+ vertices.emplace_back(mesh_mods.new_triangles[i][1]);
+ vertices.emplace_back(mesh_mods.new_triangles[i][2]);
+ new_faces.emplace_back(int(o), int(o + 1), int(o + 2));
+ }
+
+ BOOST_LOG_TRIVIAL(info)
+ << "Trimming: " << mesh_mods.to_remove_cnt() << " triangles removed";
+ BOOST_LOG_TRIVIAL(info)
+ << "Trimming: " << mesh_mods.new_triangles.size() << " triangles added";
+
+ faces.swap(new_faces);
+ new_faces = {};
+
+ mesh = TriangleMesh{mesh.its};
+ mesh.repaired = true;
mesh.require_shared_vertices();
}
diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp
index 949cc2393..5d2181e7a 100644
--- a/src/libslic3r/SLA/Hollowing.hpp
+++ b/src/libslic3r/SLA/Hollowing.hpp
@@ -19,6 +19,17 @@ struct HollowingConfig
bool enabled = true;
};
+enum HollowingFlags { hfRemoveInsideTriangles = 0x1 };
+
+// All data related to a generated mesh interior. Includes the 3D grid and mesh
+// and various metadata. No need to manipulate from outside.
+struct Interior;
+struct InteriorDeleter { void operator()(Interior *p); };
+using InteriorPtr = std::unique_ptr;
+
+TriangleMesh & get_mesh(Interior &interior);
+const TriangleMesh &get_mesh(const Interior &interior);
+
struct DrainHole
{
Vec3f pos;
@@ -60,11 +71,26 @@ using DrainHoles = std::vector;
constexpr float HoleStickOutLength = 1.f;
-std::unique_ptr generate_interior(const TriangleMesh &mesh,
- const HollowingConfig & = {},
- const JobController &ctl = {});
+InteriorPtr generate_interior(const TriangleMesh &mesh,
+ const HollowingConfig & = {},
+ const JobController &ctl = {});
-void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg);
+// Will do the hollowing
+void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0);
+
+// Hollowing prepared in "interior", merge with original mesh
+void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0);
+
+void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
+ const std::vector &exclude_mask = {});
+
+double get_distance(const Vec3f &p, const Interior &interior);
+
+template
+FloatingOnly get_distance(const Vec<3, T> &p, const Interior &interior)
+{
+ return get_distance(Vec3f(p.template cast()), interior);
+}
void cut_drainholes(std::vector & obj_slices,
const std::vector &slicegrid,
diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp
index 16b068cb9..42ed8b80f 100644
--- a/src/libslic3r/SLAPrint.cpp
+++ b/src/libslic3r/SLAPrint.cpp
@@ -1120,7 +1120,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
return this->pad_mesh();
case slaposDrillHoles:
if (m_hollowing_data)
- return m_hollowing_data->hollow_mesh_with_holes;
+ return get_mesh_to_print();
[[fallthrough]];
default:
return TriangleMesh();
@@ -1149,8 +1149,9 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const
const TriangleMesh &SLAPrintObject::hollowed_interior_mesh() const
{
- if (m_hollowing_data && m_config.hollowing_enable.getBool())
- return m_hollowing_data->interior;
+ if (m_hollowing_data && m_hollowing_data->interior &&
+ m_config.hollowing_enable.getBool())
+ return sla::get_mesh(*m_hollowing_data->interior);
return EMPTY_MESH;
}
diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp
index bed66ab4f..74c71dc1e 100644
--- a/src/libslic3r/SLAPrint.hpp
+++ b/src/libslic3r/SLAPrint.hpp
@@ -85,6 +85,10 @@ public:
// Get the mesh that is going to be printed with all the modifications
// like hollowing and drilled holes.
const TriangleMesh & get_mesh_to_print() const {
+ return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes_trimmed : transformed_mesh();
+ }
+
+ const TriangleMesh & get_mesh_to_slice() const {
return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh();
}
@@ -327,9 +331,10 @@ private:
class HollowingData
{
public:
-
- TriangleMesh interior;
+
+ sla::InteriorPtr interior;
mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh
+ mutable TriangleMesh hollow_mesh_with_holes_trimmed;
};
std::unique_ptr m_hollowing_data;
diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp
index d8bea62ae..22fee6976 100644
--- a/src/libslic3r/SLAPrintSteps.cpp
+++ b/src/libslic3r/SLAPrintSteps.cpp
@@ -1,3 +1,5 @@
+#include
+
#include
#include
#include
@@ -84,17 +86,17 @@ SLAPrint::Steps::Steps(SLAPrint *print)
void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin o)
{
if (o == soSupport && !po.m_supportdata) return;
-
+
auto faded_lyrs = size_t(po.m_config.faded_layers.getInt());
double min_w = m_print->m_printer_config.elefant_foot_min_width.getFloat() / 2.;
double start_efc = m_print->m_printer_config.elefant_foot_compensation.getFloat();
-
+
double doffs = m_print->m_printer_config.absolute_correction.getFloat();
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_efc](size_t pos) {
return (faded_lyrs_efc - pos) * start_efc / faded_lyrs_efc;
};
@@ -102,13 +104,13 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin
std::vector &slices = o == soModel ?
po.m_model_slices :
po.m_supportdata->support_slices;
-
+
if (clpr_offs != 0) for (size_t i = 0; i < po.m_slice_index.size(); ++i) {
size_t idx = po.m_slice_index[i].get_slice_idx(o);
if (idx < slices.size())
slices[idx] = offset_ex(slices[idx], float(clpr_offs));
}
-
+
if (start_efc > 0.) for (size_t i = 0; i < faded_lyrs; ++i) {
size_t idx = po.m_slice_index[i].get_slice_idx(o);
if (idx < slices.size())
@@ -124,28 +126,157 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
return;
}
-
+
BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
double thickness = po.m_config.hollowing_min_thickness.getFloat();
double quality = po.m_config.hollowing_quality.getFloat();
double closing_d = po.m_config.hollowing_closing_distance.getFloat();
sla::HollowingConfig hlwcfg{thickness, quality, closing_d};
- auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg);
- if (meshptr->empty())
+ sla::InteriorPtr interior = generate_interior(po.transformed_mesh(), hlwcfg);
+
+ if (!interior || sla::get_mesh(*interior).empty())
BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
else {
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
- po.m_hollowing_data->interior = *meshptr;
+ po.m_hollowing_data->interior = std::move(interior);
}
}
+struct FaceHash {
+
+ // A hash is created for each triangle to be identifiable. The hash uses
+ // only the triangle's geometric traits, not the index in a particular mesh.
+ std::unordered_set facehash;
+
+ static std::string facekey(const Vec3i &face,
+ const std::vector &vertices)
+ {
+ // Scale to integer to avoid floating points
+ std::array, 3> pts = {
+ scaled(vertices[face(0)]),
+ scaled(vertices[face(1)]),
+ scaled(vertices[face(2)])
+ };
+
+ // Get the first two sides of the triangle, do a cross product and move
+ // that vector to the center of the triangle. This encodes all
+ // information to identify an identical triangle at the same position.
+ Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2];
+ Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3;
+
+ // Return a concatenated string representation of the coordinates
+ return std::to_string(c(0)) + std::to_string(c(1)) + std::to_string(c(2));
+ };
+
+ FaceHash(const indexed_triangle_set &its)
+ {
+ for (const Vec3i &face : its.indices) {
+ std::string keystr = facekey(face, its.vertices);
+ facehash.insert(keystr);
+ }
+ }
+
+ bool find(const std::string &key)
+ {
+ auto it = facehash.find(key);
+ return it != facehash.end();
+ }
+};
+
+// Create exclude mask for triangle removal inside hollowed interiors.
+// This is necessary when the interior is already part of the mesh which was
+// drilled using CGAL mesh boolean operation. Excluded will be the triangles
+// originally part of the interior mesh and triangles that make up the drilled
+// hole walls.
+static std::vector create_exclude_mask(
+ const indexed_triangle_set &its,
+ const sla::Interior &interior,
+ const std::vector &holes)
+{
+ FaceHash interior_hash{sla::get_mesh(interior).its};
+
+ std::vector exclude_mask(its.indices.size(), false);
+
+ std::vector< std::vector > neighbor_index =
+ create_neighbor_index(its);
+
+ auto exclude_neighbors = [&neighbor_index, &exclude_mask](const Vec3i &face)
+ {
+ for (int i = 0; i < 3; ++i) {
+ const std::vector &neighbors = neighbor_index[face(i)];
+ for (size_t fi_n : neighbors) exclude_mask[fi_n] = true;
+ }
+ };
+
+ for (size_t fi = 0; fi < its.indices.size(); ++fi) {
+ auto &face = its.indices[fi];
+
+ if (interior_hash.find(FaceHash::facekey(face, its.vertices))) {
+ exclude_mask[fi] = true;
+ continue;
+ }
+
+ if (exclude_mask[fi]) {
+ exclude_neighbors(face);
+ continue;
+ }
+
+ // Lets deal with the holes. All the triangles of a hole and all the
+ // neighbors of these triangles need to be kept. The neigbors were
+ // created by CGAL mesh boolean operation that modified the original
+ // interior inside the input mesh to contain the holes.
+ Vec3d tr_center = (
+ its.vertices[face(0)] +
+ its.vertices[face(1)] +
+ its.vertices[face(2)]
+ ).cast() / 3.;
+
+ // If the center is more than half a mm inside the interior,
+ // it cannot possibly be part of a hole wall.
+ if (sla::get_distance(tr_center, interior) < -0.5)
+ continue;
+
+ Vec3f U = its.vertices[face(1)] - its.vertices[face(0)];
+ Vec3f V = its.vertices[face(2)] - its.vertices[face(0)];
+ Vec3f C = U.cross(V);
+ Vec3f face_normal = C.normalized();
+
+ for (const sla::DrainHole &dh : holes) {
+ Vec3d dhpos = dh.pos.cast();
+ Vec3d dhend = dhpos + dh.normal.cast() * dh.height;
+
+ Linef3 holeaxis{dhpos, dhend};
+
+ double D_hole_center = line_alg::distance_to(holeaxis, tr_center);
+ double D_hole = std::abs(D_hole_center - dh.radius);
+ float dot = dh.normal.dot(face_normal);
+
+ // Empiric tolerances for center distance and normals angle.
+ // For triangles that are part of a hole wall the angle of
+ // triangle normal and the hole axis is around 90 degrees,
+ // so the dot product is around zero.
+ double D_tol = dh.radius / sla::DrainHole::steps;
+ float normal_angle_tol = 1.f / sla::DrainHole::steps;
+
+ if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) {
+ exclude_mask[fi] = true;
+ exclude_neighbors(face);
+ }
+ }
+ }
+
+ return exclude_mask;
+}
+
// Drill holes into the hollowed/original mesh.
void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
{
bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty();
- bool is_hollowed = (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty());
+ bool is_hollowed =
+ (po.m_hollowing_data && po.m_hollowing_data->interior &&
+ !sla::get_mesh(*po.m_hollowing_data->interior).empty());
if (! is_hollowed && ! needs_drilling) {
// In this case we can dump any data that might have been
@@ -163,19 +294,25 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
// holes that are no longer on the frontend.
TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
hollowed_mesh = po.transformed_mesh();
- if (! po.m_hollowing_data->interior.empty()) {
- hollowed_mesh.merge(po.m_hollowing_data->interior);
- hollowed_mesh.require_shared_vertices();
- }
+ if (is_hollowed)
+ sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior);
+
+ TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed;
if (! needs_drilling) {
+ mesh_view = po.transformed_mesh();
+
+ if (is_hollowed)
+ sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior,
+ sla::hfRemoveInsideTriangles);
+
BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes).";
return;
}
-
+
BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes.";
sla::DrainHoles drainholes = po.transformed_drainhole_points();
-
+
std::uniform_real_distribution dist(0., float(EPSILON));
auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({});
for (sla::DrainHole holept : drainholes) {
@@ -187,15 +324,25 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
auto cgal_m = MeshBoolean::cgal::triangle_mesh_to_cgal(m);
MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_m);
}
-
+
if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal))
throw Slic3r::SlicingError(L("Too many overlapping holes."));
-
+
auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh);
-
+
try {
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal);
+ mesh_view = hollowed_mesh;
+
+ if (is_hollowed) {
+ auto &interior = *po.m_hollowing_data->interior;
+ std::vector exclude_mask =
+ create_exclude_mask(mesh_view.its, interior, drainholes);
+
+ sla::remove_inside_triangles(mesh_view, interior, exclude_mask);
+ }
+
} catch (const std::runtime_error &) {
throw Slic3r::SlicingError(L(
"Drilling holes into the mesh failed. "
@@ -212,11 +359,11 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
// of it. In any case, the model and the supports have to be sliced in the
// same imaginary grid (the height vector argument to TriangleMeshSlicer).
void SLAPrint::Steps::slice_model(SLAPrintObject &po)
-{
- const TriangleMesh &mesh = po.get_mesh_to_print();
+{
+ const TriangleMesh &mesh = po.get_mesh_to_slice();
// We need to prepare the slice index...
-
+
double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat();
float lh = float(lhd);
coord_t lhs = scaled(lhd);
@@ -226,43 +373,49 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
auto minZf = float(minZ);
coord_t minZs = scaled(minZ);
coord_t maxZs = scaled(maxZ);
-
+
po.m_slice_index.clear();
-
+
size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs);
po.m_slice_index.reserve(cap);
-
+
po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh);
-
+
for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs)
po.m_slice_index.emplace_back(h, unscaled(h) - lh / 2.f, lh);
-
+
// Just get the first record that is from the model:
auto slindex_it =
po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z)));
-
+
if(slindex_it == po.m_slice_index.end())
//TRN To be shown at the status bar on SLA slicing error.
throw Slic3r::RuntimeError(
L("Slicing had to be stopped due to an internal error: "
"Inconsistent slice index."));
-
+
po.m_model_height_levels.clear();
po.m_model_height_levels.reserve(po.m_slice_index.size());
for(auto it = slindex_it; it != po.m_slice_index.end(); ++it)
po.m_model_height_levels.emplace_back(it->slice_level());
-
+
TriangleMeshSlicer slicer(&mesh);
-
+
po.m_model_slices.clear();
float closing_r = float(po.config().slice_closing_radius.value);
auto thr = [this]() { m_print->throw_if_canceled(); };
auto &slice_grid = po.m_model_height_levels;
slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr);
-
- if (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()) {
- po.m_hollowing_data->interior.repair(true);
- TriangleMeshSlicer interior_slicer(&po.m_hollowing_data->interior);
+
+ sla::Interior *interior = po.m_hollowing_data ?
+ po.m_hollowing_data->interior.get() :
+ nullptr;
+
+ if (interior && ! sla::get_mesh(*interior).empty()) {
+ TriangleMesh interiormesh = sla::get_mesh(*interior);
+ interiormesh.repaired = false;
+ interiormesh.repair(true);
+ TriangleMeshSlicer interior_slicer(&interiormesh);
std::vector interior_slices;
interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr);
@@ -273,17 +426,17 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
diff_ex(po.m_model_slices[i], slice);
});
}
-
+
auto mit = slindex_it;
for (size_t id = 0;
id < po.m_model_slices.size() && mit != po.m_slice_index.end();
id++) {
mit->set_model_slice_idx(po, id); ++mit;
}
-
+
// We apply the printer correction offset here.
apply_printer_corrections(po, soModel);
-
+
if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool())
{
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
@@ -296,22 +449,22 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
{
// If supports are disabled, we can skip the model scan.
if(!po.m_config.supports_enable.getBool()) return;
-
- const TriangleMesh &mesh = po.get_mesh_to_print();
-
+
+ const TriangleMesh &mesh = po.get_mesh_to_slice();
+
if (!po.m_supportdata)
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
-
+
const ModelObject& mo = *po.m_model_object;
-
+
BOOST_LOG_TRIVIAL(debug) << "Support point count "
<< mo.sla_support_points.size();
-
+
// Unless the user modified the points or we already did the calculation,
// we will do the autoplacement. Otherwise we will just blindly copy the
// frontend data into the backend cache.
if (mo.sla_points_status != sla::PointsStatus::UserModified) {
-
+
// calculate heights of slices (slices are calculated already)
const std::vector& heights = po.m_model_height_levels;
@@ -319,27 +472,27 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
// calculated on slices, the algorithm then raycasts the points
// so they actually lie on the mesh.
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
-
+
throw_if_canceled();
sla::SupportPointGenerator::Config config;
const SLAPrintObjectConfig& cfg = po.config();
-
+
// the density config value is in percents:
config.density_relative = float(cfg.support_points_density_relative / 100.f);
config.minimal_distance = float(cfg.support_points_minimal_distance);
config.head_diameter = float(cfg.support_head_front_diameter);
-
+
// scaling for the sub operations
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0;
double init = current_status();
-
+
auto statuscb = [this, d, init](unsigned st)
{
double current = init + st * d;
if(std::round(current_status()) < std::round(current))
report_status(current, OBJ_STEP_LABELS(slaposSupportPoints));
};
-
+
// Construction of this object does the calculation.
throw_if_canceled();
sla::SupportPointGenerator auto_supports(
@@ -350,10 +503,10 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
const std::vector& points = auto_supports.output();
throw_if_canceled();
po.m_supportdata->pts = points;
-
+
BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
<< po.m_supportdata->pts.size();
-
+
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
// the update status to GLGizmoSlaSupports
report_status(-1, L("Generating support points"),
@@ -368,9 +521,9 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
void SLAPrint::Steps::support_tree(SLAPrintObject &po)
{
if(!po.m_supportdata) return;
-
+
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
-
+
if (pcfg.embed_object)
po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
@@ -380,15 +533,15 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
remove_bottom_points(po.m_supportdata->pts,
float(po.m_supportdata->emesh.ground_level() + EPSILON));
}
-
+
po.m_supportdata->cfg = make_support_cfg(po.m_config);
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
-
+
// scaling for the sub operations
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
double init = current_status();
sla::JobController ctl;
-
+
ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) {
double current = init + st * d;
if (std::round(current_status()) < std::round(current))
@@ -397,26 +550,26 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
};
ctl.stopcondition = [this]() { return canceled(); };
ctl.cancelfn = [this]() { throw_if_canceled(); };
-
+
po.m_supportdata->create_support_tree(ctl);
-
+
if (!po.m_config.supports_enable.getBool()) return;
-
+
throw_if_canceled();
-
+
// Create the unified mesh
auto rc = SlicingStatus::RELOAD_SCENE;
-
+
// This is to prevent "Done." being displayed during merged_mesh()
report_status(-1, L("Visualizing supports"));
-
+
BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
<< po.m_supportdata->pts.size();
-
+
// Check the mesh for later troubleshooting.
if(po.support_mesh().empty())
BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty";
-
+
report_status(-1, L("Visualizing supports"), rc);
}
@@ -424,15 +577,15 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
// this step can only go after the support tree has been created
// and before the supports had been sliced. (or the slicing has to be
// repeated)
-
+
if(po.m_config.pad_enable.getBool()) {
// Get the distilled pad configuration from the config
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
-
+
ExPolygons bp; // This will store the base plate of the pad.
double pad_h = pcfg.full_height();
const TriangleMesh &trmesh = po.transformed_mesh();
-
+
if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) {
// No support (thus no elevation) or zero elevation mode
// we sometimes call it "builtin pad" is enabled so we will
@@ -442,19 +595,19 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
float(po.m_config.layer_height.getFloat()),
[this](){ throw_if_canceled(); });
}
-
+
po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad);
-
+
if (!validate_pad(pad_mesh, pcfg))
throw Slic3r::SlicingError(
L("No pad can be generated for this model with the "
"current configuration"));
-
+
} else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) {
po.m_supportdata->support_tree_ptr->remove_pad();
}
-
+
throw_if_canceled();
report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE);
}
@@ -464,25 +617,25 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
// be part of the slices)
void SLAPrint::Steps::slice_supports(SLAPrintObject &po) {
auto& sd = po.m_supportdata;
-
+
if(sd) sd->support_slices.clear();
-
+
// Don't bother if no supports and no pad is present.
if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool())
return;
-
+
if(sd && sd->support_tree_ptr) {
auto heights = reserve_vector(po.m_slice_index.size());
-
+
for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level());
sd->support_slices = sd->support_tree_ptr->slice(
heights, float(po.config().slice_closing_radius.value));
}
-
- for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i)
+
+ for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i)
po.m_slice_index[i].set_support_slice_idx(po, i);
-
+
apply_printer_corrections(po, soSupport);
// Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
@@ -497,37 +650,37 @@ using ClipperPolygons = std::vector;
static ClipperPolygons polyunion(const ClipperPolygons &subjects)
{
ClipperLib::Clipper clipper;
-
+
bool closed = true;
-
+
for(auto& path : subjects) {
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
}
-
+
auto mode = ClipperLib::pftPositive;
-
+
return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode);
}
static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips)
{
ClipperLib::Clipper clipper;
-
+
bool closed = true;
-
+
for(auto& path : subjects) {
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
}
-
+
for(auto& path : clips) {
clipper.AddPath(path.Contour, ClipperLib::ptClip, closed);
clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed);
}
-
+
auto mode = ClipperLib::pftPositive;
-
+
return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode);
}
@@ -535,28 +688,28 @@ static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPo
static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o)
{
namespace sl = libnest2d::sl;
-
+
if (!record.print_obj()) return {};
-
+
ClipperPolygons polygons;
auto &input_polygons = record.get_slice(o);
auto &instances = record.print_obj()->instances();
bool is_lefthanded = record.print_obj()->is_left_handed();
polygons.reserve(input_polygons.size() * instances.size());
-
+
for (const ExPolygon& polygon : input_polygons) {
if(polygon.contour.empty()) continue;
-
+
for (size_t i = 0; i < instances.size(); ++i)
{
ClipperPolygon poly;
-
+
// We need to reverse if is_lefthanded is true but
bool needreverse = is_lefthanded;
-
+
// should be a move
poly.Contour.reserve(polygon.contour.size() + 1);
-
+
auto& cntr = polygon.contour.points;
if(needreverse)
for(auto it = cntr.rbegin(); it != cntr.rend(); ++it)
@@ -564,12 +717,12 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o
else
for(auto& p : cntr)
poly.Contour.emplace_back(p.x(), p.y());
-
+
for(auto& h : polygon.holes) {
poly.Holes.emplace_back();
auto& hole = poly.Holes.back();
hole.reserve(h.points.size() + 1);
-
+
if(needreverse)
for(auto it = h.points.rbegin(); it != h.points.rend(); ++it)
hole.emplace_back(it->x(), it->y());
@@ -577,42 +730,42 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o
for(auto& p : h.points)
hole.emplace_back(p.x(), p.y());
}
-
+
if(is_lefthanded) {
for(auto& p : poly.Contour) p.X = -p.X;
for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X;
}
-
+
sl::rotate(poly, double(instances[i].rotation));
sl::translate(poly, ClipperPoint{instances[i].shift.x(),
instances[i].shift.y()});
-
+
polygons.emplace_back(std::move(poly));
}
}
-
+
return polygons;
}
void SLAPrint::Steps::initialize_printer_input()
{
auto &printer_input = m_print->m_printer_input;
-
+
// clear the rasterizer input
printer_input.clear();
-
+
size_t mx = 0;
for(SLAPrintObject * o : m_print->m_objects) {
if(auto m = o->get_slice_index().size() > mx) mx = m;
}
-
+
printer_input.reserve(mx);
-
+
auto eps = coord_t(SCALED_EPSILON);
-
+
for(SLAPrintObject * o : m_print->m_objects) {
coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
-
+
for(const SliceRecord& slicerecord : o->get_slice_index()) {
if (!slicerecord.is_valid())
throw Slic3r::SlicingError(
@@ -621,7 +774,7 @@ void SLAPrint::Steps::initialize_printer_input()
"objects printable."));
coord_t lvlid = slicerecord.print_level() - gndlvl;
-
+
// Neat trick to round the layer levels to the grid.
lvlid = eps * (lvlid / eps);
@@ -631,8 +784,8 @@ void SLAPrint::Steps::initialize_printer_input()
if(it == printer_input.end() || it->level() != lvlid)
it = printer_input.insert(it, PrintLayer(lvlid));
-
-
+
+
it->add(slicerecord);
}
}
@@ -641,53 +794,53 @@ void SLAPrint::Steps::initialize_printer_input()
// Merging the slices from all the print objects into one slice grid and
// calculating print statistics from the merge result.
void SLAPrint::Steps::merge_slices_and_eval_stats() {
-
+
initialize_printer_input();
-
+
auto &print_statistics = m_print->m_print_statistics;
auto &printer_config = m_print->m_printer_config;
auto &material_config = m_print->m_material_config;
auto &printer_input = m_print->m_printer_input;
-
+
print_statistics.clear();
-
+
// libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); };
-
+
const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0;
const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0;
-
+
const double init_exp_time = material_config.initial_exposure_time.getFloat();
const double exp_time = material_config.exposure_time.getFloat();
-
+
const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20]
-
+
const auto width = scaled(printer_config.display_width.getFloat());
const auto height = scaled(printer_config.display_height.getFloat());
const double display_area = width*height;
-
+
double supports_volume(0.0);
double models_volume(0.0);
-
+
double estim_time(0.0);
std::vector layers_times;
layers_times.reserve(printer_input.size());
-
+
size_t slow_layers = 0;
size_t fast_layers = 0;
-
+
const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
double fade_layer_time = init_exp_time;
-
+
sla::ccr::SpinningMutex mutex;
using Lock = std::lock_guard;
-
+
// Going to parallel:
auto printlayerfn = [this,
// functions and read only vars
areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time,
-
+
// write vars
&mutex, &models_volume, &supports_volume, &estim_time, &slow_layers,
&fast_layers, &fade_layer_time, &layers_times](size_t sliced_layer_cnt)
@@ -696,87 +849,87 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
// vector of slice record references
auto& slicerecord_references = layer.slices();
-
+
if(slicerecord_references.empty()) return;
-
+
// Layer height should match for all object slices for a given level.
const auto l_height = double(slicerecord_references.front().get().layer_height());
-
+
// Calculation of the consumed material
-
+
ClipperPolygons model_polygons;
ClipperPolygons supports_polygons;
-
+
size_t c = std::accumulate(layer.slices().begin(),
layer.slices().end(),
size_t(0),
[](size_t a, const SliceRecord &sr) {
return a + sr.get_slice(soModel).size();
});
-
+
model_polygons.reserve(c);
-
+
c = std::accumulate(layer.slices().begin(),
layer.slices().end(),
size_t(0),
[](size_t a, const SliceRecord &sr) {
return a + sr.get_slice(soModel).size();
});
-
+
supports_polygons.reserve(c);
-
+
for(const SliceRecord& record : layer.slices()) {
-
+
ClipperPolygons modelslices = get_all_polygons(record, soModel);
for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp));
-
+
ClipperPolygons supportslices = get_all_polygons(record, soSupport);
for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp));
-
+
}
-
+
model_polygons = polyunion(model_polygons);
double layer_model_area = 0;
for (const ClipperPolygon& polygon : model_polygons)
layer_model_area += areafn(polygon);
-
+
if (layer_model_area < 0 || layer_model_area > 0) {
Lock lck(mutex); models_volume += layer_model_area * l_height;
}
-
+
if(!supports_polygons.empty()) {
if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons);
else supports_polygons = polydiff(supports_polygons, model_polygons);
// allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
}
-
+
double layer_support_area = 0;
for (const ClipperPolygon& polygon : supports_polygons)
layer_support_area += areafn(polygon);
-
+
if (layer_support_area < 0 || layer_support_area > 0) {
Lock lck(mutex); supports_volume += layer_support_area * l_height;
}
-
+
// Here we can save the expensively calculated polygons for printing
ClipperPolygons trslices;
trslices.reserve(model_polygons.size() + supports_polygons.size());
for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly));
for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly));
-
+
layer.transformed_slices(polyunion(trslices));
-
+
// Calculation of the slow and fast layers to the future controlling those values on FW
-
+
const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill;
const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt;
-
+
{ Lock lck(mutex);
if (is_fast_layer)
fast_layers++;
else
slow_layers++;
-
+
// Calculation of the printing time
double layer_times = 0.0;
@@ -794,15 +947,15 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
estim_time += layer_times;
}
};
-
+
// sequential version for debugging:
// for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
sla::ccr::for_each(size_t(0), printer_input.size(), printlayerfn);
-
+
auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR;
print_statistics.support_used_material = supports_volume * SCALING2;
print_statistics.objects_used_material = models_volume * SCALING2;
-
+
// Estimated printing time
// A layers count o the highest object
if (printer_input.size() == 0)
@@ -811,10 +964,10 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
print_statistics.estimated_print_time = estim_time;
print_statistics.layers_times = layers_times;
}
-
+
print_statistics.fast_layers_count = fast_layers;
print_statistics.slow_layers_count = slow_layers;
-
+
report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
}
@@ -822,23 +975,23 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
void SLAPrint::Steps::rasterize()
{
if(canceled() || !m_print->m_printer) return;
-
+
// coefficient to map the rasterization state (0-99) to the allocated
// portion (slot) of the process state
double sd = (100 - max_objstatus) / 100.0;
-
+
// slot is the portion of 100% that is realted to rasterization
unsigned slot = PRINT_STEP_LEVELS[slapsRasterize];
-
+
// pst: previous state
double pst = current_status();
-
+
double increment = (slot * sd) / m_print->m_printer_input.size();
double dstatus = current_status();
-
+
sla::ccr::SpinningMutex slck;
using Lock = std::lock_guard;
-
+
// procedure to process one height level. This will run in parallel
auto lvlfn =
[this, &slck, increment, &dstatus, &pst]
@@ -846,10 +999,10 @@ void SLAPrint::Steps::rasterize()
{
PrintLayer& printlayer = m_print->m_printer_input[idx];
if(canceled()) return;
-
+
for (const ClipperLib::Polygon& poly : printlayer.transformed_slices())
raster.draw(poly);
-
+
// Status indication guarded with the spinlock
{
Lock lck(slck);
@@ -861,10 +1014,10 @@ void SLAPrint::Steps::rasterize()
}
}
};
-
+
// last minute escape
if(canceled()) return;
-
+
// Print all the layers in parallel
m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn);
}
diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp
index 083b41202..d0b1e9ce2 100644
--- a/src/libslic3r/Slicing.cpp
+++ b/src/libslic3r/Slicing.cpp
@@ -112,8 +112,10 @@ SlicingParameters SlicingParameters::create_from_config(
if (! soluble_interface) {
params.gap_raft_object = object_config.raft_contact_distance.value;
- params.gap_object_support = object_config.support_material_contact_distance.value;
+ params.gap_object_support = object_config.support_material_bottom_contact_distance.value;
params.gap_support_object = object_config.support_material_contact_distance.value;
+ if (params.gap_object_support <= 0)
+ params.gap_object_support = params.gap_support_object;
}
if (params.base_raft_layers > 0) {
diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp
index 8b3d5c917..489b2768f 100644
--- a/src/libslic3r/Slicing.hpp
+++ b/src/libslic3r/Slicing.hpp
@@ -26,7 +26,7 @@ class DynamicPrintConfig;
// (using a normal flow over a soluble support, using a bridging flow over a non-soluble support).
struct SlicingParameters
{
- SlicingParameters() { memset(this, 0, sizeof(SlicingParameters)); }
+ SlicingParameters() = default;
static SlicingParameters create_from_config(
const PrintConfig &print_config,
@@ -44,58 +44,58 @@ struct SlicingParameters
// Height of the object to be printed. This value does not contain the raft height.
coordf_t object_print_z_height() const { return object_print_z_max - object_print_z_min; }
- bool valid;
+ bool valid { false };
// Number of raft layers.
- size_t base_raft_layers;
+ size_t base_raft_layers { 0 };
// Number of interface layers including the contact layer.
- size_t interface_raft_layers;
+ size_t interface_raft_layers { 0 };
// Layer heights of the raft (base, interface and a contact layer).
- coordf_t base_raft_layer_height;
- coordf_t interface_raft_layer_height;
- coordf_t contact_raft_layer_height;
+ coordf_t base_raft_layer_height { 0 };
+ coordf_t interface_raft_layer_height { 0 };
+ coordf_t contact_raft_layer_height { 0 };
// The regular layer height, applied for all but the first layer, if not overridden by layer ranges
// or by the variable layer thickness table.
- coordf_t layer_height;
+ coordf_t layer_height { 0 };
// Minimum / maximum layer height, to be used for the automatic adaptive layer height algorithm,
// or by an interactive layer height editor.
- coordf_t min_layer_height;
- coordf_t max_layer_height;
- coordf_t max_suport_layer_height;
+ coordf_t min_layer_height { 0 };
+ coordf_t max_layer_height { 0 };
+ coordf_t max_suport_layer_height { 0 };
// First layer height of the print, this may be used for the first layer of the raft
// or for the first layer of the print.
- coordf_t first_print_layer_height;
+ coordf_t first_print_layer_height { 0 };
// Thickness of the first layer. This is either the first print layer thickness if printed without a raft,
// or a bridging flow thickness if printed over a non-soluble raft,
// or a normal layer height if printed over a soluble raft.
- coordf_t first_object_layer_height;
+ coordf_t first_object_layer_height { 0 };
// If the object is printed over a non-soluble raft, the first layer may be printed with a briding flow.
- bool first_object_layer_bridging;
+ bool first_object_layer_bridging { false };
// Soluble interface? (PLA soluble in water, HIPS soluble in lemonen)
// otherwise the interface must be broken off.
- bool soluble_interface;
+ bool soluble_interface { false };
// Gap when placing object over raft.
- coordf_t gap_raft_object;
+ coordf_t gap_raft_object { 0 };
// Gap when placing support over object.
- coordf_t gap_object_support;
+ coordf_t gap_object_support { 0 };
// Gap when placing object over support.
- coordf_t gap_support_object;
+ coordf_t gap_support_object { 0 };
// Bottom and top of the printed object.
// If printed without a raft, object_print_z_min = 0 and object_print_z_max = object height.
// Otherwise object_print_z_min is equal to the raft height.
- coordf_t raft_base_top_z;
- coordf_t raft_interface_top_z;
- coordf_t raft_contact_top_z;
+ coordf_t raft_base_top_z { 0 };
+ coordf_t raft_interface_top_z { 0 };
+ coordf_t raft_contact_top_z { 0 };
// In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer.
- coordf_t object_print_z_min;
- coordf_t object_print_z_max;
+ coordf_t object_print_z_min { 0 };
+ coordf_t object_print_z_max { 0 };
};
static_assert(IsTriviallyCopyable::value, "SlicingParameters class is not POD (and it should be - see constructor).");
diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp
index 459a15603..9caac5769 100644
--- a/src/libslic3r/SupportMaterial.cpp
+++ b/src/libslic3r/SupportMaterial.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
@@ -334,7 +335,7 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
for (auto lh : m_print_config->min_layer_height.values)
m_support_layer_height_min = std::min(m_support_layer_height_min, std::max(0.01, lh));
- if (m_object_config->support_material_interface_layers.value == 0) {
+ if (m_slicing_params.soluble_interface) {
// No interface layers allowed, print everything with the base support pattern.
m_support_material_interface_flow = m_support_material_flow;
}
@@ -342,11 +343,21 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
// Evaluate the XY gap between the object outer perimeters and the support structures.
// Evaluate the XY gap between the object outer perimeters and the support structures.
coordf_t external_perimeter_width = 0.;
+ size_t num_nonempty_regions = 0;
+ coordf_t bridge_flow_ratio = 0;
for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id)
- if (! object->region_volumes[region_id].empty())
- external_perimeter_width = std::max(external_perimeter_width,
- (coordf_t)object->print()->get_region(region_id)->flow(frExternalPerimeter, slicing_params.layer_height, false, false, -1, *object).width);
+ if (! object->region_volumes[region_id].empty()) {
+ ++ num_nonempty_regions;
+ const PrintRegion ®ion = *object->print()->get_region(region_id);
+ external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width()));
+ bridge_flow_ratio += region.config().bridge_flow_ratio;
+ }
m_gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width);
+ bridge_flow_ratio /= num_nonempty_regions;
+
+ m_support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ?
+ m_support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) :
+ Flow::bridging_flow(bridge_flow_ratio * m_support_material_interface_flow.nozzle_diameter(), m_support_material_interface_flow.nozzle_diameter());
m_can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value;
if (! m_can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) {
@@ -387,14 +398,6 @@ inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const Pr
dst.insert(dst.end(), src.begin(), src.end());
}
-// Compare layers lexicographically.
-struct MyLayersPtrCompare
-{
- bool operator()(const PrintObjectSupportMaterial::MyLayer* layer1, const PrintObjectSupportMaterial::MyLayer* layer2) const {
- return *layer1 < *layer2;
- }
-};
-
void PrintObjectSupportMaterial::generate(PrintObject &object)
{
BOOST_LOG_TRIVIAL(info) << "Support generator - Start";
@@ -457,10 +460,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers(
object, bottom_contacts, top_contacts, layer_storage);
-// this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy);
- this->trim_support_layers_by_object(object, top_contacts,
- m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
- m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
+ this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy);
#ifdef SLIC3R_DEBUG
for (const MyLayer *layer : top_contacts)
@@ -542,7 +542,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
layers_append(layers_sorted, interface_layers);
layers_append(layers_sorted, base_interface_layers);
// Sort the layers lexicographically by a raising print_z and a decreasing height.
- std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare());
+ std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; });
int layer_id = 0;
assert(object.support_layers().empty());
for (size_t i = 0; i < layers_sorted.size();) {
@@ -1231,8 +1231,8 @@ namespace SupportMaterialInternal {
// since we're dealing with bridges, we can't assume width is larger than spacing,
// so we take the largest value and also apply safety offset to be ensure no gaps
// are left in between
- Flow bridge_flow = layerm->flow(frPerimeter, true);
- float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing()));
+ Flow perimeter_bridge_flow = layerm->bridging_flow(frPerimeter);
+ float w = float(std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing()));
for (Polyline &polyline : overhang_perimeters)
if (polyline.is_straight()) {
// This is a bridge
@@ -1542,16 +1542,20 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
}
}
// Offset the contact polygons outside.
+#if 0
for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) {
diff_polygons = diff(
offset(
diff_polygons,
- SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS,
+ scaled(SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS),
ClipperLib::jtRound,
// round mitter limit
scale_(0.05)),
slices_margin_cached);
}
+#else
+ diff_polygons = diff(diff_polygons, slices_margin_cached);
+#endif
}
polygons_append(contact_polygons, diff_polygons);
} // for each layer.region
@@ -1571,11 +1575,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
} else if (m_slicing_params.soluble_interface) {
// Align the contact surface height with a layer immediately below the supported layer.
// Interface layer will be synchronized with the object.
- new_layer.print_z = layer.print_z - layer.height;
+ new_layer.print_z = layer.bottom_z();
new_layer.height = object.layers()[layer_id - 1]->height;
new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z;
} else {
- new_layer.print_z = layer.print_z - layer.height - m_object_config->support_material_contact_distance;
+ new_layer.print_z = layer.bottom_z() - m_slicing_params.gap_object_support;
new_layer.bottom_z = new_layer.print_z;
new_layer.height = 0.;
// Ignore this contact area if it's too low.
@@ -1597,12 +1601,12 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// Contact layer will be printed with a normal flow, but
// it will support layers printed with a bridging flow.
- if (SupportMaterialInternal::has_bridging_extrusions(layer)) {
+ if (m_object_config->thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer)) {
coordf_t bridging_height = 0.;
for (const LayerRegion *region : layer.regions())
bridging_height += region->region()->bridging_height_avg(*m_print_config);
bridging_height /= coordf_t(layer.regions().size());
- coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance;
+ coordf_t bridging_print_z = layer.print_z - bridging_height - m_slicing_params.gap_support_object;
if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) {
// Not below the first layer height means this layer is printable.
if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) {
@@ -1875,16 +1879,13 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
layer_new.height = m_slicing_params.soluble_interface ?
// Align the interface layer with the object's layer height.
object.layers()[layer_id + 1]->height :
- // Place a bridge flow interface layer over the top surface.
- //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?)
- // According to Jindrich the bottom surfaces work well.
- //FIXME test the bridging flow instead?
- m_support_material_interface_flow.nozzle_diameter;
+ // Place a bridge flow interface layer or the normal flow interface layer over the top surface.
+ m_support_material_bottom_interface_flow.height();
layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z :
- layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value;
+ layer.print_z + layer_new.height + m_slicing_params.gap_object_support;
layer_new.bottom_z = layer.print_z;
layer_new.idx_object_layer_below = layer_id;
- layer_new.bridging = ! m_slicing_params.soluble_interface;
+ layer_new.bridging = ! m_slicing_params.soluble_interface && m_object_config->thick_bridges;
//FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
//FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks.
layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
@@ -2028,11 +2029,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
task_group.wait();
}
std::reverse(bottom_contacts.begin(), bottom_contacts.end());
-// trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy);
- trim_support_layers_by_object(object, bottom_contacts,
- m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
- m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
-
+ trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy);
} // ! top_contacts.empty()
return bottom_contacts;
@@ -2462,10 +2459,7 @@ void PrintObjectSupportMaterial::generate_base_layers(
++ iRun;
#endif /* SLIC3R_DEBUG */
-// trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy);
- this->trim_support_layers_by_object(object, intermediate_layers,
- m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
- m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
+ this->trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy);
}
void PrintObjectSupportMaterial::trim_support_layers_by_object(
@@ -2499,7 +2493,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
// BOOST_LOG_TRIVIAL(trace) << "Support generator - trim_support_layers_by_object - trimmming non-empty layer " << idx_layer << " of " << nonempty_layers.size();
assert(! support_layer.polygons.empty() && support_layer.print_z >= m_slicing_params.raft_contact_top_z + EPSILON);
// Find the overlapping object layers including the extra above / below gap.
- coordf_t z_threshold = support_layer.print_z - support_layer.height - gap_extra_below + EPSILON;
+ coordf_t z_threshold = support_layer.bottom_print_z() - gap_extra_below + EPSILON;
idx_object_layer_overlapping = idx_higher_or_equal(
object.layers().begin(), object.layers().end(), idx_object_layer_overlapping,
[z_threshold](const Layer *layer){ return layer->print_z >= z_threshold; });
@@ -2508,11 +2502,11 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
size_t i = idx_object_layer_overlapping;
for (; i < object.layers().size(); ++ i) {
const Layer &object_layer = *object.layers()[i];
- if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON)
+ if (object_layer.bottom_z() > support_layer.print_z + gap_extra_above - EPSILON)
break;
polygons_append(polygons_trimming, offset(object_layer.lslices, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
}
- if (! m_slicing_params.soluble_interface) {
+ if (! m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
// Collect all bottom surfaces, which will be extruded with a bridging flow.
for (; i < object.layers().size(); ++ i) {
const Layer &object_layer = *object.layers()[i];
@@ -2526,6 +2520,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)),
gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
if (region->region()->config().overhangs.value)
+ // Add bridging perimeters.
SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming);
}
if (! some_region_overlaps)
@@ -2709,17 +2704,23 @@ std::pairsupport_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) &&
// Base extruder: Either "print with active extruder" not soluble.
(m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1));
- int num_interface_layers = m_object_config->support_material_interface_layers.value;
- int num_base_interface_layers = soluble_interface_non_soluble_base ? std::min(num_interface_layers / 2, 2) : 0;
+ int num_interface_layers_top = m_object_config->support_material_interface_layers;
+ int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers;
+ if (num_interface_layers_bottom < 0)
+ num_interface_layers_bottom = num_interface_layers_top;
+ int num_base_interface_layers_top = soluble_interface_non_soluble_base ? std::min(num_interface_layers_top / 2, 2) : 0;
+ int num_base_interface_layers_bottom = soluble_interface_non_soluble_base ? std::min(num_interface_layers_bottom / 2, 2) : 0;
- if (! intermediate_layers.empty() && num_interface_layers > 1) {
+ if (! intermediate_layers.empty() && (num_interface_layers_top > 1 || num_interface_layers_bottom > 1)) {
// For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers.
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start";
// Since the intermediate layer index starts at zero the number of interface layer needs to be reduced by 1.
- -- num_interface_layers;
- int num_interface_layers_only = num_interface_layers - num_base_interface_layers;
+ -- num_interface_layers_top;
+ -- num_interface_layers_bottom;
+ int num_interface_layers_only_top = num_interface_layers_top - num_base_interface_layers_top;
+ int num_interface_layers_only_bottom = num_interface_layers_bottom - num_base_interface_layers_bottom;
interface_layers.assign(intermediate_layers.size(), nullptr);
- if (num_base_interface_layers)
+ if (num_base_interface_layers_top || num_base_interface_layers_bottom)
base_interface_layers.assign(intermediate_layers.size(), nullptr);
tbb::spin_mutex layer_storage_mutex;
// Insert a new layer into base_interface_layers, if intersection with base exists.
@@ -2743,7 +2744,8 @@ std::pair(0, int(intermediate_layers.size())),
- [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, num_interface_layers, num_base_interface_layers, num_interface_layers_only,
+ [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer,
+ num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom,
&interface_layers, &base_interface_layers](const tbb::blocked_range& range) {
// Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below
// this intermediate layer.
@@ -2754,45 +2756,51 @@ std::pairprint_z;
- coordf_t top_inteface_z = std::numeric_limits::max();
- coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers + 1)]->bottom_z;
- coordf_t bottom_interface_z = - std::numeric_limits::max();
- if (num_base_interface_layers > 0) {
- // Some base interface layers will be generated.
- if (num_interface_layers_only == 0)
- // Only base interface layers to generate.
- std::swap(top_inteface_z, bottom_interface_z);
- else {
- top_inteface_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only - 1)]->print_z;
- bottom_interface_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only)]->bottom_z;
- }
- }
- // Move idx_top_contact_first up until above the current print_z.
- idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
- // Collect the top contact areas above this intermediate layer, below top_z.
Polygons polygons_top_contact_projected_interface;
Polygons polygons_top_contact_projected_base;
- for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) {
- const MyLayer &top_contact_layer = *top_contacts[idx_top_contact];
- //FIXME maybe this adds one interface layer in excess?
- if (top_contact_layer.bottom_z - EPSILON > top_z)
- break;
- polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons);
- }
- // Move idx_bottom_contact_first up until touching bottom_z.
- idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; });
- // Collect the top contact areas above this intermediate layer, below top_z.
Polygons polygons_bottom_contact_projected_interface;
Polygons polygons_bottom_contact_projected_base;
- for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) {
- const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact];
- if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z)
- break;
- polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons);
+ if (num_interface_layers_top > 0) {
+ // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
+ coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_top - 1)]->print_z;
+ coordf_t top_inteface_z = std::numeric_limits::max();
+ if (num_base_interface_layers_top > 0)
+ // Some top base interface layers will be generated.
+ top_inteface_z = num_interface_layers_only_top == 0 ?
+ // Only base interface layers to generate.
+ - std::numeric_limits::max() :
+ intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only_top - 1)]->print_z;
+ // Move idx_top_contact_first up until above the current print_z.
+ idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
+ // Collect the top contact areas above this intermediate layer, below top_z.
+ for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) {
+ const MyLayer &top_contact_layer = *top_contacts[idx_top_contact];
+ //FIXME maybe this adds one interface layer in excess?
+ if (top_contact_layer.bottom_z - EPSILON > top_z)
+ break;
+ polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons);
+ }
+ }
+ if (num_interface_layers_bottom > 0) {
+ // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
+ coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_bottom + 1)]->bottom_z;
+ coordf_t bottom_interface_z = - std::numeric_limits::max();
+ if (num_base_interface_layers_bottom > 0)
+ // Some bottom base interface layers will be generated.
+ bottom_interface_z = num_interface_layers_only_bottom == 0 ?
+ // Only base interface layers to generate.
+ std::numeric_limits::max() :
+ intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only_bottom)]->bottom_z;
+ // Move idx_bottom_contact_first up until touching bottom_z.
+ idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; });
+ // Collect the top contact areas above this intermediate layer, below top_z.
+ for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) {
+ const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact];
+ if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z)
+ break;
+ polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons);
+ }
}
-
MyLayer *interface_layer = nullptr;
if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty()) {
interface_layer = insert_layer(
@@ -2810,7 +2818,7 @@ std::pair();
- eec->no_sort = true;
+ std::unique_ptr eec;
+ if (no_sort) {
+ eec = std::make_unique();
+ eec->no_sort = true;
+ }
+ ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst;
// Draw the perimeters.
Polylines polylines;
polylines.reserve(expoly.holes.size() + 1);
@@ -2903,10 +2916,11 @@ static inline void fill_expolygons_with_sheath_generate_paths(
pl.clip_end(clip_length);
polylines.emplace_back(std::move(pl));
}
- extrusion_entities_append_paths(eec->entities, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height);
+ extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height());
// Fill in the rest.
- fill_expolygons_generate_paths(eec->entities, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
- dst.emplace_back(eec.release());
+ fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
+ if (no_sort)
+ dst.emplace_back(eec.release());
}
}
@@ -3011,8 +3025,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
if (n_contact_loops == 0 || top_contact_layer.empty())
return;
- Flow flow = interface_flow_src;
- flow.height = float(top_contact_layer.layer->height);
+ Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height);
Polygons overhang_polygons;
if (top_contact_layer.layer->overhang_polygons != nullptr)
@@ -3209,7 +3222,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
extrusion_entities_append_paths(
top_contact_layer.extrusions,
std::move(loop_lines),
- erSupportMaterialInterface, flow.mm3_per_mm(), flow.width, flow.height);
+ erSupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height());
}
#ifdef SLIC3R_DEBUG
@@ -3232,6 +3245,9 @@ static std::string dbg_index_to_color(int idx)
// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers,
// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers
// to stick too firmly to the object.
+//
+// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer
+// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z.
void modulate_extrusion_by_overlapping_layers(
// Extrusions generated for this_layer.
ExtrusionEntitiesPtr &extrusions_in_out,
@@ -3320,8 +3336,8 @@ void modulate_extrusion_by_overlapping_layers(
// Collect the paths of this_layer.
{
Polylines &polylines = path_fragments.back().polylines;
- for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) {
- ExtrusionPath *path = dynamic_cast(*it);
+ for (ExtrusionEntity *ee : extrusions_in_out) {
+ ExtrusionPath *path = dynamic_cast(ee);
assert(path != nullptr);
polylines.emplace_back(Polyline(std::move(path->polyline)));
path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back()));
@@ -3342,7 +3358,7 @@ void modulate_extrusion_by_overlapping_layers(
// Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter).
assert(this_layer.print_z > overlapping_layer.print_z);
frag.height = float(this_layer.print_z - overlapping_layer.print_z);
- frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f, false).mm3_per_mm();
+ frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm();
#ifdef SLIC3R_DEBUG
svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1));
#endif /* SLIC3R_DEBUG */
@@ -3481,7 +3497,6 @@ void PrintObjectSupportMaterial::generate_toolpaths(
const MyLayersPtr &interface_layers,
const MyLayersPtr &base_interface_layers) const
{
-// Slic3r::debugf "Generating patterns\n";
// loop_interface_processor with a given circle radius.
LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_material_interface_flow.scaled_width());
loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0;
@@ -3492,7 +3507,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing);
coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing();
coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing);
- if (m_object_config->support_material_interface_layers.value == 0) {
+ if (m_slicing_params.soluble_interface) {
// No interface layers allowed, print everything with the base support pattern.
interface_spacing = support_spacing;
interface_density = support_density;
@@ -3565,7 +3580,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
//FIXME misusing contact_polygons for support columns.
((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons);
if (! to_infill_polygons.empty()) {
- Flow flow(float(m_support_material_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging);
+ assert(! raft_layer.bridging);
+ Flow flow(float(m_support_material_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter());
Fill * filler = filler_support.get();
filler->angle = raft_angle_base;
filler->spacing = m_support_material_flow.spacing();
@@ -3579,7 +3595,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
filler, float(support_density),
// Extrusion parameters
erSupportMaterial, flow,
- with_sheath);
+ with_sheath, false);
}
}
@@ -3596,7 +3612,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// We don't use $base_flow->spacing because we need a constant spacing
// value that guarantees that all layers are correctly aligned.
filler->spacing = m_support_material_flow.spacing();
- flow = Flow(float(m_support_material_interface_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging);
+ assert(! raft_layer.bridging);
+ flow = Flow(float(m_support_material_interface_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter());
density = float(interface_density);
} else
continue;
@@ -3611,7 +3628,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// Extrusion parameters
(support_layer_id < m_slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow,
// sheath at first layer
- support_layer_id == 0);
+ support_layer_id == 0, support_layer_id == 0);
}
});
@@ -3621,12 +3638,20 @@ void PrintObjectSupportMaterial::generate_toolpaths(
std::vector overlapping;
};
struct LayerCache {
- MyLayerExtruded bottom_contact_layer;
- MyLayerExtruded top_contact_layer;
- MyLayerExtruded base_layer;
- MyLayerExtruded interface_layer;
- MyLayerExtruded base_interface_layer;
- std::vector overlaps;
+ MyLayerExtruded bottom_contact_layer;
+ MyLayerExtruded top_contact_layer;
+ MyLayerExtruded base_layer;
+ MyLayerExtruded interface_layer;
+ MyLayerExtruded base_interface_layer;
+ boost::container::static_vector nonempty;
+
+ void add_nonempty_and_sort() {
+ for (MyLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer })
+ if (! item->empty())
+ this->nonempty.emplace_back(item);
+ // Sort the layers with the same print_z coordinate by their heights, thickest first.
+ std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; });
+ }
};
std::vector layer_caches(support_layers.size(), LayerCache());
@@ -3700,10 +3725,6 @@ void PrintObjectSupportMaterial::generate_toolpaths(
base_layer.merge(std::move(top_contact_layer));
else if (base_layer.empty() && !top_contact_layer.empty() && !top_contact_layer.layer->bridging)
std::swap(base_layer, top_contact_layer);
- if (base_layer.could_merge(bottom_contact_layer))
- base_layer.merge(std::move(bottom_contact_layer));
- else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging)
- std::swap(base_layer, bottom_contact_layer);
}
} else {
loop_interface_processor.generate(top_contact_layer, m_support_material_interface_flow);
@@ -3713,6 +3734,12 @@ void PrintObjectSupportMaterial::generate_toolpaths(
if (top_contact_layer.could_merge(interface_layer))
top_contact_layer.merge(std::move(interface_layer));
}
+ if ((m_object_config->support_material_interface_layers == 0 || m_object_config->support_material_bottom_interface_layers == 0) && m_can_merge_support_regions) {
+ if (base_layer.could_merge(bottom_contact_layer))
+ base_layer.merge(std::move(bottom_contact_layer));
+ else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging)
+ std::swap(base_layer, bottom_contact_layer);
+ }
#if 0
if ( ! interface_layer.empty() && ! base_layer.empty()) {
@@ -3731,14 +3758,12 @@ void PrintObjectSupportMaterial::generate_toolpaths(
MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer);
if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty())
continue;
- bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0;
+ bool interface_as_base = (&layer_ex == &interface_layer) && m_slicing_params.soluble_interface;
//FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
- Flow interface_flow(
- float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)),
- float(layer_ex.layer->height),
- m_support_material_interface_flow.nozzle_diameter,
- layer_ex.layer->bridging);
+ auto interface_flow = layer_ex.layer->bridging ?
+ Flow::bridging_flow(layer_ex.layer->height, m_support_material_bottom_interface_flow.nozzle_diameter()) :
+ (interface_as_base ? &m_support_material_flow : &m_support_material_interface_flow)->with_height(float(layer_ex.layer->height));
filler_interface->angle = interface_as_base ?
// If zero interface layers are configured, use the same angle as for the base layers.
angles[support_layer_id % angles.size()] :
@@ -3762,11 +3787,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
Fill *filler = filler_base_interface.get();
//FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
- Flow interface_flow(
- float(base_interface_layer.layer->bridging ? base_interface_layer.layer->height : m_support_material_flow.width), // m_support_material_interface_flow.width)),
- float(base_interface_layer.layer->height),
- m_support_material_flow.nozzle_diameter,
- base_interface_layer.layer->bridging);
+ assert(! base_interface_layer.layer->bridging);
+ Flow interface_flow = m_support_material_flow.with_height(float(base_interface_layer.layer->height));
filler->angle = interface_angle;
filler->spacing = m_support_material_interface_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density));
@@ -3788,15 +3810,13 @@ void PrintObjectSupportMaterial::generate_toolpaths(
filler->angle = angles[support_layer_id % angles.size()];
// We don't use $base_flow->spacing because we need a constant spacing
// value that guarantees that all layers are correctly aligned.
- Flow flow(
- float(base_layer.layer->bridging ? base_layer.layer->height : m_support_material_flow.width),
- float(base_layer.layer->height),
- m_support_material_flow.nozzle_diameter,
- base_layer.layer->bridging);
+ assert(! base_layer.layer->bridging);
+ auto flow = m_support_material_flow.with_height(float(base_layer.layer->height));
filler->spacing = m_support_material_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density));
float density = float(support_density);
bool sheath = with_sheath;
+ bool no_sort = false;
if (base_layer.layer->bottom_z < EPSILON) {
// Base flange (the 1st layer).
filler = filler_first_layer;
@@ -3808,7 +3828,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
//FIXME When paralellizing, each thread shall have its own copy of the fillers.
filler->spacing = flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
- sheath = true;
+ sheath = true;
+ no_sort = true;
}
fill_expolygons_with_sheath_generate_paths(
// Destination
@@ -3819,7 +3840,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
filler, density,
// Extrusion parameters
erSupportMaterial, flow,
- sheath);
+ sheath, no_sort);
}
@@ -3828,24 +3849,13 @@ void PrintObjectSupportMaterial::generate_toolpaths(
base_layer.could_merge(base_interface_layer))
base_layer.merge(std::move(base_interface_layer));
- layer_cache.overlaps.reserve(5);
- if (! bottom_contact_layer.empty())
- layer_cache.overlaps.push_back(&bottom_contact_layer);
- if (! top_contact_layer.empty())
- layer_cache.overlaps.push_back(&top_contact_layer);
- if (! interface_layer.empty())
- layer_cache.overlaps.push_back(&interface_layer);
- if (! base_interface_layer.empty())
- layer_cache.overlaps.push_back(&base_interface_layer);
- if (! base_layer.empty())
- layer_cache.overlaps.push_back(&base_layer);
- // Sort the layers with the same print_z coordinate by their heights, thickest first.
- std::sort(layer_cache.overlaps.begin(), layer_cache.overlaps.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; });
+ layer_cache.add_nonempty_and_sort();
+
// Collect the support areas with this print_z into islands, as there is no need
// for retraction over these islands.
Polygons polys;
// Collect the extrusions, sorted by the bottom extrusion height.
- for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) {
+ for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) {
// Collect islands to polys.
layer_cache_item.layer_extruded->polygons_append(polys);
// The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free"
@@ -3859,32 +3869,22 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// Collect overlapping top/bottom surfaces.
layer_cache_item.overlapping.reserve(20);
coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON;
- for (int i = int(idx_layer_bottom_contact) - 1; i >= 0 && bottom_contacts[i]->print_z > bottom_z; -- i)
- layer_cache_item.overlapping.push_back(bottom_contacts[i]);
- for (int i = int(idx_layer_top_contact) - 1; i >= 0 && top_contacts[i]->print_z > bottom_z; -- i)
- layer_cache_item.overlapping.push_back(top_contacts[i]);
+ auto add_overlapping = [&layer_cache_item, bottom_z](const MyLayersPtr &layers, size_t idx_top) {
+ for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i)
+ layer_cache_item.overlapping.push_back(layers[i]);
+ };
+ add_overlapping(top_contacts, idx_layer_top_contact);
if (layer_cache_item.layer_extruded->layer->layer_type == sltBottomContact) {
// Bottom contact layer may overlap with a base layer, which may be changed to interface layer.
- for (int i = int(idx_layer_intermediate) - 1; i >= 0 && intermediate_layers[i]->print_z > bottom_z; -- i)
- layer_cache_item.overlapping.push_back(intermediate_layers[i]);
- for (int i = int(idx_layer_interface) - 1; i >= 0 && interface_layers[i]->print_z > bottom_z; -- i)
- layer_cache_item.overlapping.push_back(interface_layers[i]);
- for (int i = int(idx_layer_base_interface) - 1; i >= 0 && base_interface_layers[i]->print_z > bottom_z; -- i)
- layer_cache_item.overlapping.push_back(base_interface_layers[i]);
+ add_overlapping(intermediate_layers, idx_layer_intermediate);
+ add_overlapping(interface_layers, idx_layer_interface);
+ add_overlapping(base_interface_layers, idx_layer_base_interface);
}
- std::sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), MyLayersPtrCompare());
+ // Order the layers by lexicographically by an increasing print_z and a decreasing layer height.
+ std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; });
}
if (! polys.empty())
expolygons_append(support_layer.support_islands.expolygons, union_ex(polys));
- /* {
- require "Slic3r/SVG.pm";
- Slic3r::SVG::output("islands_" . $z . ".svg",
- red_expolygons => union_ex($contact),
- green_expolygons => union_ex($interface),
- green_polylines => [ map $_->unpack->polyline, @{$layer->support_contact_fills} ],
- polylines => [ map $_->unpack->polyline, @{$layer->support_fills} ],
- );
- } */
} // for each support_layer_id
});
@@ -3895,7 +3895,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) {
SupportLayer &support_layer = *support_layers[support_layer_id];
LayerCache &layer_cache = layer_caches[support_layer_id];
- for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) {
+ // For all extrusion types at this print_z, ordered by decreasing layer height:
+ for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) {
+ // Trim the extrusion height from the bottom by the overlapping layers.
modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping);
support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions));
}
diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp
index 7123290a6..da13768f6 100644
--- a/src/libslic3r/SupportMaterial.hpp
+++ b/src/libslic3r/SupportMaterial.hpp
@@ -244,6 +244,7 @@ private:
Flow m_first_layer_flow;
Flow m_support_material_flow;
Flow m_support_material_interface_flow;
+ Flow m_support_material_bottom_interface_flow;
// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
bool m_can_merge_support_regions;
diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp
index adb9be64d..d5a349087 100644
--- a/src/libslic3r/TriangleMesh.cpp
+++ b/src/libslic3r/TriangleMesh.cpp
@@ -2063,4 +2063,22 @@ TriangleMesh make_sphere(double radius, double fa)
return mesh;
}
+std::vector > create_neighbor_index(const indexed_triangle_set &its)
+{
+ if (its.vertices.empty()) return {};
+
+ size_t res = its.indices.size() / its.vertices.size();
+ std::vector< std::vector > index(its.vertices.size(),
+ reserve_vector(res));
+
+ for (size_t fi = 0; fi < its.indices.size(); ++fi) {
+ auto &face = its.indices[fi];
+ index[face(0)].emplace_back(fi);
+ index[face(1)].emplace_back(fi);
+ index[face(2)].emplace_back(fi);
+ }
+
+ return index;
+}
+
}
diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp
index 9625298f4..e6f6dc84b 100644
--- a/src/libslic3r/TriangleMesh.hpp
+++ b/src/libslic3r/TriangleMesh.hpp
@@ -89,6 +89,12 @@ private:
std::deque find_unvisited_neighbors(std::vector &facet_visited) const;
};
+// Create an index of faces belonging to each vertex. The returned vector can
+// be indexed with vertex indices and contains a list of face indices for each
+// vertex.
+std::vector< std::vector >
+create_neighbor_index(const indexed_triangle_set &its);
+
enum FacetEdgeType {
// A general case, the cutting plane intersect a face at two different edges.
feGeneral,
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 505c8f0f2..6436de2ca 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -93,6 +93,8 @@ set(SLIC3R_GUI_SOURCES
GUI/SavePresetDialog.cpp
GUI/PhysicalPrinterDialog.hpp
GUI/PhysicalPrinterDialog.cpp
+ GUI/GUI_Factories.cpp
+ GUI/GUI_Factories.hpp
GUI/GUI_ObjectList.cpp
GUI/GUI_ObjectList.hpp
GUI/GUI_ObjectManipulation.cpp
@@ -207,6 +209,8 @@ set(SLIC3R_GUI_SOURCES
Utils/Bonjour.hpp
Utils/PresetUpdater.cpp
Utils/PresetUpdater.hpp
+ Utils/Platform.cpp
+ Utils/Platform.hpp
Utils/Process.cpp
Utils/Process.hpp
Utils/Profile.hpp
diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp
index 4cab8f3db..6c226cd74 100644
--- a/src/slic3r/GUI/3DScene.cpp
+++ b/src/slic3r/GUI/3DScene.cpp
@@ -845,6 +845,57 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
return contained_min_one;
}
+bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut)
+{
+ if (config == nullptr)
+ return false;
+
+ const ConfigOptionPoints* opt = dynamic_cast(config->option("bed_shape"));
+ if (opt == nullptr)
+ return false;
+
+ BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
+ BoundingBoxf3 print_volume(Vec3d(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Vec3d(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config->opt_float("max_print_height")));
+ // Allow the objects to protrude below the print bed
+ print_volume.min(2) = -1e10;
+ print_volume.min(0) -= BedEpsilon;
+ print_volume.min(1) -= BedEpsilon;
+ print_volume.max(0) += BedEpsilon;
+ print_volume.max(1) += BedEpsilon;
+
+ bool contained_min_one = false;
+
+ partlyOut = false;
+ fullyOut = false;
+ for (GLVolume* volume : this->volumes)
+ {
+ if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled))
+ continue;
+
+ const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box();
+ bool contained = print_volume.contains(bb);
+
+ volume->is_outside = !contained;
+ if (!volume->printable)
+ continue;
+
+ if (contained)
+ contained_min_one = true;
+
+ if (volume->is_outside) {
+ if (print_volume.intersects(bb))
+ partlyOut = true;
+ else
+ fullyOut = true;
+ }
+ }
+ /*
+ if (out_state != nullptr)
+ *out_state = state;
+ */
+ return contained_min_one;
+}
+
void GLVolumeCollection::reset_outside_state()
{
for (GLVolume* volume : this->volumes)
diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp
index e4d9c6067..2ae2a36b2 100644
--- a/src/slic3r/GUI/3DScene.hpp
+++ b/src/slic3r/GUI/3DScene.hpp
@@ -569,6 +569,7 @@ public:
// returns true if all the volumes are completely contained in the print volume
// returns the containment state in the given out_state, if non-null
bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state);
+ bool check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut);
void reset_outside_state();
void update_colors_by_extruder(const DynamicPrintConfig* config);
diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp
index 01ec72837..6e0f13528 100644
--- a/src/slic3r/GUI/ConfigManipulation.cpp
+++ b/src/slic3r/GUI/ConfigManipulation.cpp
@@ -285,8 +285,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
"support_material_xy_spacing" })
toggle_field(el, have_support_material);
toggle_field("support_material_threshold", have_support_material_auto);
+ toggle_field("support_material_bottom_contact_distance", have_support_material && ! have_support_soluble);
- for (auto el : { "support_material_interface_spacing", "support_material_interface_extruder",
+ for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder",
"support_material_interface_speed", "support_material_interface_contact_loops" })
toggle_field(el, have_support_material && have_support_interface);
toggle_field("support_material_synchronize_layers", have_support_soluble);
diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp
index c12d36112..00b9c2e29 100644
--- a/src/slic3r/GUI/DoubleSlider.cpp
+++ b/src/slic3r/GUI/DoubleSlider.cpp
@@ -1317,7 +1317,12 @@ wxString Control::get_tooltip(int tick/*=-1*/)
"This code won't be processed during G-code generation.");
// Show custom Gcode as a first string of tooltop
- tooltip = " ";
+ std::string space = " ";
+ tooltip = space;
+ auto format_gcode = [space](std::string gcode) {
+ boost::replace_all(gcode, "\n", "\n" + space);
+ return gcode;
+ };
tooltip +=
tick_code_it->type == ColorChange ?
(m_mode == SingleExtruder ?
@@ -1329,7 +1334,7 @@ wxString Control::get_tooltip(int tick/*=-1*/)
format_wxstr(_L("Custom template (\"%1%\")"), gcode(Template)) :
tick_code_it->type == ToolChange ?
format_wxstr(_L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) :
- from_u8(tick_code_it->extra);// tick_code_it->type == Custom
+ from_u8(format_gcode(tick_code_it->extra));// tick_code_it->type == Custom
// If tick is marked as a conflict (exclamation icon),
// we should to explain why
@@ -1925,6 +1930,8 @@ void Control::auto_color_change()
double delta_area = scale_(scale_(25)); // equal to 25 mm2
for (auto object : print.objects()) {
+ if (object->layer_count() == 0)
+ continue;
double prev_area = area(object->get_layer(0)->lslices);
for (size_t i = 1; i < object->layers().size(); i++) {
diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp
index 7303f4d72..cbbe7f381 100644
--- a/src/slic3r/GUI/Field.cpp
+++ b/src/slic3r/GUI/Field.cpp
@@ -902,7 +902,7 @@ void Choice::BUILD() {
if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
choice_ctrl* temp;
- if (!m_opt.gui_type.empty() && m_opt.gui_type.compare("select_open") != 0) {
+ if (m_opt.gui_type != ConfigOptionDef::GUIType::undefined && m_opt.gui_type != ConfigOptionDef::GUIType::select_open) {
m_is_editable = true;
temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
}
@@ -965,15 +965,27 @@ void Choice::BUILD() {
}
if (is_defined_input_value(window, m_opt.type)) {
- if (m_opt.type == coFloatOrPercent) {
+ switch (m_opt.type) {
+ case coFloatOrPercent:
+ {
std::string old_val = !m_value.empty() ? boost::any_cast(m_value) : "";
if (old_val == boost::any_cast(get_value()))
return;
+ break;
}
- else {
- double old_val = !m_value.empty() ? boost::any_cast(m_value) : -99999;
- if (fabs(old_val - boost::any_cast(get_value())) <= 0.0001)
+ case coInt:
+ {
+ int old_val = !m_value.empty() ? boost::any_cast(m_value) : 0;
+ if (old_val == boost::any_cast(get_value()))
return;
+ break;
+ }
+ default:
+ {
+ double old_val = !m_value.empty() ? boost::any_cast(m_value) : -99999;
+ if (fabs(old_val - boost::any_cast(get_value())) <= 0.0001)
+ return;
+ }
}
on_change_field();
}
@@ -1225,7 +1237,7 @@ boost::any& Choice::get_value()
else if (m_opt_id == "brim_type")
m_value = static_cast(ret_enum);
}
- else if (m_opt.gui_type == "f_enum_open") {
+ else if (m_opt.gui_type == ConfigOptionDef::GUIType::f_enum_open || m_opt.gui_type == ConfigOptionDef::GUIType::i_enum_open) {
const int ret_enum = field->GetSelection();
if (ret_enum < 0 || m_opt.enum_values.empty() || m_opt.type == coStrings ||
(ret_str != m_opt.enum_values[ret_enum] && ret_str != _(m_opt.enum_labels[ret_enum])))
@@ -1233,6 +1245,8 @@ boost::any& Choice::get_value()
get_value_by_opt_type(ret_str);
else if (m_opt.type == coFloatOrPercent)
m_value = m_opt.enum_values[ret_enum];
+ else if (m_opt.type == coInt)
+ m_value = atoi(m_opt.enum_values[ret_enum].c_str());
else
m_value = atof(m_opt.enum_values[ret_enum].c_str());
}
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index a77581535..7bbdc72b1 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -641,6 +641,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
error = true;
break;
}
+ BOOST_LOG_TRIVIAL(error) << state << " : " << text ;
auto ¬ification_manager = *wxGetApp().plater()->get_notification_manager();
if (state) {
if(error)
@@ -1620,9 +1621,6 @@ void GLCanvas3D::render()
wxGetApp().plater()->init_environment_texture();
#endif // ENABLE_ENVIRONMENT_MAP
- m_render_timer.Stop();
- m_extra_frame_requested_delayed = std::numeric_limits::max();
-
const Size& cnv_size = get_canvas_size();
// Probably due to different order of events on Linux/GTK2, when one switched from 3D scene
// to preview, this was called before canvas had its final size. It reported zero width
@@ -1754,7 +1752,8 @@ void GLCanvas3D::render()
m_tooltip.render(m_mouse.position, *this);
wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this);
- wxGetApp().plater()->get_notification_manager()->render_notifications(get_overlay_window_width());
+
+ wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width());
wxGetApp().imgui()->render();
@@ -2238,24 +2237,24 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
// checks for geometry outside the print volume to render it accordingly
if (!m_volumes.empty()) {
- ModelInstanceEPrintVolumeState state;
-
- const bool contained_min_one = m_volumes.check_outside_state(m_config, &state);
+ bool partlyOut = false;
+ bool fullyOut = false;
+ const bool contained_min_one = m_volumes.check_outside_state(m_config, partlyOut, fullyOut);
#if ENABLE_WARNING_TEXTURE_REMOVAL
- _set_warning_notification(EWarning::ObjectClashed, state == ModelInstancePVS_Partly_Outside);
- _set_warning_notification(EWarning::ObjectOutside, state == ModelInstancePVS_Fully_Outside);
- if (printer_technology != ptSLA || state == ModelInstancePVS_Inside)
+ _set_warning_notification(EWarning::ObjectClashed, partlyOut);
+ _set_warning_notification(EWarning::ObjectOutside, fullyOut);
+ if (printer_technology != ptSLA || !contained_min_one)
_set_warning_notification(EWarning::SlaSupportsOutside, false);
#else
- _set_warning_texture(WarningTexture::ObjectClashed, state == ModelInstancePVS_Partly_Outside);
- _set_warning_texture(WarningTexture::ObjectOutside, state == ModelInstancePVS_Fully_Outside);
- if(printer_technology != ptSLA || state == ModelInstancePVS_Inside)
+ _set_warning_texture(WarningTexture::ObjectClashed, partlyOut);
+ _set_warning_texture(WarningTexture::ObjectOutside, fullyOut);
+ if(printer_technology != ptSLA || !contained_min_one)
_set_warning_texture(WarningTexture::SlaSupportsOutside, false);
#endif // ENABLE_WARNING_TEXTURE_REMOVAL
post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS,
- contained_min_one && !m_model->objects.empty() && state != ModelInstancePVS_Partly_Outside));
+ contained_min_one && !m_model->objects.empty() && !partlyOut));
}
else {
#if ENABLE_WARNING_TEXTURE_REMOVAL
@@ -2442,13 +2441,13 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
if (!m_initialized)
return;
- // FIXME
m_dirty |= m_main_toolbar.update_items_state();
m_dirty |= m_undoredo_toolbar.update_items_state();
m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state();
m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state();
bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera());
m_dirty |= mouse3d_controller_applied;
+ m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
if (!m_dirty)
return;
@@ -2986,30 +2985,39 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt)
void GLCanvas3D::on_render_timer(wxTimerEvent& evt)
{
- // If slicer is not top window -> restart timer with one second to try again
- wxWindow* p = dynamic_cast(wxGetApp().plater());
- while (p->GetParent() != nullptr)
- p = p->GetParent();
- wxTopLevelWindow* top_level_wnd = dynamic_cast(p);
- if (!top_level_wnd->IsActive()) {
- request_extra_frame_delayed(1000);
- return;
- }
- //render();
- m_dirty = true;
- wxWakeUpIdle();
+ // no need to do anything here
+ // right after this event is recieved, idle event is fired
+
+ //m_dirty = true;
+ //wxWakeUpIdle();
}
-void GLCanvas3D::request_extra_frame_delayed(int miliseconds)
+
+void GLCanvas3D::schedule_extra_frame(int miliseconds)
{
+ // Schedule idle event right now
+ if (miliseconds == 0)
+ {
+ // We want to wakeup idle evnt but most likely this is call inside render cycle so we need to wait
+ if (m_in_render)
+ miliseconds = 33;
+ else {
+ m_dirty = true;
+ wxWakeUpIdle();
+ return;
+ }
+ }
+ // Start timer
int64_t now = timestamp_now();
+ // Timer is not running
if (! m_render_timer.IsRunning()) {
m_extra_frame_requested_delayed = miliseconds;
m_render_timer.StartOnce(miliseconds);
m_render_timer_start = now;
+ // Timer is running - restart only if new period is shorter than remaning period
} else {
const int64_t remaining_time = (m_render_timer_start + m_extra_frame_requested_delayed) - now;
- if (miliseconds < remaining_time) {
+ if (miliseconds + 20 < remaining_time) {
m_render_timer.Stop();
m_extra_frame_requested_delayed = miliseconds;
m_render_timer.StartOnce(miliseconds);
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 10294931f..f4d862b66 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -743,7 +743,8 @@ public:
void msw_rescale();
void request_extra_frame() { m_extra_frame_requested = true; }
- void request_extra_frame_delayed(int miliseconds);
+
+ void schedule_extra_frame(int miliseconds);
int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); }
void force_main_toolbar_left_action(int item_id) { m_main_toolbar.force_left_action(item_id, *this); }
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 2fde30cd1..ecc5f46a7 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -1978,6 +1978,11 @@ wxNotebook* GUI_App::tab_panel() const
return mainframe->m_tabpanel;
}
+NotificationManager* GUI_App::notification_manager()
+{
+ return plater_->get_notification_manager();
+}
+
// extruders count from selected printer preset
int GUI_App::extruders_cnt() const
{
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index 5572c5071..f1ee0746a 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -43,6 +43,7 @@ class ObjectSettings;
class ObjectList;
class ObjectLayers;
class Plater;
+class NotificationManager;
struct GUI_InitParams;
@@ -226,14 +227,14 @@ public:
void MacOpenFiles(const wxArrayString &fileNames) override;
#endif /* __APPLE */
- Sidebar& sidebar();
- ObjectManipulation* obj_manipul();
- ObjectSettings* obj_settings();
- ObjectList* obj_list();
- ObjectLayers* obj_layers();
- Plater* plater();
- Model& model();
-
+ Sidebar& sidebar();
+ ObjectManipulation* obj_manipul();
+ ObjectSettings* obj_settings();
+ ObjectList* obj_list();
+ ObjectLayers* obj_layers();
+ Plater* plater();
+ Model& model();
+ NotificationManager* notification_manager();
// Parameters extracted from the command line to be passed to GUI after initialization.
const GUI_InitParams* init_params { nullptr };
diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp
new file mode 100644
index 000000000..d8518a6c8
--- /dev/null
+++ b/src/slic3r/GUI/GUI_Factories.cpp
@@ -0,0 +1,1027 @@
+#include "libslic3r/libslic3r.h"
+#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/Model.hpp"
+
+#include "GUI_Factories.hpp"
+#include "GUI_ObjectList.hpp"
+#include "GUI_App.hpp"
+#include "I18N.hpp"
+#include "Plater.hpp"
+#include "ObjectDataViewModel.hpp"
+
+#include "OptionsGroup.hpp"
+#include "GLCanvas3D.hpp"
+#include "Selection.hpp"
+#include "format.hpp"
+
+#include
+#include "slic3r/Utils/FixModelByWin10.hpp"
+
+namespace Slic3r
+{
+namespace GUI
+{
+
+static PrinterTechnology printer_technology()
+{
+ return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology();
+}
+
+static int extruders_count()
+{
+ return wxGetApp().extruders_edited_cnt();
+}
+
+static bool is_improper_category(const std::string& category, const int extruders_cnt, const bool is_object_settings = true)
+{
+ return category.empty() ||
+ (extruders_cnt == 1 && (category == "Extruders" || category == "Wipe options")) ||
+ (!is_object_settings && category == "Support material");
+}
+
+
+//-------------------------------------
+// SettingsFactory
+//-------------------------------------
+
+// pt_FFF
+static SettingsFactory::Bundle FREQ_SETTINGS_BUNDLE_FFF =
+{
+ { L("Layers and Perimeters"), { "layer_height" , "perimeters", "top_solid_layers", "bottom_solid_layers" } },
+ { L("Infill") , { "fill_density", "fill_pattern" } },
+ { L("Support material") , { "support_material", "support_material_auto", "support_material_threshold",
+ "support_material_pattern", "support_material_interface_pattern", "support_material_buildplate_only",
+ "support_material_spacing" } },
+ { L("Wipe options") , { "wipe_into_infill", "wipe_into_objects" } }
+};
+
+// pt_SLA
+static SettingsFactory::Bundle FREQ_SETTINGS_BUNDLE_SLA =
+{
+ { L("Pad and Support") , { "supports_enable", "pad_enable" } }
+};
+
+std::vector SettingsFactory::get_options(const bool is_part)
+{
+ if (printer_technology() == ptSLA) {
+ SLAPrintObjectConfig full_sla_config;
+ auto options = full_sla_config.keys();
+ options.erase(find(options.begin(), options.end(), "layer_height"));
+ return options;
+ }
+
+ PrintRegionConfig reg_config;
+ auto options = reg_config.keys();
+ if (!is_part) {
+ PrintObjectConfig obj_config;
+ std::vector obj_options = obj_config.keys();
+ options.insert(options.end(), obj_options.begin(), obj_options.end());
+ }
+ return options;
+}
+
+SettingsFactory::Bundle SettingsFactory::get_bundle(const DynamicPrintConfig* config, bool is_object_settings)
+{
+ auto opt_keys = config->keys();
+ if (opt_keys.empty())
+ return Bundle();
+
+ // update options list according to print technology
+ auto full_current_opts = get_options(!is_object_settings);
+ for (int i = opt_keys.size() - 1; i >= 0; --i)
+ if (find(full_current_opts.begin(), full_current_opts.end(), opt_keys[i]) == full_current_opts.end())
+ opt_keys.erase(opt_keys.begin() + i);
+
+ if (opt_keys.empty())
+ return Bundle();
+
+ const int extruders_cnt = wxGetApp().extruders_edited_cnt();
+
+ Bundle bundle;
+ for (auto& opt_key : opt_keys)
+ {
+ auto category = config->def()->get(opt_key)->category;
+ if (is_improper_category(category, extruders_cnt, is_object_settings))
+ continue;
+
+ std::vector< std::string > new_category;
+
+ auto& cat_opt = bundle.find(category) == bundle.end() ? new_category : bundle.at(category);
+ cat_opt.push_back(opt_key);
+ if (cat_opt.size() == 1)
+ bundle[category] = cat_opt;
+ }
+
+ return bundle;
+}
+
+// Fill CategoryItem
+std::map SettingsFactory::CATEGORY_ICON =
+{
+// settings category name related bitmap name
+ // ptFFF
+ { L("Layers and Perimeters"), "layers" },
+ { L("Infill") , "infill" },
+ { L("Ironing") , "ironing" },
+ { L("Fuzzy Skin") , "fuzzy_skin" },
+ { L("Support material") , "support" },
+ { L("Speed") , "time" },
+ { L("Extruders") , "funnel" },
+ { L("Extrusion Width") , "funnel" },
+ { L("Wipe options") , "funnel" },
+ { L("Skirt and brim") , "skirt+brim" },
+// { L("Speed > Acceleration") , "time" },
+ { L("Advanced") , "wrench" },
+ // ptSLA ,
+ { L("Supports") , "support" },
+ { L("Pad") , "pad" },
+ { L("Hollowing") , "hollowing" }
+};
+
+wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name)
+{
+ if (CATEGORY_ICON.find(category_name) == CATEGORY_ICON.end())
+ return wxNullBitmap;
+ return create_scaled_bitmap(CATEGORY_ICON.at(category_name));
+}
+
+
+//-------------------------------------
+// MenuFactory
+//-------------------------------------
+
+// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important
+std::vector> MenuFactory::ADD_VOLUME_MENU_ITEMS = {
+// menu_item Name menu_item bitmap name
+ {L("Add part"), "add_part" }, // ~ModelVolumeType::MODEL_PART
+ {L("Add modifier"), "add_modifier"}, // ~ModelVolumeType::PARAMETER_MODIFIER
+ {L("Add support enforcer"), "support_enforcer"}, // ~ModelVolumeType::SUPPORT_ENFORCER
+ {L("Add support blocker"), "support_blocker"} // ~ModelVolumeType::SUPPORT_BLOCKER
+};
+
+static Plater* plater()
+{
+ return wxGetApp().plater();
+}
+
+static ObjectList* obj_list()
+{
+ return wxGetApp().obj_list();
+}
+
+static ObjectDataViewModel* list_model()
+{
+ return wxGetApp().obj_list()->GetModel();
+}
+
+static const Selection& get_selection()
+{
+ return plater()->canvas3D()->get_selection();
+}
+
+// category -> vector ( option ; label )
+typedef std::map< std::string, std::vector< std::pair > > FullSettingsHierarchy;
+static void get_full_settings_hierarchy(FullSettingsHierarchy& settings_menu, const bool is_part)
+{
+ auto options = SettingsFactory::get_options(is_part);
+
+ const int extruders_cnt = extruders_count();
+
+ DynamicPrintConfig config;
+ for (auto& option : options)
+ {
+ auto const opt = config.def()->get(option);
+ auto category = opt->category;
+ if (is_improper_category(category, extruders_cnt, !is_part))
+ continue;
+
+ const std::string& label = !opt->full_label.empty() ? opt->full_label : opt->label;
+ std::pair option_label(option, label);
+ std::vector< std::pair > new_category;
+ auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category);
+ cat_opt_label.push_back(option_label);
+ if (cat_opt_label.size() == 1)
+ settings_menu[category] = cat_opt_label;
+ }
+}
+
+static wxMenu* create_settings_popupmenu(wxMenu* parent_menu, const bool is_object_settings, wxDataViewItem item/*, ModelConfig& config*/)
+{
+ wxMenu* menu = new wxMenu;
+
+ FullSettingsHierarchy categories;
+ get_full_settings_hierarchy(categories, !is_object_settings);
+
+ auto get_selected_options_for_category = [categories, item](const wxString& category_name) {
+ wxArrayString names;
+ wxArrayInt selections;
+
+ std::vector< std::pair > category_options;
+ for (auto& cat : categories) {
+ if (_(cat.first) == category_name) {
+ ModelConfig& config = obj_list()->get_item_config(item);
+ auto opt_keys = config.keys();
+
+ int sel = 0;
+ for (const std::pair& pair : cat.second) {
+ names.Add(_(pair.second));
+ if (find(opt_keys.begin(), opt_keys.end(), pair.first) != opt_keys.end())
+ selections.Add(sel);
+ sel++;
+ category_options.push_back(std::make_pair(pair.first, false));
+ }
+ break;
+ }
+ }
+
+ if (!category_options.empty() &&
+ wxGetSelectedChoices(selections, _L("Select showing settings"), category_name, names) != -1) {
+ for (auto sel : selections)
+ category_options[sel].second = true;
+ }
+ return category_options;
+
+#if 0
+ if (selections.size() > 0)
+ {
+ // Add selected items to the "Quick menu"
+ SettingsFactory::Bundle& freq_settings = printer_technology() == ptSLA ?
+ m_freq_settings_sla : m_freq_settings_fff;
+ bool changed_existing = false;
+
+ std::vector tmp_freq_cat = {};
+
+ for (auto& cat : freq_settings)
+ {
+ if (_(cat.first) == category_name)
+ {
+ std::vector& freq_settings_category = cat.second;
+ freq_settings_category.clear();
+ freq_settings_category.reserve(selection_cnt);
+ for (auto sel : selections)
+ freq_settings_category.push_back((*settings_list)[sel].first);
+
+ changed_existing = true;
+ break;
+ }
+ }
+
+ if (!changed_existing)
+ {
+ // Create new "Quick menu" item
+ for (auto& cat : settings_menu)
+ {
+ if (_(cat.first) == category_name)
+ {
+ freq_settings[cat.first] = std::vector{};
+
+ std::vector& freq_settings_category = freq_settings.find(cat.first)->second;
+ freq_settings_category.reserve(selection_cnt);
+ for (auto sel : selections)
+ freq_settings_category.push_back((*settings_list)[sel].first);
+ break;
+ }
+ }
+ }
+ }
+#endif
+ };
+
+ for (auto cat : categories) {
+ append_menu_item(menu, wxID_ANY, _(cat.first), "",
+ [menu, item, get_selected_options_for_category](wxCommandEvent& event) {
+ std::vector< std::pair > category_options = get_selected_options_for_category(menu->GetLabel(event.GetId()));
+ obj_list()->add_category_to_settings_from_selection(category_options, item);
+ }, SettingsFactory::get_category_bitmap(cat.first), parent_menu,
+ []() { return true; }, plater());
+ }
+
+ return menu;
+}
+
+static void create_freq_settings_popupmenu(wxMenu* menu, const bool is_object_settings, wxDataViewItem item)
+{
+ // Add default settings bundles
+ const SettingsFactory::Bundle& bundle = printer_technology() == ptFFF ? FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA;
+
+ const int extruders_cnt = extruders_count();
+
+ for (auto& category : bundle) {
+ if (is_improper_category(category.first, extruders_cnt, is_object_settings))
+ continue;
+
+ append_menu_item(menu, wxID_ANY, _(category.first), "",
+ [menu, item, is_object_settings, bundle](wxCommandEvent& event) {
+ wxString category_name = menu->GetLabel(event.GetId());
+ std::vector options;
+ for (auto& category : bundle)
+ if (category_name == _(category.first)) {
+ options = category.second;
+ break;
+ }
+ if (options.empty())
+ return;
+ // Because of we couldn't edited layer_height for ItVolume from settings list,
+ // correct options according to the selected item type : remove "layer_height" option
+ if (!is_object_settings && category_name == _("Layers and Perimeters")) {
+ const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height");
+ if (layer_height_it != options.end())
+ options.erase(layer_height_it);
+ }
+
+ obj_list()->add_category_to_settings_from_frequent(options, item);
+ },
+ SettingsFactory::get_category_bitmap(category.first), menu,
+ []() { return true; }, plater());
+ }
+#if 0
+ // Add "Quick" settings bundles
+ const SettingsFactory::Bundle& bundle_quick = printer_technology() == ptFFF ? m_freq_settings_fff : m_freq_settings_sla;
+
+ for (auto& category : bundle_quick) {
+ if (is_improper_category(category.first, extruders_cnt))
+ continue;
+
+ append_menu_item(menu, wxID_ANY, from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()), "",
+ [menu, item, is_object_settings, bundle](wxCommandEvent& event) {
+ wxString category_name = menu->GetLabel(event.GetId());
+ std::vector options;
+ for (auto& category : bundle)
+ if (category_name == from_u8((boost::format(_L("Quick Add Settings (%s)")) % _(category.first)).str())) {
+ options = category.second;
+ break;
+ }
+ if (options.empty())
+ return;
+ // Because of we couldn't edited layer_height for ItVolume from settings list,
+ // correct options according to the selected item type : remove "layer_height" option
+ if (!is_object_settings) {
+ const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height");
+ if (layer_height_it != options.end())
+ options.erase(layer_height_it);
+ }
+ obj_list()->add_category_to_settings_from_frequent(options, item);
+ },
+ SettingsFactory::get_category_bitmap(category.first), menu,
+ [this]() { return true; }, plater());
+ }
+#endif
+}
+
+std::vector MenuFactory::get_volume_bitmaps()
+{
+ std::vector volume_bmps;
+ volume_bmps.reserve(ADD_VOLUME_MENU_ITEMS.size());
+ for (auto item : ADD_VOLUME_MENU_ITEMS)
+ volume_bmps.push_back(create_scaled_bitmap(item.second));
+ return volume_bmps;
+}
+
+void MenuFactory::append_menu_item_delete(wxMenu* menu)
+{
+ append_menu_item(menu, wxID_ANY, _L("Delete") + "\tDel", _L("Remove the selected object"),
+ [](wxCommandEvent&) { plater()->remove_selected(); }, "delete", nullptr,
+ []() { return plater()->can_delete(); }, m_parent);
+
+ menu->AppendSeparator();
+
+}
+
+wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType type) {
+ auto sub_menu = new wxMenu;
+
+ if (wxGetApp().get_mode() == comExpert && type != ModelVolumeType::INVALID) {
+ append_menu_item(sub_menu, wxID_ANY, _L("Load") + " " + dots, "",
+ [type](wxCommandEvent&) { obj_list()->load_subobject(type); }, "", menu);
+ sub_menu->AppendSeparator();
+ }
+
+ for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") })
+ {
+ if (type == ModelVolumeType::INVALID && strncmp(item, "Slab", 4) == 0)
+ continue;
+ append_menu_item(sub_menu, wxID_ANY, _(item), "",
+ [type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu);
+ }
+
+ return sub_menu;
+}
+
+void MenuFactory::append_menu_items_add_volume(wxMenu* menu)
+{
+ // Update "add" items(delete old & create new) settings popupmenu
+ for (auto& item : ADD_VOLUME_MENU_ITEMS) {
+ const auto settings_id = menu->FindItem(_(item.first));
+ if (settings_id != wxNOT_FOUND)
+ menu->Destroy(settings_id);
+ }
+
+ const ConfigOptionMode mode = wxGetApp().get_mode();
+
+ if (mode == comAdvanced) {
+ append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "",
+ [](wxCommandEvent&) { obj_list()->load_subobject(ModelVolumeType::MODEL_PART); },
+ ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr,
+ []() { return obj_list()->is_instance_or_object_selected(); }, m_parent);
+ }
+ if (mode == comSimple) {
+ append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "",
+ [](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); },
+ ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].second, nullptr,
+ []() { return obj_list()->is_instance_or_object_selected(); }, m_parent);
+ append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].first), "",
+ [](wxCommandEvent&) { obj_list()->load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_BLOCKER); },
+ ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].second, nullptr,
+ []() { return obj_list()->is_instance_or_object_selected(); }, m_parent);
+
+ return;
+ }
+
+ for (size_t type = (mode == comExpert ? 0 : 1); type < ADD_VOLUME_MENU_ITEMS.size(); type++)
+ {
+ auto& item = ADD_VOLUME_MENU_ITEMS[type];
+
+ wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type));
+ append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second,
+ []() { return obj_list()->is_instance_or_object_selected(); }, m_parent);
+ }
+}
+
+wxMenuItem* MenuFactory::append_menu_item_layers_editing(wxMenu* menu)
+{
+ return append_menu_item(menu, wxID_ANY, _L("Height range Modifier"), "",
+ [](wxCommandEvent&) { obj_list()->layers_editing(); }, "edit_layers_all", menu,
+ []() { return obj_list()->is_instance_or_object_selected(); }, m_parent);
+}
+
+wxMenuItem* MenuFactory::append_menu_item_settings(wxMenu* menu_)
+{
+ MenuWithSeparators* menu = dynamic_cast(menu_);
+
+ const wxString menu_name = _L("Add settings");
+ // Delete old items from settings popupmenu
+ auto settings_id = menu->FindItem(menu_name);
+ if (settings_id != wxNOT_FOUND)
+ menu->Destroy(settings_id);
+
+ for (auto& it : FREQ_SETTINGS_BUNDLE_FFF)
+ {
+ settings_id = menu->FindItem(_(it.first));
+ if (settings_id != wxNOT_FOUND)
+ menu->Destroy(settings_id);
+ }
+ for (auto& it : FREQ_SETTINGS_BUNDLE_SLA)
+ {
+ settings_id = menu->FindItem(_(it.first));
+ if (settings_id != wxNOT_FOUND)
+ menu->Destroy(settings_id);
+ }
+#if 0
+ for (auto& it : m_freq_settings_fff)
+ {
+ settings_id = menu->FindItem(from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()));
+ if (settings_id != wxNOT_FOUND)
+ menu->Destroy(settings_id);
+ }
+ for (auto& it : m_freq_settings_sla)
+ {
+ settings_id = menu->FindItem(from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()));
+ if (settings_id != wxNOT_FOUND)
+ menu->Destroy(settings_id);
+ }
+#endif
+ menu->DestroySeparators(); // delete old separators
+
+ // If there are selected more then one instance but not all of them
+ // don't add settings menu items
+ const Selection& selection = get_selection();
+ if ((selection.is_multiple_full_instance() && !selection.is_single_full_object()) ||
+ selection.is_multiple_volume() || selection.is_mixed()) // more than one volume(part) is selected on the scene
+ return nullptr;
+
+ const auto sel_vol = obj_list()->get_selected_model_volume();
+ if (sel_vol && sel_vol->type() >= ModelVolumeType::SUPPORT_ENFORCER)
+ return nullptr;
+
+ const ConfigOptionMode mode = wxGetApp().get_mode();
+ if (mode == comSimple)
+ return nullptr;
+
+ // Create new items for settings popupmenu
+
+ if (printer_technology() == ptFFF ||
+ (menu->GetMenuItems().size() > 0 && !menu->GetMenuItems().back()->IsSeparator()))
+ menu->SetFirstSeparator();
+
+ // detect itemm for adding of the setting
+ ObjectList* object_list = obj_list();
+ ObjectDataViewModel* obj_model = list_model();
+
+ const wxDataViewItem sel_item = // when all instances in object are selected
+ object_list->GetSelectedItemsCount() > 1 && selection.is_single_full_object() ?
+ obj_model->GetItemById(selection.get_object_idx()) :
+ object_list->GetSelection();
+ if (!sel_item)
+ return nullptr;
+
+ // If we try to add settings for object/part from 3Dscene,
+ // for the second try there is selected ItemSettings in ObjectList.
+ // So, check if selected item isn't SettingsItem. And get a SettingsItem's parent item, if yes
+ wxDataViewItem item = obj_model->GetItemType(sel_item) & itSettings ? obj_model->GetParent(sel_item) : sel_item;
+ const ItemType item_type = obj_model->GetItemType(item);
+ const bool is_object_settings = !(item_type& itVolume || item_type & itLayer);
+
+ // Add frequently settings
+ create_freq_settings_popupmenu(menu, is_object_settings, item);
+
+ if (mode == comAdvanced)
+ return nullptr;
+
+ menu->SetSecondSeparator();
+
+ // Add full settings list
+ auto menu_item = new wxMenuItem(menu, wxID_ANY, menu_name);
+ menu_item->SetBitmap(create_scaled_bitmap("cog"));
+ menu_item->SetSubMenu(create_settings_popupmenu(menu, is_object_settings, item));
+
+ return menu->Append(menu_item);
+}
+
+wxMenuItem* MenuFactory::append_menu_item_change_type(wxMenu* menu)
+{
+ return append_menu_item(menu, wxID_ANY, _L("Change type"), "",
+ [](wxCommandEvent&) { obj_list()->change_part_type(); }, "", menu,
+ []() {
+ wxDataViewItem item = obj_list()->GetSelection();
+ return item.IsOk() || obj_list()->GetModel()->GetItemType(item) == itVolume;
+ }, m_parent);
+}
+
+wxMenuItem* MenuFactory::append_menu_item_instance_to_object(wxMenu* menu)
+{
+ wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Set as a Separated Object"), "",
+ [](wxCommandEvent&) { obj_list()->split_instances(); }, "", menu);
+
+ /* New behavior logic:
+ * 1. Split Object to several separated object, if ALL instances are selected
+ * 2. Separate selected instances from the initial object to the separated object,
+ * if some (not all) instances are selected
+ */
+ m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt)
+ {
+ const Selection& selection = plater()->canvas3D()->get_selection();
+ evt.SetText(selection.is_single_full_object() ?
+ _L("Set as a Separated Objects") : _L("Set as a Separated Object"));
+
+ evt.Enable(plater()->can_set_instance_to_object());
+ }, menu_item->GetId());
+
+ return menu_item;
+}
+
+wxMenuItem* MenuFactory::append_menu_item_printable(wxMenu* menu)
+{
+ return append_menu_check_item(menu, wxID_ANY, _L("Printable"), "", [](wxCommandEvent&) {
+ const Selection& selection = plater()->canvas3D()->get_selection();
+ wxDataViewItem item;
+ if (obj_list()->GetSelectedItemsCount() > 1 && selection.is_single_full_object())
+ item = obj_list()->GetModel()->GetItemById(selection.get_object_idx());
+ else
+ item = obj_list()->GetSelection();
+
+ if (item)
+ obj_list()->toggle_printable_state(item);
+ }, menu);
+}
+
+void MenuFactory::append_menu_items_osx(wxMenu* menu)
+{
+ append_menu_item(menu, wxID_ANY, _L("Rename"), "",
+ [](wxCommandEvent&) { obj_list()->rename_item(); }, "", menu);
+
+ menu->AppendSeparator();
+}
+
+wxMenuItem* MenuFactory::append_menu_item_fix_through_netfabb(wxMenu* menu)
+{
+ if (!is_windows10())
+ return nullptr;
+ wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix through the Netfabb"), "",
+ [](wxCommandEvent&) { obj_list()->fix_through_netfabb(); }, "", menu,
+ []() {return plater()->can_fix_through_netfabb(); }, plater());
+ menu->AppendSeparator();
+
+ return menu_item;
+}
+
+void MenuFactory::append_menu_item_export_stl(wxMenu* menu)
+{
+ append_menu_item(menu, wxID_ANY, _L("Export as STL") + dots, "",
+ [](wxCommandEvent&) { plater()->export_stl(false, true); }, "", nullptr,
+ []() {
+ const Selection& selection = plater()->canvas3D()->get_selection();
+ return selection.is_single_full_instance() || selection.is_single_full_object();
+ }, m_parent);
+ menu->AppendSeparator();
+}
+
+void MenuFactory::append_menu_item_reload_from_disk(wxMenu* menu)
+{
+ append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected volumes from disk"),
+ [](wxCommandEvent&) { plater()->reload_from_disk(); }, "", menu,
+ []() { return plater()->can_reload_from_disk(); }, m_parent);
+}
+
+void MenuFactory::append_menu_item_change_extruder(wxMenu* menu)
+{
+ const std::vector names = { _L("Change extruder"), _L("Set extruder for selected items") };
+ // Delete old menu item
+ for (const wxString& name : names) {
+ const int item_id = menu->FindItem(name);
+ if (item_id != wxNOT_FOUND)
+ menu->Destroy(item_id);
+ }
+
+ const int extruders_cnt = extruders_count();
+ if (extruders_cnt <= 1)
+ return;
+
+ wxDataViewItemArray sels;
+ obj_list()->GetSelections(sels);
+ if (sels.IsEmpty())
+ return;
+
+ std::vector icons = get_extruder_color_icons(true);
+ wxMenu* extruder_selection_menu = new wxMenu();
+ const wxString& name = sels.Count() == 1 ? names[0] : names[1];
+
+ int initial_extruder = -1; // negative value for multiple object/part selection
+ if (sels.Count() == 1) {
+ const ModelConfig& config = obj_list()->get_item_config(sels[0]);
+ initial_extruder = config.has("extruder") ? config.extruder() : 0;
+ }
+
+ for (int i = 0; i <= extruders_cnt; i++)
+ {
+ bool is_active_extruder = i == initial_extruder;
+ int icon_idx = i == 0 ? 0 : i - 1;
+
+ const wxString& item_name = (i == 0 ? _L("Default") : wxString::Format(_L("Extruder %d"), i)) +
+ (is_active_extruder ? " (" + _L("active") + ")" : "");
+
+ append_menu_item(extruder_selection_menu, wxID_ANY, item_name, "",
+ [i](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(i); }, *icons[icon_idx], menu,
+ [is_active_extruder]() { return !is_active_extruder; }, m_parent);
+
+ }
+
+ menu->AppendSubMenu(extruder_selection_menu, name);
+}
+
+void MenuFactory::append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu)
+{
+ append_menu_item(menu, wxID_ANY, _L("Scale to print volume"), _L("Scale the selected object to fit the print volume"),
+ [](wxCommandEvent&) { plater()->scale_selection_to_fit_print_volume(); }, "", menu);
+}
+
+void MenuFactory::append_menu_items_convert_unit(wxMenu* menu, int insert_pos/* = 1*/)
+{
+ std::vector obj_idxs, vol_idxs;
+ obj_list()->get_selection_indexes(obj_idxs, vol_idxs);
+ if (obj_idxs.empty() && vol_idxs.empty())
+ return;
+
+ auto volume_respects_conversion = [](ModelVolume* volume, ConversionType conver_type)
+ {
+ return (conver_type == ConversionType::CONV_FROM_INCH && volume->source.is_converted_from_inches) ||
+ (conver_type == ConversionType::CONV_TO_INCH && !volume->source.is_converted_from_inches) ||
+ (conver_type == ConversionType::CONV_FROM_METER && volume->source.is_converted_from_meters) ||
+ (conver_type == ConversionType::CONV_TO_METER && !volume->source.is_converted_from_meters);
+ };
+
+ auto can_append = [obj_idxs, vol_idxs, volume_respects_conversion](ConversionType conver_type)
+ {
+ ModelObjectPtrs objects;
+ for (int obj_idx : obj_idxs) {
+ ModelObject* object = obj_list()->object(obj_idx);
+ if (vol_idxs.empty()) {
+ for (ModelVolume* volume : object->volumes)
+ if (volume_respects_conversion(volume, conver_type))
+ return false;
+ }
+ else {
+ for (int vol_idx : vol_idxs)
+ if (volume_respects_conversion(object->volumes[vol_idx], conver_type))
+ return false;
+ }
+ }
+ return true;
+ };
+
+ std::vector> items = {
+ {ConversionType::CONV_FROM_INCH , _L("Convert from imperial units") },
+ {ConversionType::CONV_TO_INCH , _L("Revert conversion from imperial units") },
+ {ConversionType::CONV_FROM_METER, _L("Convert from meters") },
+ {ConversionType::CONV_TO_METER , _L("Revert conversion from meters") } };
+
+ for (auto item : items) {
+ int menu_id = menu->FindItem(item.second);
+ if (can_append(item.first)) {
+ // Add menu item if it doesn't exist
+ if (menu_id == wxNOT_FOUND)
+ append_menu_item(menu, wxID_ANY, item.second, item.second,
+ [item](wxCommandEvent&) { plater()->convert_unit(item.first); }, "", menu,
+ []() { return true; }, m_parent, insert_pos);
+ }
+ else if (menu_id != wxNOT_FOUND) {
+ // Delete menu item
+ menu->Destroy(menu_id);
+ }
+ }
+}
+
+void MenuFactory::append_menu_item_merge_to_multipart_object(wxMenu* menu)
+{
+ menu->AppendSeparator();
+ append_menu_item(menu, wxID_ANY, _L("Merge"), _L("Merge objects to the one multipart object"),
+ [](wxCommandEvent&) { obj_list()->merge(true); }, "", menu,
+ []() { return obj_list()->can_merge_to_multipart_object(); }, m_parent);
+}
+/*
+void MenuFactory::append_menu_item_merge_to_single_object(wxMenu* menu)
+{
+ menu->AppendSeparator();
+ append_menu_item(menu, wxID_ANY, _L("Merge"), _L("Merge objects to the one single object"),
+ [](wxCommandEvent&) { obj_list()->merge(false); }, "", menu,
+ []() { return obj_list()->can_merge_to_single_object(); }, m_parent);
+}
+*/
+void MenuFactory::append_menu_items_mirror(wxMenu* menu)
+{
+ wxMenu* mirror_menu = new wxMenu();
+ if (!mirror_menu)
+ return;
+
+ append_menu_item(mirror_menu, wxID_ANY, _L("Along X axis"), _L("Mirror the selected object along the X axis"),
+ [](wxCommandEvent&) { plater()->mirror(X); }, "mark_X", menu);
+ append_menu_item(mirror_menu, wxID_ANY, _L("Along Y axis"), _L("Mirror the selected object along the Y axis"),
+ [](wxCommandEvent&) { plater()->mirror(Y); }, "mark_Y", menu);
+ append_menu_item(mirror_menu, wxID_ANY, _L("Along Z axis"), _L("Mirror the selected object along the Z axis"),
+ [](wxCommandEvent&) { plater()->mirror(Z); }, "mark_Z", menu);
+
+ append_submenu(menu, mirror_menu, wxID_ANY, _L("Mirror"), _L("Mirror the selected object"), "",
+ []() { return plater()->can_mirror(); }, m_parent);
+}
+
+MenuFactory::MenuFactory()
+{
+ for (int i = 0; i < mtCount; i++) {
+ items_increase[i] = nullptr;
+ items_decrease[i] = nullptr;
+ items_set_number_of_copies[i] = nullptr;
+ }
+}
+
+void MenuFactory::create_default_menu()
+{
+ wxMenu* sub_menu = append_submenu_add_generic(&m_default_menu, ModelVolumeType::INVALID);
+ append_submenu(&m_default_menu, sub_menu, wxID_ANY, _L("Add Shape"), "", "add_part",
+ []() {return true; }, m_parent);
+}
+
+void MenuFactory::create_common_object_menu(wxMenu* menu)
+{
+#ifdef __WXOSX__
+ append_menu_items_osx(menu);
+#endif // __WXOSX__
+ append_menu_items_instance_manipulation(menu);
+ // Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake.
+ append_menu_item_delete(menu);
+ append_menu_item_instance_to_object(menu);
+ menu->AppendSeparator();
+
+ wxMenuItem* menu_item_printable = append_menu_item_printable(menu);
+ menu->AppendSeparator();
+
+ append_menu_item_reload_from_disk(menu);
+ append_menu_item_export_stl(menu);
+ // "Scale to print volume" makes a sense just for whole object
+ append_menu_item_scale_selection_to_fit_print_volume(menu);
+
+ append_menu_item_fix_through_netfabb(menu);
+ append_menu_items_mirror(menu);
+
+ m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) {
+ const Selection& selection = get_selection();
+ int instance_idx = selection.get_instance_idx();
+ evt.Enable(selection.is_single_full_instance() || selection.is_single_full_object());
+ if (instance_idx != -1) {
+ evt.Check(obj_list()->object(selection.get_object_idx())->instances[instance_idx]->printable);
+ plater()->set_current_canvas_as_dirty();//view3D->set_as_dirty();
+ }
+ }, menu_item_printable->GetId());
+}
+
+void MenuFactory::create_object_menu()
+{
+ create_common_object_menu(&m_object_menu);
+ wxMenu* split_menu = new wxMenu();
+ if (!split_menu)
+ return;
+
+ append_menu_item(split_menu, wxID_ANY, _L("To objects"), _L("Split the selected object into individual objects"),
+ [](wxCommandEvent&) { plater()->split_object(); }, "split_object_SMALL", &m_object_menu,
+ []() { return plater()->can_split(true); }, m_parent);
+ append_menu_item(split_menu, wxID_ANY, _L("To parts"), _L("Split the selected object into individual parts"),
+ [](wxCommandEvent&) { plater()->split_volume(); }, "split_parts_SMALL", &m_object_menu,
+ []() { return plater()->can_split(false); }, m_parent);
+
+ append_submenu(&m_object_menu, split_menu, wxID_ANY, _L("Split"), _L("Split the selected object"), "",
+ []() { return plater()->can_split(true) && wxGetApp().get_mode() > comSimple; }, m_parent);
+ m_object_menu.AppendSeparator();
+
+ // Layers Editing for object
+ append_menu_item_layers_editing(&m_object_menu);
+ m_object_menu.AppendSeparator();
+
+ // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume()
+}
+
+void MenuFactory::create_sla_object_menu()
+{
+ create_common_object_menu(&m_sla_object_menu);
+ append_menu_item(&m_sla_object_menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual objects"),
+ [](wxCommandEvent&) { plater()->split_object(); }, "split_object_SMALL", nullptr,
+ []() { return plater()->can_split(true); }, m_parent);
+
+ m_sla_object_menu.AppendSeparator();
+
+ // Add the automatic rotation sub-menu
+ append_menu_item(&m_sla_object_menu, wxID_ANY, _L("Optimize orientation"), _L("Optimize the rotation of the object for better print results."),
+ [](wxCommandEvent&) { plater()->optimize_rotation(); });
+}
+
+void MenuFactory::create_part_menu()
+{
+ wxMenu* menu = &m_part_menu;
+#ifdef __WXOSX__
+ append_menu_items_osx(menu);
+#endif // __WXOSX__
+ append_menu_item_delete(menu);
+ append_menu_item_reload_from_disk(menu);
+ append_menu_item_export_stl(menu);
+ append_menu_item_fix_through_netfabb(menu);
+ append_menu_items_mirror(menu);
+
+ append_menu_item(menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual parts"),
+ [](wxCommandEvent&) { plater()->split_volume(); }, "split_parts_SMALL", nullptr,
+ []() { return plater()->can_split(false); }, m_parent);
+
+ menu->AppendSeparator();
+ append_menu_item_change_type(menu);
+
+}
+
+void MenuFactory::init(wxWindow* parent)
+{
+ m_parent = parent;
+
+ create_default_menu();
+ create_object_menu();
+ create_sla_object_menu();
+ create_part_menu();
+
+ // create "Instance to Object" menu item
+ append_menu_item_instance_to_object(&m_instance_menu);
+}
+
+wxMenu* MenuFactory::default_menu()
+{
+ return &m_default_menu;
+}
+
+wxMenu* MenuFactory::object_menu()
+{
+ append_menu_items_convert_unit(&m_object_menu, 11);
+ append_menu_item_settings(&m_object_menu);
+ append_menu_item_change_extruder(&m_object_menu);
+ update_menu_items_instance_manipulation(mtObjectFFF);
+
+ return &m_object_menu;
+}
+
+wxMenu* MenuFactory::sla_object_menu()
+{
+ append_menu_items_convert_unit(&m_sla_object_menu, 11);
+ append_menu_item_settings(&m_sla_object_menu);
+ update_menu_items_instance_manipulation(mtObjectSLA);
+
+ return &m_sla_object_menu;
+}
+
+wxMenu* MenuFactory::part_menu()
+{
+ append_menu_items_convert_unit(&m_part_menu, 2);
+ append_menu_item_settings(&m_part_menu);
+ append_menu_item_change_extruder(&m_part_menu);
+
+ return &m_part_menu;
+}
+
+wxMenu* MenuFactory::instance_menu()
+{
+ return &m_instance_menu;
+}
+
+wxMenu* MenuFactory::layer_menu()
+{
+ MenuWithSeparators* menu = new MenuWithSeparators();
+ append_menu_item_settings(menu);
+
+ return menu;
+}
+
+wxMenu* MenuFactory::multi_selection_menu()
+{
+ wxDataViewItemArray sels;
+ obj_list()->GetSelections(sels);
+
+ for (const wxDataViewItem& item : sels)
+ if (!(list_model()->GetItemType(item) & (itVolume | itObject | itInstance)))
+ // show this menu only for Objects(Instances mixed with Objects)/Volumes selection
+ return nullptr;
+
+ wxMenu* menu = new MenuWithSeparators();
+
+ append_menu_item_reload_from_disk(menu);
+ append_menu_items_convert_unit(menu);
+ if (obj_list()->can_merge_to_multipart_object())
+ append_menu_item_merge_to_multipart_object(menu);
+ if (extruders_count() > 1)
+ append_menu_item_change_extruder(menu);
+
+ return menu;
+}
+
+void MenuFactory::append_menu_items_instance_manipulation(wxMenu* menu)
+{
+ MenuType type = menu == &m_object_menu ? mtObjectFFF : mtObjectSLA;
+
+ items_increase[type] = append_menu_item(menu, wxID_ANY, _L("Add instance") + "\t+", _L("Add one more instance of the selected object"),
+ [](wxCommandEvent&) { plater()->increase_instances(); }, "add_copies", nullptr,
+ []() { return plater()->can_increase_instances(); }, m_parent);
+ items_decrease[type] = append_menu_item(menu, wxID_ANY, _L("Remove instance") + "\t-", _L("Remove one instance of the selected object"),
+ [](wxCommandEvent&) { plater()->decrease_instances(); }, "remove_copies", nullptr,
+ []() { return plater()->can_decrease_instances(); }, m_parent);
+ items_set_number_of_copies[type] = append_menu_item(menu, wxID_ANY, _L("Set number of instances") + dots, _L("Change the number of instances of the selected object"),
+ [](wxCommandEvent&) { plater()->set_number_of_copies(); }, "number_of_copies", nullptr,
+ []() { return plater()->can_increase_instances(); }, m_parent);
+
+ append_menu_item(menu, wxID_ANY, _L("Fill bed with instances") + dots, _L("Fill the remaining area of bed with instances of the selected object"),
+ [](wxCommandEvent&) { plater()->fill_bed_with_instances(); }, "", nullptr,
+ []() { return plater()->can_increase_instances(); }, m_parent);
+
+}
+
+void MenuFactory::update_menu_items_instance_manipulation(MenuType type)
+{
+ wxMenu* menu = type == mtObjectFFF ? &m_object_menu : type == mtObjectSLA ? &m_sla_object_menu : nullptr;
+ if (menu)
+ return;
+ // Remove/Prepend "increase/decrease instances" menu items according to the view mode.
+ // Suppress to show those items for a Simple mode
+ if (wxGetApp().get_mode() == comSimple) {
+ if (menu->FindItem(_L("Add instance")) != wxNOT_FOUND)
+ {
+ // Detach an items from the menu, but don't delete them
+ // so that they can be added back later
+ // (after switching to the Advanced/Expert mode)
+ menu->Remove(items_increase[type]);
+ menu->Remove(items_decrease[type]);
+ menu->Remove(items_set_number_of_copies[type]);
+ }
+ }
+ else {
+ if (menu->FindItem(_L("Add instance")) == wxNOT_FOUND)
+ {
+ // Prepend items to the menu, if those aren't not there
+ menu->Prepend(items_set_number_of_copies[type]);
+ menu->Prepend(items_decrease[type]);
+ menu->Prepend(items_increase[type]);
+ }
+ }
+}
+
+void MenuFactory::update_object_menu()
+{
+ append_menu_items_add_volume(&m_object_menu);
+}
+
+void MenuFactory::msw_rescale()
+{
+ for (MenuWithSeparators* menu : { &m_object_menu, &m_sla_object_menu, &m_part_menu, &m_default_menu })
+ msw_rescale_menu(dynamic_cast(menu));
+}
+
+} //namespace GUI
+} //namespace Slic3r
diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp
new file mode 100644
index 000000000..3cc5759a1
--- /dev/null
+++ b/src/slic3r/GUI/GUI_Factories.hpp
@@ -0,0 +1,104 @@
+#ifndef slic3r_GUI_Factories_hpp_
+#define slic3r_GUI_Factories_hpp_
+
+#include