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 @@ + +image/svg+xml + + + + + + + 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 @@ + +image/svg+xml + + + + + + + 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 +#include +#include + +#include + +#include "libslic3r/PrintConfig.hpp" +#include "wxExtensions.hpp" + +class wxMenu; +class wxMenuItem; + +namespace Slic3r { + +enum class ModelVolumeType : int; + +namespace GUI { + +struct SettingsFactory +{ +// category -> vector ( option ) + typedef std::map> Bundle; + static std::map CATEGORY_ICON; + + static wxBitmap get_category_bitmap(const std::string& category_name); + static Bundle get_bundle(const DynamicPrintConfig* config, bool is_object_settings); + static std::vector get_options(bool is_part); +}; + +class MenuFactory +{ +public: + static std::vector> ADD_VOLUME_MENU_ITEMS; + static std::vector get_volume_bitmaps(); + + MenuFactory(); + ~MenuFactory() = default; + + void init(wxWindow* parent); + void update_object_menu(); + void msw_rescale(); + + wxMenu* default_menu(); + wxMenu* object_menu(); + wxMenu* sla_object_menu(); + wxMenu* part_menu(); + wxMenu* instance_menu(); + wxMenu* layer_menu(); + wxMenu* multi_selection_menu(); + +private: + enum MenuType { + mtObjectFFF = 0, + mtObjectSLA, + mtCount + }; + + wxWindow* m_parent {nullptr}; + + MenuWithSeparators m_object_menu; + MenuWithSeparators m_part_menu; + MenuWithSeparators m_sla_object_menu; + MenuWithSeparators m_default_menu; + MenuWithSeparators m_instance_menu; + + // Removed/Prepended Items according to the view mode + std::array items_increase; + std::array items_decrease; + std::array items_set_number_of_copies; + + void create_default_menu(); + void create_common_object_menu(wxMenu *menu); + void create_object_menu(); + void create_sla_object_menu(); + void create_part_menu(); + + wxMenu* append_submenu_add_generic(wxMenu* menu, ModelVolumeType type); + void append_menu_items_add_volume(wxMenu* menu); + wxMenuItem* append_menu_item_layers_editing(wxMenu* menu); + wxMenuItem* append_menu_item_settings(wxMenu* menu); + wxMenuItem* append_menu_item_change_type(wxMenu* menu); + wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu); + wxMenuItem* append_menu_item_printable(wxMenu* menu); + void append_menu_items_osx(wxMenu* menu); + wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); + void append_menu_item_export_stl(wxMenu* menu); + void append_menu_item_reload_from_disk(wxMenu* menu); + void append_menu_item_change_extruder(wxMenu* menu); + void append_menu_item_delete(wxMenu* menu); + void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); + void append_menu_items_convert_unit(wxMenu* menu, int insert_pos = 1); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk" + void append_menu_item_merge_to_multipart_object(wxMenu *menu); +// void append_menu_item_merge_to_single_object(wxMenu *menu); + void append_menu_items_mirror(wxMenu *menu); + void append_menu_items_instance_manipulation(wxMenu *menu); + void update_menu_items_instance_manipulation(MenuType type); +}; + +}} + +#endif //slic3r_GUI_Factories_hpp_ diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 839782741..3c77f3335 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -9,6 +9,7 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/Utils/Platform.hpp" // To show a message box if GUI initialization ends up with an exception thrown. #include @@ -36,6 +37,8 @@ int GUI_Run(GUI_InitParams ¶ms) signal(SIGCHLD, SIG_DFL); #endif // __APPLE__ + detect_platform(); + try { GUI::GUI_App* gui = new GUI::GUI_App(params.start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor); if (gui->get_app_mode() != GUI::GUI_App::EAppMode::GCodeViewer) { diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 8768f39ff..725a396c3 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -260,16 +260,15 @@ void ObjectLayers::msw_rescale() editor->msw_rescale(); } - const std::vector btns = {2, 3}; // del_btn, add_btn - for (auto btn : btns) - { - wxSizerItem* b_item = item->GetSizer()->GetItem(btn); - if (b_item->IsWindow()) { - auto button = dynamic_cast(b_item->GetWindow()); - if (button != nullptr) - button->msw_rescale(); - } - } + if (item->GetSizer()->GetItemCount() > 2) // if there are Add/Del buttons + for (size_t btn : {2, 3}) { // del_btn, add_btn + wxSizerItem* b_item = item->GetSizer()->GetItem(btn); + if (b_item->IsWindow()) { + auto button = dynamic_cast(b_item->GetWindow()); + if (button != nullptr) + button->msw_rescale(); + } + } } } m_grid_sizer->Layout(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 57f1841e3..f37276beb 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PresetBundle.hpp" #include "GUI_ObjectList.hpp" +#include "GUI_Factories.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" #include "GUI_App.hpp" @@ -31,32 +32,6 @@ namespace GUI wxDEFINE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); -// pt_FFF -static SettingsBundle 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 SettingsBundle FREQ_SETTINGS_BUNDLE_SLA = -{ - { L("Pad and Support") , { "supports_enable", "pad_enable" } } -}; - -// Note: id accords to type of the sub-object (adding volume), so sequence of the menu items is important -static std::vector> 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 PrinterTechnology printer_technology() { return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(); @@ -86,35 +61,11 @@ static void take_snapshot(const wxString& snapshot_name) } ObjectList::ObjectList(wxWindow* parent) : - wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), - m_parent(parent) + wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE) { - // Fill CATEGORY_ICON - { - // ptFFF - CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); - CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); - CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("ironing"); - CATEGORY_ICON[L("Fuzzy Skin")] = create_scaled_bitmap("fuzzy_skin"); - CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); - CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); - CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap("skirt+brim"); -// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap("time"); - CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap("wrench"); - // ptSLA - CATEGORY_ICON[L("Supports")] = create_scaled_bitmap("support"/*"sla_supports"*/); - CATEGORY_ICON[L("Pad")] = create_scaled_bitmap("pad"); - CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap("hollowing"); - } - // create control create_objects_ctrl(); - init_icons(); - // describe control behavior Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent& event) { // detect the current mouse position here, to pass it to list_manipulation() method @@ -125,11 +76,6 @@ ObjectList::ObjectList(wxWindow* parent) : #endif #ifndef __APPLE__ - // On Windows and Linux, forces a kill focus emulation on the object manipulator fields because this event handler is called - // before the kill focus event handler on the object manipulator when changing selection in the list, invalidating the object - // manipulator cache with the following call to selection_changed() -// wxGetApp().obj_manipul()->emulate_kill_focus(); // It's not necessury anymore #ys_FIXME delete after testing - // On Windows and Linux: // It's not invoked KillFocus event for "temporary" panels (like "Manipulation panel", "Settings", "Layer ranges"), // if we change selection in object list. @@ -347,16 +293,6 @@ void ObjectList::create_objects_ctrl() } } -void ObjectList::create_popup_menus() -{ - // create popup menus for object and part - create_object_popupmenu(&m_menu_object); - create_part_popupmenu(&m_menu_part); - create_sla_object_popupmenu(&m_menu_sla_object); - create_instance_popupmenu(&m_menu_instance); - create_default_popupmenu(&m_menu_default); -} - void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(nullptr)*/) { const wxDataViewItem item = input_item == wxDataViewItem(nullptr) ? GetSelection() : input_item; @@ -381,10 +317,14 @@ void ObjectList::get_selection_indexes(std::vector& obj_idxs, std::vectorGetItemType(sels[0]) & itVolume) { + if ( m_objects_model->GetItemType(sels[0]) & itVolume || + (sels.Count()==1 && m_objects_model->GetItemType(m_objects_model->GetParent(sels[0])) & itVolume) ) { for (wxDataViewItem item : sels) { obj_idxs.emplace_back(m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item))); + if (sels.Count() == 1 && m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itVolume) + item = m_objects_model->GetParent(item); + assert(m_objects_model->GetItemType(item) & itVolume); vol_idxs.emplace_back(m_objects_model->GetVolumeIdByItem(item)); } @@ -392,8 +332,6 @@ void ObjectList::get_selection_indexes(std::vector& obj_idxs, std::vectorGetItemType(item); - assert(type & (itObject | itInstance | itInstanceRoot)); - obj_idxs.emplace_back(type & itObject ? m_objects_model->GetIdByItem(item) : m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item))); } @@ -633,75 +571,6 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const (*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data(); } -void ObjectList::init_icons() -{ - m_bmp_solidmesh = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART) ].second); - m_bmp_modifiermesh = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::PARAMETER_MODIFIER)].second); - m_bmp_support_enforcer = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER) ].second); - m_bmp_support_blocker = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER) ].second); - - m_bmp_vector.reserve(4); // bitmaps for different types of parts - m_bmp_vector.push_back(&m_bmp_solidmesh.bmp()); - m_bmp_vector.push_back(&m_bmp_modifiermesh.bmp()); - m_bmp_vector.push_back(&m_bmp_support_enforcer.bmp()); - m_bmp_vector.push_back(&m_bmp_support_blocker.bmp()); - - - // Set volumes default bitmaps for the model - m_objects_model->SetVolumeBitmaps(m_bmp_vector); - - // init icon for manifold warning - m_bmp_manifold_warning = ScalableBitmap(this, "exclamation"); - // Set warning bitmap for the model - m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); - - // init bitmap for "Add Settings" context menu - m_bmp_cog = ScalableBitmap(this, "cog"); -} - -void ObjectList::msw_rescale_icons() -{ - m_bmp_vector.clear(); - m_bmp_vector.reserve(4); // bitmaps for different types of parts - for (ScalableBitmap* bitmap : { &m_bmp_solidmesh, // Add part - &m_bmp_modifiermesh, // Add modifier - &m_bmp_support_enforcer, // Add support enforcer - &m_bmp_support_blocker }) // Add support blocker - { - bitmap->msw_rescale(); - m_bmp_vector.push_back(& bitmap->bmp()); - } - // Set volumes default bitmaps for the model - m_objects_model->SetVolumeBitmaps(m_bmp_vector); - - m_bmp_manifold_warning.msw_rescale(); - // Set warning bitmap for the model - m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); - - m_bmp_cog.msw_rescale(); - - - // Update CATEGORY_ICON according to new scale - { - // ptFFF - CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); - CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); - CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("ironing"); - CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); - CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); - CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap("funnel"); - CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap("skirt+brim"); -// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap("time"); - CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap("wrench"); - // ptSLA - CATEGORY_ICON[L("Supports")] = create_scaled_bitmap("support"/*"sla_supports"*/); - CATEGORY_ICON[L("Pad")] = create_scaled_bitmap("pad"); - } -} - - void ObjectList::selection_changed() { if (m_prevent_list_events) return; @@ -821,7 +690,7 @@ void ObjectList::paste_settings_into_list() assert(!config_cache.empty()); auto keys = config_cache.keys(); - auto part_options = get_options(true); + auto part_options = SettingsFactory::get_options(true); for (const std::string& opt_key: keys) { if (item_type & (itVolume | itLayer) && @@ -867,9 +736,7 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol } select_items(items); -//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -//#endif //no __WXOSX__ //__WXMSW__ } void ObjectList::paste_objects_into_list(const std::vector& object_idxs) @@ -887,9 +754,7 @@ void ObjectList::paste_objects_into_list(const std::vector& object_idxs) wxGetApp().plater()->changed_objects(object_idxs); select_items(items); -//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -//#endif //no __WXOSX__ //__WXMSW__ } #ifdef __WXOSX__ @@ -1002,39 +867,35 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me void ObjectList::show_context_menu(const bool evt_context_menu) { + wxMenu* menu {nullptr}; + Plater* plater = wxGetApp().plater(); + if (multiple_selection()) { if (selected_instances_of_same_object()) - wxGetApp().plater()->PopupMenu(&m_menu_instance); + menu = plater->instance_menu(); else - show_multi_selection_menu(); - - return; + menu = plater->multi_selection_menu(); } + else { + const auto item = GetSelection(); + if (item) + { + const ItemType type = m_objects_model->GetItemType(item); + if (!(type & (itObject | itVolume | itLayer | itInstance))) + return; - const auto item = GetSelection(); - wxMenu* menu {nullptr}; - if (item) - { - const ItemType type = m_objects_model->GetItemType(item); - if (!(type & (itObject | itVolume | itLayer | itInstance))) - return; - - menu = type & itInstance ? &m_menu_instance : - type & itLayer ? &m_menu_layer : - m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? &m_menu_part : - printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; - - if (type & (itObject | itVolume)) - append_menu_items_convert_unit(menu); - if (!(type & itInstance)) - append_menu_item_settings(menu); + menu = type & itInstance ? plater->instance_menu() : + type & itLayer ? plater->layer_menu() : + m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? plater->part_menu() : + printer_technology() == ptFFF ? plater->object_menu() : plater->sla_object_menu(); + } + else if (evt_context_menu) + menu = plater->default_menu(); } - else if (evt_context_menu) - menu = &m_menu_default; if (menu) - wxGetApp().plater()->PopupMenu(menu); + plater->PopupMenu(menu); } void ObjectList::extruder_editing() @@ -1274,15 +1135,6 @@ void ObjectList::OnDrop(wxDataViewEvent &event) return; } -// It looks like a fixed in current version of the wxWidgets -// #ifdef __WXGTK__ -// /* Under GTK, DnD moves an item between another two items. -// * And event.GetItem() return item, which is under "insertion line" -// * So, if we move item down we should to decrease the to_volume_id value -// **/ -// if (to_volume_id > from_volume_id) to_volume_id--; -// #endif // __WXGTK__ - take_snapshot(_((m_dragged_data.type() == itVolume) ? L("Volumes in Object reordered") : L("Object reordered"))); if (m_dragged_data.type() & itVolume) @@ -1320,207 +1172,34 @@ void ObjectList::OnDrop(wxDataViewEvent &event) wxGetApp().plater()->set_current_canvas_as_dirty(); } - -// Context Menu - -std::vector ObjectList::get_options(const bool is_part) +void ObjectList::add_category_to_settings_from_selection(const std::vector< std::pair >& category_options, wxDataViewItem item) { - 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; -} - -const std::vector& ObjectList::get_options_for_bundle(const wxString& bundle_name) -{ - const SettingsBundle& bundle = printer_technology() == ptSLA ? - FREQ_SETTINGS_BUNDLE_SLA : FREQ_SETTINGS_BUNDLE_FFF; - - for (auto& it : bundle) - { - if (bundle_name == _(it.first)) - return it.second; - } -#if 0 - // if "Quick menu" is selected - SettingsBundle& bundle_quick = printer_technology() == ptSLA ? - m_freq_settings_sla: m_freq_settings_fff; - - for (auto& it : bundle_quick) - { - if ( bundle_name == from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()) ) - return it.second; - } -#endif - - static std::vector empty; - return empty; -} - -static bool 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"); -} - -static bool is_object_item(ItemType item_type) -{ - return item_type & itObject || item_type & itInstance || - // multi-selection in ObjectList, but full_object in Selection - (item_type == itUndef && scene_selection().is_single_full_object()); -} - -void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part) -{ - auto options = 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 (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; - } -} - -void ObjectList::get_settings_choice(const wxString& category_name) -{ - wxArrayString names; - wxArrayInt selections; - - /* 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 - */ - const wxDataViewItem selected_item = GetSelection(); - wxDataViewItem item = m_objects_model->GetItemType(selected_item) & itSettings ? m_objects_model->GetParent(selected_item) : selected_item; + if (category_options.empty()) + return; const ItemType item_type = m_objects_model->GetItemType(item); - settings_menu_hierarchy settings_menu; - const bool is_part = item_type & (itVolume | itLayer); - get_options_menu(settings_menu, is_part); - std::vector< std::pair > *settings_list = nullptr; - if (!m_config) m_config = &get_item_config(item); assert(m_config); auto opt_keys = m_config->keys(); - for (auto& cat : settings_menu) - { - if (_(cat.first) == category_name) { - int sel = 0; - for (auto& pair : cat.second) { - names.Add(_(pair.second)); - if (find(opt_keys.begin(), opt_keys.end(), pair.first) != opt_keys.end()) - selections.Add(sel); - sel++; - } - settings_list = &cat.second; - break; - } - } - - if (!settings_list) - return; - - if (wxGetSelectedChoices(selections, _(L("Select showing settings")), category_name, names) == -1) - return; - - const int selection_cnt = selections.size(); -#if 0 - if (selection_cnt > 0) - { - // Add selected items to the "Quick menu" - SettingsBundle& 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 - - const wxString snapshot_text = item_type & itLayer ? _(L("Add Settings for Layers")) : - item_type & itVolume ? _(L("Add Settings for Sub-object")) : - _(L("Add Settings for Object")); + const wxString snapshot_text = item_type & itLayer ? _L("Add Settings for Layers") : + item_type & itVolume ? _L("Add Settings for Sub-object") : + _L("Add Settings for Object"); take_snapshot(snapshot_text); - std::vector selected_options; - selected_options.reserve(selection_cnt); - for (auto sel : selections) - selected_options.push_back((*settings_list)[sel].first); - const DynamicPrintConfig& from_config = printer_technology() == ptFFF ? wxGetApp().preset_bundle->prints.get_edited_preset().config : wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - for (auto& setting : (*settings_list)) - { - auto& opt_key = setting.first; - if (find(opt_keys.begin(), opt_keys.end(), opt_key) != opt_keys.end() && - find(selected_options.begin(), selected_options.end(), opt_key) == selected_options.end()) + for (auto& opt : category_options) { + auto& opt_key = opt.first; + if (find(opt_keys.begin(), opt_keys.end(), opt_key) != opt_keys.end() && !opt.second) m_config->erase(opt_key); - if (find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end() && - find(selected_options.begin(), selected_options.end(), opt_key) != selected_options.end()) { + if (find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end() && opt.second) { const ConfigOption* option = from_config.option(opt_key); if (!option) { // if current option doesn't exist in prints.get_edited_preset(), @@ -1531,48 +1210,25 @@ void ObjectList::get_settings_choice(const wxString& category_name) } } - // Add settings item for object/sub-object and show them if (!(item_type & (itObject | itVolume | itLayer))) item = m_objects_model->GetTopParent(item); show_settings(add_settings_item(item, &m_config->get())); } -void ObjectList::get_freq_settings_choice(const wxString& bundle_name) +void ObjectList::add_category_to_settings_from_frequent(const std::vector& options, wxDataViewItem item) { - std::vector options = get_options_for_bundle(bundle_name); - const Selection& selection = scene_selection(); - const wxDataViewItem sel_item = // when all instances in object are selected - GetSelectedItemsCount() > 1 && selection.is_single_full_object() ? - m_objects_model->GetItemById(selection.get_object_idx()) : - GetSelection(); - - /* 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 = m_objects_model->GetItemType(sel_item) & itSettings ? m_objects_model->GetParent(sel_item) : sel_item; const ItemType item_type = m_objects_model->GetItemType(item); - /* 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 ((item_type & itVolume) && bundle_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); - } - if (!m_config) m_config = &get_item_config(item); assert(m_config); auto opt_keys = m_config->keys(); - const wxString snapshot_text = item_type & itLayer ? _(L("Add Settings Bundle for Height range")) : - item_type & itVolume ? _(L("Add Settings Bundle for Sub-object")) : - _(L("Add Settings Bundle for Object")); + const wxString snapshot_text = item_type & itLayer ? _L("Add Settings Bundle for Height range") : + item_type & itVolume ? _L("Add Settings Bundle for Sub-object") : + _L("Add Settings Bundle for Object"); take_snapshot(snapshot_text); const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; @@ -1607,520 +1263,12 @@ void ObjectList::show_settings(const wxDataViewItem settings_item) update_selections_on_canvas(); } -wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const 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, "", - [this, type](wxCommandEvent&) { 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), "", - [this, type, item](wxCommandEvent&) { load_generic_subobject(item, type); }, "", menu); - } - - return sub_menu; -} - -void ObjectList::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(); - - wxWindow* parent = wxGetApp().plater(); - - if (mode == comAdvanced) { - append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "", - [this](wxCommandEvent&) { load_subobject(ModelVolumeType::MODEL_PART); }, - ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr, - [this]() { return is_instance_or_object_selected(); }, parent); - } - if (mode == comSimple) { - append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "", - [this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); }, - ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].second, nullptr, - [this]() { return is_instance_or_object_selected(); }, parent); - append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].first), "", - [this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_BLOCKER); }, - ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].second, nullptr, - [this]() { return is_instance_or_object_selected(); }, 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, - [this]() { return is_instance_or_object_selected(); }, parent); - } -} - -wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu) -{ - return append_menu_item(menu, wxID_ANY, _(L("Split to parts")), "", - [this](wxCommandEvent&) { split(); }, "split_parts_SMALL", menu, - [this]() { return is_splittable(); }, wxGetApp().plater()); -} - bool ObjectList::is_instance_or_object_selected() { const Selection& selection = scene_selection(); return selection.is_single_full_instance() || selection.is_single_full_object(); } -wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu, wxWindow* parent) -{ - return append_menu_item(menu, wxID_ANY, _(L("Height range Modifier")), "", - [this](wxCommandEvent&) { layers_editing(); }, "edit_layers_all", menu, - [this]() { return is_instance_or_object_selected(); }, parent); -} - -wxMenuItem* ObjectList::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 = scene_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 = 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(); - - // Add frequently settings - const ItemType item_type = m_objects_model->GetItemType(GetSelection()); - if (item_type == itUndef && !selection.is_single_full_object()) - return nullptr; - const bool is_object_settings = is_object_item(item_type); - create_freq_settings_popupmenu(menu, is_object_settings); - - if (mode == comAdvanced) - return nullptr; - - menu->SetSecondSeparator(); - - // Add full settings list - auto menu_item = new wxMenuItem(menu, wxID_ANY, menu_name); - menu_item->SetBitmap(m_bmp_cog.bmp()); - - menu_item->SetSubMenu(create_settings_popupmenu(menu)); - - return menu->Append(menu_item); -} - -wxMenuItem* ObjectList::append_menu_item_change_type(wxMenu* menu, wxWindow* parent/* = nullptr*/) -{ - return append_menu_item(menu, wxID_ANY, _(L("Change type")), "", - [this](wxCommandEvent&) { change_part_type(); }, "", menu, - [this]() { - wxDataViewItem item = GetSelection(); - return item.IsOk() || m_objects_model->GetItemType(item) == itVolume; - }, parent); -} - -wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent) -{ - wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")), "", - [this](wxCommandEvent&) { 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 - */ - parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) - { - const Selection& selection = wxGetApp().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(wxGetApp().plater()->can_set_instance_to_object()); - }, menu_item->GetId()); - - return menu_item; -} - -wxMenuItem* ObjectList::append_menu_item_printable(wxMenu* menu, wxWindow* /*parent*/) -{ - return append_menu_check_item(menu, wxID_ANY, _(L("Printable")), "", [this](wxCommandEvent&) { - const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); - wxDataViewItem item; - if (GetSelectedItemsCount() > 1 && selection.is_single_full_object()) - item = m_objects_model->GetItemById(selection.get_object_idx()); - else - item = GetSelection(); - - if (item) - toggle_printable_state(item); - }, menu); -} - -void ObjectList::append_menu_items_osx(wxMenu* menu) -{ - append_menu_item(menu, wxID_ANY, _(L("Rename")), "", - [this](wxCommandEvent&) { rename_item(); }, "", menu); - - menu->AppendSeparator(); -} - -wxMenuItem* ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu) -{ - if (!is_windows10()) - return nullptr; - Plater* plater = wxGetApp().plater(); - wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Fix through the Netfabb")), "", - [this](wxCommandEvent&) { fix_through_netfabb(); }, "", menu, - [plater]() {return plater->can_fix_through_netfabb(); }, plater); - menu->AppendSeparator(); - - return menu_item; -} - -void ObjectList::append_menu_item_export_stl(wxMenu* menu) const -{ - append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, "", - [](wxCommandEvent&) { wxGetApp().plater()->export_stl(false, true); }, "", menu); - menu->AppendSeparator(); -} - -void ObjectList::append_menu_item_reload_from_disk(wxMenu* menu) const -{ - append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")), - [](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, - []() { return wxGetApp().plater()->can_reload_from_disk(); }, wxGetApp().plater()); -} - -void ObjectList::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; - 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 = 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, "", - [this, i](wxCommandEvent&) { set_extruder_for_selected_items(i); }, *icons[icon_idx], menu, - [is_active_extruder]() { return !is_active_extruder; }, GUI::wxGetApp().plater()); - - } - - menu->AppendSubMenu(extruder_selection_menu, name); -} - -void ObjectList::append_menu_item_delete(wxMenu* menu) -{ - append_menu_item(menu, wxID_ANY, _(L("Delete")), "", - [this](wxCommandEvent&) { remove(); }, "", menu); -} - -void ObjectList::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&) { wxGetApp().plater()->scale_selection_to_fit_print_volume(); }, "", menu); -} - -void ObjectList::append_menu_items_convert_unit(wxMenu* menu, int insert_pos/* = 1*/) -{ - std::vector obj_idxs, vol_idxs; - 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 = [this, obj_idxs, vol_idxs, volume_respects_conversion](ConversionType conver_type) - { - ModelObjectPtrs objects; - for (int obj_idx : obj_idxs) { - ModelObject* object = (*m_objects)[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&) { wxGetApp().plater()->convert_unit(item.first); }, "", menu, - []() {return true; }, nullptr, insert_pos); - } - else if (menu_id != wxNOT_FOUND) { - // Delete menu item - menu->Destroy(menu_id); - } - } -} - -void ObjectList::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"), - [this](wxCommandEvent&) { merge(true); }, "", menu, - [this]() { return this->can_merge_to_multipart_object(); }, wxGetApp().plater()); -} - -void ObjectList::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"), - [this](wxCommandEvent&) { merge(false); }, "", menu, - [this]() { return this->can_merge_to_single_object(); }, wxGetApp().plater()); -} - -void ObjectList::create_object_popupmenu(wxMenu *menu) -{ -#ifdef __WXOSX__ - append_menu_items_osx(menu); -#endif // __WXOSX__ - - append_menu_item_reload_from_disk(menu); - append_menu_item_export_stl(menu); - append_menu_item_fix_through_netfabb(menu); - append_menu_item_scale_selection_to_fit_print_volume(menu); - - // Split object to parts - append_menu_item_split(menu); -// menu->AppendSeparator(); - - // Merge multipart object to the single object -// append_menu_item_merge_to_single_object(menu); - menu->AppendSeparator(); - - // Layers Editing for object - append_menu_item_layers_editing(menu, wxGetApp().plater()); - menu->AppendSeparator(); - - // rest of a object_menu will be added later in: - // - append_menu_items_add_volume() -> for "Add (volumes)" - // - append_menu_item_settings() -> for "Add (settings)" -} - -void ObjectList::create_sla_object_popupmenu(wxMenu *menu) -{ -#ifdef __WXOSX__ - append_menu_items_osx(menu); -#endif // __WXOSX__ - - append_menu_item_reload_from_disk(menu); - append_menu_item_export_stl(menu); - append_menu_item_fix_through_netfabb(menu); - // rest of a object_sla_menu will be added later in: - // - append_menu_item_settings() -> for "Add (settings)" -} - -void ObjectList::create_part_popupmenu(wxMenu *menu) -{ -#ifdef __WXOSX__ - append_menu_items_osx(menu); -#endif // __WXOSX__ - - append_menu_item_reload_from_disk(menu); - append_menu_item_export_stl(menu); - append_menu_item_fix_through_netfabb(menu); - - append_menu_item_split(menu); - - // Append change part type - menu->AppendSeparator(); - append_menu_item_change_type(menu); - - // rest of a object_sla_menu will be added later in: - // - append_menu_item_settings() -> for "Add (settings)" -} - -void ObjectList::create_instance_popupmenu(wxMenu*menu) -{ - m_menu_item_split_instances = append_menu_item_instance_to_object(menu, wxGetApp().plater()); -} - -void ObjectList::create_default_popupmenu(wxMenu*menu) -{ - wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::INVALID); - append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part", - [](){return true;}, this); -} - -wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) -{ - wxMenu *menu = new wxMenu; - - settings_menu_hierarchy settings_menu; - - /* 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 - */ - const wxDataViewItem selected_item = GetSelection(); - wxDataViewItem item = m_objects_model->GetItemType(selected_item) & itSettings ? m_objects_model->GetParent(selected_item) : selected_item; - - get_options_menu(settings_menu, !is_object_item(m_objects_model->GetItemType(item))); - - for (auto cat : settings_menu) { - append_menu_item(menu, wxID_ANY, _(cat.first), "", - [this, menu](wxCommandEvent& event) { get_settings_choice(menu->GetLabel(event.GetId())); }, - CATEGORY_ICON.find(cat.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(cat.first), parent_menu, - []() { return true; }, wxGetApp().plater()); - } - - return menu; -} - -void ObjectList::create_freq_settings_popupmenu(wxMenu *menu, const bool is_object_settings/* = true*/) -{ - // Add default settings bundles - const SettingsBundle& bundle = printer_technology() == ptFFF ? - FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA; - - const int extruders_cnt = extruders_count(); - - for (auto& it : bundle) { - if (improper_category(it.first, extruders_cnt, is_object_settings)) - continue; - - append_menu_item(menu, wxID_ANY, _(it.first), "", - [this, menu](wxCommandEvent& event) { get_freq_settings_choice(menu->GetLabel(event.GetId())); }, - CATEGORY_ICON.find(it.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(it.first), menu, - []() { return true; }, wxGetApp().plater()); - } -#if 0 - // Add "Quick" settings bundles - const SettingsBundle& bundle_quick = printer_technology() == ptFFF ? - m_freq_settings_fff : m_freq_settings_sla; - - for (auto& it : bundle_quick) { - if (improper_category(it.first, extruders_cnt)) - continue; - - append_menu_item(menu, wxID_ANY, from_u8((boost::format(_utf8(L("Quick Add Settings (%s)"))) % _(it.first)).str()), "", - [menu, this](wxCommandEvent& event) { get_freq_settings_choice(menu->GetLabel(event.GetId())); }, - CATEGORY_ICON.find(it.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(it.first), menu, - [this]() { return true; }, wxGetApp().plater()); - } -#endif -} - -void ObjectList::update_opt_keys(t_config_option_keys& opt_keys, const bool is_object) -{ - auto full_current_opts = get_options(!is_object); - 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); -} - void ObjectList::load_subobject(ModelVolumeType type) { wxDataViewItem item = GetSelection(); @@ -2152,9 +1300,7 @@ void ObjectList::load_subobject(ModelVolumeType type) if (sel_item) select_item(sel_item); -//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -//#endif //no __WXOSX__ //__WXMSW__ } void ObjectList::load_part( ModelObject* model_object, @@ -2297,9 +1443,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode const auto object_item = m_objects_model->GetTopParent(GetSelection()); select_item([this, object_item, name, type, new_volume]() { return m_objects_model->AddVolumeChild(object_item, name, type, new_volume->get_mesh_errors_count() > 0); }); -//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -//#endif //no __WXOSX__ //__WXMSW__ } void ObjectList::load_shape_object(const std::string& type_name) @@ -2503,6 +1647,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con wxString extruder = object->config.has("extruder") ? wxString::Format("%d", object->config.extruder()) : _L("default"); m_objects_model->SetExtruder(extruder, obj_item); } + // add settings to the object, if it has them + add_settings_item(obj_item, &object->config.get()); } } } @@ -2863,11 +2009,27 @@ bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& vo return true; } -bool ObjectList::is_splittable() +bool ObjectList::is_splittable(bool to_objects) { const wxDataViewItem item = GetSelection(); if (!item) return false; + if (to_objects) + { + ItemType type = m_objects_model->GetItemType(item); + if (type == itVolume) + return false; + if (type == itObject || m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itObject) { + auto obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) + return false; + if ((*m_objects)[obj_idx]->volumes.size() > 1) + return true; + return (*m_objects)[obj_idx]->volumes[0]->is_splittable(); + } + return false; + } + ModelVolume* volume; if (!get_volume_by_item(item, volume) || !volume) return false; @@ -3044,37 +2206,6 @@ void ObjectList::part_selection_changed() panel.Thaw(); } -SettingsBundle ObjectList::get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_object_settings) -{ - auto opt_keys = config->keys(); - if (opt_keys.empty()) - return SettingsBundle(); - - update_opt_keys(opt_keys, is_object_settings); // update options list according to print technology - - if (opt_keys.empty()) - return SettingsBundle(); - - const int extruders_cnt = wxGetApp().extruders_edited_cnt(); - - SettingsBundle bundle; - for (auto& opt_key : opt_keys) - { - auto category = config->def()->get(opt_key)->category; - if (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; -} - // Add new SettingsItem for parent_item if it doesn't exist, or just update a digest according to new config wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config) { @@ -3084,7 +2215,13 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D return ret; const bool is_object_settings = m_objects_model->GetItemType(parent_item) == itObject; - SettingsBundle cat_options = get_item_settings_bundle(config, is_object_settings); + if (!is_object_settings) { + ModelVolumeType volume_type = m_objects_model->GetVolumeType(parent_item); + if (volume_type == ModelVolumeType::SUPPORT_BLOCKER || volume_type == ModelVolumeType::SUPPORT_ENFORCER) + return ret; + } + + SettingsFactory::Bundle cat_options = SettingsFactory::get_bundle(config, is_object_settings); if (cat_options.empty()) return ret; @@ -3687,13 +2824,12 @@ void ObjectList::update_selections() if ( ( m_selection_mode & (smSettings|smLayer|smLayerRoot) ) == 0) m_selection_mode = smInstance; - // We doesn't update selection if SettingsItem for the current object/part is selected -// if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) + // We doesn't update selection if itSettings | itLayerRoot | itLayer Item for the current object/part is selected if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) & (itSettings | itLayerRoot | itLayer)) { const auto item = GetSelection(); if (selection.is_single_full_object()) { - if (m_objects_model->GetObjectIdByItem(item) == selection.get_object_idx()) + if (m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itObject) return; sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); } @@ -4161,9 +3297,15 @@ void ObjectList::fix_multiselection_conflicts() ModelVolume* ObjectList::get_selected_model_volume() { - auto item = GetSelection(); - if (!item || m_objects_model->GetItemType(item) != itVolume) + wxDataViewItem item = GetSelection(); + if (!item) return nullptr; + if (m_objects_model->GetItemType(item) != itVolume) { + if (m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume) + item = m_objects_model->GetParent(item); + else + return nullptr; + } const auto vol_idx = m_objects_model->GetVolumeIdByItem(item); const auto obj_idx = get_selected_obj_idx(); @@ -4206,8 +3348,10 @@ void ObjectList::change_part_type() take_snapshot(_(L("Change Part Type"))); - const auto item = GetSelection(); volume->set_type(new_type); + wxDataViewItem item = GetSelection(); + if (m_objects_model->GetItemType(item) != itVolume && m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume) + item = m_objects_model->GetParent(item); m_objects_model->SetVolumeType(item, new_type); changed_object(get_selected_obj_idx()); @@ -4351,11 +3495,6 @@ void ObjectList::update_object_list_by_printer_technology() m_prevent_canvas_selection_update = false; } -void ObjectList::update_object_menu() -{ - append_menu_items_add_volume(&m_menu_object); -} - void ObjectList::instances_to_separated_object(const int obj_idx, const std::set& inst_idxs) { if ((*m_objects)[obj_idx]->instances.size() == inst_idxs.size()) @@ -4528,41 +3667,17 @@ void ObjectList::msw_rescale() GetColumn(colExtruder)->SetWidth( 8 * em); GetColumn(colEditing )->SetWidth( 3 * em); - // rescale all icons, used by ObjectList - msw_rescale_icons(); - // rescale/update existing items with bitmaps m_objects_model->Rescale(); - // rescale menus - for (MenuWithSeparators* menu : { &m_menu_object, - &m_menu_part, - &m_menu_sla_object, - &m_menu_instance, - &m_menu_layer, - &m_menu_default}) - msw_rescale_menu(menu); - Layout(); } void ObjectList::sys_color_changed() { - // msw_rescale_icons() updates icons, so use it - msw_rescale_icons(); - // update existing items with bitmaps m_objects_model->Rescale(); - // msw_rescale_menu updates just icons, so use it - for (MenuWithSeparators* menu : { &m_menu_object, - &m_menu_part, - &m_menu_sla_object, - &m_menu_instance, - &m_menu_layer, - &m_menu_default}) - msw_rescale_menu(menu); - Layout(); } @@ -4607,33 +3722,6 @@ void ObjectList::OnEditingDone(wxDataViewEvent &event) plater->set_current_canvas_as_dirty(); } -void ObjectList::show_multi_selection_menu() -{ - wxDataViewItemArray sels; - GetSelections(sels); - - for (const wxDataViewItem& item : sels) - if (!(m_objects_model->GetItemType(item) & (itVolume | itObject | itInstance))) - // show this menu only for Objects(Instances mixed with Objects)/Volumes selection - return; - - wxMenu* menu = new wxMenu(); - - if (extruders_count() > 1) - append_menu_item_change_extruder(menu); - - append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")), - [](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, []() { - return wxGetApp().plater()->can_reload_from_disk(); - }, wxGetApp().plater()); - - append_menu_items_convert_unit(menu); - if (can_merge_to_multipart_object()) - append_menu_item_merge_to_multipart_object(menu); - - wxGetApp().plater()->PopupMenu(menu); -} - void ObjectList::extruder_selection() { wxArrayString choices; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 0846def53..35a8fdee0 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -31,18 +31,11 @@ class TriangleMesh; enum class ModelVolumeType : int; // FIXME: broken build on mac os because of this is missing: -typedef std::vector t_config_option_keys; - -typedef std::map> SettingsBundle; - -// category -> vector ( option ; label ) -typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; - -typedef std::vector ModelVolumePtrs; - -typedef double coordf_t; -typedef std::pair t_layer_height_range; -typedef std::map t_layer_config_ranges; +typedef std::vector t_config_option_keys; +typedef std::vector ModelVolumePtrs; +typedef double coordf_t; +typedef std::pair t_layer_height_range; +typedef std::map t_layer_config_ranges; namespace GUI { @@ -106,7 +99,7 @@ public: private: SELECTION_MODE m_selection_mode {smUndef}; - int m_selected_layers_range_idx; + int m_selected_layers_range_idx {-1}; Clipboard m_clipboard; @@ -147,23 +140,6 @@ private: } m_dragged_data; wxBoxSizer *m_sizer {nullptr}; - wxWindow *m_parent {nullptr}; - - ScalableBitmap m_bmp_modifiermesh; - ScalableBitmap m_bmp_solidmesh; - ScalableBitmap m_bmp_support_enforcer; - ScalableBitmap m_bmp_support_blocker; - ScalableBitmap m_bmp_manifold_warning; - ScalableBitmap m_bmp_cog; - - MenuWithSeparators m_menu_object; - MenuWithSeparators m_menu_part; - MenuWithSeparators m_menu_sla_object; - MenuWithSeparators m_menu_instance; - MenuWithSeparators m_menu_layer; - MenuWithSeparators m_menu_default; - wxMenuItem* m_menu_item_settings { nullptr }; - wxMenuItem* m_menu_item_split_instances { nullptr }; ObjectDataViewModel *m_objects_model{ nullptr }; ModelConfig *m_config {nullptr}; @@ -185,7 +161,6 @@ private: // update_settings_items - updating canvas selection is undesirable, // because it would turn off the gizmos (mainly a problem for the SLA gizmo) - int m_selected_row = 0; wxDataViewItem m_last_selected_item {nullptr}; #ifdef __WXMSW__ // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. @@ -193,8 +168,8 @@ private: #endif /* __MSW__ */ #if 0 - SettingsBundle m_freq_settings_fff; - SettingsBundle m_freq_settings_sla; + SettingsFactory::Bundle m_freq_settings_fff; + SettingsFactory::Bundle m_freq_settings_sla; #endif size_t m_items_count { size_t(-1) }; @@ -212,8 +187,6 @@ public: void set_min_height(); void update_min_height(); - std::map CATEGORY_ICON; - ObjectDataViewModel* GetModel() const { return m_objects_model; } ModelConfig* config() const { return m_config; } std::vector* objects() const { return m_objects; } @@ -221,7 +194,6 @@ public: ModelObject* object(const int obj_idx) const ; void create_objects_ctrl(); - void create_popup_menus(); void update_objects_list_extruder_column(size_t extruders_count); void update_extruder_colors(); // show/hide "Extruder" column for Objects List @@ -232,9 +204,6 @@ public: void update_name_in_model(const wxDataViewItem& item) const; void update_extruder_values_for_items(const size_t max_extruder); - void init_icons(); - void msw_rescale_icons(); - // Get obj_idx and vol_idx values for the selected (by default) or an adjusted item void get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& item = wxDataViewItem(0)); void get_selection_indexes(std::vector& obj_idxs, std::vector& vol_idxs); @@ -264,39 +233,11 @@ public: void increase_instances(); void decrease_instances(); - void get_settings_choice(const wxString& category_name); - void get_freq_settings_choice(const wxString& bundle_name); + void add_category_to_settings_from_selection(const std::vector< std::pair >& category_options, wxDataViewItem item); + void add_category_to_settings_from_frequent(const std::vector& category_options, wxDataViewItem item); void show_settings(const wxDataViewItem settings_item); bool is_instance_or_object_selected(); - wxMenu* append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type); - void append_menu_items_add_volume(wxMenu* menu); - wxMenuItem* append_menu_item_split(wxMenu* menu); - wxMenuItem* append_menu_item_layers_editing(wxMenu* menu, wxWindow* parent); - wxMenuItem* append_menu_item_settings(wxMenu* menu); - wxMenuItem* append_menu_item_change_type(wxMenu* menu, wxWindow* parent = nullptr); - wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent); - wxMenuItem* append_menu_item_printable(wxMenu* menu, wxWindow* parent); - void append_menu_items_osx(wxMenu* menu); - wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); - void append_menu_item_export_stl(wxMenu* menu) const; - void append_menu_item_reload_from_disk(wxMenu* menu) const; - void append_menu_item_change_extruder(wxMenu* menu); - void append_menu_item_delete(wxMenu* menu); - void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); - void append_menu_items_convert_unit(wxMenu* menu, int insert_pos = 1); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk" - void append_menu_item_merge_to_multipart_object(wxMenu *menu); - void append_menu_item_merge_to_single_object(wxMenu *menu); - void create_object_popupmenu(wxMenu *menu); - void create_sla_object_popupmenu(wxMenu*menu); - void create_part_popupmenu(wxMenu*menu); - void create_instance_popupmenu(wxMenu*menu); - void create_default_popupmenu(wxMenu *menu); - wxMenu* create_settings_popupmenu(wxMenu *parent_menu); - void create_freq_settings_popupmenu(wxMenu *parent_menu, const bool is_object_settings = true); - - void update_opt_keys(t_config_option_keys& t_optopt_keys, const bool is_object); - void load_subobject(ModelVolumeType type); void load_part(ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); @@ -318,7 +259,7 @@ public: DynamicPrintConfig get_default_layer_config(const int obj_idx); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); - bool is_splittable(); + bool is_splittable(bool to_objects); bool selected_instances_of_same_object(); bool can_split_instances(); bool can_merge_to_multipart_object() const; @@ -328,7 +269,6 @@ public: wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; ModelConfig& get_item_config(const wxDataViewItem& item) const; - SettingsBundle get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_object_settings); void changed_object(const int obj_idx = -1) const; void part_selection_changed(); @@ -404,11 +344,9 @@ public: void change_part_type(); void last_volume_is_deleted(const int obj_idx); - void update_settings_items(); void update_and_show_object_settings_item(); void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); void update_object_list_by_printer_technology(); - void update_object_menu(); void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); void instances_to_separated_objects(const int obj_idx); @@ -433,7 +371,7 @@ public: void update_printable_state(int obj_idx, int instance_idx); void toggle_printable_state(wxDataViewItem item); - void show_multi_selection_menu(); + void set_extruder_for_selected_items(const int extruder) const ; private: #ifdef __WXOSX__ @@ -454,11 +392,6 @@ private: #endif /* __WXMSW__ */ void OnEditingDone(wxDataViewEvent &event); void extruder_selection(); - void set_extruder_for_selected_items(const int extruder) const ; - - std::vector get_options(const bool is_part); - const std::vector& get_options_for_bundle(const wxString& bundle_name); - void get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part); }; diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 2501ea499..e6689b43c 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -1,5 +1,6 @@ #include "GUI_ObjectSettings.hpp" #include "GUI_ObjectList.hpp" +#include "GUI_Factories.hpp" #include "OptionsGroup.hpp" #include "GUI_App.hpp" @@ -83,7 +84,7 @@ bool ObjectSettings::update_settings_list() return false; const bool is_object_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itObject; - SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(&config->get(), is_object_settings); + SettingsFactory::Bundle cat_options = SettingsFactory::get_bundle(&config->get(), is_object_settings); if (!cat_options.empty()) { diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 2f11d66f5..9f9f20ffb 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1,4 +1,5 @@ #include "libslic3r/libslic3r.h" +#include "libslic3r/Layer.hpp" #include "GUI_Preview.hpp" #include "GUI_App.hpp" #include "GUI.hpp" @@ -24,6 +25,7 @@ // this include must follow the wxWidgets ones or it won't compile on Windows -> see http://trac.wxwidgets.org/ticket/2421 #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" +#include "NotificationManager.hpp" namespace Slic3r { namespace GUI { @@ -639,6 +641,53 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee else m_layers_slider->SetLayersTimes(m_gcode_result->time_statistics.modes.front().layers_times); + // Suggest the auto color change, if model looks like sign + if (ticks_info_from_model.gcodes.empty()) + { + NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); + notif_mngr->close_notification_of_type(NotificationType::SignDetected); + + const Print& print = wxGetApp().plater()->fff_print(); + double delta_area = scale_(scale_(25)); // equal to 25 mm2 + + //bool is_possible_auto_color_change = false; + for (auto object : print.objects()) { + double height = object->height(); + coord_t longer_side = std::max(object->size().x(), object->size().y()); + if (height / longer_side > 0.3) + continue; + + const ExPolygons& bottom = object->get_layer(0)->lslices; + if (bottom.size() > 1 || !bottom[0].holes.empty()) + continue; + + double bottom_area = area(bottom); + int i; + for (i = 1; i < int(0.3 * object->layers().size()); i++) + if (area(object->get_layer(1)->lslices) != bottom_area) + break; + if (i < int(0.3 * object->layers().size())) + continue; + + double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); + if( bottom_area - top_area > delta_area) { + notif_mngr->push_notification( + NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotification, + _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", + _u8L("Apply auto color change to print"), + [this, notif_mngr](wxEvtHandler*) { + notif_mngr->close_notification_of_type(NotificationType::SignDetected); + m_layers_slider->auto_color_change(); + return true; + }); + + notif_mngr->set_in_preview(true); + + break; + } + } + } + m_layers_slider_sizer->Show((size_t)0); Layout(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index a34c7562e..7f6b10670 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -200,12 +200,20 @@ void HollowedMesh::on_update() if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; if (timestamp > m_old_hollowing_timestamp) { - const TriangleMesh& backend_mesh = print_object->get_mesh_to_print(); + const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice(); if (! backend_mesh.empty()) { m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse(); m_hollowed_mesh_transformed->transform(trafo_inv); m_old_hollowing_timestamp = timestamp; + + const TriangleMesh &interior = print_object->hollowed_interior_mesh(); + if (!interior.empty()) { + m_hollowed_interior_transformed = std::make_unique(interior); + m_hollowed_interior_transformed->repaired = false; + m_hollowed_interior_transformed->repair(true); + m_hollowed_interior_transformed->transform(trafo_inv); + } } else m_hollowed_mesh_transformed.reset(nullptr); @@ -230,6 +238,10 @@ const TriangleMesh* HollowedMesh::get_hollowed_mesh() const return m_hollowed_mesh_transformed.get(); } +const TriangleMesh* HollowedMesh::get_hollowed_interior() const +{ + return m_hollowed_interior_transformed.get(); +} @@ -306,6 +318,10 @@ void ObjectClipper::on_update() m_clippers.back()->set_mesh(*mesh); } m_old_meshes = meshes; + + if (has_hollowed) + m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); + m_active_inst_bb_radius = mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); //if (has_hollowed && m_clp_ratio != 0.) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 61c273297..ace256748 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -199,6 +199,7 @@ public: #endif // NDEBUG const TriangleMesh* get_hollowed_mesh() const; + const TriangleMesh* get_hollowed_interior() const; protected: void on_update() override; @@ -206,6 +207,7 @@ protected: private: std::unique_ptr m_hollowed_mesh_transformed; + std::unique_ptr m_hollowed_interior_transformed; size_t m_old_hollowing_timestamp = 0; int m_print_object_idx = -1; int m_print_objects_count = 0; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 1ed4b492f..db7af046b 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -51,7 +51,9 @@ static const std::map font_icons_large = { {ImGui::EjectButton , "notification_eject_sd" }, {ImGui::EjectHoverButton , "notification_eject_sd_hover" }, {ImGui::WarningMarker , "notification_warning" }, - {ImGui::ErrorMarker , "notification_error" } + {ImGui::ErrorMarker , "notification_error" }, + {ImGui::CancelButton , "notification_cancel" }, + {ImGui::CancelHoverButton , "notification_cancel_hover" }, }; const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f }; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index c1aadb370..f50c7e356 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -562,8 +562,6 @@ void MainFrame::init_tabpanel() wxGetApp().plater_ = m_plater; - wxGetApp().obj_list()->create_popup_menus(); - if (wxGetApp().is_editor()) create_preset_tabs(); @@ -1219,6 +1217,9 @@ void MainFrame::init_menubar_as_editor() append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse sidebar") + sep + "Shift+" + sep_space + "Tab", _L("Collapse sidebar"), [this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this, []() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this); + append_menu_check_item(viewMenu, wxID_ANY, _L("&Full screen") + "\t" + "F11", _L("Full screen"), + [this](wxCommandEvent&) { this->ShowFullScreen(!this->IsFullScreen()); }, this, + []() { return true; }, [this]() { return this->IsFullScreen(); }, this); } // Help menu diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index ee0abe76f..f9ccfd0d6 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -2,6 +2,7 @@ #include "libslic3r/Tesselate.hpp" #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/ClipperUtils.hpp" #include "slic3r/GUI/Camera.hpp" @@ -31,6 +32,15 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh) } } +void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) +{ + if (m_negative_mesh != &mesh) { + m_negative_mesh = &mesh; + m_triangles_valid = false; + m_triangles2d.resize(0); + } +} + void MeshClipper::set_transformation(const Geometry::Transformation& trafo) @@ -74,6 +84,15 @@ void MeshClipper::recalculate_triangles() std::vector list_of_expolys; m_tms->set_up_direction(up.cast()); m_tms->slice(std::vector{height_mesh}, SlicingMode::Regular, 0.f, &list_of_expolys, [](){}); + + if (m_negative_mesh && !m_negative_mesh->empty()) { + TriangleMeshSlicer negative_tms{m_negative_mesh}; + negative_tms.set_up_direction(up.cast()); + + std::vector neg_polys; + negative_tms.slice(std::vector{height_mesh}, SlicingMode::Regular, 0.f, &neg_polys, [](){}); + list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front()); + } m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.); // Rotate the cut into world coords: diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 60dcb30c8..09caf199b 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -78,6 +78,8 @@ public: // must make sure that it stays valid. void set_mesh(const TriangleMesh& mesh); + void set_negative_mesh(const TriangleMesh &mesh); + // Inform the MeshClipper about the transformation that transforms the mesh // into world coordinates. void set_transformation(const Geometry::Transformation& trafo); @@ -91,6 +93,7 @@ private: Geometry::Transformation m_trafo; const TriangleMesh* m_mesh = nullptr; + const TriangleMesh* m_negative_mesh = nullptr; ClippingPlane m_plane; std::vector m_triangles2d; GLIndexedVertexArray m_vertex_array; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index a6fd9cfd3..1dfd02c2f 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1,11 +1,9 @@ #include "NotificationManager.hpp" -#include "GUI_App.hpp" -#include "GUI.hpp" -#include "Plater.hpp" -#include "GLCanvas3D.hpp" -#include "ImGuiWrapper.hpp" +#include "GUI.hpp" +#include "ImGuiWrapper.hpp" +#include "PrintHostDialogs.hpp" #include "wxExtensions.hpp" #include @@ -22,9 +20,6 @@ static constexpr float SPACE_RIGHT_PANEL = 10.0f; static constexpr float FADING_OUT_DURATION = 2.0f; // Time in Miliseconds after next render when fading out is requested static constexpr int FADING_OUT_TIMEOUT = 100; -// If timeout is changed to higher than 1 second, substract_time call should be revorked -//static constexpr int MAX_TIMEOUT_MILISECONDS = 1000; -//static constexpr int MAX_TIMEOUT_SECONDS = 1; namespace Slic3r { namespace GUI { @@ -131,35 +126,35 @@ void NotificationManager::NotificationIDProvider::release_id(int) {} NotificationManager::PopNotification::PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler) : m_data (n) , m_id_provider (id_provider) - , m_remaining_time (n.duration) - , m_last_remaining_time (n.duration) - , m_counting_down (n.duration != 0) , m_text1 (n.text1) , m_hypertext (n.hypertext) , m_text2 (n.text2) , m_evt_handler (evt_handler) , m_notification_start (GLCanvas3D::timestamp_now()) -{ - //init(); -} +{} void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) { - if (!m_initialized) + + if (m_state == EState::Unknown) init(); - if (m_hidden) { + if (m_state == EState::Hidden) { m_top_y = initial_y - GAP_WIDTH; return; } - if (m_fading_out) - m_last_render_fading = GLCanvas3D::timestamp_now(); + if (m_state == EState::ClosePending || m_state == EState::Finished) + { + m_state = EState::Finished; + return; + } - Size cnv_size = canvas.get_canvas_size(); + Size cnv_size = canvas.get_canvas_size(); ImGuiWrapper& imgui = *wxGetApp().imgui(); - ImVec2 mouse_pos = ImGui::GetMousePos(); - float right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0); + ImVec2 mouse_pos = ImGui::GetMousePos(); + float right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0); + bool fading_pop = false; if (m_line_height != ImGui::CalcTextSize("A").y) init(); @@ -174,54 +169,46 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); // find if hovered - m_hovered = false; + if (m_state == EState::Hovered) + m_state = EState::Shown; + if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) { ImGui::SetNextWindowFocus(); - m_hovered = true; + m_state = EState::Hovered; } - + // color change based on fading out - bool fading_pop = false; - if (m_fading_out) { - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity); + if (m_state == EState::FadingOut) { + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), true, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), true, m_current_fade_opacity); fading_pop = true; } - + // background color if (m_is_gray) { ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); } else if (m_data.level == NotificationLevel::ErrorNotification) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); } else if (m_data.level == NotificationLevel::WarningNotification) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; backcolor.y += 0.15f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); } - - // name of window - probably indentifies window and is shown so last_end add whitespaces according to id + + // name of window indentifies window - has to be unique string if (m_id == 0) m_id = m_id_provider.allocate_id(); std::string name = "!!Ntfctn" + std::to_string(m_id); + if (imgui.begin(name, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar)) { ImVec2 win_size = ImGui::GetWindowSize(); - //FIXME: dont forget to us this for texts - //GUI::format(_utf8(L())); - - /* - //countdown numbers - ImGui::SetCursorPosX(15); - ImGui::SetCursorPosY(15); - imgui.text(std::to_string(m_remaining_time).c_str()); - */ - render_left_sign(imgui); render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); @@ -253,9 +240,14 @@ void NotificationManager::PopNotification::count_spaces() m_window_width_offset = m_left_indentation + m_line_height * 3.f; m_window_width = m_line_height * 25; } + void NotificationManager::PopNotification::init() { - std::string text = m_text1 + " " + m_hypertext; + // Do not init closing notification + if (is_finished()) + return; + + std::string text = m_text1 + " " + m_hypertext; size_t last_end = 0; m_lines_count = 0; @@ -306,7 +298,9 @@ void NotificationManager::PopNotification::init() } if (m_lines_count == 3) m_multiline = true; - m_initialized = true; + m_notification_start = GLCanvas3D::timestamp_now(); + //if (m_state != EState::Hidden) + // m_state = EState::Shown; } void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) { @@ -375,12 +369,14 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); // line2 - std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(cursor_y); - imgui.text(line.c_str()); - cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; + if (m_text1.length() > m_endlines[0]) { + std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(line.c_str()); + cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; + } } else { ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(cursor_y); @@ -423,8 +419,8 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, m_multiline = true; set_next_window_size(imgui); } - else { - m_close_pending = on_text_click(); + else if (on_text_click()) { + close(); } } ImGui::PopStyleColor(); @@ -432,12 +428,12 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, ImGui::PopStyleColor(); //hover color - ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f);//ImGui::GetStyleColorVec4(ImGuiCol_Button); + ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) orange_color.y += 0.2f; //text - Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_state == EState::FadingOut, m_current_fade_opacity); ImGui::SetCursorPosX(text_x); ImGui::SetCursorPosY(text_y); imgui.text(text.c_str()); @@ -448,7 +444,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, lineEnd.y -= 2; ImVec2 lineStart = lineEnd; lineStart.x = ImGui::GetItemRectMin().x; - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f)))); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f)))); } @@ -458,12 +454,11 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); - //button - if part if treggered std::string button_text; button_text = ImGui::CloseNotifButton; @@ -479,7 +474,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) { - m_close_pending = true; + close(); } //invisible large button @@ -487,7 +482,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImGui::SetCursorPosY(0); if (imgui.button(" ", m_line_height * 2.125, win_size.y - ( m_minimize_b_visible ? 2 * m_line_height : 0))) { - m_close_pending = true; + close(); } ImGui::PopStyleColor(); ImGui::PopStyleColor(); @@ -510,9 +505,9 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); //button - if part if treggered @@ -564,71 +559,62 @@ bool NotificationManager::PopNotification::compare_text(const std::string& text) return false; } -void NotificationManager::PopNotification::update_state() +bool NotificationManager::PopNotification::update_state(bool paused, const int64_t delta) { - if (!m_initialized) - init(); m_next_render = std::numeric_limits::max(); - if (m_hidden) { - m_state = EState::Hidden; - return; + if (m_state == EState::Unknown) { + init(); + return true; + } + + if (m_state == EState::Hidden) { + return false; } int64_t now = GLCanvas3D::timestamp_now(); - if (m_hovered) { - // reset fading - m_fading_out = false; + // reset timers - hovered state is set in render + if (m_state == EState::Hovered) { m_current_fade_opacity = 1.0f; - m_remaining_time = m_data.duration; m_notification_start = now; - } - - - - if (m_counting_down) { + // Timers when not fading + } else if (m_state != EState::FadingOut && m_data.duration != 0 && !paused) { int64_t up_time = now - m_notification_start; + if (up_time >= m_data.duration * 1000) { + m_state = EState::FadingOut; + m_fading_start = now; + } else { + m_next_render = m_data.duration * 1000 - up_time; + } + } + // Timers when fading + if (m_state == EState::FadingOut && !paused) { + int64_t curr_time = now - m_fading_start; + int64_t next_render = FADING_OUT_TIMEOUT - delta; + m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f); + if (m_current_fade_opacity <= 0.0f) { + m_state = EState::Finished; + return true; + } else if (next_render <= 20) { + m_next_render = FADING_OUT_TIMEOUT; + return true; + } else { + m_next_render = next_render; + return false; + } + } - if (m_fading_out && m_current_fade_opacity <= 0.0f) - m_finished = true; - else if (!m_fading_out && /*m_remaining_time <=0*/up_time >= m_data.duration * 1000) { - m_fading_out = true; - m_fading_start = now; - m_last_render_fading = now; - } else if (!m_fading_out) { - m_next_render = m_data.duration * 1000 - up_time;//std::min(/*m_data.duration * 1000 - up_time*/m_remaining_time * 1000, MAX_TIMEOUT_MILISECONDS); - } - + if (m_state == EState::Finished) { + return true; } - - if (m_finished) { + + if (m_state == EState::ClosePending) { m_state = EState::Finished; - m_next_render = 0; - return; - } - if (m_close_pending) { - m_finished = true; - m_state = EState::ClosePending; - m_next_render = 0; - return; - } - if (m_fading_out) { - if (!m_paused) { - m_state = EState::FadingOutStatic; - int64_t curr_time = now - m_fading_start; - int64_t no_render_time = now - m_last_render_fading; - m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f); - auto next_render = FADING_OUT_TIMEOUT - no_render_time; - if (next_render <= 0) { - //m_last_render_fading = GLCanvas3D::timestamp_now(); - m_state = EState::FadingOutRender; - m_next_render = 0; - } else - m_next_render = next_render; - } + return true; } + return false; } NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool large) : @@ -672,9 +658,11 @@ void NotificationManager::SlicingCompleteLargeNotification::set_print_info(const void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l) { m_is_large = l; - m_counting_down = !l; + //FIXME this information should not be lost (change m_data?) +// m_counting_down = !l; m_hypertext = l ? _u8L("Export G-Code.") : std::string(); - m_hidden = !l; + m_state = l ? EState::Shown : EState::Hidden; + init(); } //---------------ExportFinishedNotification----------- void NotificationManager::ExportFinishedNotification::count_spaces() @@ -733,8 +721,8 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW ImVec2 win_pos(win_pos_x, win_pos_y); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); std::string button_text; @@ -768,7 +756,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW assert(m_evt_handler != nullptr); if (m_evt_handler != nullptr) wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED)); - m_close_pending = true; + close(); } //invisible large button @@ -779,7 +767,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW assert(m_evt_handler != nullptr); if (m_evt_handler != nullptr) wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED)); - m_close_pending = true; + close(); } ImGui::PopStyleColor(); ImGui::PopStyleColor(); @@ -799,30 +787,157 @@ void NotificationManager::ProgressBarNotification::init() m_lines_count++; m_endlines.push_back(m_endlines.back()); } +void NotificationManager::ProgressBarNotification::count_spaces() +{ + //determine line width + m_line_height = ImGui::CalcTextSize("A").y; + + m_left_indentation = m_line_height; + if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + std::string text; + text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + float picture_width = ImGui::CalcTextSize(text.c_str()).x; + m_left_indentation = picture_width + m_line_height / 2; + } + m_window_width_offset = m_line_height * (m_has_cancel_button ? 6 : 4); + m_window_width = m_line_height * 25; +} + void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + // line1 - we do not print any more text than what fits on line 1. Line 2 is bar. + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 - win_size_y / 6 - m_line_height / 2); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); - float invisible_length = 0;//((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x); - //invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame); - ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length - m_window_width_offset, win_pos_y + win_size_y/2 + m_line_height / 2); - ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y/2 + m_line_height / 2); - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.7f); - /* - //countdown line - ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); - float invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x); - invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame); - ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5); - ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5); - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f); - if (!m_paused) - m_countdown_frame++; - */ + ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); + ImVec4 gray_color = ImVec4(.34f, .34f, .34f, 1.0f); + ImVec2 lineEnd = ImVec2(win_pos_x - m_window_width_offset, win_pos_y + win_size_y / 2 + m_line_height / 4); + ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y / 2 + m_line_height / 4); + ImVec2 midPoint = ImVec2(lineStart.x + (lineEnd.x - lineStart.x) * m_percentage, lineStart.y); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); + ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); +} +//------PrintHostUploadNotification---------------- +void NotificationManager::PrintHostUploadNotification::set_percentage(float percent) +{ + if (m_uj_state == UploadJobState::PB_CANCELLED) + return; + m_percentage = percent; + if (percent >= 1.0f) { + m_uj_state = UploadJobState::PB_COMPLETED; + m_has_cancel_button = false; + } else if (percent < 0.0f) { + error(); + } else { + m_uj_state = UploadJobState::PB_PROGRESS; + m_has_cancel_button = true; + } +} +void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + std::string text; + switch (m_uj_state) { + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_PROGRESS: + { + ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + float uploaded = m_file_size / 100 * m_percentage; + std::stringstream stream; + stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "% - " << uploaded << " of " << m_file_size << "MB uploaded"; + text = stream.str(); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 /*- m_line_height / 4 * 3*/); + break; + } + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_ERROR: + text = _u8L("ERROR"); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); + break; + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_CANCELLED: + text = _u8L("CANCELED"); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); + break; + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_COMPLETED: + text = _u8L("COMPLETED"); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - m_line_height / 2); + break; + } + + imgui.text(text.c_str()); + +} +void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + std::string button_text; + button_text = ImGui::CancelButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y), + ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y), + true)) + { + button_text = ImGui::CancelHoverButton; + // tooltip + long time_now = wxGetLocalTime(); + if (m_hover_time > 0 && m_hover_time < time_now) { + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(_u8L("Cancel upload") + " " + GUI::shortkey_ctrl_prefix() + "T"); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + } + if (m_hover_time == 0) + m_hover_time = time_now; + } + else + m_hover_time = 0; + + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + assert(m_evt_handler != nullptr); + if (m_evt_handler != nullptr) { + auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, m_job_id, m_job_id); + wxQueueEvent(m_evt_handler, evt); + } + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.f, win_size.y)) + { + assert(m_evt_handler != nullptr); + if (m_evt_handler != nullptr) { + auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, m_job_id, m_job_id); + wxQueueEvent(m_evt_handler, evt); + } + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + } //------NotificationManager-------- NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : @@ -881,12 +996,7 @@ void NotificationManager::push_plater_error_notification(const std::string& text { push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, 0); } -void NotificationManager::push_plater_warning_notification(const std::string& text) -{ - push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }, 0); - // dissaper if in preview - set_in_preview(m_in_preview); -} + void NotificationManager::close_plater_error_notification(const std::string& text) { for (std::unique_ptr ¬ification : m_pop_notifications) { @@ -895,11 +1005,32 @@ void NotificationManager::close_plater_error_notification(const std::string& tex } } } + +void NotificationManager::push_plater_warning_notification(const std::string& text) +{ + // Find if was not hidden + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) { + if (notification->get_state() == PopNotification::EState::Hidden) { + //dynamic_cast(notification.get())->show(); + return; + } + } + } + + NotificationData data{ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }; + + auto notification = std::make_unique(data, m_id_provider, m_evt_handler); + push_notification_data(std::move(notification), 0); + // dissaper if in preview + set_in_preview(m_in_preview); +} + void NotificationManager::close_plater_warning_notification(const std::string& text) { for (std::unique_ptr ¬ification : m_pop_notifications) { if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) { - notification->close(); + dynamic_cast(notification.get())->real_close(); } } } @@ -997,23 +1128,50 @@ void NotificationManager::push_exporting_finished_notification(const std::string NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotification, on_removable ? 0 : 20, _u8L("Exporting finished.") + "\n" + path }; push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0); } -void NotificationManager::push_progress_bar_notification(const std::string& text, float percentage) + +void NotificationManager::push_upload_job_notification(wxEvtHandler* evt_handler, int id, float filesize, const std::string& filename, const std::string& host, float percentage) { - NotificationData data{ NotificationType::ProgressBar, NotificationLevel::ProgressBarNotification, 0, text }; - push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0), 0); + std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); + NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotification, 0, text }; + push_notification_data(std::make_unique(data, m_id_provider, evt_handler, 0, id, filesize), 0); } -void NotificationManager::set_progress_bar_percentage(const std::string& text, float percentage) +void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage) { - bool found = false; + std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); +// bool found = false; for (std::unique_ptr& notification : m_pop_notifications) { if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { - dynamic_cast(notification.get())->set_percentage(percentage); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - found = true; + dynamic_cast(notification.get())->set_percentage(percentage); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); +// found = true; } } + /* if (!found) { - push_progress_bar_notification(text, percentage); + push_upload_job_notification(id, filename, host, percentage); + } + */ +} +void NotificationManager::upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host) +{ + std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PrintHostUpload && notification->compare_text(text)) { + dynamic_cast(notification.get())->cancel(); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } + } +} +void NotificationManager::upload_job_notification_show_error(int id, const std::string& filename, const std::string& host) +{ + std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PrintHostUpload && notification->compare_text(text)) { + dynamic_cast(notification.get())->error(); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } } } bool NotificationManager::push_notification_data(const NotificationData& notification_data, int timestamp) @@ -1035,20 +1193,19 @@ bool NotificationManager::push_notification_data(std::unique_ptractivate_existing(notification.get())) { m_pop_notifications.back()->update(notification->get_data()); - canvas.request_extra_frame_delayed(33); + canvas.schedule_extra_frame(0); return false; } else { m_pop_notifications.emplace_back(std::move(notification)); - canvas.request_extra_frame_delayed(33); + canvas.schedule_extra_frame(0); return true; } } -void NotificationManager::render_notifications(float overlay_width) +void NotificationManager::render_notifications(GLCanvas3D& canvas, float overlay_width) { sort_notifications(); - - GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D(); + float last_y = 0.0f; for (const auto& notification : m_pop_notifications) { @@ -1059,7 +1216,49 @@ void NotificationManager::render_notifications(float overlay_width) } } - update_notifications(); + m_last_render = GLCanvas3D::timestamp_now(); +} + +bool NotificationManager::update_notifications(GLCanvas3D& canvas) +{ + // no update if not top window + 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()) + return false; + + // next_render() returns numeric_limits::max if no need for frame + const int64_t max = std::numeric_limits::max(); + int64_t next_render = max; + const int64_t time_since_render = GLCanvas3D::timestamp_now() - m_last_render; + bool request_render = false; + // During render, each notification detects if its currently hovered and changes its state to EState::Hovered + // If any notification is hovered, all restarts its countdown + bool hover = false; + for (const std::unique_ptr& notification : m_pop_notifications) { + if (notification->is_hovered()) { + hover = true; + break; + } + } + // update state of all notif and erase finished + for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { + std::unique_ptr& notification = *it; + request_render |= notification->update_state(hover, time_since_render); + next_render = std::min(next_render, notification->next_render()); + if (notification->get_state() == PopNotification::EState::Finished) + it = m_pop_notifications.erase(it); + else + ++it; + } + + // request next frame in future + if (next_render < max) + canvas.schedule_extra_frame(int(next_render)); + + return request_render; } void NotificationManager::sort_notifications() @@ -1080,9 +1279,11 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi const std::string &new_text = notification->get_data().text1; for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) { if ((*it)->get_type() == new_type && !(*it)->is_finished()) { - if (new_type == NotificationType::CustomNotification || new_type == NotificationType::PlaterWarning) { - if (!(*it)->compare_text(new_text)) + if (std::find(m_multiple_types.begin(), m_multiple_types.end(), new_type) != m_multiple_types.end()) { + // If found same type and same text, return true - update will be performed on the old notif + if ((*it)->compare_text(new_text) == false) { continue; + } } else if (new_type == NotificationType::SlicingWarning) { auto w1 = dynamic_cast(notification); auto w2 = dynamic_cast(it->get()); @@ -1094,7 +1295,6 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi continue; } } - if (it != m_pop_notifications.end() - 1) std::rotate(it, it + 1, m_pop_notifications.end()); return true; @@ -1108,107 +1308,12 @@ void NotificationManager::set_in_preview(bool preview) m_in_preview = preview; for (std::unique_ptr ¬ification : m_pop_notifications) { if (notification->get_type() == NotificationType::PlaterWarning) - notification->hide(preview); + notification->hide(preview); + if (notification->get_type() == NotificationType::SignDetected) + notification->hide(!preview); } } -void NotificationManager::update_notifications() -{ - // no update if not top window - 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()) - return; - - //static size_t last_size = m_pop_notifications.size(); - - //request frames - int64_t next_render = std::numeric_limits::max(); - for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { - std::unique_ptr& notification = *it; - notification->set_paused(m_hovered); - notification->update_state(); - next_render = std::min(next_render, notification->next_render()); - if (notification->get_state() == PopNotification::EState::Finished) - it = m_pop_notifications.erase(it); - else { - - ++it; - } - } - /* - m_requires_update = false; - for (const std::unique_ptr& notification : m_pop_notifications) { - if (notification->requires_update()) { - m_requires_update = true; - break; - } - } - */ - // update hovering state - m_hovered = false; - for (const std::unique_ptr& notification : m_pop_notifications) { - if (notification->is_hovered()) { - m_hovered = true; - break; - } - } - - /* - // Reuire render if some notification was just deleted. - size_t curr_size = m_pop_notifications.size(); - m_requires_render = m_hovered || (last_size != curr_size); - last_size = curr_size; - - // Ask notification if it needs render - if (!m_requires_render) { - for (const std::unique_ptr& notification : m_pop_notifications) { - if (notification->requires_render()) { - m_requires_render = true; - break; - } - } - } - // Make sure there will be update after last notification erased - if (m_requires_render) - m_requires_update = true; - */ - - - if (next_render == 0) - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(33); //few milliseconds to get from GLCanvas::render - else if (next_render < std::numeric_limits::max()) - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(int(next_render)); - - /* - // actualizate timers - 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()) - return; - - { - // Control the fade-out. - // time in seconds - long now = wxGetLocalTime(); - // Pausing fade-out when the mouse is over some notification. - if (!m_hovered && m_last_time < now) { - if (now - m_last_time >= MAX_TIMEOUT_SECONDS) { - for (auto& notification : m_pop_notifications) { - //if (notification->get_state() != PopNotification::EState::Static) - notification->substract_remaining_time(MAX_TIMEOUT_SECONDS); - } - m_last_time = now; - } - } - } - */ -} - bool NotificationManager::has_slicing_error_notification() { return std::any_of(m_pop_notifications.begin(), m_pop_notifications.end(), [](auto &n) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 62c4ea845..651deace8 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -1,6 +1,9 @@ #ifndef slic3r_GUI_NotificationManager_hpp_ #define slic3r_GUI_NotificationManager_hpp_ +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "GLCanvas3D.hpp" #include "Event.hpp" #include "I18N.hpp" @@ -66,12 +69,16 @@ enum class NotificationType PlaterWarning, // Progress bar instead of text. ProgressBar, + // Progress bar with info from Print Host Upload Queue dialog. + PrintHostUpload, // Notification, when Color Change G-code is empty and user try to add color change on DoubleSlider. EmptyColorChangeCode, // Notification that custom supports/seams were deleted after mesh repair. CustomSupportsAndSeamRemovedAfterRepair, // Notification that auto adding of color changes is impossible EmptyAutoColorChange, + // Notification about detected sign + SignDetected, // Notification emitted by Print::validate PrintValidateWarning, // Notification telling user to quit SLA supports manual editing @@ -140,21 +147,24 @@ public: // Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); // notification with progress bar - void push_progress_bar_notification(const std::string& text, float percentage = 0); - void set_progress_bar_percentage(const std::string& text, float percentage); + void push_upload_job_notification(wxEvtHandler* evt_handler, int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); + void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage); + void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host); + void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host); // Close old notification ExportFinished. void new_export_began(bool on_removable); // finds ExportFinished notification and closes it if it was to removable device void device_ejected(); // renders notifications in queue and deletes expired ones - void render_notifications(float overlay_width); + void render_notifications(GLCanvas3D& canvas, float overlay_width); // finds and closes all notifications of given type void close_notification_of_type(const NotificationType type); // Which view is active? Plater or G-code preview? Hide warnings in G-code preview. void set_in_preview(bool preview); // Move to left to avoid colision with variable layer height gizmo. void set_move_from_overlay(bool move) { m_move_from_overlay = move; } - + // perform update_state on each notification and ask for more frames if needed, return true for render needed + bool update_notifications(GLCanvas3D& canvas); private: // duration 0 means not disapearing struct NotificationData { @@ -192,23 +202,24 @@ private: enum class EState { - Unknown, + Unknown, // NOT initialized Hidden, - FadingOutRender, // Requesting Render - FadingOutStatic, + Shown, // Requesting Render at some time if duration != 0 + FadingOut, // Requesting Render at some time ClosePending, // Requesting Render Finished, // Requesting Render + Hovered, // Followed by Shown + Paused }; PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler); virtual ~PopNotification() { if (m_id) m_id_provider.release_id(m_id); } - void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width); + virtual void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width); // close will dissapear notification on next render - void close() { m_close_pending = true; } + virtual void close() { m_state = EState::ClosePending; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);} // data from newer notification of same type void update(const NotificationData& n); - bool is_finished() const { return m_finished || m_close_pending; } - bool is_hovered() const { return m_hovered; } + bool is_finished() const { return m_state == EState::ClosePending || m_state == EState::Finished; } // returns top after movement float get_top() const { return m_top_y; } //returns top in actual frame @@ -216,23 +227,17 @@ private: const NotificationType get_type() const { return m_data.type; } const NotificationData get_data() const { return m_data; } const bool is_gray() const { return m_is_gray; } - // Call equals one second down - void substract_remaining_time(int seconds) { m_remaining_time -= seconds; } void set_gray(bool g) { m_is_gray = g; } - void set_paused(bool p) { m_paused = p; } bool compare_text(const std::string& text); - void hide(bool h) { m_hidden = h; } - // sets m_next_render with time of next mandatory rendering - void update_state(); - int64_t next_render() const { return m_next_render; } - /* - bool requires_render() const { return m_state == EState::FadingOutRender || m_state == EState::ClosePending || m_state == EState::Finished; } - bool requires_update() const { return m_state != EState::Hidden; } - */ - EState get_state() const { return m_state; } - protected: + void hide(bool h) { if (is_finished()) return; m_state = h ? EState::Hidden : EState::Unknown; } + // sets m_next_render with time of next mandatory rendering. Delta is time since last render. + bool update_state(bool paused, const int64_t delta); + int64_t next_render() const { return is_finished() ? 0 : m_next_render; } + EState get_state() const { return m_state; } + bool is_hovered() const { return m_state == EState::Hovered; } + // Call after every size change - void init(); + virtual void init(); // Part of init() virtual void count_spaces(); // Calculetes correct size but not se it in imgui! @@ -254,45 +259,36 @@ private: // Hypertext action, returns true if notification should close. // Action is stored in NotificationData::callback as std::function virtual bool on_text_click(); - + protected: const NotificationData m_data; - // For reusing ImGUI windows. NotificationIDProvider &m_id_provider; + int m_id{ 0 }; + // State for rendering EState m_state { EState::Unknown }; - int m_id { 0 }; - bool m_initialized { false }; + // Time values for rendering fade-out + + int64_t m_fading_start{ 0LL }; + + // first appereance of notification or last hover; + int64_t m_notification_start; + // time to next must-do render + int64_t m_next_render{ std::numeric_limits::max() }; + float m_current_fade_opacity{ 1.0f }; + + // Notification data + // Main text std::string m_text1; // Clickable text std::string m_hypertext; // Aditional text after hypertext - currently not used std::string m_text2; - // Countdown variables - long m_remaining_time; - bool m_counting_down; - long m_last_remaining_time; - bool m_paused { false }; - int m_countdown_frame { 0 }; - bool m_fading_out { false }; - int64_t m_fading_start { 0LL }; - // time of last done render when fading - int64_t m_last_render_fading { 0LL }; - // first appereance of notification or last hover; - int64_t m_notification_start; - // time to next must-do render - int64_t m_next_render { std::numeric_limits::max() }; - float m_current_fade_opacity { 1.0f }; - // If hidden the notif is alive but not visible to user - bool m_hidden { false }; - // m_finished = true - does not render, marked to delete - bool m_finished { false }; - // Will go to m_finished next render - bool m_close_pending { false }; - bool m_hovered { false }; - // variables to count positions correctly + + // inner variables to position notification window, texts and buttons correctly + // all space without text float m_window_width_offset; // Space on left side without text @@ -302,9 +298,7 @@ private: float m_window_width { 450.0f }; //Distance from bottom of notifications to top of this notification float m_top_y { 0.0f }; - - // Height of text - // Used as basic scaling unit! + // Height of text - Used as basic scaling unit! float m_line_height; std::vector m_endlines; // Gray are f.e. eorrors when its uknown if they are still valid @@ -322,10 +316,16 @@ private: { public: SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool largeds); - void set_large(bool l); - bool get_large() { return m_is_large; } - - void set_print_info(const std::string &info); + void set_large(bool l); + bool get_large() { return m_is_large; } + void set_print_info(const std::string &info); + virtual void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) + { + // This notification is always hidden if !large (means side bar is collapsed) + if (!get_large() && !is_finished()) + m_state = EState::Hidden; + PopNotification::render(canvas, initial_y, move_from_overlay, overlay_width); + } protected: virtual void render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, @@ -344,21 +344,78 @@ private: int warning_step; }; + class PlaterWarningNotification : public PopNotification + { + public: + PlaterWarningNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) {} + virtual void close() { if(is_finished()) return; m_state = EState::Hidden; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } + void real_close() { m_state = EState::ClosePending; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); } + void show() { m_state = EState::Unknown; } + }; + + class ProgressBarNotification : public PopNotification { public: + ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { set_percentage(percentage); } - void set_percentage(float percent) { m_percentage = percent; if (percent >= 1.0f) m_progress_complete = true; else m_progress_complete = false; } + virtual void set_percentage(float percent) { m_percentage = percent; } protected: - virtual void init(); + virtual void init() override; + virtual void count_spaces() override; virtual void render_text(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); - void render_bar(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); - bool m_progress_complete{ false }; - float m_percentage; + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + virtual void render_bar(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + virtual void render_cancel_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) + {} + float m_percentage; + + bool m_has_cancel_button {false}; + // local time of last hover for showing tooltip + + }; + + + + class PrintHostUploadNotification : public ProgressBarNotification + { + public: + enum class UploadJobState + { + PB_PROGRESS, + PB_ERROR, + PB_CANCELLED, + PB_COMPLETED + }; + PrintHostUploadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage, int job_id, float filesize) + :ProgressBarNotification(n, id_provider, evt_handler, percentage) + , m_job_id(job_id) + , m_file_size(filesize) + { + m_has_cancel_button = true; + } + static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return "[" + std::to_string(id) + "] " + filename + " -> " + host; } + virtual void set_percentage(float percent); + void cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; } + void error() { m_uj_state = UploadJobState::PB_ERROR; m_has_cancel_button = false; } + protected: + virtual void render_bar(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + virtual void render_cancel_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + // Identifies job in cancel callback + int m_job_id; + // Size of uploaded size to be displayed in MB + float m_file_size; + long m_hover_time{ 0 }; + UploadJobState m_uj_state{ UploadJobState::PB_PROGRESS }; }; class ExportFinishedNotification : public PopNotification @@ -405,32 +462,25 @@ private: void sort_notifications(); // If there is some error notification active, then the "Export G-code" notification after the slicing is finished is suppressed. bool has_slicing_error_notification(); - // perform update_state on each notification and ask for more frames if needed - void update_notifications(); - + // Target for wxWidgets events sent by clicking on the hyperlink available at some notifications. wxEvtHandler* m_evt_handler; // Cache of IDs to identify and reuse ImGUI windows. NotificationIDProvider m_id_provider; std::deque> m_pop_notifications; - // Last render time in seconds for fade out control. - long m_last_time { 0 }; - // When mouse hovers over some notification, the fade-out of all notifications is suppressed. - bool m_hovered { false }; //timestamps used for slicing finished - notification could be gone so it needs to be stored here std::unordered_set m_used_timestamps; // True if G-code preview is active. False if the Plater is active. bool m_in_preview { false }; // True if the layer editing is enabled in Plater, so that the notifications are shifted left of it. bool m_move_from_overlay { false }; - + // Timestamp of last rendering + int64_t m_last_render { 0LL }; + // Notification types that can be shown multiple types at once (compared by text) + const std::vector m_multiple_types = { NotificationType::CustomNotification, NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload }; //prepared (basic) notifications const std::vector basic_notifications = { -// {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")}, -// {NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") }, {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, -// {NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") }, -// {NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") }, {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr) { if (evnthndlr != nullptr) diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 6433bf2d1..aba3404e7 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -91,7 +91,7 @@ void OG_CustomCtrl::init_ctrl_lines() height = m_bmp_blinking_sz.GetHeight() + m_v_gap; ctrl_lines.emplace_back(CtrlLine(height, this, line, true)); } - else if (opt_group->label_width != 0 && (!line.label.IsEmpty() || option_set.front().opt.gui_type == "legend") ) + else if (opt_group->label_width != 0 && (!line.label.IsEmpty() || option_set.front().opt.gui_type == ConfigOptionDef::GUIType::legend) ) { wxSize label_sz = GetTextExtent(line.label); height = label_sz.y * (label_sz.GetWidth() > int(opt_group->label_width * m_em_unit) ? 2 : 1) + m_v_gap; @@ -186,11 +186,11 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) #endif //__WXMSW__ h_pos += label_w + 1 + m_h_gap; } - h_pos += (opt.opt.gui_type == "legend" ? 1 : 3) * blinking_button_width; + h_pos += (opt.opt.gui_type == ConfigOptionDef::GUIType::legend ? 1 : 3) * blinking_button_width; if (field == field_in) break; - if (opt.opt.gui_type == "legend") + if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend) h_pos += 2 * blinking_button_width; h_pos += field->getWindow()->GetSize().x; @@ -580,7 +580,7 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_mode_bmp(wxDC& dc, wxCoord v_pos) wxBitmap bmp = create_scaled_bitmap(bmp_name, ctrl, wxOSX ? 10 : 12); wxCoord y_draw = v_pos + lround((height - get_bitmap_size(bmp).GetHeight()) / 2); - if (og_line.get_options().front().opt.gui_type != "legend") + if (og_line.get_options().front().opt.gui_type != ConfigOptionDef::GUIType::legend) dc.DrawBitmap(bmp, 0, y_draw); return get_bitmap_size(bmp).GetWidth() + ctrl->m_h_gap; diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 7de37fb48..80655352b 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -2,7 +2,7 @@ #include "wxExtensions.hpp" #include "BitmapCache.hpp" #include "GUI_App.hpp" -#include "GUI_ObjectList.hpp" +#include "GUI_Factories.hpp" #include "I18N.hpp" #include "libslic3r/Model.hpp" @@ -44,8 +44,9 @@ void ObjectDataViewModelNode::init_container() #endif //__WXGTK__ } -#define LAYER_ROOT_ICON "edit_layers_all" -#define LAYER_ICON "edit_layers_some" +static constexpr char LayerRootIcon[] = "edit_layers_all"; +static constexpr char LayerIcon[] = "edit_layers_some"; +static constexpr char WarningIcon[] = "exclamation"; ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type) : m_parent(parent), @@ -65,7 +66,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } else if (type == itLayerRoot) { - m_bmp = create_scaled_bitmap(LAYER_ROOT_ICON); // FIXME: pass window ptr + m_bmp = create_scaled_bitmap(LayerRootIcon); // FIXME: pass window ptr m_name = _(L("Layers")); } @@ -94,7 +95,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; - m_bmp = create_scaled_bitmap(LAYER_ICON); // FIXME: pass window ptr + m_bmp = create_scaled_bitmap(LayerIcon); // FIXME: pass window ptr set_action_and_extruder_icons(); init_container(); @@ -140,17 +141,14 @@ void ObjectDataViewModelNode::update_settings_digest_bitmaps() { m_bmp = m_empty_bmp; - std::map& categories_icon = Slic3r::GUI::wxGetApp().obj_list()->CATEGORY_ICON; - std::string scaled_bitmap_name = m_name.ToUTF8().data(); scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : ""); wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); if (bmp == nullptr) { std::vector bmps; - for (auto& cat : m_opt_categories) - bmps.emplace_back( categories_icon.find(cat) == categories_icon.end() ? - wxNullBitmap : categories_icon.at(cat)); + for (auto& category : m_opt_categories) + bmps.emplace_back(SettingsFactory::get_category_bitmap(category)); bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); } @@ -249,6 +247,9 @@ static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType roo ObjectDataViewModel::ObjectDataViewModel() { m_bitmap_cache = new Slic3r::GUI::BitmapCache; + + m_volume_bmps = MenuFactory::get_volume_bitmaps(); + m_warning_bmp = create_scaled_bitmap(WarningIcon); } ObjectDataViewModel::~ObjectDataViewModel() @@ -267,7 +268,7 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name, auto root = new ObjectDataViewModelNode(name, extruder_str); // Add error icon if detected auto-repaire if (has_errors) - root->m_bmp = *m_warning_bmp; + root->m_bmp = m_warning_bmp; m_objects.push_back(root); // notify control @@ -317,7 +318,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent // if part with errors is added, but object wasn't marked, then mark it if (!obj_errors && has_errors) - root->SetBitmap(*m_warning_bmp); + root->SetBitmap(m_warning_bmp); // notify control const wxDataViewItem child((void*)node); @@ -1434,10 +1435,20 @@ void ObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const Slic3r return; ObjectDataViewModelNode *node = static_cast(item.GetID()); - node->SetBitmap(*m_volume_bmps[int(type)]); + node->SetVolumeType(type); + node->SetBitmap(m_volume_bmps[int(type)]); ItemChanged(item); } +ModelVolumeType ObjectDataViewModel::GetVolumeType(const wxDataViewItem& item) +{ + if (!item.IsOk() || GetItemType(item) != itVolume) + return ModelVolumeType::INVALID; + + ObjectDataViewModelNode *node = static_cast(item.GetID()); + return node->GetVolumeType(); +} + wxDataViewItem ObjectDataViewModel::SetPrintableState( PrintIndicator printable, int obj_idx, @@ -1480,6 +1491,9 @@ wxDataViewItem ObjectDataViewModel::SetObjectPrintableState( void ObjectDataViewModel::Rescale() { + m_volume_bmps = MenuFactory::get_volume_bitmaps(); + m_warning_bmp = create_scaled_bitmap(WarningIcon); + wxDataViewItemArray all_items; GetAllChildren(wxDataViewItem(0), all_items); @@ -1494,15 +1508,15 @@ void ObjectDataViewModel::Rescale() switch (node->m_type) { case itObject: - if (node->m_bmp.IsOk()) node->m_bmp = *m_warning_bmp; + if (node->m_bmp.IsOk()) node->m_bmp = m_warning_bmp; break; case itVolume: node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_bmp.GetWidth() != node->m_bmp.GetHeight()); break; case itLayerRoot: - node->m_bmp = create_scaled_bitmap(LAYER_ROOT_ICON); + node->m_bmp = create_scaled_bitmap(LayerRootIcon); case itLayer: - node->m_bmp = create_scaled_bitmap(LAYER_ICON); + node->m_bmp = create_scaled_bitmap(LayerIcon); break; default: break; } @@ -1514,7 +1528,7 @@ void ObjectDataViewModel::Rescale() wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked/* = false*/) { if (!is_marked) - return *m_volume_bmps[static_cast(vol_type)]; + return m_volume_bmps[static_cast(vol_type)]; std::string scaled_bitmap_name = "warning" + std::to_string(static_cast(vol_type)); scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); @@ -1523,8 +1537,8 @@ wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_ty if (bmp == nullptr) { std::vector bmps; - bmps.emplace_back(*m_warning_bmp); - bmps.emplace_back(*m_volume_bmps[static_cast(vol_type)]); + bmps.emplace_back(m_warning_bmp); + bmps.emplace_back(m_volume_bmps[static_cast(vol_type)]); bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); } @@ -1543,7 +1557,7 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo return; if (node->GetType() & itVolume) { - node->SetBitmap(*m_volume_bmps[static_cast(node->volume_type())]); + node->SetBitmap(m_volume_bmps[static_cast(node->volume_type())]); return; } diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 17ad2047f..c23ec195b 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -171,13 +171,14 @@ public: } bool SetValue(const wxVariant &variant, unsigned int col); - + void SetVolumeType(ModelVolumeType type) { m_volume_type = type; } void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } const wxBitmap& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } ItemType GetType() const { return m_type; } void SetIdx(const int& idx); int GetIdx() const { return m_idx; } + ModelVolumeType GetVolumeType() { return m_volume_type; } t_layer_height_range GetLayerRange() const { return m_layer_range; } PrintIndicator IsPrintable() const { return m_printable; } @@ -241,8 +242,8 @@ wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); class ObjectDataViewModel :public wxDataViewModel { std::vector m_objects; - std::vector m_volume_bmps; - wxBitmap* m_warning_bmp { nullptr }; + std::vector m_volume_bmps; + wxBitmap m_warning_bmp; wxDataViewCtrl* m_ctrl { nullptr }; @@ -348,9 +349,8 @@ public: void UpdateObjectPrintable(wxDataViewItem parent_item); void UpdateInstancesPrintable(wxDataViewItem parent_item); - void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } - void SetWarningBitmap(wxBitmap* bitmap) { m_warning_bmp = bitmap; } void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); + ModelVolumeType GetVolumeType(const wxDataViewItem &item); wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, int subobj_idx = -1, ItemType subobj_type = itInstance); diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 0fa97bfc1..8e8774036 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -4,6 +4,7 @@ #include "GUI.hpp" #include "I18N.hpp" #include "3DScene.hpp" +#include "slic3r/Utils/Platform.hpp" #include @@ -319,7 +320,13 @@ void OpenGLManager::detect_multisample(int* attribList) { int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; bool enable_multisample = wxVersion >= 30003; - s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? EMultisampleState::Enabled : EMultisampleState::Disabled; + s_multisample = + enable_multisample && + // Disable multi-sampling on ChromeOS, as the OpenGL virtualization swaps Red/Blue channels with multi-sampling enabled, + // at least on some platforms. + (platform() != Platform::Linux || platform_flavor() != PlatformFlavor::LinuxOnChromium) && + wxGLCanvas::IsDisplaySupported(attribList) + ? EMultisampleState::Enabled : EMultisampleState::Disabled; // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows // s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample"); } diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index b87e2047d..c81f4c644 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -25,23 +25,27 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id) { const t_field& OptionsGroup::build_field(const t_config_option_key& id, const ConfigOptionDef& opt) { // Check the gui_type field first, fall through // is the normal type. - if (opt.gui_type == "select") { - } else if (opt.gui_type == "select_open") { + switch (opt.gui_type) { + case ConfigOptionDef::GUIType::select_open: m_fields.emplace(id, Choice::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "color") { + break; + case ConfigOptionDef::GUIType::color: m_fields.emplace(id, ColourPicker::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "f_enum_open" || - opt.gui_type == "i_enum_open" || - opt.gui_type == "i_enum_closed") { + break; + case ConfigOptionDef::GUIType::f_enum_open: + case ConfigOptionDef::GUIType::i_enum_open: m_fields.emplace(id, Choice::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "slider") { + break; + case ConfigOptionDef::GUIType::slider: m_fields.emplace(id, SliderCtrl::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "i_spin") { // Spinctrl - } else if (opt.gui_type == "legend") { // StaticText + break; + case ConfigOptionDef::GUIType::legend: // StaticText m_fields.emplace(id, StaticText::Create(this->ctrl_parent(), opt, id)); - } else if (opt.gui_type == "one_string") { + break; + case ConfigOptionDef::GUIType::one_string: m_fields.emplace(id, TextCtrl::Create(this->ctrl_parent(), opt, id)); - } else { + break; + default: switch (opt.type) { case coFloatOrPercent: case coFloat: @@ -122,7 +126,7 @@ bool OptionsGroup::is_legend_line() { if (m_lines.size() == 1) { const std::vector