diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 572b37c7f..2f2ca5fb5 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -64,6 +64,9 @@ void main() float world_normal_z_fs = world_normal_z; if (compute_triangle_normals_in_fs) { vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); +#ifdef FLIP_TRIANGLE_NORMALS + triangle_normal = -triangle_normal; +#endif // First transform the normal into camera space and normalize the result. eye_normal_fs = normalize(gl_NormalMatrix * triangle_normal); diff --git a/resources/shapes/OTHER_recycling_symbol.png b/resources/shapes/OTHER_recycling_symbol.png new file mode 100644 index 000000000..7dde19458 Binary files /dev/null and b/resources/shapes/OTHER_recycling_symbol.png differ diff --git a/resources/shapes/PETG_recycling_symbol.png b/resources/shapes/PETG_recycling_symbol.png new file mode 100644 index 000000000..1c985814e Binary files /dev/null and b/resources/shapes/PETG_recycling_symbol.png differ diff --git a/resources/shapes/box.png b/resources/shapes/box.png new file mode 100644 index 000000000..6df18fbc1 Binary files /dev/null and b/resources/shapes/box.png differ diff --git a/resources/shapes/bunny.png b/resources/shapes/bunny.png new file mode 100644 index 000000000..d7eafe8a8 Binary files /dev/null and b/resources/shapes/bunny.png differ diff --git a/resources/shapes/cylinder.png b/resources/shapes/cylinder.png new file mode 100644 index 000000000..07feae2e8 Binary files /dev/null and b/resources/shapes/cylinder.png differ diff --git a/resources/shapes/pyramid.png b/resources/shapes/pyramid.png new file mode 100644 index 000000000..5db425f69 Binary files /dev/null and b/resources/shapes/pyramid.png differ diff --git a/resources/shapes/sphere.png b/resources/shapes/sphere.png new file mode 100644 index 000000000..c3853d68d Binary files /dev/null and b/resources/shapes/sphere.png differ diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index b3e6841c9..3490b8183 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -330,6 +330,8 @@ int CLI::run(int argc, char **argv) } } else if (opt_key == "dont_arrange") { // do nothing - this option alters other transform options + } else if (opt_key == "ensure_on_bed") { + // do nothing, the value is used later } else if (opt_key == "rotate") { for (auto &model : m_models) for (auto &o : model.objects) @@ -432,6 +434,13 @@ int CLI::run(int argc, char **argv) } } + // All transforms have been dealt with. Now ensure that the objects are on bed. + // (Unless the user said otherwise.) + if (m_config.opt_bool("ensure_on_bed")) + for (auto &model : m_models) + for (auto &o : model.objects) + o->ensure_on_bed(); + // loop through action options for (auto const &opt_key : m_actions) { if (opt_key == "help") { diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index b9f9b266d..cbc434b39 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -871,6 +871,7 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option token.erase(equals_pos); } } + // Look for the cli -> option mapping. const auto it = opts.find(token); if (it == opts.end()) { @@ -879,15 +880,46 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option } const t_config_option_key opt_key = it->second; const ConfigOptionDef &optdef = this->def()->options.at(opt_key); + // If the option type expects a value and it was not already provided, // look for it in the next token. - if (optdef.type != coBool && optdef.type != coBools && value.empty()) { - if (i == (argc-1)) { - boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl; + if (value.empty()) { + if (optdef.type != coBool && optdef.type != coBools) { + if (i == (argc-1)) { + boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl; + return false; + } + value = argv[++ i]; + } else { + // This is a bool or bools. The value is optional, but may still be there. + // Check if the next token can be deserialized into ConfigOptionBool. + // If it is in fact bools, it will be rejected later anyway. + if (i != argc-1) { // There is still a token to read. + ConfigOptionBool cobool; + if (cobool.deserialize(argv[i+1])) + value = argv[++i]; + } + } + + } + + if (no) { + if (optdef.type != coBool && optdef.type != coBools) { + boost::nowide::cerr << "Only boolean config options can be negated with --no- prefix." << std::endl; + return false; + } + else if (! value.empty()) { + boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl; return false; } - value = argv[++ i]; } + if (optdef.type == coBools && ! value.empty()) { + boost::nowide::cerr << "Vector boolean options cannot have a value. Fill them in by " + "repeating them and negate by --no- prefix." << std::endl; + return false; + } + + // Store the option value. const bool existing = this->has(opt_key); if (keys != nullptr && ! existing) { @@ -911,7 +943,10 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option // unescaped by the calling shell. opt_vector->deserialize(value, true); } else if (opt_base->type() == coBool) { - static_cast(opt_base)->value = !no; + if (value.empty()) + static_cast(opt_base)->value = !no; + else + opt_base->deserialize(value); } else if (opt_base->type() == coString) { // Do not unescape single string values, the unescaping is left to the calling shell. static_cast(opt_base)->value = value; diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 613b7444a..16d86ac28 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -2860,9 +2860,10 @@ namespace Slic3r { stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; } + assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); if (volume->source.is_converted_from_inches) stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n"; - if (volume->source.is_converted_from_meters) + else if (volume->source.is_converted_from_meters) stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n"; } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index d03cfd4fa..0312c7f22 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -1241,9 +1241,10 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, stream << " " << volume->source.mesh_offset(1) << "\n"; stream << " " << volume->source.mesh_offset(2) << "\n"; } + assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); if (volume->source.is_converted_from_inches) stream << " 1\n"; - if (volume->source.is_converted_from_meters) + else if (volume->source.is_converted_from_meters) stream << " 1\n"; stream << std::setprecision(std::numeric_limits::max_digits10); const indexed_triangle_set &its = volume->mesh().its; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 0b9614fa2..c45acc048 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -460,13 +460,15 @@ void Model::convert_multipart_object(unsigned int max_extruders) this->objects.push_back(object); } +static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3; + bool Model::looks_like_imperial_units() const { if (this->objects.size() == 0) return false; for (ModelObject* obj : this->objects) - if (obj->get_object_stl_stats().volume < 9.0) // 9 = 3*3*3; + if (obj->get_object_stl_stats().volume < volume_threshold_inches) return true; return false; @@ -474,22 +476,26 @@ bool Model::looks_like_imperial_units() const void Model::convert_from_imperial_units(bool only_small_volumes) { - double in_to_mm = 25.4; + static constexpr const in_to_mm = 25.4; for (ModelObject* obj : this->objects) - if (! only_small_volumes || obj->get_object_stl_stats().volume < 9.0) { // 9 = 3*3*3; + if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_inches) { obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm)); - for (ModelVolume* v : obj->volumes) + for (ModelVolume* v : obj->volumes) { + assert(! v->source.is_converted_from_meters); v->source.is_converted_from_inches = true; + } } } +static constexpr const double volume_threshold_meters = 0.001; // 0.001 = 0.1*0.1*0.1 + bool Model::looks_like_saved_in_meters() const { if (this->objects.size() == 0) return false; for (ModelObject* obj : this->objects) - if (obj->get_object_stl_stats().volume < 0.001) // 0.001 = 0.1*0.1*0.1; + if (obj->get_object_stl_stats().volume < volume_threshold_meters) return true; return false; @@ -497,12 +503,14 @@ bool Model::looks_like_saved_in_meters() const void Model::convert_from_meters(bool only_small_volumes) { - double m_to_mm = 1000; + static constexpr const double m_to_mm = 1000; for (ModelObject* obj : this->objects) - if (! only_small_volumes || obj->get_object_stl_stats().volume < 0.001) { // 0.001 = 0.1*0.1*0.1; + if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_meters) { obj->scale_mesh_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm)); - for (ModelVolume* v : obj->volumes) + for (ModelVolume* v : obj->volumes) { + assert(! v->source.is_converted_from_inches); v->source.is_converted_from_meters = true; + } } } @@ -725,6 +733,11 @@ void ModelObject::clear_volumes() this->invalidate_bounding_box(); } +bool ModelObject::is_mm_painted() const +{ + return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); +} + void ModelObject::sort_volumes(bool full_sort) { // sort volumes inside the object to order "Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. " @@ -1070,6 +1083,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con vol->source.is_converted_from_inches = conv_type == ConversionType::CONV_FROM_INCH; if (conv_type == ConversionType::CONV_FROM_METER || conv_type == ConversionType::CONV_TO_METER) vol->source.is_converted_from_meters = conv_type == ConversionType::CONV_FROM_METER; + assert(! vol->source.is_converted_from_inches || ! vol->source.is_converted_from_meters); } else vol->set_offset(volume->get_offset()); @@ -1740,18 +1754,15 @@ void ModelVolume::scale(const Vec3d& scaling_factors) void ModelObject::scale_to_fit(const Vec3d &size) { -/* - BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; Vec3d orig_size = this->bounding_box().size(); - float factor = fminf( - size.x / orig_size.x, - fminf( - size.y / orig_size.y, - size.z / orig_size.z + double factor = std::min( + size.x() / orig_size.x(), + std::min( + size.y() / orig_size.y(), + size.z() / orig_size.z() ) ); this->scale(factor); -*/ } void ModelVolume::assign_new_unique_ids_recursive() @@ -1825,6 +1836,7 @@ void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_hand void ModelVolume::convert_from_imperial_units() { + assert(! this->source.is_converted_from_meters); double in_to_mm = 25.4; this->scale_geometry_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm)); this->set_offset(Vec3d(0, 0, 0)); @@ -1833,6 +1845,7 @@ void ModelVolume::convert_from_imperial_units() void ModelVolume::convert_from_meters() { + assert(! this->source.is_converted_from_inches); double m_to_mm = 1000; this->scale_geometry_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm)); this->set_offset(Vec3d(0, 0, 0)); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index fda500810..11dcfa775 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -285,6 +285,8 @@ public: void clear_volumes(); void sort_volumes(bool full_sort); bool is_multiparts() const { return volumes.size() > 1; } + // Checks if any of object volume is painted using the multi-material painting gizmo. + bool is_mm_painted() const; ModelInstance* add_instance(); ModelInstance* add_instance(const ModelInstance &instance); @@ -715,6 +717,8 @@ public: this->mmu_segmentation_facets.set_new_unique_id(); } + bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } + protected: friend class Print; friend class SLAPrint; diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 65284ffac..95475b0d7 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -5,6 +5,7 @@ #include "Print.hpp" #include "VoronoiVisualUtils.hpp" #include "MutablePolygon.hpp" +#include "format.hpp" #include #include @@ -502,11 +503,13 @@ static std::vector> colorize_polygons(const std::vector using boost::polygon::voronoi_diagram; -static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); } +static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return {coord_t(point->x()), coord_t(point->y())}; } -static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } +static inline Point mk_point(const Voronoi::Internal::point_type &point) { return {coord_t(point.x()), coord_t(point.y())}; } -static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } +static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return {coord_t(point.x()), coord_t(point.y())}; } + +static inline Vec2d mk_vec2(const voronoi_diagram::vertex_type *point) { return {point->x(), point->y()}; } struct MMU_Graph { @@ -696,9 +699,9 @@ struct MMU_Graph assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); vertex.color(this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx); } else if (bbox.contains(vertex_point)) { - if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < 3 * SCALED_EPSILON) { + if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < Slic3r::sqr(3 * SCALED_EPSILON)) { vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx)); - } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= 3 * SCALED_EPSILON) { + } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(3 * SCALED_EPSILON)) { closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count())); vertex.color(this->nodes_count()); this->nodes.push_back({vertex_point}); @@ -796,6 +799,27 @@ static inline void init_polygon_indices(const MMU_Graph } } +// Voronoi edges produced by Voronoi generator cloud have coordinates that don't fit inside coord_t (int32_t). +// Because of that, this function tries to clip edges that have one endpoint of the edge inside the BoundingBox. +static inline Line clip_finite_voronoi_edge(const Voronoi::VD::edge_type &edge, const BoundingBoxf &bbox) +{ + assert(edge.is_finite()); + Vec2d v0 = mk_vec2(edge.vertex0()); + Vec2d v1 = mk_vec2(edge.vertex1()); + bool contains_v0 = bbox.contains(v0); + bool contains_v1 = bbox.contains(v1); + if ((contains_v0 && contains_v1) || (!contains_v0 && !contains_v1)) + return {mk_point(edge.vertex0()), mk_point(edge.vertex1())}; + + Vec2d vector = (v1 - v0).normalized() * bbox.size().norm(); + if (!contains_v0) + v0 = (v1 - vector); + else + v1 = (v0 + vector); + + return {v0.cast(), v1.cast()}; +} + static MMU_Graph build_graph(size_t layer_idx, const std::vector> &color_poly) { Geometry::VoronoiDiagram vd; @@ -852,7 +876,8 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector(), bbox.max.cast()); + const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); // Make a copy of the input segments with the double type. std::vector segments; @@ -890,72 +915,74 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vectoris_finite()) { - const Point v0 = mk_point(edge_it->vertex0()); - const Point v1 = mk_point(edge_it->vertex1()); - const size_t from_idx = edge_it->vertex0()->color(); - const size_t to_idx = edge_it->vertex1()->color(); - // Both points are on contour, so skip them. In cases of duplicate Voronoi vertices, skip edges between the same two points. - if (graph.is_edge_connecting_two_contour_vertices(edge_it) || (edge_it->vertex0()->color() == edge_it->vertex1()->color())) continue; + if (graph.is_edge_connecting_two_contour_vertices(edge_it) || (edge_it->vertex0()->color() == edge_it->vertex1()->color())) + continue; - const Line edge_line(v0, v1); + const Line edge_line = clip_finite_voronoi_edge(*edge_it, bbox_clip); const Line contour_line = lines_colored[edge_it->cell()->source_index()].line; const ColoredLine colored_line = lines_colored[edge_it->cell()->source_index()]; const ColoredLine contour_line_prev = get_prev_contour_line(edge_it); const ColoredLine contour_line_next = get_next_contour_line(edge_it); - Point intersection; if (edge_it->vertex0()->color() >= graph.nodes_count() || edge_it->vertex1()->color() >= graph.nodes_count()) { -// if(edge_it->vertex0()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex0())) { -// -// } - if (edge_it->vertex1()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex1())) { - Line contour_line_twin = lines_colored[edge_it->twin()->cell()->source_index()].line; + enum class Vertex { VERTEX0, VERTEX1 }; + auto append_edge_if_intersects_with_contour = [&graph, &lines_colored, &edge_line, &contour_line](const voronoi_diagram::const_edge_iterator &edge_iterator, const Vertex vertex) { + Point intersection; + Line contour_line_twin = lines_colored[edge_iterator->twin()->cell()->source_index()].line; if (line_intersection_with_epsilon(contour_line_twin, edge_line, &intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_it->twin()->cell()->source_index()); + const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->twin()->cell()->source_index()); const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line_twin, intersection) ? graph_arc.from_idx : graph_arc.to_idx; - graph.append_edge(edge_it->vertex1()->color(), to_idx_l); + graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l); } else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_it->cell()->source_index()); + const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->cell()->source_index()); const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line, intersection) ? graph_arc.from_idx : graph_arc.to_idx; - graph.append_edge(edge_it->vertex1()->color(), to_idx_l); + graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l); } - mark_processed(edge_it); - } + mark_processed(edge_iterator); + }; + + if (edge_it->vertex0()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex0())) + append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX0); + + if (edge_it->vertex1()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex1())) + append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX1); } else if (graph.is_edge_attach_to_contour(edge_it)) { mark_processed(edge_it); // Skip edges witch connection two points on a contour if (graph.is_edge_connecting_two_contour_vertices(edge_it)) continue; + const size_t from_idx = edge_it->vertex0()->color(); + const size_t to_idx = edge_it->vertex1()->color(); if (graph.is_vertex_on_contour(edge_it->vertex0())) { - if (is_point_closer_to_beginning_of_line(contour_line, v0)) { - if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, v1)) { + if (is_point_closer_to_beginning_of_line(contour_line, edge_line.a)) { + if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.b)) { graph.append_edge(from_idx, to_idx); force_edge_adding[colored_line.poly_idx] = false; } } else { - if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, v1)) { + if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.b)) { graph.append_edge(from_idx, to_idx); force_edge_adding[colored_line.poly_idx] = false; } } } else { assert(graph.is_vertex_on_contour(edge_it->vertex1())); - if (is_point_closer_to_beginning_of_line(contour_line, v1)) { - if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, v0)) { + if (is_point_closer_to_beginning_of_line(contour_line, edge_line.b)) { + if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.a)) { graph.append_edge(from_idx, to_idx); force_edge_adding[colored_line.poly_idx] = false; } } else { - if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, v0)) { + if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.a)) { graph.append_edge(from_idx, to_idx); force_edge_adding[colored_line.poly_idx] = false; } } } - } else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { + } else if (Point intersection; line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { mark_processed(edge_it); Point real_v0 = graph.nodes[edge_it->vertex0()->color()].point; Point real_v1 = graph.nodes[edge_it->vertex1()->color()].point; @@ -1202,7 +1229,7 @@ static void cut_segmented_layers(const std::vector BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end"; } -// #define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM +//#define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM // Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo static inline std::vector> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object, @@ -1671,7 +1698,7 @@ static void export_regions_to_svg(const std::string &path, const std::vector ®ion : regions) { - int region_color = region.second; + int region_color = int(region.second); if (region_color >= 0 && region_color < int(colors.size())) svg.draw(region.first, colors[region_color]); else diff --git a/src/libslic3r/Platform.cpp b/src/libslic3r/Platform.cpp index ae02c42b3..4aa72c95f 100644 --- a/src/libslic3r/Platform.cpp +++ b/src/libslic3r/Platform.cpp @@ -3,6 +3,12 @@ #include #include +#if defined(__APPLE__) +#include +#include +#include +#endif + namespace Slic3r { static auto s_platform = Platform::Uninitialized; @@ -16,8 +22,39 @@ void detect_platform() s_platform_flavor = PlatformFlavor::Generic; #elif defined(__APPLE__) BOOST_LOG_TRIVIAL(info) << "Platform: OSX"; - s_platform = Platform::OSX; - s_platform_flavor = PlatformFlavor::Generic; + s_platform = Platform::OSX; + s_platform_flavor = PlatformFlavor::GenericOSX; + { + cpu_type_t type = 0; + size_t size = sizeof(type); + if (sysctlbyname("hw.cputype", &type, &size, NULL, 0) == 0) { + type &= ~CPU_ARCH_MASK; + if (type == CPU_TYPE_X86) { + int proc_translated = 0; + size = sizeof(proc_translated); + // Detect if native CPU is really X86 or PrusaSlicer runs through Rosetta. + if (sysctlbyname("sysctl.proc_translated", &proc_translated, &size, NULL, 0) == -1) { + if (errno == ENOENT) { + // Native CPU is X86, and property sysctl.proc_translated doesn't exist. + s_platform_flavor = PlatformFlavor::OSXOnX86; + BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnX86"; + } + } else if (proc_translated == 1) { + // Native CPU is ARM and PrusaSlicer runs through Rosetta. + s_platform_flavor = PlatformFlavor::OSXOnArm; + BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnArm"; + } else { + // Native CPU is X86. + s_platform_flavor = PlatformFlavor::OSXOnX86; + BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnX86"; + } + } else if (type == CPU_TYPE_ARM) { + // Native CPU is ARM + s_platform_flavor = PlatformFlavor::OSXOnArm; + BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnArm"; + } + } + } #elif defined(__linux__) BOOST_LOG_TRIVIAL(info) << "Platform: Linux"; s_platform = Platform::Linux; diff --git a/src/libslic3r/Platform.hpp b/src/libslic3r/Platform.hpp index 735728e89..1b4d74c02 100644 --- a/src/libslic3r/Platform.hpp +++ b/src/libslic3r/Platform.hpp @@ -28,6 +28,12 @@ enum class PlatformFlavor WSL2, // For Platform::BSDUnix OpenBSD, + // For Platform::OSX + GenericOSX, + // For Apple's on Intel X86 CPU + OSXOnX86, + // For Apple's on Arm CPU + OSXOnArm, }; // To be called on program start-up. diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index ba631c3ee..1854800cc 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -317,6 +317,8 @@ public: bool has_support() const { return m_config.support_material || m_config.support_material_enforce_layers > 0; } bool has_raft() const { return m_config.raft_layers > 0; } bool has_support_material() const { return this->has_support() || this->has_raft(); } + // Checks if the model object is painted using the multi-material painting gizmo. + bool is_mm_painted() const { return this->model_object()->is_mm_painted(); }; // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) std::vector object_extruders() const; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 577220423..0e252ac6f 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -812,7 +812,8 @@ static PrintObjectRegions* generate_print_object_regions( layer_ranges_regions.push_back({ range.layer_height_range, range.config }); } - update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, std::max(0.f, xy_size_compensation)); + const bool is_mm_painted = std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); + update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation)); std::vector region_set; auto get_create_region = [®ion_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* { @@ -1313,7 +1314,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_default_region_config, model_object_status.print_instances.front().trafo, num_extruders, - float(print_object.config().xy_size_compensation.value), + print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value), painting_extruders); } for (auto it = it_print_object; it != it_print_object_end; ++it) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 512843110..ed7961ce1 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4154,6 +4154,11 @@ CLITransformConfigDef::CLITransformConfigDef() def->label = L("Don't arrange"); def->tooltip = L("Do not rearrange the given models before merging and keep their original XY coordinates."); + def = this->add("ensure_on_bed", coBool); + def->label = L("Ensure on bed"); + def->tooltip = L("Lift the object above the bed when it is partially below. Enabled by default, use --no-ensure-on-bed to disable."); + def->set_default_value(new ConfigOptionBool(true)); + def = this->add("duplicate", coInt); def->label = L("Duplicate"); def->tooltip =L("Multiply copies by this factor."); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 7aa39c823..8ebe1eb10 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -509,13 +509,32 @@ bool PrintObject::invalidate_state_by_config_options( } else if ( opt_key == "perimeters" || opt_key == "extra_perimeters" - || opt_key == "gap_fill_enabled" - || opt_key == "gap_fill_speed" || opt_key == "first_layer_extrusion_width" || opt_key == "perimeter_extrusion_width" || opt_key == "infill_overlap" || opt_key == "external_perimeters_first") { steps.emplace_back(posPerimeters); + } else if ( + opt_key == "gap_fill_enabled" + || opt_key == "gap_fill_speed") { + // Return true if gap-fill speed has changed from zero value to non-zero or from non-zero value to zero. + auto is_gap_fill_changed_state_due_to_speed = [&opt_key, &old_config, &new_config]() -> bool { + if (opt_key == "gap_fill_speed") { + const auto *old_gap_fill_speed = old_config.option(opt_key); + const auto *new_gap_fill_speed = new_config.option(opt_key); + assert(old_gap_fill_speed && new_gap_fill_speed); + return (old_gap_fill_speed->value > 0.f && new_gap_fill_speed->value == 0.f) || + (old_gap_fill_speed->value == 0.f && new_gap_fill_speed->value > 0.f); + } + return false; + }; + + // Filtering of unprintable regions in multi-material segmentation depends on if gap-fill is enabled or not. + // So step posSlice is invalidated when gap-fill was enabled/disabled by option "gap_fill_enabled" or by + // changing "gap_fill_speed" to force recomputation of the multi-material segmentation. + if (this->is_mm_painted() && (opt_key == "gap_fill_enabled" || (opt_key == "gap_fill_speed" && is_gap_fill_changed_state_due_to_speed()))) + steps.emplace_back(posSlice); + steps.emplace_back(posPerimeters); } else if ( opt_key == "layer_height" || opt_key == "first_layer_height" diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 82fd04bce..818519be4 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -167,7 +167,8 @@ static std::vector slice_volumes_inner( params_base.mode_below = params_base.mode; - const auto extra_offset = std::max(0.f, float(print_object_config.xy_size_compensation.value)); + const bool is_mm_painted = std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); + const auto extra_offset = is_mm_painted ? 0.f : std::max(0.f, float(print_object_config.xy_size_compensation.value)); for (const ModelVolume *model_volume : model_volumes) if (model_volume_needs_slicing(*model_volume)) { @@ -725,6 +726,17 @@ void PrintObject::slice_volumes() // Is any ModelVolume MMU painted? if (const auto& volumes = this->model_object()->volumes; std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) { + + // If XY Size compensation is also enabled, notify the user that XY Size compensation + // would not be used because the object is multi-material painted. + if (m_config.xy_size_compensation.value != 0.f) { + this->active_step_add_warning( + PrintStateBase::WarningLevel::CRITICAL, + L("An object has enabled XY Size compensation which will not be used because it is also multi-material painted.\nXY Size " + "compensation cannot be combined with multi-material painting.") + + "\n" + (L("Object name")) + ": " + this->model_object()->name); + } + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - MMU segmentation"; apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); }); } @@ -733,8 +745,8 @@ void PrintObject::slice_volumes() BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin"; { // Compensation value, scaled. Only applying the negative scaling here, as the positive scaling has already been applied during slicing. - const auto xy_compensation_scaled = scaled(std::min(m_config.xy_size_compensation.value, 0.)); - const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ? + const auto xy_compensation_scaled = this->is_mm_painted() ? scaled(0.f) : scaled(std::min(m_config.xy_size_compensation.value, 0.)); + const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ? // Only enable Elephant foot compensation if printing directly on the print bed. float(scale_(m_config.elefant_foot_compensation.value)) : 0.f; diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index ef90402a9..aa2763968 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -372,7 +372,7 @@ static inline IntersectionLines slice_make_lines( FaceFilter face_filter) { IntersectionLines lines; - for (int face_idx = 0; face_idx < mesh_faces.size(); ++ face_idx) + for (int face_idx = 0; face_idx < int(mesh_faces.size()); ++ face_idx) if (face_filter(face_idx)) { const Vec3i &indices = mesh_faces[face_idx]; stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) }; diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index ad823c55d..758b3ab4d 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -277,22 +277,22 @@ void TriangleSelector::append_touching_subtriangles(int itriangle, int vertexi, if (itriangle == -1) return; - auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_subtriangles_out](const int subtriangle_idx) -> void { + auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_subtriangles_out](const int subtriangle_idx, Partition partition) -> void { assert(subtriangle_idx == -1); if (!m_triangles[subtriangle_idx].is_split()) touching_subtriangles_out.emplace_back(subtriangle_idx); else if (int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj); midpoint != -1) - append_touching_subtriangles(subtriangle_idx, vertexi, midpoint, touching_subtriangles_out); + append_touching_subtriangles(subtriangle_idx, partition == Partition::First ? vertexi : midpoint, partition == Partition::First ? midpoint : vertexj, touching_subtriangles_out); else append_touching_subtriangles(subtriangle_idx, vertexi, vertexj, touching_subtriangles_out); }; std::pair touching = this->triangle_subtriangles(itriangle, vertexi, vertexj); if (touching.first != -1) - process_subtriangle(touching.first); + process_subtriangle(touching.first, Partition::First); if (touching.second != -1) - process_subtriangle(touching.second); + process_subtriangle(touching.second, Partition::Second); } void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate) @@ -437,7 +437,7 @@ int TriangleSelector::neighbor_child(int itriangle, int vertexi, int vertexj, Pa std::pair TriangleSelector::triangle_subtriangles(int itriangle, int vertexi, int vertexj) const { - return itriangle == -1 ? std::make_pair(-1, -1) : this->triangle_subtriangles(m_triangles[itriangle], vertexi, vertexj); + return itriangle == -1 ? std::make_pair(-1, -1) : Slic3r::TriangleSelector::triangle_subtriangles(m_triangles[itriangle], vertexi, vertexj); } std::pair TriangleSelector::triangle_subtriangles(const Triangle &tr, int vertexi, int vertexj) diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 81897553c..2ae726b97 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -100,8 +100,8 @@ extern bool is_ini_file(const boost::filesystem::directory_entry &path); extern bool is_idx_file(const boost::filesystem::directory_entry &path); extern bool is_gcode_file(const std::string &path); extern bool is_img_file(const std::string& path); -extern bool is_stl_file(const boost::filesystem::directory_entry& path); -extern bool is_stl_file(const std::string& path); +extern bool is_gallery_file(const boost::filesystem::directory_entry& path, char const* type); +extern bool is_gallery_file(const std::string& path, char const* type); extern bool is_shapes_dir(const std::string& dir); // File path / name / extension splitting utilities, working with UTF-8, diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 085db8705..3c2a0810b 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -766,14 +766,14 @@ bool is_img_file(const std::string &path) return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg"); } -bool is_stl_file(const boost::filesystem::directory_entry& dir_entry) +bool is_gallery_file(const boost::filesystem::directory_entry& dir_entry, char const* type) { - return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), ".stl") == 0; + return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), type) == 0; } -bool is_stl_file(const std::string &path) +bool is_gallery_file(const std::string &path, char const* type) { - return boost::iends_with(path, ".stl"); + return boost::iends_with(path, type); } bool is_shapes_dir(const std::string& dir) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 1e85a10b6..d7fb937d0 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -499,10 +499,10 @@ void Bed3D::render_model() const if (shader != nullptr) { shader->start_using(); shader->set_uniform("emission_factor", 0.0); - ::glPushMatrix(); - ::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z()); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z())); model->render(); - ::glPopMatrix(); + glsafe(::glPopMatrix()); shader->stop_using(); } } diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index 65d5524c2..be0e20eb2 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -93,6 +93,7 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech) }); Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this); + GUI::wxGetApp().UpdateDlgDarkUI(this); } BonjourDialog::~BonjourDialog() diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 9d6a19169..3fd6b9f04 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -335,3 +335,36 @@ bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& val } +// ---------------------------------------------------------------------------- +// TextRenderer +// ---------------------------------------------------------------------------- + +bool TextRenderer::SetValue(const wxVariant& value) +{ + m_value = value.GetString(); + return true; +} + +bool TextRenderer::GetValue(wxVariant& value) const +{ + return false; +} + +bool TextRenderer::Render(wxRect rect, wxDC* dc, int state) +{ +#ifdef _WIN32 + // workaround for Windows DarkMode : Don't respect to the state & wxDATAVIEW_CELL_SELECTED to avoid update of the text color + RenderText(m_value, 0, rect, dc, state & wxDATAVIEW_CELL_SELECTED ? 0 : state); +#else + RenderText(m_value, 0, rect, dc, state); +#endif + + return true; +} + +wxSize TextRenderer::GetSize() const +{ + return GetTextExtent(m_value); +} + + diff --git a/src/slic3r/GUI/ExtraRenderers.hpp b/src/slic3r/GUI/ExtraRenderers.hpp index ef8336d39..aebe52631 100644 --- a/src/slic3r/GUI/ExtraRenderers.hpp +++ b/src/slic3r/GUI/ExtraRenderers.hpp @@ -161,4 +161,29 @@ private: }; + +// ---------------------------------------------------------------------------- +// TextRenderer +// ---------------------------------------------------------------------------- + +class TextRenderer : public wxDataViewCustomRenderer +{ +public: + TextRenderer(wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT + , int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL + ) : wxDataViewCustomRenderer(wxT("string"), mode, align) {} + + bool SetValue(const wxVariant& value) override; + bool GetValue(wxVariant& value) const override; + + virtual bool Render(wxRect cell, wxDC* dc, int state) override; + virtual wxSize GetSize() const override; + + bool HasEditorCtrl() const override { return false; } + +private: + wxString m_value; +}; + + #endif // slic3r_GUI_ExtraRenderers_hpp_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 0fd1930d5..090accb6a 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2379,8 +2379,12 @@ void GCodeViewer::render_toolpaths() const shader.set_uniform("uniform_color", color4); }; +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color] +#else auto render_as_points = [zoom, point_size, near_plane_height, set_uniform_color] - (const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { +#endif // ENABLE_GCODE_VIEWER_STATISTICS + (const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { #if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS shader.set_uniform("use_fixed_screen_size", 1); #else @@ -2409,7 +2413,12 @@ void GCodeViewer::render_toolpaths() const glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; - auto render_as_lines = [light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_as_lines = [this, light_intensity, set_uniform_color] +#else + auto render_as_lines = [light_intensity, set_uniform_color] +#endif // ENABLE_GCODE_VIEWER_STATISTICS + (const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { shader.set_uniform("light_intensity", light_intensity); for (const RenderPath& path : buffer.render_paths) { if (path.ibuffer_id == ibuffer_id) { @@ -2422,7 +2431,12 @@ void GCodeViewer::render_toolpaths() const } }; - auto render_as_triangles = [set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_as_triangles = [this, set_uniform_color] +#else + auto render_as_triangles = [set_uniform_color] +#endif // ENABLE_GCODE_VIEWER_STATISTICS +(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { for (const RenderPath& path : buffer.render_paths) { if (path.ibuffer_id == ibuffer_id) { set_uniform_color(path.color, shader); @@ -2495,7 +2509,12 @@ void GCodeViewer::render_toolpaths() const } } - auto render_sequential_range_cap = [set_uniform_color](const SequentialRangeCap& cap) { +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_sequential_range_cap = [this, set_uniform_color] +#else + auto render_sequential_range_cap = [set_uniform_color] +#endif // ENABLE_GCODE_VIEWER_STATISTICS + (const SequentialRangeCap& cap) { GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str()); if (shader != nullptr) { shader->start_using(); diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 5ee14c526..788fe90c0 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -1,4 +1,5 @@ #include "libslic3r/libslic3r.h" +#include "libslic3r/Platform.hpp" #include "GLShadersManager.hpp" #include "3DScene.hpp" #include "GUI_App.hpp" @@ -43,9 +44,19 @@ std::pair GLShadersManager::init() // used to render extrusion and travel paths as lines in gcode preview valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); // used to render objects in 3d editor - valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } + // For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction. + // Because of this, objects had darker colors inside the multi-material gizmo. + // Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU. + if (platform_flavor() == PlatformFlavor::OSXOnArm) + valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv #if ENABLE_ENVIRONMENT_MAP - , { "ENABLE_ENVIRONMENT_MAP"sv } + , "ENABLE_ENVIRONMENT_MAP"sv +#endif + }); + else + valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } +#if ENABLE_ENVIRONMENT_MAP + , { "ENABLE_ENVIRONMENT_MAP"sv } #endif ); // used to render variable layers heights in 3d editor diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 4196dfdac..9333efb20 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -428,6 +428,7 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) /* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC", /* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA", /* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF", + /* FT_GALLERY */ "Known files (*.stl, *.obj)|*.stl;*.STL;*.obj;*.OBJ", /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", @@ -662,7 +663,7 @@ void GUI_App::post_init() } // show "Did you know" notification - if (app_config->get("show_hints") == "1") + if (app_config->get("show_hints") == "1" && ! is_gcode_viewer()) plater_->get_notification_manager()->push_hint_notification(); // The extra CallAfter() is needed because of Mac, where this is the only way diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index fc62d4c34..be6c71f6c 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -59,6 +59,7 @@ enum FileType FT_GCODE, FT_MODEL, FT_PROJECT, + FT_GALLERY, FT_INI, FT_SVG, diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 014fc4a44..92ba427b5 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -433,7 +433,7 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty [type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu); } - if (wxGetApp().get_mode() == comExpert) { + if (wxGetApp().get_mode() >= comAdvanced) { sub_menu->AppendSeparator(); append_menu_item(sub_menu, wxID_ANY, _L("Gallery"), "", [type](wxCommandEvent&) { obj_list()->load_subobject(type, true); }, "", menu); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 18711a664..0eb535ee8 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2517,7 +2517,7 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D } -void ObjectList::update_info_items(size_t obj_idx) +void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/) { const ModelObject* model_object = (*m_objects)[obj_idx]; wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx); @@ -2565,9 +2565,21 @@ void ObjectList::update_info_items(size_t obj_idx) } else if (shows && ! should_show) { - Unselect(item); + if (!selections) + Unselect(item); m_objects_model->Delete(item); - Select(item_obj); + if (selections) { + if (selections->Index(item) != wxNOT_FOUND) { + // If info item was deleted from the list, + // it's need to be deleted from selection array, if it was there + selections->Remove(item); + // Select item_obj, if info_item doesn't exist for item anymore, but was selected + if (selections->Index(item_obj) == wxNOT_FOUND) + selections->Add(item_obj); + } + } + else + Select(item_obj); } } } @@ -3760,7 +3772,7 @@ void ObjectList::update_object_list_by_printer_technology() for (auto& object_item : object_items) { // update custom supports info - update_info_items(m_objects_model->GetObjectIdByItem(object_item)); + update_info_items(m_objects_model->GetObjectIdByItem(object_item), &sel); // Update Settings Item for object update_settings_item_and_selection(object_item, sel); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 898b2b970..0f1bcd601 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -350,7 +350,7 @@ public: 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_info_items(size_t obj_idx); + void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr); void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); void instances_to_separated_objects(const int obj_idx); diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index f306dff98..5429e2658 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -32,6 +32,7 @@ #include "libslic3r/AppConfig.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/Format/OBJ.hpp" #include "../Utils/MacDarkMode.hpp" namespace Slic3r { @@ -205,7 +206,11 @@ static void add_lock(wxImage& image) static void add_default_image(wxImageList* img_list, bool is_system) { - wxBitmap bmp = create_scaled_bitmap("cog", nullptr, IMG_PX_CNT, true); + int sz = IMG_PX_CNT; +#ifdef __APPLE__ + sz /= mac_max_scaling_factor(); +#endif + wxBitmap bmp = create_scaled_bitmap("cog", nullptr, sz, true); if (is_system) { wxImage image = bmp.ConvertToImage(); @@ -232,10 +237,11 @@ static std::string get_dir_path(bool sys_dir) #endif } -static void generate_thumbnail_from_stl(const std::string& filename) +static void generate_thumbnail_from_model(const std::string& filename) { - if (!boost::algorithm::iends_with(filename, ".stl")) { - BOOST_LOG_TRIVIAL(error) << "Found invalid file type in generate_thumbnail_from_stl() [" << filename << "]"; + if (!boost::algorithm::iends_with(filename, ".stl") && + !boost::algorithm::iends_with(filename, ".obj")) { + BOOST_LOG_TRIVIAL(error) << "Found invalid file type in generate_thumbnail_from_model() [" << filename << "]"; return; } @@ -244,7 +250,7 @@ static void generate_thumbnail_from_stl(const std::string& filename) model = Model::read_from_file(filename); } catch (std::exception&) { - BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_stl()"; + BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_model()"; return; } @@ -294,25 +300,28 @@ static void generate_thumbnail_from_stl(const std::string& filename) void GalleryDialog::load_label_icon_list() { // load names from files - auto add_files_from_gallery = [](std::vector& items, bool sys_dir, std::string& dir_path) + auto add_files_from_gallery = [](std::vector& items, bool is_sys_dir, std::string& dir_path) { - fs::path dir = get_dir(sys_dir); + fs::path dir = get_dir(is_sys_dir); if (!fs::exists(dir)) return; - dir_path = get_dir_path(sys_dir); + dir_path = get_dir_path(is_sys_dir); std::vector sorted_names; - for (auto& dir_entry : fs::directory_iterator(dir)) - if (TriangleMesh mesh; is_stl_file(dir_entry) && mesh.ReadSTLFile(dir_entry.path().string().c_str())) - sorted_names.push_back(dir_entry.path().stem().string()); + for (auto& dir_entry : fs::directory_iterator(dir)) { + TriangleMesh mesh; + if ((is_gallery_file(dir_entry, ".stl") && mesh.ReadSTLFile(dir_entry.path().string().c_str())) || + (is_gallery_file(dir_entry, ".obj") && load_obj(dir_entry.path().string().c_str(), &mesh) ) ) + sorted_names.push_back(dir_entry.path().filename().string()); + } // sort the filename case insensitive std::sort(sorted_names.begin(), sorted_names.end(), [](const std::string& a, const std::string& b) { return boost::algorithm::to_lower_copy(a) < boost::algorithm::to_lower_copy(b); }); for (const std::string& name : sorted_names) - items.push_back(Item{ name, sys_dir }); + items.push_back(Item{ name, is_sys_dir }); }; wxBusyCursor busy; @@ -330,10 +339,24 @@ void GalleryDialog::load_label_icon_list() std::string ext = ".png"; for (const auto& item : list_items) { - std::string img_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ext; - std::string stl_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ".stl"; - if (!fs::exists(img_name)) - generate_thumbnail_from_stl(stl_name); + fs::path model_path = fs::path((item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name); + std::string model_name = model_path.string(); + model_path.replace_extension("png"); + std::string img_name = model_path.string(); + +#if 0 // use "1" just in DEBUG mode to the generation of the thumbnails for the sistem shapes + bool can_generate_thumbnail = true; +#else + bool can_generate_thumbnail = !item.is_system; +#endif //DEBUG + if (!fs::exists(img_name)) { + if (can_generate_thumbnail) + generate_thumbnail_from_model(model_name); + else { + add_default_image(m_image_list, item.is_system); + continue; + } + } wxImage image; if (!image.CanRead(from_u8(img_name)) || @@ -363,15 +386,15 @@ void GalleryDialog::load_label_icon_list() void GalleryDialog::get_input_files(wxArrayString& input_files) { for (const Item& item : m_selected_items) - input_files.Add(from_u8(get_dir_path(item.is_system) + item.name + ".stl")); + input_files.Add(from_u8(get_dir_path(item.is_system) + item.name)); } void GalleryDialog::add_custom_shapes(wxEvent& event) { wxArrayString input_files; - wxFileDialog dialog(this, _L("Choose one or more files (STL):"), + wxFileDialog dialog(this, _L("Choose one or more files (STL, OBJ):"), from_u8(wxGetApp().app_config->get_last_dir()), "", - file_wildcards(FT_STL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + file_wildcards(FT_GALLERY), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); if (dialog.ShowModal() == wxID_OK) dialog.GetPaths(input_files); @@ -398,8 +421,10 @@ void GalleryDialog::del_custom_shapes(wxEvent& event) }; for (const Item& item : m_selected_items) { - remove_file(item.name + ".stl"); - remove_file(item.name + ".png"); + remove_file(item.name); + fs::path path = fs::path(item.name); + path.replace_extension("png"); + remove_file(path.string()); } update(); @@ -490,26 +515,32 @@ bool GalleryDialog::load_files(const wxArrayString& input_files) return false; } - // Iterate through the source directory + // Iterate through the input files for (size_t i = 0; i < input_files.size(); ++i) { std::string input_file = into_u8(input_files.Item(i)); - if (TriangleMesh mesh; !mesh.ReadSTLFile(input_file.c_str())) { + TriangleMesh mesh; + if (is_gallery_file(input_file, ".stl") && !mesh.ReadSTLFile(input_file.c_str())) { show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "STL"); continue; } + if (is_gallery_file(input_file, ".obj") && !load_obj(input_file.c_str(), &mesh)) { + show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "OBJ"); + continue; + } + try { fs::path current = fs::path(input_file); if (!fs::exists(dest_dir / current.filename())) fs::copy_file(current, dest_dir / current.filename()); else { - std::string filename = current.stem().string(); + std::string filename = current.filename().string(); int file_idx = 0; for (auto& dir_entry : fs::directory_iterator(dest_dir)) - if (is_stl_file(dir_entry)) { - std::string name = dir_entry.path().stem().string(); + if (is_gallery_file(dir_entry, ".stl") || is_gallery_file(dir_entry, ".obj")) { + std::string name = dir_entry.path().filename().string(); if (filename == name) { if (file_idx == 0) file_idx++; @@ -524,7 +555,7 @@ bool GalleryDialog::load_files(const wxArrayString& input_files) file_idx = cur_idx+1; } if (file_idx > 0) { - filename += " (" + std::to_string(file_idx) + ").stl"; + filename += " (" + std::to_string(file_idx) + ")." + (is_gallery_file(input_file, ".stl") ? "stl" : "obj"); fs::copy_file(current, dest_dir / filename); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 425e11f73..115a675ac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -359,10 +359,10 @@ void ObjectClipper::render_cut() const clipper->set_plane(*m_clp); clipper->set_transformation(trafo); - ::glPushMatrix(); - ::glColor3f(1.0f, 0.37f, 0.0f); + glsafe(::glPushMatrix()); + glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); clipper->render_cut(); - ::glPopMatrix(); + glsafe(::glPopMatrix()); ++clipper_id; } @@ -472,10 +472,10 @@ void SupportsClipper::render_cut() const m_clipper->set_plane(*ocl->get_clipping_plane()); m_clipper->set_transformation(supports_trafo); - ::glPushMatrix(); - ::glColor3f(1.0f, 0.f, 0.37f); + glsafe(::glPushMatrix()); + glsafe(::glColor3f(1.0f, 0.f, 0.37f)); m_clipper->render_cut(); - ::glPopMatrix(); + glsafe(::glPopMatrix()); } diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index f7593ba16..f189378a6 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -434,6 +434,12 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr e.Skip(); temp->GetToolTip()->Enable(true); #endif // __WXGTK__ + // Remove all leading and trailing spaces from the input + std::string trimed_str, str = trimed_str = temp->GetValue().ToStdString(); + boost::trim(trimed_str); + if (trimed_str != str) + temp->SetValue(trimed_str); + TextCtrl* field = dynamic_cast(printhost_field); if (field) field->propagate_value(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 209604afb..652524c5c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2389,8 +2389,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ //wxMessageDialog msg_dlg(q, _L( MessageDialog msg_dlg(q, _L( "This file contains several objects positioned at multiple heights.\n" - "Instead of considering them as multiple objects, should I consider\n" - "this file as a single object having multiple parts?") + "\n", + "Instead of considering them as multiple objects, should \n" + "should the file be loaded as a single object having multiple parts?") + "\n", _L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO); if (msg_dlg.ShowModal() == wxID_YES) { model.convert_multipart_object(nozzle_dmrs->values.size()); @@ -3212,9 +3212,10 @@ void Plater::priv::replace_with_stl() new_volume->set_material_id(old_volume->material_id()); new_volume->set_transformation(old_volume->get_transformation()); new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); + assert(! old_volume->source.is_converted_from_inches || ! old_volume->source.is_converted_from_meters); if (old_volume->source.is_converted_from_inches) new_volume->convert_from_imperial_units(); - if (old_volume->source.is_converted_from_meters) + else if (old_volume->source.is_converted_from_meters) new_volume->convert_from_meters(); new_volume->supported_facets.assign(old_volume->supported_facets); new_volume->seam_facets.assign(old_volume->seam_facets); @@ -3420,13 +3421,11 @@ void Plater::priv::reload_from_disk() new_volume->set_material_id(old_volume->material_id()); new_volume->set_transformation(old_volume->get_transformation()); new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); + assert(! old_volume->source.is_converted_from_inches || ! old_volume->source.is_converted_from_meters); if (old_volume->source.is_converted_from_inches) new_volume->convert_from_imperial_units(); - if (old_volume->source.is_converted_from_meters) + else if (old_volume->source.is_converted_from_meters) new_volume->convert_from_meters(); - new_volume->supported_facets.assign(old_volume->supported_facets); - new_volume->seam_facets.assign(old_volume->seam_facets); - new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets); std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); if (!sinking) @@ -5223,7 +5222,7 @@ void Plater::convert_unit(ConversionType conv_type) void Plater::toggle_layers_editing(bool enable) { if (canvas3D()->is_layers_editing_enabled() != enable) - wxPostEvent(canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); + canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting")); } void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes) diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 9b07157fd..3f2dc5a44 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -27,6 +27,7 @@ #include "MainFrame.hpp" #include "libslic3r/AppConfig.hpp" #include "NotificationManager.hpp" +#include "ExtraRenderers.hpp" namespace fs = boost::filesystem; @@ -214,14 +215,25 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) } job_list = new wxDataViewListCtrl(this, wxID_ANY); + + // MSW DarkMode: workaround for the selected item in the list + auto append_text_column = [this](const wxString& label, int width, wxAlignment align = wxALIGN_LEFT, + int flags = wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE) { +#ifdef _WIN32 + job_list->AppendColumn(new wxDataViewColumn(label, new TextRenderer(), job_list->GetColumnCount(), width, align, flags)); +#else + job_list->AppendTextColumn(label, wxDATAVIEW_CELL_INERT, width, align, flags); +#endif + }; + // Note: Keep these in sync with Column - job_list->AppendTextColumn(_L("ID"), wxDATAVIEW_CELL_INERT, widths[0], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendTextColumn(_L("Status"), wxDATAVIEW_CELL_INERT, widths[2], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendTextColumn(_L("Host"), wxDATAVIEW_CELL_INERT, widths[3], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendTextColumn(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), wxDATAVIEW_CELL_INERT, widths[4], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendTextColumn(_L("Filename"), wxDATAVIEW_CELL_INERT, widths[5], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); - job_list->AppendTextColumn(_L("Error Message"), wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN); + append_text_column(_L("ID"), widths[0]); + job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE); + append_text_column(_L("Status"),widths[2]); + append_text_column(_L("Host"), widths[3]); + append_text_column(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]); + append_text_column(_L("Filename"), widths[5]); + append_text_column(_L("Error Message"), -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN); auto *btnsizer = new wxBoxSizer(wxHORIZONTAL); btn_cancel = new wxButton(this, wxID_DELETE, _L("Cancel selected"));