diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 0de0b4e51..f749221d7 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -221,6 +221,10 @@ set(SLIC3R_SOURCES MutablePriorityQueue.hpp NormalUtils.cpp NormalUtils.hpp + NonplanarFacet.cpp + NonplanarFacet.hpp + NonplanarSurface.cpp + NonplanarSurface.hpp NSVGUtils.cpp NSVGUtils.hpp ObjectID.cpp diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 19489bddb..863ce7f12 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -418,7 +418,7 @@ bool has_duplicate_points(const ExPolygons &expolys) { #if 1 // Check globally. -#if 0 +#if 1 // Detect duplicates by sorting with quicksort. It is quite fast, but ankerl::unordered_dense is around 1/4 faster. Points allpts; allpts.reserve(count_points(expolys)); diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 277ac7824..8affb8d26 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -10,6 +10,8 @@ #include #include +#include + namespace Slic3r { class ExPolygon; @@ -67,6 +69,8 @@ public: float width; // Height of the extrusion, used for visualization purposes. float height; + /// distance to surface layer in nonplanar extrusions -1.0 if not part of nonplanar extrusion + float distance_to_top = -1.0; ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {} ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {} @@ -122,6 +126,11 @@ private: ExtrusionRole m_role; }; +class ExtrusionPath3 : public ExtrusionPath +{ + Polyline3 polyline; +}; + class ExtrusionPathOriented : public ExtrusionPath { public: diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index 55167861c..d44701542 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -1,5 +1,7 @@ #include "ExtrusionEntityCollection.hpp" #include "ShortestPath.hpp" +#include "BoundingBox.hpp" +#include "SVG.hpp" #include #include #include @@ -152,4 +154,17 @@ double ExtrusionEntityCollection::min_mm3_per_mm() const return min_mm3_per_mm; } +void ExtrusionEntityCollection::export_to_svg(const char *path) const +{ + BoundingBox bbox; + for (const ExtrusionEntity *entity : this->entities) + bbox.merge(get_extents(entity->as_polylines())); + + SVG svg(path, bbox); + for (const ExtrusionEntity *entity : this->entities) + svg.draw(entity->as_polylines(), "black", scale_(0.1f)); + + svg.Close(); +} + } diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 676bdd891..0024f4bbe 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -124,6 +124,7 @@ public: ExtrusionEntityCollection flatten(bool preserve_ordering = false) const; double min_mm3_per_mm() const override; double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } + void export_to_svg(const char *path) const; // Following methods shall never be called on an ExtrusionEntityCollection. Polyline as_polyline() const override { diff --git a/src/libslic3r/ExtrusionRole.cpp b/src/libslic3r/ExtrusionRole.cpp index a7ec31949..6d9484a3d 100644 --- a/src/libslic3r/ExtrusionRole.cpp +++ b/src/libslic3r/ExtrusionRole.cpp @@ -19,7 +19,9 @@ GCodeExtrusionRole extrusion_role_to_gcode_extrusion_role(ExtrusionRole role) } if (role == ExtrusionRole::InternalInfill) return GCodeExtrusionRole::InternalInfill; if (role == ExtrusionRole::SolidInfill) return GCodeExtrusionRole::SolidInfill; + if (role == ExtrusionRole::SolidInfillNonplanar) return GCodeExtrusionRole::SolidInfillNonplanar; if (role == ExtrusionRole::TopSolidInfill) return GCodeExtrusionRole::TopSolidInfill; + if (role == ExtrusionRole::TopSolidInfillNonplanar) return GCodeExtrusionRole::TopSolidInfillNonplanar; if (role == ExtrusionRole::Ironing) return GCodeExtrusionRole::Ironing; if (role == ExtrusionRole::BridgeInfill) return GCodeExtrusionRole::BridgeInfill; if (role == ExtrusionRole::GapFill) return GCodeExtrusionRole::GapFill; @@ -40,7 +42,9 @@ std::string gcode_extrusion_role_to_string(GCodeExtrusionRole role) case GCodeExtrusionRole::OverhangPerimeter : return L("Overhang perimeter"); case GCodeExtrusionRole::InternalInfill : return L("Internal infill"); case GCodeExtrusionRole::SolidInfill : return L("Solid infill"); + case GCodeExtrusionRole::SolidInfillNonplanar : return L("Nonplanar solid infill"); case GCodeExtrusionRole::TopSolidInfill : return L("Top solid infill"); + case GCodeExtrusionRole::TopSolidInfillNonplanar : return L("Nonplanar top solid infill"); case GCodeExtrusionRole::Ironing : return L("Ironing"); case GCodeExtrusionRole::BridgeInfill : return L("Bridge infill"); case GCodeExtrusionRole::GapFill : return L("Gap fill"); @@ -66,8 +70,12 @@ GCodeExtrusionRole string_to_gcode_extrusion_role(const std::string_view role) return GCodeExtrusionRole::InternalInfill; else if (role == L("Solid infill")) return GCodeExtrusionRole::SolidInfill; + else if (role == L("Nonplanar solid infill")) + return GCodeExtrusionRole::SolidInfill; else if (role == L("Top solid infill")) return GCodeExtrusionRole::TopSolidInfill; + else if (role == L("Nonplanar top solid infill")) + return GCodeExtrusionRole::TopSolidInfillNonplanar; else if (role == L("Ironing")) return GCodeExtrusionRole::Ironing; else if (role == L("Bridge infill")) diff --git a/src/libslic3r/ExtrusionRole.hpp b/src/libslic3r/ExtrusionRole.hpp index 986c139a2..04f7d94e2 100644 --- a/src/libslic3r/ExtrusionRole.hpp +++ b/src/libslic3r/ExtrusionRole.hpp @@ -26,6 +26,7 @@ enum class ExtrusionRoleModifier : uint16_t { Solid, Ironing, Bridge, + Nonplanar, // 3) Special types // Indicator that the extrusion role was mixed from multiple differing extrusion roles, // for example from Support and SupportInterface. @@ -55,9 +56,11 @@ struct ExtrusionRole : public ExtrusionRoleModifiers static constexpr const ExtrusionRoleModifiers InternalInfill{ ExtrusionRoleModifier::Infill }; // Solid internal infill. static constexpr const ExtrusionRoleModifiers SolidInfill{ ExtrusionRoleModifier::Infill | ExtrusionRoleModifier::Solid }; + static constexpr const ExtrusionRoleModifiers SolidInfillNonplanar{ ExtrusionRoleModifier::Infill | ExtrusionRoleModifier::Solid | ExtrusionRoleModifier::Nonplanar }; // Top solid infill (visible). //FIXME why there is no bottom solid infill type? static constexpr const ExtrusionRoleModifiers TopSolidInfill{ ExtrusionRoleModifier::Infill | ExtrusionRoleModifier::Solid | ExtrusionRoleModifier::External }; + static constexpr const ExtrusionRoleModifiers TopSolidInfillNonplanar{ ExtrusionRoleModifier::Infill | ExtrusionRoleModifier::Solid | ExtrusionRoleModifier::External | ExtrusionRoleModifier::Nonplanar }; // Ironing infill at the top surfaces. static constexpr const ExtrusionRoleModifiers Ironing{ ExtrusionRoleModifier::Infill | ExtrusionRoleModifier::Solid | ExtrusionRoleModifier::Ironing | ExtrusionRoleModifier::External }; // Visible bridging infill at the bottom of an object. @@ -84,6 +87,7 @@ struct ExtrusionRole : public ExtrusionRoleModifiers bool is_solid_infill() const { return this->is_infill() && this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); } bool is_external() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::External); } bool is_bridge() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Bridge); } + bool is_nonplanar() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Nonplanar); } bool is_support() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Support); } bool is_support_base() const { return this->is_support() && ! this->is_external(); } @@ -108,7 +112,9 @@ enum class GCodeExtrusionRole : uint8_t { OverhangPerimeter, InternalInfill, SolidInfill, + SolidInfillNonplanar, TopSolidInfill, + TopSolidInfillNonplanar, Ironing, BridgeInfill, GapFill, diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 499e7b85a..eb213900a 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -2,6 +2,8 @@ #include #include +#include "SVG.hpp" + #include "../ClipperUtils.hpp" #include "../Geometry.hpp" #include "../Layer.hpp" @@ -144,7 +146,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) ? + params.pattern = (surface.is_external() && ! is_bridge && !surface.is_nonplanar()) ? (surface.is_top() ? region_config.top_fill_pattern.value : region_config.bottom_fill_pattern.value) : fill_type_monotonic(region_config.top_fill_pattern) ? ipMonotonic : ipRectilinear; } else if (params.density <= 0) @@ -154,7 +156,10 @@ std::vector group_fills(const Layer &layer) is_bridge ? ExtrusionRole::BridgeInfill : (surface.is_solid() ? - (surface.is_top() ? ExtrusionRole::TopSolidInfill : ExtrusionRole::SolidInfill) : + (surface.is_top() ? + (surface.is_nonplanar() ? ExtrusionRole::TopSolidInfillNonplanar : ExtrusionRole::TopSolidInfill) : + (surface.is_nonplanar() ? ExtrusionRole::SolidInfillNonplanar : ExtrusionRole::SolidInfill) + ) : ExtrusionRole::InternalInfill); params.bridge_angle = float(surface.bridge_angle); params.angle = float(Geometry::deg2rad(region_config.fill_angle.value)); @@ -252,7 +257,7 @@ std::vector group_fills(const Layer &layer) if (! surface_fill.expolygons.empty()) { distance_between_surfaces = std::max(distance_between_surfaces, surface_fill.params.flow.scaled_spacing()); append((surface_fill.surface.surface_type == stInternalVoid) ? voids : surfaces_polygons, to_polygons(surface_fill.expolygons)); - if (surface_fill.surface.surface_type == stInternalSolid) + if (surface_fill.surface.is_internal_solid()) region_internal_infill = (int)surface_fill.region_id; if (surface_fill.surface.is_solid()) region_solid_infill = (int)surface_fill.region_id; @@ -281,10 +286,10 @@ 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) { - internal_solid_fill = &surface_fill; - break; - } + if ((surface_fill.surface.is_internal_solid()) && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) { + internal_solid_fill = &surface_fill; + break; + } if (internal_solid_fill == nullptr) { // Produce another solid fill. params.extruder = layerm.region().extruder(frSolidInfill); @@ -309,7 +314,7 @@ std::vector group_fills(const Layer &layer) // Use ipEnsuring pattern for all internal Solids. { for (size_t surface_fill_id = 0; surface_fill_id < surface_fills.size(); ++surface_fill_id) - if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) { + if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.is_internal_solid()) { fill.params.pattern = ipEnsuring; } } @@ -449,10 +454,12 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { static int iRun = 0; - export_group_fills_to_svg(debug_out_path("Layer-fill_surfaces-10_fill-final-%d.svg", iRun ++).c_str(), surface_fills); + export_group_fills_to_svg(debug_out_path("Layer-%d-fill_surfaces-10_fill-final-%d.svg", id(), iRun ++).c_str(), surface_fills); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + BOOST_LOG_TRIVIAL(trace) << "make_fills: found " << surface_fills.size() << " surfaces to fill for layer " << id(); + size_t first_object_layer_id = this->object()->get_layer(0)->id(); for (SurfaceFill &surface_fill : surface_fills) { // Create the filler object. @@ -519,6 +526,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: else polylines = f->fill_surface(&surface_fill.surface, params); } catch (InfillFailedException &) { + BOOST_LOG_TRIVIAL(trace) << "make_fills: InfillFailedException!"; } if (!polylines.empty() || !thick_polylines.empty()) { // calculate actual flow from spacing (which might have been adjusted by the infill @@ -562,7 +570,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height()); } insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size())); - } + } else { + BOOST_LOG_TRIVIAL(trace) << "make_fills: no infill was generated for layer " << id(); + } } } @@ -854,7 +864,7 @@ void Layer::make_ironing() polygons_append(polys, surface.expolygon); } else { for (const Surface &surface : ironing_params.layerm->slices()) - if (surface.surface_type == stTop || (iron_everything && surface.surface_type == stBottom)) + if (surface.is_top() || (iron_everything && surface.surface_type == stBottom)) // stBottomBridge is not being ironed on purpose, as it would likely destroy the bridges. polygons_append(polys, surface.expolygon); } @@ -862,7 +872,7 @@ void Layer::make_ironing() // Add solid fill surfaces. This may not be ideal, as one will not iron perimeters touching these // solid fill surfaces, but it is likely better than nothing. for (const Surface &surface : ironing_params.layerm->fill_surfaces()) - if (surface.surface_type == stInternalSolid) + if (surface.is_internal_solid()) polygons_append(infills, surface.expolygon); } } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1f306c83c..d5919f552 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2459,7 +2459,10 @@ void GCode::process_layer_single_object( }; ExtrusionEntitiesPtr temp_fill_extrusions; - if (const Layer *layer = layer_to_print.object_layer; layer) + if (const Layer *layer = layer_to_print.object_layer; layer) { +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + layer->export_lslices_polygons_to_svg_debug("process_gcode_layer"); +#endif for (size_t idx : layer->lslice_indices_sorted_by_print_order) { const LayerSlice &lslice = layer->lslices_ex[idx]; auto extrude_infill_range = [&]( @@ -2472,15 +2475,25 @@ void GCode::process_layer_single_object( for (auto it_fill_range = it_fill_ranges_begin; it_fill_range != it_fill_ranges_end; ++ it_fill_range) { assert(it_fill_range->region() == it_fill_ranges_begin->region()); for (uint32_t fill_id : *it_fill_range) { - assert(dynamic_cast(fills.entities[fill_id])); - if (auto *eec = static_cast(fills.entities[fill_id]); - (eec->role() == ExtrusionRole::Ironing) == ironing && shall_print_this_extrusion_collection(eec, region)) { + auto *eec = static_cast(fills.entities[fill_id]); + assert(eec); + if ((eec->role() == ExtrusionRole::Ironing) == ironing && shall_print_this_extrusion_collection(eec, region)) { +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + static int extrude_infill_range_run = 0; + eec->export_to_svg(debug_out_path("Layer-%d_gcode-extrude_infill_range-shall-be-printed-%u.svg", layerm.layer()->id(), extrude_infill_range_run++).c_str()); +#endif if (eec->can_reverse()) // Flatten the infill collection for better path planning. - for (auto *ee : eec->entities) + for (auto *ee : eec->entities) { temp_fill_extrusions.emplace_back(ee); + } else temp_fill_extrusions.emplace_back(eec); + } else { +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + static int iRun = 0; + eec->export_to_svg(debug_out_path("Layer-%d_gcode-extrude_infill_range-shall-not-be-printed-%u.svg", layerm.layer()->id(), iRun++).c_str()); +#endif } } } @@ -2491,17 +2504,30 @@ void GCode::process_layer_single_object( // Will parallel access of initial G-code preview to these extrusions while reordering them at backend cause issues? chain_and_reorder_extrusion_entities(temp_fill_extrusions, &m_last_pos); const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; - for (const ExtrusionEntity *fill : temp_fill_extrusions) + for (const ExtrusionEntity *fill : temp_fill_extrusions) { if (auto *eec = dynamic_cast(fill); eec) { - for (const ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) + for (const ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) { +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + static int iRun = 0; + eec->export_to_svg(debug_out_path("Layer-%d_gcode-extrude_infill_range-collection-%u.svg", layerm.layer()->id(), iRun++).c_str()); +#endif gcode += this->extrude_entity(*ee, extrusion_name); - } else + } + } else { +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + static int iRun = 0; + export_to_svg(debug_out_path("Layer-%d_gcode-extrude_infill_range-entity-%u.svg", layerm.layer()->id(), iRun++).c_str(), + fill->as_polyline(), get_extents(layerm.slices().surfaces), scale_(0.1f)); +#endif gcode += this->extrude_entity(*fill, extrusion_name); + } + } } }; //FIXME order islands? // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) + BOOST_LOG_TRIVIAL(trace) << "Generating GCode for layer " << layer->id() << " - slice " << idx << " has " << lslice.islands.size() << " islands"; for (const LayerIsland &island : lslice.islands) { auto process_perimeters = [&]() { const LayerRegion &layerm = *layer->get_region(island.perimeters.region()); @@ -2510,9 +2536,13 @@ void GCode::process_layer_single_object( const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); bool first = true; for (uint32_t perimeter_id : island.perimeters) { - assert(dynamic_cast(layerm.perimeters().entities[perimeter_id])); - if (const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); - shall_print_this_extrusion_collection(eec, region)) { + const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); + assert(eec); + if (shall_print_this_extrusion_collection(eec, region)) { +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + static int iRun = 0; + eec->export_to_svg(debug_out_path("Layer-%d_gcode-process_perimeters-%u.svg", layerm.layer()->id(), iRun++).c_str()); +#endif // This may not apply to Arachne, but maybe the Arachne gap fill should disable reverse as well? // assert(! eec->can_reverse()); if (first) { @@ -2520,8 +2550,14 @@ void GCode::process_layer_single_object( init_layer_delayed(); m_config.apply(region.config()); } - for (const ExtrusionEntity *ee : *eec) + for (const ExtrusionEntity *ee : *eec) { gcode += this->extrude_entity(*ee, comment_perimeter, -1.); + } + } else { +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + static int iRun = 0; + eec->export_to_svg(debug_out_path("Layer-%d_gcode-process_perimeters-not-printed-%u.svg", layerm.layer()->id(), iRun++).c_str()); +#endif } } }; @@ -2535,6 +2571,7 @@ void GCode::process_layer_single_object( it = it_end; } }; + BOOST_LOG_TRIVIAL(trace) << "Generating GCode for layer " << layer->id() << " island - " << island.fills.size() << " fills, " << island.perimeters.size() << " perimeters."; if (print.config().infill_first) { process_infill(); process_perimeters(); @@ -2558,6 +2595,7 @@ void GCode::process_layer_single_object( } } } + } if (! first && this->config().gcode_label_objects) gcode += std::string("; stop printing object ") + print_object.model_object()->name + " id:" + std::to_string(object_id) + " copy " + std::to_string(print_instance.instance_id) + "\n"; } @@ -2793,6 +2831,11 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, const std::s std::string GCode::extrude_entity(const ExtrusionEntity &entity, const std::string_view description, double speed) { +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + static int extrude_infill_range_run = 0; + export_to_svg(debug_out_path("gcode-extrude_entity-%u.svg", extrude_infill_range_run++).c_str(), + entity.as_polyline(), get_extents(entity.as_polyline()), scale_(0.1f)); +#endif if (const ExtrusionPath* path = dynamic_cast(&entity)) return this->extrude_path(*path, description, speed); else if (const ExtrusionMultiPath* multipath = dynamic_cast(&entity)) @@ -2943,7 +2986,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de acceleration = m_config.first_layer_acceleration_over_raft.value; } else if (m_config.bridge_acceleration.value > 0 && path.role().is_bridge()) { acceleration = m_config.bridge_acceleration.value; - } else if (m_config.top_solid_infill_acceleration > 0 && path.role() == ExtrusionRole::TopSolidInfill) { + } else if (m_config.top_solid_infill_acceleration > 0 && (path.role() == ExtrusionRole::TopSolidInfill || path.role() == ExtrusionRole::TopSolidInfillNonplanar)) { acceleration = m_config.top_solid_infill_acceleration.value; } else if (m_config.solid_infill_acceleration > 0 && path.role().is_solid_infill()) { acceleration = m_config.solid_infill_acceleration.value; @@ -2976,9 +3019,9 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de speed = m_config.get_abs_value("bridge_speed"); } else if (path.role() == ExtrusionRole::InternalInfill) { speed = m_config.get_abs_value("infill_speed"); - } else if (path.role() == ExtrusionRole::SolidInfill) { + } else if (path.role() == ExtrusionRole::SolidInfill || path.role() == ExtrusionRole::SolidInfillNonplanar) { speed = m_config.get_abs_value("solid_infill_speed"); - } else if (path.role() == ExtrusionRole::TopSolidInfill) { + } else if (path.role() == ExtrusionRole::TopSolidInfill || path.role() == ExtrusionRole::TopSolidInfillNonplanar) { speed = m_config.get_abs_value("top_solid_infill_speed"); } else if (path.role() == ExtrusionRole::Ironing) { speed = m_config.get_abs_value("ironing_speed"); @@ -3118,15 +3161,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de comment = description; comment += description_bridge; } - Vec2d prev = this->point_to_gcode_quantized(path.polyline.points.front()); + Vec3d prev3 = this->point3_to_gcode_quantized(path.polyline.points.front()); auto it = path.polyline.points.begin(); auto end = path.polyline.points.end(); for (++ it; it != end; ++ it) { - Vec2d p = this->point_to_gcode_quantized(*it); - const double line_length = (p - prev).norm(); + Vec3d p3 = this->point3_to_gcode_quantized(*it); + const double line_length = (p3 - prev3).norm(); path_length += line_length; - gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment); - prev = p; + gcode += m_writer.extrude_to_xyz(p3, e_per_mm * line_length, comment); + prev3 = p3; } } else { std::string marked_comment; @@ -3138,13 +3181,13 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de double last_set_fan_speed = new_points[0].fan_speed; gcode += m_writer.set_speed(last_set_speed, "", cooling_marker_setspeed_comments); gcode += "\n;_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n"; - Vec2d prev = this->point_to_gcode_quantized(new_points[0].p); + Vec3d prev3 = this->point3_to_gcode_quantized(new_points[0].p); for (size_t i = 1; i < new_points.size(); i++) { const ProcessedPoint &processed_point = new_points[i]; - Vec2d p = this->point_to_gcode_quantized(processed_point.p); - const double line_length = (p - prev).norm(); - gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, marked_comment); - prev = p; + Vec3d p3 = this->point3_to_gcode_quantized(processed_point.p); + const double line_length = (p3 - prev3).norm(); + gcode += m_writer.extrude_to_xyz(p3, e_per_mm * line_length, marked_comment); + prev3 = p3; double new_speed = processed_point.speed * 60.0; if (last_set_speed != new_speed) { gcode += m_writer.set_speed(new_speed, "", cooling_marker_setspeed_comments); @@ -3186,6 +3229,8 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // check whether a straight travel move would need retraction bool needs_retraction = this->needs_retraction(travel, role); + // check whether we need to move to a different z layer + bool needs_zmove = this->needs_zmove(travel); // check whether wipe could be disabled without causing visible stringing bool could_be_wipe_disabled = false; // Save state of use_external_mp_once for the case that will be needed to call twice m_avoid_crossing_perimeters.travel_to. @@ -3232,10 +3277,20 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // use G1 because we rely on paths being straight (G0 may make round paths) if (travel.size() >= 2) { + // Move Z up if necessary + if (needs_zmove) { + gcode += m_writer.travel_to_z(this->layer()->print_z, "Move up for non planar extrusion"); + } + gcode += m_writer.set_travel_acceleration((unsigned int)(m_config.travel_acceleration.value + 0.5)); - for (size_t i = 1; i < travel.size(); ++ i) - gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); + for (size_t i = 1; i < travel.size(); ++ i) { + if (needs_zmove) { + gcode += m_writer.travel_to_xyz(this->point3_to_gcode(travel.points[i]), comment); + } else { + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); + } + } if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) { // In case that this flavor does not support separate print and travel acceleration, @@ -3243,8 +3298,18 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string gcode += m_writer.set_travel_acceleration((unsigned int)(m_config.travel_acceleration.value + 0.5)); } + if (needs_zmove) { + float move_z = unscale(point.nonplanar_z); + if(point.nonplanar_z == -1) { + move_z = this->layer()->print_z; + } + gcode += m_writer.travel_to_z(move_z, "Move down for non planar extrusion"); + } + this->set_last_pos(travel.points.back()); } + + return gcode; } @@ -3287,6 +3352,24 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) return true; } +bool +GCode::needs_zmove(const Polyline &travel) +{ + if (travel.length() < scale_(1.0)) { + // skip zmove if the move is shorter 1 mm + return false; + } + + //check if any point in travel is below the layer z + for (Point p : travel.points) + { + if ((p.nonplanar_z != -1) && (p.nonplanar_z < scale_(this->layer()->print_z))) + return true; + } + + return false; +} + std::string GCode::retract(bool toolchange) { std::string gcode; @@ -3421,12 +3504,28 @@ Vec2d GCode::point_to_gcode(const Point &point) const return unscaled(point) + m_origin - extruder_offset; } +// convert a model-space scaled point into G-code coordinates +Vec3d GCode::point3_to_gcode(const Point &point) const +{ + Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); + double p_x = unscaled(point.x()) + m_origin.x() - extruder_offset.x(); + double p_y = unscaled(point.y()) + m_origin.y() - extruder_offset.y(); + double p_z = point.nonplanar_z == -1 ? this->layer()->print_z : unscale(point.nonplanar_z); + return { p_x, p_y, p_z }; +} + Vec2d GCode::point_to_gcode_quantized(const Point &point) const { Vec2d p = this->point_to_gcode(point); return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) }; } +Vec3d GCode::point3_to_gcode_quantized(const Point &point) const +{ + Vec3d p = this->point3_to_gcode(point); + return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()), GCodeFormatter::quantize_xyzf(p.z()) }; +} + // convert a model-space scaled point into G-code coordinates Point GCode::gcode_to_point(const Vec2d &point) const { @@ -3435,7 +3534,6 @@ Point GCode::gcode_to_point(const Vec2d &point) const // This function may be called at the very start from toolchange G-code when the extruder is not assigned yet. pt += m_config.extruder_offset.get_at(extruder->id()); return scaled(pt); - } } // namespace Slic3r diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 346ececba..9f918bbcd 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -166,8 +166,10 @@ public: const Point& last_pos() const { return m_last_pos; } // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset. Vec2d point_to_gcode(const Point &point) const; + Vec3d point3_to_gcode(const Point &point) const; // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution. Vec2d point_to_gcode_quantized(const Point &point) const; + Vec3d point3_to_gcode_quantized(const Point &point) const; Point gcode_to_point(const Vec2d &point) const; const FullPrintConfig &config() const { return m_config; } const Layer* layer() const { return m_layer; } @@ -326,6 +328,7 @@ private: std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); + bool needs_zmove(const Polyline &travel); std::string retract(bool toolchange = false); std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } std::string set_extruder(unsigned int extruder_id, double print_z); diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 9c330c38e..f0e43aaf6 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -274,7 +274,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co // FIXME: This function was not being used when travel_speed_z was separated (bd6badf). // Calculation of feedrate was not updated accordingly. If you want to use // this function, fix it first. - std::terminate(); + // std::terminate(); /* If target Z is lower than current Z but higher than nominal Z we don't perform the Z move but we only move in the XY plane and @@ -360,16 +360,16 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: return w.string(); } -#if 0 +#if 1 std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment) { m_pos = point; m_lifted = 0; - m_extruder->extrude(dE); + //m_extruder->extrude(dE); GCodeG1Formatter w; w.emit_xyz(point); - w.emit_e(m_extrusion_axis, m_extruder->E()); + w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second); w.emit_comment(this->config.gcode_comments, comment); return w.string(); } diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 0d376cb15..56b0cafc9 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -62,7 +62,7 @@ public: std::string travel_to_z(double z, const std::string &comment = std::string()); bool will_move_z(double z) const; std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string()); -// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string()); + std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string()); std::string retract(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false); std::string unretract(); diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index f2860ea8e..09ef54215 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -746,4 +746,74 @@ bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d & return std::abs(d * d) < EPSILON * lx2 * ly2; } +bool +Point_in_triangle(Vec2f pt, Vec2f v1, Vec2f v2, Vec2f v3) +{ + //Check if point is right of every edge + if (sign(pt, v1, v2) <= 0.0f) return false; + if (sign(pt, v2, v3) <= 0.0f) return false; + if (sign(pt, v3, v1) <= 0.0f) return false; + + return true; +} + +//https://graphics.stanford.edu/~mdfisher/Code/Engine/Plane.cpp.html +coord_t +Project_point_on_plane(Vec3f v1, Vec3f n, Point pt) +{ + //if no intersection leave point unchanged (should never happen) + if(n.z() == 0) { + return -1; + } + + //unscale point for calculations + float px = unscale(pt.x()); + float py = unscale(pt.y()); + float pz = 0; + + //Calculate space plane + float d = -(v1.x() * n.x() + v1.y() * n.y() + v1.z() * n.z()); + + float u = -(n.x() * px + n.y() * py + n.z() * pz + d) / n.z(); + + //scale up again + return scale_(u); +} + +// http://paulbourke.net/geometry/pointlineplane/index.html +Vec3d* +Line_intersection(Vec3d p1, Vec3d p2, Point p3, Point p4) { + + float denom = ((p4.y() - p3.y())*(p2.x() - p1.x())) - + ((p4.x() - p3.x())*(p2.y() - p1.y())); + + float nume_a = ((p4.x() - p3.x())*(p1.y() - p3.y())) - + ((p4.y() - p3.y())*(p1.x() - p3.x())); + + float nume_b = ((p2.x() - p1.x())*(p1.y() - p3.y())) - + ((p2.y() - p1.y())*(p1.x() - p3.x())); + + if(denom == 0.0f) + { + return NULL; + } + + float ua = nume_a / denom; + float ub = nume_b / denom; + + if(ua >= 0.0f && ua <= 1.0f && ub >= 0.0f && ub <= 1.0f) + { + // Get the intersection point anc calculate z component + Vec3d* ret = new Vec3d(); + ret->x() = p1.x() + ua*(p2.x() - p1.x()); + ret->y() = p1.y() + ua*(p2.y() - p1.y()); + ret->z() = p1.z() - ((sqrt((p1.x()-ret->x())*(p1.x()-ret->x()) + (p1.y()-ret->y())*(p1.y()-ret->y())) + / sqrt((p1.x()-p2.x())*(p1.x()-p2.x()) + (p1.y()-p2.y())*(p1.y()-p2.y()))) + * (p1.z() - p2.z())); + return ret; + } + + return NULL; +} + }} // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 9d04bb7eb..806f04095 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -529,6 +529,23 @@ Vec<3, T> spheric_to_dir(const Pair &v) return spheric_to_dir(plr, azm); } +inline float sign(Vec2f p1, Vec2f p2, Vec2f p3) +{ + return (p1.x() - p3.x()) * (p2.y() - p3.y()) - (p2.x() - p3.x()) * (p1.y() - p3.y()); +} + +bool Point_in_triangle(Vec2f pt, Vec2f v1, Vec2f v2, Vec2f v3); + +//https://graphics.stanford.edu/~mdfisher/Code/Engine/Plane.cpp.html +coord_t Project_point_on_plane(Vec3f v1, Vec3f n, Point pt); + +// http://paulbourke.net/geometry/pointlineplane/index.html +Vec3d *Line_intersection(Vec3d p1, Vec3d p2, Point p3, Point p4); + +inline float triangle_surface(Point p1, Point p2, Point p3) { + return 0.5 * ((p2.x()-p1.x()) * (p3.y()-p1.y()) - (p2.y()-p1.y()) * (p3.x()-p1.x())); +} + } } // namespace Slicer::Geometry #endif diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 9d658841c..c5d578e58 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -54,6 +54,8 @@ void Layer::make_slices() } // lslices are sorted by topological order from outside to inside from the clipper union used above this->lslices = slices; + + BOOST_LOG_TRIVIAL(trace) << "make_slices layer " << id() << " found " << slices.size() << " slices"; } this->lslice_indices_sorted_by_print_order = chain_expolygons(this->lslices); @@ -1066,7 +1068,7 @@ void Layer::export_region_slices_to_svg(const char *path) const void Layer::export_region_slices_to_svg_debug(const char *name) const { static size_t idx = 0; - this->export_region_slices_to_svg(debug_out_path("Layer-slices-%s-%d.svg", name, idx ++).c_str()); + this->export_region_slices_to_svg(debug_out_path("Layer-%d-slices-%s-%d.svg", id(), name, idx ++).c_str()); } void Layer::export_region_fill_surfaces_to_svg(const char *path) const @@ -1092,7 +1094,57 @@ void Layer::export_region_fill_surfaces_to_svg(const char *path) const void Layer::export_region_fill_surfaces_to_svg_debug(const char *name) const { static size_t idx = 0; - this->export_region_fill_surfaces_to_svg(debug_out_path("Layer-fill_surfaces-%s-%d.svg", name, idx ++).c_str()); + this->export_region_fill_surfaces_to_svg(debug_out_path("Layer-%d-fill_surfaces-%s-%d.svg", id(), name, idx ++).c_str()); +} + +void Layer::export_lslices_polygons_to_svg(const char *path) const +{ + BoundingBox bbox; + for (const auto *region : m_regions) + for (const auto &surface : region->slices()) + bbox.merge(get_extents(surface.expolygon)); + + SVG svg(path, bbox); + for (int i = 0; i< lslices.size(); i++) + svg.draw(lslices[i], "black", 1.0); + svg.Close(); +} + +// Export to "out/Layer-%name-%idx.svg" with an increasing index with every export. +void Layer::export_lslices_polygons_to_svg_debug(const char *name) const +{ + static size_t idx = 0; + this->export_lslices_polygons_to_svg(debug_out_path("Layer-%d-lslice_polygons-%s-%d.svg", id(), name, idx ++).c_str()); +} + +void +LayerRegion::remove_nonplanar_slices(SurfaceCollection topNonplanar) { + Surfaces layerm_slices_surfaces(m_slices.surfaces); + + //save previously detected nonplanar surfaces + SurfaceCollection polyNonplanar; + for(Surface s : m_slices.surfaces) { + if (s.is_nonplanar()) { + polyNonplanar.surfaces.push_back(s); + } + } + + // clear internal surfaces + m_slices.clear(); + + // append internal surfaces again without the found topNonplanar surfaces + m_slices.append( + diff_ex( + union_ex(layerm_slices_surfaces), + topNonplanar.surfaces, + ApplySafetyOffset::No), + stInternal + ); +} + +void +LayerRegion::append_top_nonplanar_slices(SurfaceCollection topNonplanar) { + m_slices.append(std::move(topNonplanar)); } BoundingBox get_extents(const LayerRegion &layer_region) diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 5cfdf9cfa..c2e5eed9a 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -7,6 +7,7 @@ #include "Flow.hpp" #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" +#include "NonplanarSurface.hpp" #include @@ -132,6 +133,9 @@ public: // ordered collection of extrusion paths to fill surfaces // (this collection contains only ExtrusionEntityCollection objects) [[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; } + + // Vector of nonplanar_surfaces which are homed in this layer + [[nodiscard]] const NonplanarSurfaces& nonplanar_surfaces() const { return m_nonplanar_surfaces; } Flow flow(FlowRole role) const; Flow flow(FlowRole role, double layer_height) const; @@ -167,6 +171,20 @@ public: // Is there any valid extrusion assigned to this LayerRegion? bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); } + //append a new nonplanar surface to the list skip if already in list + void append_nonplanar_surface(NonplanarSurface& surface); + // Projects the paths of a collection regarding the structure of a stl mesh + void project_nonplanar_extrusion(ExtrusionEntityCollection* collection); + /// Projects nonplanar surfaces downwards regarding the structure of the stl mesh. + void project_nonplanar_surfaces(); + ///project a nonplanar path + void project_nonplanar_path(ExtrusionPath* path); + ///sets the z coordinates correctly for all points TODO move to generation of points + void correct_z_on_path(ExtrusionPath *path); + // + void remove_nonplanar_slices(SurfaceCollection topNonplanar); + void append_top_nonplanar_slices(SurfaceCollection topNonplanar); + protected: friend class Layer; friend class PrintObject; @@ -225,6 +243,8 @@ private: // (this collection contains only ExtrusionEntityCollection objects) ExtrusionEntityCollection m_fills; + NonplanarSurfaces m_nonplanar_surfaces; + // collection of expolygons representing the bridged areas (thus not // needing support material) // Polygons bridged; @@ -379,9 +399,11 @@ public: void export_region_slices_to_svg(const char *path) const; void export_region_fill_surfaces_to_svg(const char *path) const; + void export_lslices_polygons_to_svg(const char *name) const; // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. void export_region_slices_to_svg_debug(const char *name) const; void export_region_fill_surfaces_to_svg_debug(const char *name) const; + void export_lslices_polygons_to_svg_debug(const char *name) const; // Is there any valid extrusion assigned to this LayerRegion? virtual bool has_extrusions() const { for (auto layerm : m_regions) if (layerm->has_extrusions()) return true; return false; } diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 73f225c57..2f466aef2 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -463,10 +463,16 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells, RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps), sparse, expansion_params_into_sparse_infill, closing_radius); + Surfaces nonplanarTops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTopNonplanar, shells, + RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps), + sparse, expansion_params_into_sparse_infill, closing_radius); + Surfaces nonplanarInternals = expand_merge_surfaces(m_fill_surfaces.surfaces, stInternalSolidNonplanar, shells, + RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps), + sparse, expansion_params_into_sparse_infill, closing_radius); // m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid }); m_fill_surfaces.clear(); - reserve_more(m_fill_surfaces.surfaces, shells.size() + sparse.size() + bridges.size() + bottoms.size() + tops.size()); + reserve_more(m_fill_surfaces.surfaces, shells.size() + sparse.size() + bridges.size() + bottoms.size() + tops.size() + nonplanarTops.size() + nonplanarInternals.size()); { Surface solid_templ(stInternalSolid, {}); solid_templ.thickness = layer_thickness; @@ -480,6 +486,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly m_fill_surfaces.append(std::move(bridges.surfaces)); m_fill_surfaces.append(std::move(bottoms)); m_fill_surfaces.append(std::move(tops)); + m_fill_surfaces.append(std::move(nonplanarTops)); + m_fill_surfaces.append(std::move(nonplanarInternals)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final"); @@ -876,7 +884,7 @@ void LayerRegion::export_region_slices_to_svg_debug(const char *name) const { static std::map idx_map; size_t &idx = idx_map[name]; - this->export_region_slices_to_svg(debug_out_path("LayerRegion-slices-%s-%d.svg", name, idx ++).c_str()); + this->export_region_slices_to_svg(debug_out_path("LayerRegion-%d-slices-%s-%d.svg", layer()->id(), name, idx ++).c_str()); } void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const @@ -903,7 +911,246 @@ void LayerRegion::export_region_fill_surfaces_to_svg_debug(const char *name) con { static std::map idx_map; size_t &idx = idx_map[name]; - this->export_region_fill_surfaces_to_svg(debug_out_path("LayerRegion-fill_surfaces-%s-%d.svg", name, idx ++).c_str()); + this->export_region_fill_surfaces_to_svg(debug_out_path("LayerRegion-%d-fill_surfaces-%s-%d.svg", layer()->id(), name, idx ++).c_str()); +} + +void +LayerRegion::append_nonplanar_surface(NonplanarSurface& surface) +{ + for(auto & s : m_nonplanar_surfaces){ + if (s == surface){ + return; + } + } + m_nonplanar_surfaces.push_back(surface); +} + +void +LayerRegion::project_nonplanar_extrusion(ExtrusionEntityCollection *collection) +{ + for (auto& entity : collection->entities) { + if (ExtrusionLoop* loop = dynamic_cast(entity)) { + BOOST_LOG_TRIVIAL(trace) << "Projecting extrusion loop with " << loop->paths.size() << " paths"; + + for(auto& path : loop->paths) { + project_nonplanar_path(&path); + correct_z_on_path(&path); + } + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + export_to_svg(debug_out_path("Layer-%d_project_extrusion_loop-%u-after.svg", layer()->id(), iRun ++).c_str(), + loop->as_polyline(), get_extents(this->slices().surfaces), scale_(0.1f)); + } +#endif + } else if (ExtrusionMultiPath* multipath = dynamic_cast(entity)) { + BOOST_LOG_TRIVIAL(trace) << "Projecting extrusion multipath with " << multipath->paths.size() << " paths"; + + for (auto& path : multipath->paths) { + project_nonplanar_path(&path); + correct_z_on_path(&path); + } +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + export_to_svg(debug_out_path("Layer-%d_project_extrusion_multipath-%u-after.svg", layer()->id(), iRun ++).c_str(), + multipath->as_polyline(), get_extents(this->slices().surfaces), scale_(0.1f)); + } +#endif + } else if (ExtrusionPath* path = dynamic_cast(entity)) { + BOOST_LOG_TRIVIAL(trace) << "Projecting 1 extrusion path"; + + project_nonplanar_path(path); + correct_z_on_path(path); +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + export_to_svg(debug_out_path("Layer-%d_project_extrusion_path-%u-after.svg", layer()->id(), iRun ++).c_str(), + path->as_polyline(), get_extents(this->slices().surfaces), scale_(0.1f)); + } +#endif + } else { + BOOST_LOG_TRIVIAL(trace) << "Unknown entity class " << typeid(entity).name(); + assert(false); + } + } +} + +void +LayerRegion::project_nonplanar_surfaces() +{ + // skip if there are no nonplanar_surfaces on this LayerRegion + if (m_nonplanar_surfaces.size() == 0) { + return; + } + + BOOST_LOG_TRIVIAL(trace) << "Projecting " << m_nonplanar_surfaces.size() << " nonplanar surfaces for region " << + region().print_region_id() << " and layer " << layer()->id() << " (z = " << layer()->print_z << ")"; + + // for all perimeters do path projection + BOOST_LOG_TRIVIAL(trace) << "Projecting " << m_perimeters.size() << " nonplanar perimeters"; + for (auto& col : m_perimeters.entities) { + ExtrusionEntityCollection* collection = dynamic_cast(col); + this->project_nonplanar_extrusion(collection); + } + + // and all fill paths do path projection + BOOST_LOG_TRIVIAL(trace) << "Projecting " << m_fills.size() << " nonplanar fills"; + for (auto& col : m_fills.entities) { + ExtrusionEntityCollection* collection = dynamic_cast(col); + this->project_nonplanar_extrusion(collection); + } +} + +//Sorting functions for soring of paths +bool +greaterX(const Vec3d &a, const Vec3d &b) +{ + return (a.x() < b.x()); +} + +bool +smallerX(const Vec3d &a, const Vec3d &b) +{ + return (a.x() > b.x()); +} + +bool +greaterY(const Vec3d &a, const Vec3d &b) +{ + return (a.y() < b.y()); +} + +bool +smallerY(const Vec3d &a, const Vec3d &b) +{ + return (a.y() > b.y()); +} + +void +LayerRegion::project_nonplanar_path(ExtrusionPath *path) +{ + //First check all points and project them regarding the triangle mesh + for (Point& point : path->polyline.points) { + for (auto& surface : m_nonplanar_surfaces) { + float distance_to_top = surface.stats.max.z - this->layer()->print_z; + for(auto& facet : surface.mesh) { + // skip if point is outside of the bounding box of the triangle + if (unscale(point).x() < std::min({facet.second.vertex[0].x, facet.second.vertex[1].x, facet.second.vertex[2].x}) || + unscale(point).x() > std::max({facet.second.vertex[0].x, facet.second.vertex[1].x, facet.second.vertex[2].x}) || + unscale(point).y() < std::min({facet.second.vertex[0].y, facet.second.vertex[1].y, facet.second.vertex[2].y}) || + unscale(point).y() > std::max({facet.second.vertex[0].y, facet.second.vertex[1].y, facet.second.vertex[2].y})) + { + continue; + } + + //check if point is inside of Triangle + if (Slic3r::Geometry::Point_in_triangle( + Vec2f(unscale(point).x(), unscale(point).y()), + Vec2f(facet.second.vertex[0].x, facet.second.vertex[0].y), + Vec2f(facet.second.vertex[1].x, facet.second.vertex[1].y), + Vec2f(facet.second.vertex[2].x, facet.second.vertex[2].y)) + && (facet.second.normal.z != 0)) + { + coord_t z = Slic3r::Geometry::Project_point_on_plane(Vec3f(facet.second.vertex[0].x,facet.second.vertex[0].y,facet.second.vertex[0].z), + Vec3f(facet.second.normal.x,facet.second.normal.y,facet.second.normal.z), + point); + + //Shift down when on lower layer + point.nonplanar_z = z - scale_(distance_to_top); + //break; + } + } + } + } + + // Then check all line intersections, cut line on intersection and project new point + std::vector::size_type size = path->polyline.points.size(); + for (std::vector::size_type i = 0; i < size-1; ++i) + { + Pointf3s intersections; + // check against every facet if lines intersect + for (auto& surface : m_nonplanar_surfaces) { + float distance_to_top = surface.stats.max.z - this->layer()->print_z; + for(auto& facet : surface.mesh) { + for(int j= 0; j < 3; j++) { + // TODO precheck for faster computation + Vec3d p1 = Vec3d(scale_(facet.second.vertex[j].x), scale_(facet.second.vertex[j].y), scale_(facet.second.vertex[j].z)); + Vec3d p2 = Vec3d(scale_(facet.second.vertex[(j+1) % 3].x), scale_(facet.second.vertex[(j+1) % 3].y), scale_(facet.second.vertex[(j+1) % 3].z)); + Vec3d* p = Slic3r::Geometry::Line_intersection(p1, p2, path->polyline.points[i], path->polyline.points[i+1]); + + if (p) { + // add distance to top for every added point + p->z() = p->z() - scale_(distance_to_top); + intersections.push_back(*p); + } + } + } + } + + // Stop if no intersections are found + if (intersections.size() == 0) continue; + + // sort found intersectons if there are more than 1 + if ( intersections.size() > 1 ){ + if (abs(path->polyline.points[i+1].x() - path->polyline.points[i].x()) >= abs(path->polyline.points[i+1].y() - path->polyline.points[i].y()) ) { + // sort by X + if(path->polyline.points[i].x() < path->polyline.points[i+1].x()) { + std::sort(intersections.begin(), intersections.end(), smallerX); + }else { + std::sort(intersections.begin(), intersections.end(), greaterX); + } + } else { + // sort by Y + if(path->polyline.points[i].y() < path->polyline.points[i+1].y()) { + std::sort(intersections.begin(), intersections.end(), smallerY); + }else { + std::sort(intersections.begin(), intersections.end(), greaterY); + } + } + } + + // remove duplicates + Pointf3s::iterator point = intersections.begin(); + while (point != intersections.end()-1) { + bool delete_point = false; + Pointf3s::iterator point2 = point; + ++point2; + //compare with next point if they are the same, delete current point + if ((*point).x() == (*point2).x() && (*point).y() == (*point2).y()) { + //remove duplicate point + delete_point = true; + point = intersections.erase(point); + } + //continue loop when no point is removed. Otherwise the new point is set while deleting the old one. + if (!delete_point) { + ++point; + } + } + + //insert new points into array + for (Vec3d p : intersections) + { + Point *pt = new Point(p.x(), p.y()); + pt->nonplanar_z = p.z(); + path->polyline.points.insert(path->polyline.points.begin()+i+1, *pt); + } + + //modifiy array boundary + i = i + intersections.size(); + size = size + intersections.size(); + } +} + +void +LayerRegion::correct_z_on_path(ExtrusionPath *path) +{ + for (Point& point : path->polyline.points) { + if(point.nonplanar_z == -1) { + point.nonplanar_z = scale_(this->layer()->print_z); + } + } } } diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index fb4727abe..d0c680b42 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -103,8 +103,28 @@ bool MultiPoint::remove_duplicate_points() return false; } +bool +MultiPoint::needs_zmove(const Points &pts) +{ + coord_t z = -1; + //check if any point in travel is below the layer z + for (Point p : pts) + { + if(z == -1) { + z = p.nonplanar_z; + } + else if ((p.nonplanar_z != -1) && (p.nonplanar_z != z)) + return true; + } + + return false; +} + Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance) { + if(MultiPoint::needs_zmove(pts)) + return pts; + Points result_pts; auto tolerance_sq = int64_t(sqr(tolerance)); if (! pts.empty()) { diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 62b53255b..20980c31e 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -81,6 +81,7 @@ public: } } + static bool needs_zmove(const Points &pts); static Points douglas_peucker(const Points &points, const double tolerance); static Points visivalingam(const Points& pts, const double& tolerance); diff --git a/src/libslic3r/NonplanarFacet.cpp b/src/libslic3r/NonplanarFacet.cpp new file mode 100644 index 000000000..fbc953e76 --- /dev/null +++ b/src/libslic3r/NonplanarFacet.cpp @@ -0,0 +1,72 @@ +#include "NonplanarFacet.hpp" + +namespace Slic3r { + +void +NonplanarFacet::calculate_stats() { + //calculate min and max values + this->stats.min.x = this->vertex[0].x; + this->stats.min.y = this->vertex[0].y; + this->stats.min.z = this->vertex[0].z; + this->stats.max.x = this->vertex[0].x; + this->stats.max.y = this->vertex[0].y; + this->stats.max.z = this->vertex[0].z; + for(int j = 1; j < 3; j++) { + this->stats.min.x = std::min(this->stats.min.x, this->vertex[j].x); + this->stats.min.y = std::min(this->stats.min.y, this->vertex[j].y); + this->stats.min.z = std::min(this->stats.min.z, this->vertex[j].z); + this->stats.max.x = std::max(this->stats.max.x, this->vertex[j].x); + this->stats.max.y = std::max(this->stats.max.y, this->vertex[j].y); + this->stats.max.z = std::max(this->stats.max.z, this->vertex[j].z); + } +} + +void +NonplanarFacet::translate(float x, float y, float z) +{ + //translate facet + for(int j = 0; j < 3; j++) { + this->vertex[j].x += x; + this->vertex[j].y += y; + this->vertex[j].z += z; + } + + //translate min and max values + this->stats.min.x += x; + this->stats.min.y += y; + this->stats.min.z += z; + this->stats.max.x += x; + this->stats.max.y += y; + this->stats.max.z += z; +} + +void +NonplanarFacet::scale(float versor[3]) +{ + //scale facet + for(int j = 0; j < 3; j++) { + this->vertex[j].x *= versor[0]; + this->vertex[j].y *= versor[1]; + this->vertex[j].z *= versor[2]; + } + + //scale min and max values + this->stats.min.x *= versor[0]; + this->stats.min.y *= versor[1]; + this->stats.min.z *= versor[2]; + this->stats.max.x *= versor[0]; + this->stats.max.y *= versor[1]; + this->stats.max.z *= versor[2]; +} + +float +NonplanarFacet::calculate_surface_area() +{ + return Slic3r::Geometry::triangle_surface( + Point(this->vertex[0].x, this->vertex[0].y), + Point(this->vertex[1].x, this->vertex[1].y), + Point(this->vertex[2].x, this->vertex[2].y) + ); +} + +} diff --git a/src/libslic3r/NonplanarFacet.hpp b/src/libslic3r/NonplanarFacet.hpp new file mode 100644 index 000000000..c38d5addd --- /dev/null +++ b/src/libslic3r/NonplanarFacet.hpp @@ -0,0 +1,39 @@ +#ifndef slic3r_NonplanarFacet_hpp_ +#define slic3r_NonplanarFacet_hpp_ + +#include "libslic3r.h" +#include "Geometry.hpp" + +namespace Slic3r { + +typedef struct { + float x; + float y; + float z; +} facet_vertex; + +typedef struct { + facet_vertex max; + facet_vertex min; +} facet_stats; + +class NonplanarFacet +{ + public: + facet_vertex vertex[3]; + facet_vertex normal; + int neighbor[3]; + facet_stats stats; + bool marked = false; + + NonplanarFacet() {}; + ~NonplanarFacet() {}; + void calculate_stats(); + void translate(float x, float y, float z); + void scale(float versor[3]); + float calculate_surface_area(); + +}; +}; + +#endif \ No newline at end of file diff --git a/src/libslic3r/NonplanarSurface.cpp b/src/libslic3r/NonplanarSurface.cpp new file mode 100644 index 000000000..5d212e1d9 --- /dev/null +++ b/src/libslic3r/NonplanarSurface.cpp @@ -0,0 +1,240 @@ +#include "NonplanarSurface.hpp" + +namespace Slic3r { + +NonplanarSurface::NonplanarSurface(std::map &_mesh) +{ + this->mesh = _mesh; + this->calculate_stats(); +} + +bool +NonplanarSurface::operator==(const NonplanarSurface& other) const { + return (stats.min.x == other.stats.min.x && + stats.min.y == other.stats.min.y && + stats.min.z == other.stats.min.z && + stats.max.x == other.stats.max.x && + stats.max.y == other.stats.max.y && + stats.max.z == other.stats.max.z); +} + +void +NonplanarSurface::calculate_stats() +{ + //calculate min and max values + this->stats.min.x = 10000000; + this->stats.min.y = 10000000; + this->stats.min.z = 10000000; + this->stats.max.x = -10000000; + this->stats.max.y = -10000000; + this->stats.max.z = -10000000; + for(auto& facet : this->mesh) { + this->stats.min.x = std::min(this->stats.min.x, facet.second.stats.min.x); + this->stats.min.y = std::min(this->stats.min.y, facet.second.stats.min.y); + this->stats.min.z = std::min(this->stats.min.z, facet.second.stats.min.z); + this->stats.max.x = std::max(this->stats.max.x, facet.second.stats.max.x); + this->stats.max.y = std::max(this->stats.max.y, facet.second.stats.max.y); + this->stats.max.z = std::max(this->stats.max.z, facet.second.stats.max.z); + } +} + +void +NonplanarSurface::translate(float x, float y, float z) +{ + //translate all facets + for(auto& facet : this->mesh) { + facet.second.translate(x, y, z); + } + + //translate min and max values + this->stats.min.x += x; + this->stats.min.y += y; + this->stats.min.z += z; + this->stats.max.x += x; + this->stats.max.y += y; + this->stats.max.z += z; +} + +void +NonplanarSurface::scale(float factor) +{ + float versor[3]; + versor[0] = factor; + versor[1] = factor; + versor[2] = factor; + this->scale(versor); +} + +void +NonplanarSurface::scale(float versor[3]) +{ + //scale all facets + for(auto& facet : this->mesh) { + facet.second.scale(versor); + } + + //scale min and max values + this->stats.min.x *= versor[0]; + this->stats.min.y *= versor[1]; + this->stats.min.z *= versor[2]; + this->stats.max.x *= versor[0]; + this->stats.max.y *= versor[1]; + this->stats.max.z *= versor[2]; +} + +void +NonplanarSurface::rotate_z(float angle) { + double radian_angle = (angle / 180.0) * 3.1415927; + double c = cos(radian_angle); + double s = sin(radian_angle); + + for(auto& facet : this->mesh) { + for(int j = 0; j < 3; j++) { + double xold = facet.second.vertex[j].x; + double yold = facet.second.vertex[j].y; + facet.second.vertex[j].x = c * xold - s * yold; + facet.second.vertex[j].y = s * xold + c * yold; + } + facet.second.calculate_stats(); + } + + this->calculate_stats(); +} + +void +NonplanarSurface::debug_output() +{ + std::cout << "Facets(" << this->mesh.size() << "): (min:X:" << this->stats.min.x << " Y:" << this->stats.min.y << " Z:" << this->stats.min.z << + " max:X:" << this->stats.max.x << " Y:" << this->stats.max.y << " Z:" << this->stats.max.z << ")" << + "Height " << this->stats.max.z - this->stats.min.z << std::endl; + for(auto& facet : this->mesh) { + std::cout << "triangle: (" << facet.first << ")(" << facet.second.marked << ") "; + std::cout << " (" << (180*std::acos(facet.second.normal.z))/3.14159265 << "°)"; + + std::cout << " | V0:"; + std::cout << " X:"<< facet.second.vertex[0].x; + std::cout << " Y:"<< facet.second.vertex[0].y; + std::cout << " Z:"<< facet.second.vertex[0].z; + + std::cout << " | V1:"; + std::cout << " X:"<< facet.second.vertex[1].x; + std::cout << " Y:"<< facet.second.vertex[1].y; + std::cout << " Z:"<< facet.second.vertex[1].z; + + std::cout << " | V2:"; + std::cout << " X:"<< facet.second.vertex[2].x; + std::cout << " Y:"<< facet.second.vertex[2].y; + std::cout << " Z:"<< facet.second.vertex[2].z; + + std::cout << " | Normal:"; + std::cout << " X:"<< facet.second.normal.x; + std::cout << " Y:"<< facet.second.normal.y; + std::cout << " Z:"<< facet.second.normal.z; + + //TODO check if neighbors exist + // stl_neighbors* neighbors = mesh.stl.neighbors_start + facet.first; + std::cout << " | Neighbors:"; + std::cout << " 0:"<< facet.second.neighbor[0]; + std::cout << " 1:"<< facet.second.neighbor[1]; + std::cout << " 2:"<< facet.second.neighbor[2]; + std::cout << std::endl; + } +} + +NonplanarSurfaces +NonplanarSurface::group_surfaces() +{ + std::pair begin = *this->mesh.begin(); + this->mark_neighbor_surfaces(begin.first); + NonplanarSurface newSurface; + for (std::map::iterator it=this->mesh.begin(); it!=this->mesh.end();) { + if((*it).second.marked == false) { + newSurface.mesh[(*it).first] = (*it).second; + it = this->mesh.erase(it); + } + else { + ++it; + } + } + this->calculate_stats(); + if (newSurface.mesh.size() == 0) { + //return only this + NonplanarSurfaces nonplanar_surfaces; + nonplanar_surfaces.push_back(*this); + return nonplanar_surfaces; + } + else { + //return union of this and recursion + NonplanarSurfaces nonplanar_surfaces = newSurface.group_surfaces(); + nonplanar_surfaces.push_back(*this); + return nonplanar_surfaces; + } +} + +void +NonplanarSurface::mark_neighbor_surfaces(int id) +{ + //if already marked return + if(this->mesh.find(id) == this->mesh.end() || this->mesh[id].marked == true) return; + //mark this facet + this->mesh[id].marked = true; + //mark all neighbors + for(int j = 0; j < 3; j++) { + this->mark_neighbor_surfaces(this->mesh[id].neighbor[j]); + } +} + +bool +NonplanarSurface::check_max_printing_height(float height) +{ + if ((this->stats.max.z - this->stats.min.z) > height ) { + std::cout << "Surface removed: printheight too heigh (" << (this->stats.max.z - this->stats.min.z) << " mm)" << '\n'; + return true; + } else { + return false; + } +} + +bool +NonplanarSurface::check_surface_area() +{ + //calculate surface area of nonplanar surface. + float area = 0.0f; + for(auto& facet : this->mesh) { + area += facet.second.calculate_surface_area(); + } + if (area < 20.0f) { + std::cout << "Surface removed: area too small (" << area << " mm²)" << '\n'; + return true; + } else { + return false; + } +} + +void +NonplanarSurface::check_printable_surfaces(float max_angle) +{ + //TODO do something +} + +/* this will return scaled ExPolygons */ +ExPolygons +NonplanarSurface::horizontal_projection() const +{ + Polygons pp; + pp.reserve(this->mesh.size()); + for(auto& facet : this->mesh) { + Polygon p; + p.points.resize(3); + p.points[0] = Point(scale_(facet.second.vertex[0].x), scale_(facet.second.vertex[0].y)); + p.points[1] = Point(scale_(facet.second.vertex[1].x), scale_(facet.second.vertex[1].y)); + p.points[2] = Point(scale_(facet.second.vertex[2].x), scale_(facet.second.vertex[2].y)); + p.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that + pp.push_back(p); + } + + // the offset factor was tuned using groovemount.stl + return union_ex(offset(pp, scale_(0.01))); +} + +} \ No newline at end of file diff --git a/src/libslic3r/NonplanarSurface.hpp b/src/libslic3r/NonplanarSurface.hpp new file mode 100644 index 000000000..fd832fbc8 --- /dev/null +++ b/src/libslic3r/NonplanarSurface.hpp @@ -0,0 +1,54 @@ +#ifndef slic3r_NonplanarSurface_hpp_ +#define slic3r_NonplanarSurface_hpp_ + +#include "libslic3r.h" +#include "NonplanarFacet.hpp" +#include "Point.hpp" +#include "Polygon.hpp" +#include "ExPolygon.hpp" +#include "Geometry.hpp" +#include "ClipperUtils.hpp" + +namespace Slic3r { + +typedef struct { + float x; + float y; + float z; +} mesh_vertex; + +typedef struct { + mesh_vertex max; + mesh_vertex min; +} mesh_stats; + +class NonplanarSurface; + +typedef std::vector NonplanarSurfaces; + +class NonplanarSurface +{ + public: + std::map mesh; + mesh_stats stats; + NonplanarSurface() {}; + ~NonplanarSurface() {}; + NonplanarSurface(std::map &_mesh); + bool operator==(const NonplanarSurface& other) const; + void calculate_stats(); + void translate(float x, float y, float z); + void scale(float factor); + void scale(float versor[3]); + void rotate_z(float angle); + void debug_output(); + NonplanarSurfaces group_surfaces(); + void mark_neighbor_surfaces(int id); + bool check_max_printing_height(float height); + void check_printable_surfaces(float max_angle); + bool check_surface_area(); + ExPolygons horizontal_projection() const; + +}; +}; + +#endif \ No newline at end of file diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index c4b821ca6..d15428373 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -159,11 +159,14 @@ class Point : public Vec2crd public: using coord_type = coord_t; + coord_t nonplanar_z { -1 }; + Point() : Vec2crd(0, 0) {} Point(int32_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {} Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} Point(double x, double y) : Vec2crd(coord_t(std::round(x)), coord_t(std::round(y))) {} Point(const Point &rhs) { *this = rhs; } + Point(Vec3crd &p) : Vec2crd(p.x(), p.y()) {} explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {} // This constructor allows you to construct Point from Eigen expressions template diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 88ac1b03f..5ba07e711 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -402,7 +402,7 @@ bool has_duplicate_points(const Polygons &polys) { #if 1 // Check globally. -#if 0 +#if 1 // Detect duplicates by sorting with quicksort. It is quite fast, but ankerl::unordered_dense is around 1/4 faster. Points allpts; allpts.reserve(count_points(polys)); diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 5261a8cfc..ec7732ccf 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -4,6 +4,7 @@ #include "ExPolygon.hpp" #include "Line.hpp" #include "Polygon.hpp" +#include "SVG.hpp" #include #include @@ -332,4 +333,29 @@ Lines3 Polyline3::lines() const return lines; } +bool export_to_svg(const char *path, const Polyline &polyline, BoundingBox bbox, const float stroke_width) +{ + SVG svg(path, bbox); + svg.draw(polyline, "black", stroke_width); + svg.Close(); + return true; +} + +bool export_to_svg(const char *path, const Polylines &polylines, BoundingBox bbox, const float stroke_width) +{ + SVG svg(path, bbox); + svg.draw(polylines, "black", stroke_width); + svg.Close(); + return true; +} + +bool export_to_svg(const char *path, const ThickPolylines &polylines, BoundingBox bbox, const float stroke_width) +{ + SVG svg(path, bbox); + svg.draw(polylines, "black", stroke_width); + svg.Close(); + return true; +} + + } diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 703e50cfa..adee699d2 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -234,6 +234,10 @@ public: typedef std::vector Polylines3; +extern bool export_to_svg(const char *path, const Polyline &polyline, BoundingBox bbox, const float stroke_width = 1.f); +extern bool export_to_svg(const char *path, const Polylines &polylines, BoundingBox bbox, const float stroke_width = 1.f); +extern bool export_to_svg(const char *path, const ThickPolylines &polylines, BoundingBox bbox, const float stroke_width = 1.f); + } #endif diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index f42b3f770..67e6c43c1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -432,6 +432,7 @@ static std::vector s_Preset_print_options { "ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing", "max_print_speed", "max_volumetric_speed", "avoid_crossing_perimeters_max_detour", "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_dist", + "use_nonplanar_layers", "nonplanar_layers_angle", "nonplanar_layers_height", "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", "enable_dynamic_overhang_speeds", "overhang_speed_0", "overhang_speed_1", "overhang_speed_2", "overhang_speed_3", diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 059491951..70df5e55a 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -17,6 +17,8 @@ #include "GCode/ThumbnailData.hpp" #include "GCode/GCodeProcessor.hpp" #include "MultiMaterialSegmentation.hpp" +#include "NonplanarSurface.hpp" +#include "NonplanarFacet.hpp" #include "libslic3r.h" @@ -67,7 +69,9 @@ enum PrintStep : unsigned int { enum PrintObjectStep : unsigned int { posSlice, posPerimeters, posPrepareInfill, - posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCount, + posInfill, posIroning, posSupportSpotsSearch, + posSupportMaterial, posNonplanarProjection, posEstimateCurledExtrusions, + posCount, }; // A PrintRegion object represents a group of volumes to print @@ -264,6 +268,8 @@ public: coord_t height() const { return m_size.z(); } // Centering offset of the sliced mesh from the scaled and rotated mesh of the model. const Point& center_offset() const { return m_center_offset; } + // + NonplanarSurfaces nonplanar_surfaces() { return m_nonplanar_surfaces; } bool has_brim() const { return this->config().brim_type != btNoBrim @@ -378,8 +384,16 @@ private: void estimate_curled_extrusions(); void slice_volumes(); + void make_slices(); + void lslices_were_updated(); // Has any support (not counting the raft). void detect_surfaces_type(); + void merge_nonplanar_surfaces(); + void debug_svg_print(); + bool check_nonplanar_collisions(NonplanarSurface &surface); + void project_nonplanar_surfaces(); + void find_nonplanar_surfaces(); + void detect_nonplanar_surfaces(); void process_external_surfaces(); void discover_vertical_shells(); void bridge_over_infill(); @@ -414,6 +428,8 @@ private: // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; + NonplanarSurfaces m_nonplanar_surfaces; + std::pair m_adaptive_fill_octrees; FillLightning::GeneratorPtr m_lightning_generator; }; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3da804066..ce53e5bed 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1369,6 +1369,33 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.8)); + + def = this->add("use_nonplanar_layers", coBool); + def->label = L("Use nonplanar layers"); + def->category = L("Nonplanar layers"); + def->tooltip = L("Generate nonplanar layers on top of the object"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("nonplanar_layers_angle", coFloat); + def->label = L("Maximum nonplanar angle"); + def->category = L("Nonplanar layers"); + def->tooltip = L("The maximum angle the printer can print without collisions."); + def->sidetext = L("°"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(10.0)); + + def = this->add("nonplanar_layers_height", coFloat); + def->label = L("Maximum nonplanar height"); + def->category = L("Nonplanar layers"); + def->tooltip = L("The maximum height the printer can print without collisions."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(10.0)); + + def = this->add("gap_fill_enabled", coBool); def->label = L("Fill gaps"); def->category = L("Layers and Perimeters"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 54a835fe7..ff88997c4 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -562,6 +562,10 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, thick_bridges)) ((ConfigOptionFloat, xy_size_compensation)) ((ConfigOptionBool, wipe_into_objects)) + + ((ConfigOptionBool, use_nonplanar_layers)) + ((ConfigOptionFloat, nonplanar_layers_angle)) + ((ConfigOptionFloat, nonplanar_layers_height)) ) PRINT_CONFIG_CLASS_DEFINE( diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ff7906da0..1877c34e4 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -33,6 +33,7 @@ #include "TriangleSelectorWrapper.hpp" #include "format.hpp" #include "libslic3r.h" +#include "SVG.hpp" #include #include @@ -53,6 +54,7 @@ #include #include +#include #include #include @@ -178,6 +180,8 @@ void PrintObject::make_perimeters() } m_typed_slices = false; } + + this->detect_nonplanar_surfaces(); // compare each layer to the one below, and mark those slices needing // one additional inner perimeter, like the top of domed objects- @@ -275,16 +279,17 @@ void PrintObject::prepare_infill() m_print->set_status(30, _u8L("Preparing infill")); - if (m_typed_slices) { - // To improve robustness of detect_surfaces_type() when reslicing (working with typed slices), see GH issue #7442. - // The preceding step (perimeter generator) only modifies extra_perimeters and the extra perimeters are only used by discover_vertical_shells() - // with more than a single region. If this step does not use Surface::extra_perimeters or Surface::extra_perimeters is always zero, it is safe - // to reset to the untyped slices before re-runnning detect_surfaces_type(). - for (Layer* layer : m_layers) { - layer->restore_untyped_slices_no_extra_perimeters(); - m_print->throw_if_canceled(); - } - } + // if (m_typed_slices) { + // // To improve robustness of detect_surfaces_type() when reslicing (working with typed slices), see GH issue #7442. + // // The preceding step (perimeter generator) only modifies extra_perimeters and the extra perimeters are only used by discover_vertical_shells() + // // with more than a single region. If this step does not use Surface::extra_perimeters or Surface::extra_perimeters is always zero, it is safe + // // to reset to the untyped slices before re-runnning detect_surfaces_type(). + // for (Layer* layer : m_layers) { + // layer->restore_untyped_slices_no_extra_perimeters(); + // m_print->throw_if_canceled(); + // } + // this->detect_nonplanar_surfaces(); + // } // This will assign a type (top/bottom/internal) to $layerm->slices. // Then the classifcation of $layerm->slices is transfered onto @@ -434,6 +439,9 @@ void PrintObject::infill() } } ); + + this->project_nonplanar_surfaces(); + m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - end"; /* we could free memory now, but this would make this step not idempotent @@ -792,6 +800,13 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posInfill); steps.emplace_back(posSupportMaterial); } + } else if ( + opt_key == "use_nonplanar_layers" + || opt_key == "nonplanar_layers_angle" + || opt_key == "nonplanar_layers_height") { + // steps.emplace_back(posPerimeters); + // steps.emplace_back(posInfill); + steps.emplace_back(posSlice); } else if ( opt_key == "perimeter_generator" || opt_key == "wall_transition_length" @@ -899,6 +914,8 @@ void PrintObject::cleanup() // stBottomBridge - Part of a region, which is not fully supported, but it hangs in the air, or it hangs losely on a support or a raft. // stBottom - Part of a region, which is not supported by the same region, but it is supported either by another region, or by a soluble interface layer. // stInternal - Part of a region, which is supported by the same region type. +// stTopNonplanar - +// stInternalSolidNonplanar - // If a part of a region is of stBottom and stTop, the stBottom wins. void PrintObject::detect_surfaces_type() { @@ -952,6 +969,22 @@ void PrintObject::detect_surfaces_type() // collapse very narrow parts (using the safety offset in the diff is not enough) float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f; + //Find mark nonplanar surfaces + Surfaces nonplanar_surfaces; + for(auto& surface : layerm->nonplanar_surfaces()) { + surfaces_append( + nonplanar_surfaces, + intersection_ex(surface.horizontal_projection(), union_ex(layerm->slices().surfaces)), + (surface.stats.max.z <= layer->slice_z + layer->height ? stTopNonplanar : stInternalSolidNonplanar) + ); + BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces - layer " << (idx_layer+1) << "/" << m_layers.size() << " is " << + (surface.stats.max.z <= layer->slice_z + layer->height ? "top nonplanar" : "internal nonplanar") << + ", surface max z=" << surface.stats.max.z << ", slice_z=" << layer->slice_z << ", layer height=" << layer->height; + } + + //remove non planar surfaces form all surfaces to get planar surfaces + ExPolygons planar_surfaces = diff_ex(layerm->slices().surfaces, nonplanar_surfaces, ApplySafetyOffset::Yes); + // find top surfaces (difference between current surfaces // of current layer and upper one) Surfaces top; @@ -959,13 +992,11 @@ void PrintObject::detect_surfaces_type() ExPolygons upper_slices = interface_shells ? diff_ex(layerm->slices().surfaces, upper_layer->m_regions[region_id]->slices().surfaces, ApplySafetyOffset::Yes) : diff_ex(layerm->slices().surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); - surfaces_append(top, opening_ex(upper_slices, offset), stTop); + surfaces_append(top, diff_ex(opening_ex(upper_slices, offset), nonplanar_surfaces, ApplySafetyOffset::Yes), stTop); } else { // if no upper layer, all surfaces of this one are solid // we clone surfaces because we're going to clear the slices collection - top = layerm->slices().surfaces; - for (Surface &surface : top) - surface.surface_type = stTop; + surfaces_append(top, union_ex(planar_surfaces), stTop); } // Find bottom surfaces (difference between current surfaces of current layer and lower one). @@ -984,7 +1015,11 @@ void PrintObject::detect_surfaces_type() surfaces_append( bottom, opening_ex( - diff_ex(layerm->slices().surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), + diff_ex( + diff_ex(layerm->slices().surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), + nonplanar_surfaces, + ApplySafetyOffset::Yes + ), offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces @@ -996,8 +1031,11 @@ void PrintObject::detect_surfaces_type() bottom, opening_ex( diff_ex( - intersection(layerm->slices().surfaces, lower_layer->lslices), // supported - lower_layer->m_regions[region_id]->slices().surfaces, + diff_ex( + intersection(layerm->slices().surfaces, lower_layer->lslices), // supported + lower_layer->m_regions[region_id]->slices().surfaces, + ApplySafetyOffset::Yes), + nonplanar_surfaces, ApplySafetyOffset::Yes), offset), stBottom); @@ -1029,6 +1067,7 @@ void PrintObject::detect_surfaces_type() std::vector> expolygons_with_attributes; expolygons_with_attributes.emplace_back(std::make_pair(union_ex(top), SVG::ExPolygonAttributes("green"))); expolygons_with_attributes.emplace_back(std::make_pair(union_ex(bottom), SVG::ExPolygonAttributes("brown"))); + expolygons_with_attributes.emplace_back(std::make_pair(union_ex(nonplanar_surfaces), SVG::ExPolygonAttributes("red"))); expolygons_with_attributes.emplace_back(std::make_pair(to_expolygons(layerm->slices().surfaces), SVG::ExPolygonAttributes("black"))); SVG::export_expolygons(debug_out_path("1_detect_surfaces_type_%d_region%d-layer_%f.svg", iRun ++, region_id, layer->print_z).c_str(), expolygons_with_attributes); } @@ -1045,13 +1084,15 @@ void PrintObject::detect_surfaces_type() // find internal surfaces (difference between top/bottom surfaces and others) { - Polygons topbottom = to_polygons(top); - polygons_append(topbottom, to_polygons(bottom)); - surfaces_append(surfaces_out, diff_ex(surfaces_prev, topbottom), stInternal); + Polygons solid_surfaces = to_polygons(top); + polygons_append(solid_surfaces, to_polygons(bottom)); + polygons_append(solid_surfaces, to_polygons(nonplanar_surfaces)); + surfaces_append(surfaces_out, diff_ex(surfaces_prev, solid_surfaces), stInternal); } surfaces_append(surfaces_out, std::move(top)); surfaces_append(surfaces_out, std::move(bottom)); + surfaces_append(surfaces_out, std::move(nonplanar_surfaces)); // Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", // $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug; @@ -1101,6 +1142,240 @@ void PrintObject::detect_surfaces_type() m_typed_slices = true; } +bool +PrintObject::check_nonplanar_collisions(NonplanarSurface &surface) +{ + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + Polygons collider; + Polygons nonplanar_polygon = to_polygons(surface.horizontal_projection()); + //check each layer + for (size_t idx_layer = 0; idx_layer < m_layers.size(); ++ idx_layer) { + m_print->throw_if_canceled(); + // BOOST_LOG_TRIVIAL(trace) << "Check nonplanar collisions for region " << region_id << " and layer " << layer->print_z; + Layer *layer = m_layers[idx_layer]; + LayerRegion *layerm = layer->m_regions[region_id]; + + //skip if below minimum nonplanar surface + if (surface.stats.min.z-layer->height > layer->slice_z) continue; + //break if above nonplanar surface + if (surface.stats.max.z < layer->slice_z) break; + + float angle_rad = m_config.nonplanar_layers_angle.value * 3.14159265/180.0; + float angle_offset = scale_(layer->height*std::sin(1.57079633-angle_rad)/std::sin(angle_rad)); + + //debug + // SVG svg("svg/collider" + std::to_string(layer->id()) + ".svg"); + // svg.draw(layerm_slices_surfaces, "blue"); + // svg.draw(union_ex(diff(collider,nonplanar_polygon)), "red", 0.7f); + // svg.draw_outline(collider); + // svg.arrows = false; + // svg.Close(); + + //check if current surface collides with previous collider + ExPolygons collisions = union_ex(intersection(layerm->slices().surfaces, diff(collider, nonplanar_polygon))); + + if (!collisions.empty()){ + double area = 0; + for (auto& c : collisions){ + area += c.area(); + } + + //collsion found abort when area > 1.0 mm² + if (1.0 < unscale(unscale(area))) { + std::cout << "Surface removed: collision on layer " << layer->print_z << "mm (" << unscale(unscale(area)) << " mm²)" << '\n'; + return true; + } + } + + if (layer->upper_layer != NULL) { + Layer* upper_layer = layer->upper_layer; + LayerRegion *upper_layerm = upper_layer->m_regions[region_id]; + //merge the ofsetted surface to the collider + collider= offset( + union_( + intersection( + diff_ex(layerm->slices().surfaces, upper_layerm->slices().surfaces, ApplySafetyOffset::No), + nonplanar_polygon, + ApplySafetyOffset::No), + collider), + angle_offset); + } + } + } + + return false; +} + +void +PrintObject::detect_nonplanar_surfaces() +{ + //skip if not active + if(!m_config.use_nonplanar_layers.value) return; + + bool moved_surfaces = false; + + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(trace) << "detect_nonplanar_surfaces for region " << region_id; + const PrintRegion ®ion = this->printing_region(region_id); + + //repeat detection for every nonplanar_surface + for (auto& nonplanar_surface: this->nonplanar_surfaces()) { + float distance_to_top = 0.0f; + for (int shell_thickness = 0; region.config().top_solid_layers > shell_thickness; ++shell_thickness){ + //search home layer where the area is projected to + for (LayerPtrs::reverse_iterator home_layer_it = this->m_layers.rbegin(); home_layer_it != this->m_layers.rend(); ++home_layer_it){ + Layer* home_layer = *home_layer_it; + LayerRegion &home_layerm = *home_layer->m_regions[region_id]; + //continue if home layer is not maximum height of nonplanar_surface - the desired distance to the top of the surface for more than one top solid layer + if (home_layer->slice_z > nonplanar_surface.stats.max.z - distance_to_top) continue; + + //process layers + for (LayerPtrs::iterator layer_it = this->m_layers.begin(); layer_it != this->m_layers.end(); ++layer_it){ + Layer* layer = *layer_it; + LayerRegion &layerm = *layer->m_regions[region_id]; + + //skip if below minimum nonplanar surface and below the last possible surface layer + if (nonplanar_surface.stats.min.z-layer->height-distance_to_top > layer->slice_z) continue; + //break if above home layer + if (home_layer->slice_z < layer->slice_z) break; + //skip if bottom layer because we dont want to project the bottom layers up + if (layer->lower_layer == NULL) continue; + + BOOST_LOG_TRIVIAL(trace) << "detect_nonplanar_surfaces for region " << region_id << " and layer " << layer->print_z; + + Surfaces layerm_slices_surfaces = layerm.slices().surfaces; + SurfaceCollection topNonplanar; + if (layer->upper_layer != NULL) { + //append layers where nothing is above + Layer* upper_layer = layer->upper_layer; + LayerRegion &upper_layerm = *upper_layer->m_regions[region_id]; + Surfaces upper_surfaces = upper_layerm.slices().surfaces; + topNonplanar.append( + intersection_ex( + nonplanar_surface.horizontal_projection(), + union_ex( + diff_ex( + layerm_slices_surfaces, + upper_surfaces, + ApplySafetyOffset::No)), + ApplySafetyOffset::No), + (shell_thickness == 0 ? stTopNonplanar : stInternalSolidNonplanar), + distance_to_top + ); + + // append layers where nonplanar areas with a lower distance_to_top are above + SurfaceCollection upper_nonplanar; + for (auto& s : upper_surfaces){ + if (s.is_nonplanar() && s.distance_to_top < distance_to_top) { + upper_nonplanar.surfaces.push_back(s); + } + } + if (upper_nonplanar.size() > 0) + topNonplanar.append( + intersection_ex( + nonplanar_surface.horizontal_projection(), + to_expolygons(upper_nonplanar.surfaces), + ApplySafetyOffset::No), + (shell_thickness == 0 ? stTopNonplanar : stInternalSolidNonplanar), + distance_to_top + ); + } + else { + topNonplanar.append( + intersection_ex( + nonplanar_surface.horizontal_projection(), + union_ex(to_expolygons(layerm_slices_surfaces)), + ApplySafetyOffset::No), + (shell_thickness == 0 ? stTopNonplanar : stInternalSolidNonplanar), + distance_to_top + ); + } + + if (topNonplanar.size() > 0) { + // layerm.export_region_slices_to_svg_debug("home_layer_append-layerm"); + // home_layerm.export_region_slices_to_svg_debug("home_layer_append-home_layerm"); + + BOOST_LOG_TRIVIAL(trace) << "Removing " << topNonplanar.size() << " nonplanar surfaces from layer " << layer->print_z; + + layerm.remove_nonplanar_slices(topNonplanar); + + BOOST_LOG_TRIVIAL(trace) << "Adding " << topNonplanar.size() << " nonplanar surfaces to layer " << home_layer->print_z; + + // move nonplanar surfaces to home layer + home_layerm.append_top_nonplanar_slices(topNonplanar); + + //save nonplanar_surface to home_layers nonplanar_surface list + home_layerm.append_nonplanar_surface(nonplanar_surface); + + moved_surfaces = true; + } + } + + //increase distance to the top layer + distance_to_top += home_layer->height; + break; + } + } + } + } + + if(moved_surfaces) { + // After changing a layer's slices, we must rebuild its lslices into islands + this->make_slices(); + this->lslices_were_updated(); + } + + // Debugging output. +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + for (const Layer *layer : m_layers) { + LayerRegion *layerm = layer->m_regions[region_id]; + layerm->export_region_slices_to_svg_debug("0_detect_nonplanar_surfaces"); + layerm->export_region_fill_surfaces_to_svg_debug("0_detect_nonplanar_surfaces"); + } // for each layer + } // for each region +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + //set typed_slices to true to force merge + m_typed_slices = true; +} + +void +PrintObject::project_nonplanar_surfaces() +{ + if(!m_config.use_nonplanar_layers.value) return; + + //TODO check when steps should be invalidated + if (is_step_done(posNonplanarProjection)) return; + set_started(posNonplanarProjection); + + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + BOOST_LOG_TRIVIAL(debug) << "Processing nonplanar surfaces for region " << region_id << " in parallel - start"; + tbb::parallel_for( + tbb::blocked_range(0, m_layers.size()), + [this, region_id](const tbb::blocked_range& range) { + PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + m_print->throw_if_canceled(); + LayerRegion *layerm = m_layers[idx_layer]->m_regions[region_id]; + + BOOST_LOG_TRIVIAL(trace) << "Processing nonplanar surfaces for region " << region_id << " and layer " << \ + idx_layer << " (z = " << layerm->layer()->print_z << ")"; + + layerm->project_nonplanar_surfaces(); +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + layerm->export_region_fill_surfaces_to_svg_debug("11_project_nonplanar_surfaces-final"); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + } // for each layer of a region + }); + } + + BOOST_LOG_TRIVIAL(debug) << "Processing nonplanar surfaces - end"; + + set_done(posNonplanarProjection); +} + void PrintObject::process_external_surfaces() { BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..." << log_memory_info(); @@ -1223,6 +1498,7 @@ void PrintObject::discover_vertical_shells() [this, &cache_top_botom_regions](const tbb::blocked_range& range) { PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); const std::initializer_list surfaces_bottom { stBottom, stBottomBridge }; + const std::initializer_list surfaces_top { stTop, stTopNonplanar }; const size_t num_regions = this->num_printing_regions(); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); @@ -1239,7 +1515,7 @@ void PrintObject::discover_vertical_shells() LayerRegion &layerm = *layer.m_regions[region_id]; float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff; // Top surfaces. - append(cache.top_surfaces, offset(layerm.slices().filter_by_type(stTop), top_bottom_expansion)); + append(cache.top_surfaces, offset(layerm.slices().filter_by_types(surfaces_top), top_bottom_expansion)); // append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), top_bottom_expansion)); // Bottom surfaces. append(cache.bottom_surfaces, offset(layerm.slices().filter_by_types(surfaces_bottom), top_bottom_expansion)); @@ -1297,6 +1573,7 @@ void PrintObject::discover_vertical_shells() [this, region_id, &cache_top_botom_regions](const tbb::blocked_range& range) { PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); const std::initializer_list surfaces_bottom { stBottom, stBottomBridge }; + const std::initializer_list surfaces_top { stTop, stTopNonplanar }; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); Layer &layer = *m_layers[idx_layer]; @@ -1304,7 +1581,7 @@ void PrintObject::discover_vertical_shells() float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff; // Top surfaces. auto &cache = cache_top_botom_regions[idx_layer]; - cache.top_surfaces = offset(layerm.slices().filter_by_type(stTop), top_bottom_expansion); + cache.top_surfaces = offset(layerm.slices().filter_by_types(surfaces_top), top_bottom_expansion); // append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), top_bottom_expansion)); // Bottom surfaces. cache.bottom_surfaces = offset(layerm.slices().filter_by_types(surfaces_bottom), top_bottom_expansion); @@ -1485,7 +1762,7 @@ void PrintObject::discover_vertical_shells() svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); - } + } { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell)); svg.draw(layerm->fill_surfaces().filter_by_type(stInternalVoid), "yellow", 0.5); @@ -1493,7 +1770,7 @@ void PrintObject::discover_vertical_shells() svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); - } + } { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalsolid-wshell-%d.svg", debug_idx), get_extents(shell)); svg.draw(layerm->fill_surfaces().filter_by_type(stInternalSolid), "yellow", 0.5); @@ -1501,18 +1778,32 @@ void PrintObject::discover_vertical_shells() svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); - } + } + { + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalsolidnonplanar-wshell-%d.svg", debug_idx), get_extents(shell)); + svg.draw(layerm->fill_surfaces().filter_by_type(stInternalSolidNonplanar), "yellow", 0.5); + svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternalSolidNonplanar), "black", "blue", scale_(0.05)); + svg.draw(shell_ex, "blue", 0.5); + svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); + svg.Close(); + } + { + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-holes-wshell-%d.svg", debug_idx), get_extents(shell)); + svg.draw_outline(to_expolygons(holes), "black", "blue", scale_(0.05)); + svg.Close(); + } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the shells region by the internal & internal void surfaces. - const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces().filter_by_types({ stInternal, stInternalVoid, stInternalSolid })); + const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces().filter_by_types({ stInternal, stInternalVoid, stInternalSolid, stInternalSolidNonplanar })); + const Polygons polygonsInternalNonplanar = to_polygons(layerm->fill_surfaces().filter_by_type(stInternalSolidNonplanar)); shell = intersection(shell, polygonsInternal, ApplySafetyOffset::Yes); polygons_append(shell, diff(polygonsInternal, holes)); if (shell.empty()) continue; // Append the internal solids, so they will be merged with the new ones. - polygons_append(shell, to_polygons(layerm->fill_surfaces().filter_by_type(stInternalSolid))); + polygons_append(shell, to_polygons(layerm->fill_surfaces().filter_by_types({ stInternalSolid, stInternalSolidNonplanar }))); // These regions will be filled by a rectilinear full infill. Currently this type of infill // only fills regions, which fit at least a single line. To avoid gaps in the sparse infill, @@ -1560,7 +1851,8 @@ void PrintObject::discover_vertical_shells() if (regularized_shell.empty()) continue; - ExPolygons new_internal_solid = intersection_ex(polygonsInternal, regularized_shell); + ExPolygons new_internal_solid = intersection_ex(diff_ex(polygonsInternal, polygonsInternalNonplanar), regularized_shell); + ExPolygons new_internal_solid_nonplanar = intersection_ex(polygonsInternalNonplanar, regularized_shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", debug_idx), get_extents(shell_before)); @@ -1583,14 +1875,16 @@ void PrintObject::discover_vertical_shells() SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal-%d.svg", debug_idx), get_extents(shell), new_internal, "black", "blue", scale_(0.05)); SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_void-%d.svg", debug_idx), get_extents(shell), new_internal_void, "black", "blue", scale_(0.05)); SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_solid-%d.svg", debug_idx), get_extents(shell), new_internal_solid, "black", "blue", scale_(0.05)); + SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_solid_nonplanar-%d.svg", debug_idx), get_extents(shell), new_internal_solid_nonplanar, "black", "blue", scale_(0.05)); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Assign resulting internal surfaces to layer. - layerm->m_fill_surfaces.keep_types({ stTop, stBottom, stBottomBridge }); + layerm->m_fill_surfaces.keep_types({ stTop, stTopNonplanar, stBottom, stBottomBridge }); layerm->m_fill_surfaces.append(new_internal, stInternal); layerm->m_fill_surfaces.append(new_internal_void, stInternalVoid); layerm->m_fill_surfaces.append(new_internal_solid, stInternalSolid); + layerm->m_fill_surfaces.append(new_internal_solid_nonplanar, stInternalSolidNonplanar); } // for each layer }); m_print->throw_if_canceled(); @@ -1688,7 +1982,7 @@ void PrintObject::bridge_over_infill() unsupported_area = diff(unsupported_area, lower_layer_solids); for (const LayerRegion *region : layer->regions()) { - SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_types({stInternalSolid, stInternalSolidNonplanar}); for (const Surface *s : region_internal_solids) { Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); // The following flag marks those surfaces, which overlap with unuspported area, but at least part of them is supported. diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 6f6080cb8..74ddef783 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -5,6 +5,7 @@ #include "MultiMaterialSegmentation.hpp" #include "Print.hpp" #include "ShortestPath.hpp" +#include "AABBMesh.hpp" #include @@ -518,6 +519,14 @@ void PrintObject::slice() if (! warning.empty()) BOOST_LOG_TRIVIAL(info) << warning; #endif + this->lslices_were_updated(); + if (m_layers.empty()) + throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); + this->set_done(posSlice); +} + +void PrintObject::lslices_were_updated() +{ // Update bounding boxes, back up raw slices of complex models. tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), @@ -541,9 +550,6 @@ void PrintObject::slice() Layer::build_up_down_graph(*m_layers[layer_idx - 1], *m_layers[layer_idx]); } }); - if (m_layers.empty()) - throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); - this->set_done(posSlice); } template @@ -708,7 +714,7 @@ void PrintObject::slice_volumes() m_layers[layer_id]->regions()[region_id]->m_slices.append(std::move(by_layer[layer_id]), stInternal); } region_slices.clear(); - + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - removing top empty layers"; while (! m_layers.empty()) { const Layer *layer = m_layers.back(); @@ -740,7 +746,16 @@ void PrintObject::slice_volumes() apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); }); } + this->find_nonplanar_surfaces(); + this->make_slices(); + + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - end"; +} + +void PrintObject::make_slices() +{ + const Print *print = this->print(); 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. @@ -802,6 +817,7 @@ void PrintObject::slice_volumes() layer->m_regions[region_id]->trim_surfaces(trimming); } } + // Merge all regions' slices to get islands sorted topologically, chain them by a shortest path in separate index list layer->make_slices(); } @@ -822,6 +838,106 @@ void PrintObject::slice_volumes() BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - end"; } +void +PrintObject::find_nonplanar_surfaces() +{ + //skip if not active + if(!m_config.use_nonplanar_layers.value) { + BOOST_LOG_TRIVIAL(debug) << "Find nonplanar surfaces - disabled"; + return; + } + + BOOST_LOG_TRIVIAL(info) << "Find nonplanar surfaces - start"; + + //Itterate over all model volumes + const ModelVolumePtrs volumes = this->model_object()->volumes; + for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it) { + //only check non modifier volumes + if (! (*it)->is_modifier()) { + const TriangleMesh tmesh = (*it)->mesh(); + AABBMesh mesh(tmesh, true); + std::map facets; + + // store all meshes with slope <= nonplanar_layers_angle in map. Map is necessary to keep facet ID + for (int face_id = 0; face_id < mesh.indices().size(); ++ face_id) { + auto &face = mesh.indices(face_id); + Vec3d normal = mesh.normal_by_face_id(face_id); + + //TODO check if normals exist + if (normal.z() >= std::cos(m_config.nonplanar_layers_angle.value * 3.14159265/180.0)) { + //copy facet + NonplanarFacet new_facet; + new_facet.normal.x = normal.x(); + new_facet.normal.y = normal.y(); + new_facet.normal.z = normal.z(); + + Vec3i neighbor = mesh.face_neighbor_index()[face_id]; + its_triangle vertex = its_triangle_vertices(*mesh.get_triangle_mesh(), face_id); + for (int j=0; j<=2 ;j++) { + new_facet.vertex[j].x = vertex[j].x(); + new_facet.vertex[j].y = vertex[j].y(); + new_facet.vertex[j].z = vertex[j].z(); + new_facet.neighbor[j] = neighbor[j]; + } + new_facet.calculate_stats(); + facets[face_id] = new_facet; + } + } + + // create nonplanar surface from facets + NonplanarSurface nf = NonplanarSurface(facets); + BOOST_LOG_TRIVIAL(trace) << "Find nonplanar surfaces - moving surfaces by z=" << -tmesh.stats().min.z(); + nf.translate(0, 0, -tmesh.stats().min.z()); + + // group surfaces and attach all nonplanar surfaces to the PrintObject + m_nonplanar_surfaces = nf.group_surfaces(); + + // check if surfaces maintain maximum printing height, if not, erase it + for (NonplanarSurfaces::iterator it = m_nonplanar_surfaces.begin(); it!=m_nonplanar_surfaces.end();) { + if((*it).check_max_printing_height(m_config.nonplanar_layers_height.value)) { + it = m_nonplanar_surfaces.erase(it); + }else { + it++; + } + } + + // check if surfaces area is not too small + for (NonplanarSurfaces::iterator it = m_nonplanar_surfaces.begin(); it!=m_nonplanar_surfaces.end();) { + if((*it).check_surface_area()) { + it = m_nonplanar_surfaces.erase(it); + }else { + it++; + } + } + + // check if surfaces areas collide + for (NonplanarSurfaces::iterator it = m_nonplanar_surfaces.begin(); it!=m_nonplanar_surfaces.end();) { + if(check_nonplanar_collisions((*it))) { + it = m_nonplanar_surfaces.erase(it); + } else { + it++; + } + } + + //nf.debug_output(); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + for (size_t id = 0; id < m_nonplanar_surfaces.size(); ++ id) { + auto& surface = m_nonplanar_surfaces[id]; + Surfaces surfaces; + surfaces_append(surfaces, surface.horizontal_projection(), SurfaceType::stTopNonplanar); + SurfaceCollection c(surfaces); + c.export_to_svg(debug_out_path("0_find_nonplanar_surface-%d.svg", id).c_str(), true); + } +#endif + + BOOST_LOG_TRIVIAL(info) << "Find nonplanar surfaces - found " << m_nonplanar_surfaces.size() << " in " << (*it)->name; + } + } + + BOOST_LOG_TRIVIAL(info) << "Find nonplanar surfaces - end"; +} + std::vector PrintObject::slice_support_volumes(const ModelVolumeType model_volume_type) const { auto it_volume = this->model_object()->volumes.begin(); diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 8936830f2..b0f2d0d34 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -243,9 +243,9 @@ float get_flow_width(const LayerRegion *region, ExtrusionRole role) if (role == ExtrusionRole::ExternalPerimeter) return region->flow(FlowRole::frExternalPerimeter).width(); if (role == ExtrusionRole::GapFill) return region->flow(FlowRole::frInfill).width(); if (role == ExtrusionRole::Perimeter) return region->flow(FlowRole::frPerimeter).width(); - if (role == ExtrusionRole::SolidInfill) return region->flow(FlowRole::frSolidInfill).width(); + if (role == ExtrusionRole::SolidInfill || role == ExtrusionRole::SolidInfillNonplanar) return region->flow(FlowRole::frSolidInfill).width(); if (role == ExtrusionRole::InternalInfill) return region->flow(FlowRole::frInfill).width(); - if (role == ExtrusionRole::TopSolidInfill) return region->flow(FlowRole::frTopSolidInfill).width(); + if (role == ExtrusionRole::TopSolidInfill || role == ExtrusionRole::TopSolidInfillNonplanar) return region->flow(FlowRole::frTopSolidInfill).width(); // default return region->flow(FlowRole::frPerimeter).width(); } diff --git a/src/libslic3r/Surface.cpp b/src/libslic3r/Surface.cpp index 58ac7294c..fbef74ac2 100644 --- a/src/libslic3r/Surface.cpp +++ b/src/libslic3r/Surface.cpp @@ -35,10 +35,12 @@ const char* surface_type_to_color_name(const SurfaceType surface_type) { switch (surface_type) { case stTop: return "rgb(255,0,0)"; // "red"; + case stTopNonplanar: return "rgb(166,2,255)"; // "purple"; case stBottom: return "rgb(0,255,0)"; // "green"; case stBottomBridge: return "rgb(0,0,255)"; // "blue"; case stInternal: return "rgb(255,255,128)"; // yellow case stInternalSolid: return "rgb(255,0,255)"; // magenta + case stInternalSolidNonplanar: return "rgb(255,133,2)"; // orange case stInternalBridge: return "rgb(0,255,255)"; case stInternalVoid: return "rgb(128,128,128)"; case stPerimeter: return "rgb(128,0,0)"; // maroon @@ -48,7 +50,7 @@ const char* surface_type_to_color_name(const SurfaceType surface_type) Point export_surface_type_legend_to_svg_box_size() { - return Point(scale_(1.+10.*8.), scale_(3.)); + return Point(scale_(1.+16.*8.), scale_(3.)); } void export_surface_type_legend_to_svg(SVG &svg, const Point &pos) @@ -57,11 +59,13 @@ void export_surface_type_legend_to_svg(SVG &svg, const Point &pos) coord_t pos_x0 = pos(0) + scale_(1.); coord_t pos_x = pos_x0; coord_t pos_y = pos(1) + scale_(1.5); - coord_t step_x = scale_(10.); + coord_t step_x = scale_(16.); svg.draw_legend(Point(pos_x, pos_y), "perimeter" , surface_type_to_color_name(stPerimeter)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "top" , surface_type_to_color_name(stTop)); pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "top nonplanar" , surface_type_to_color_name(stTopNonplanar)); + pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "bottom" , surface_type_to_color_name(stBottom)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "bottom bridge" , surface_type_to_color_name(stBottomBridge)); @@ -74,6 +78,8 @@ void export_surface_type_legend_to_svg(SVG &svg, const Point &pos) pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "internal solid" , surface_type_to_color_name(stInternalSolid)); pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "internal solid nonplanar" , surface_type_to_color_name(stInternalSolidNonplanar)); + pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "internal bridge", surface_type_to_color_name(stInternalBridge)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "internal void" , surface_type_to_color_name(stInternalVoid)); diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index 1f352e977..3b82c52ed 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -24,6 +24,10 @@ enum SurfaceType { stInternalVoid, // Inner/outer perimeters. stPerimeter, + // + stTopNonplanar, + // + stInternalSolidNonplanar, // Number of SurfaceType enums. stCount, }; @@ -37,6 +41,7 @@ public: unsigned short thickness_layers { 1 }; // in layers double bridge_angle { -1. }; // in radians, ccw, 0 = East, only 0+ (negative means undefined) unsigned short extra_perimeters { 0 }; + float distance_to_top { 0 }; Surface(const Slic3r::Surface &rhs) : surface_type(rhs.surface_type), expolygon(rhs.expolygon), @@ -67,6 +72,7 @@ public: thickness_layers = rhs.thickness_layers; bridge_angle = rhs.bridge_angle; extra_perimeters = rhs.extra_perimeters; + distance_to_top = rhs.distance_to_top; return *this; } @@ -78,6 +84,7 @@ public: thickness_layers = rhs.thickness_layers; bridge_angle = rhs.bridge_angle; extra_perimeters = rhs.extra_perimeters; + distance_to_top = rhs.distance_to_top; return *this; } @@ -86,12 +93,14 @@ public: void clear() { expolygon.clear(); } // The following methods do not test for stPerimeter. - bool is_top() const { return this->surface_type == stTop; } + bool is_top() const { return this->surface_type == stTop || this->surface_type == stTopNonplanar; } bool is_bottom() const { return this->surface_type == stBottom || this->surface_type == stBottomBridge; } bool is_bridge() const { return this->surface_type == stBottomBridge || this->surface_type == stInternalBridge; } bool is_external() const { return this->is_top() || this->is_bottom(); } bool is_internal() const { return ! this->is_external(); } - bool is_solid() const { return this->is_external() || this->surface_type == stInternalSolid || this->surface_type == stInternalBridge; } + bool is_internal_solid() const { return this->surface_type == stInternalSolid || this->surface_type == stInternalSolidNonplanar; } + bool is_solid() const { return this->is_external() || this->surface_type == stInternalSolid || this->surface_type == stInternalSolidNonplanar || this->surface_type == stInternalBridge; } + bool is_nonplanar() const { return this->surface_type == stTopNonplanar || this->surface_type == stInternalSolidNonplanar; } }; typedef std::vector Surfaces; @@ -228,9 +237,20 @@ inline void polygons_append(Polygons &dst, SurfacesPtr &&src) inline void surfaces_append(Surfaces &dst, const ExPolygons &src, SurfaceType surfaceType) { dst.reserve(dst.size() + src.size()); - for (const ExPolygon &expoly : src) + for (const ExPolygon &expoly : src) { dst.emplace_back(Surface(surfaceType, expoly)); + } } +inline void surfaces_append(Surfaces &dst, const ExPolygons &src, SurfaceType surfaceType, float distance_to_top) +{ + dst.reserve(dst.size() + src.size()); + for (const ExPolygon &expoly : src) { + Surface s = Surface(surfaceType, expoly); + s.distance_to_top = distance_to_top; + dst.emplace_back(s); + } +} + inline void surfaces_append(Surfaces &dst, const ExPolygons &src, const Surface &surfaceTempl) { dst.reserve(dst.size() + number_polygons(src)); @@ -278,7 +298,8 @@ inline bool surfaces_could_merge(const Surface &s1, const Surface &s2) s1.surface_type == s2.surface_type && s1.thickness == s2.thickness && s1.thickness_layers == s2.thickness_layers && - s1.bridge_angle == s2.bridge_angle; + s1.bridge_angle == s2.bridge_angle && + s1.distance_to_top == s2.distance_to_top; } class SVG; diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index 0f62875b2..5491909b8 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -68,9 +68,11 @@ public: void append(const SurfaceCollection &coll) { this->append(coll.surfaces); } void append(SurfaceCollection &&coll) { this->append(std::move(coll.surfaces)); } void append(const ExPolygons &src, SurfaceType surfaceType) { surfaces_append(this->surfaces, src, surfaceType); } + void append(const ExPolygons &src, SurfaceType surfaceType, float distance_to_top) { surfaces_append(this->surfaces, src, surfaceType, distance_to_top); } void append(const ExPolygons &src, const Surface &surfaceTempl) { surfaces_append(this->surfaces, src, surfaceTempl); } void append(const Surfaces &src) { surfaces_append(this->surfaces, src); } void append(ExPolygons &&src, SurfaceType surfaceType) { surfaces_append(this->surfaces, std::move(src), surfaceType); } + void append(ExPolygons &&src, SurfaceType surfaceType, float distance_to_top) { surfaces_append(this->surfaces, std::move(src), surfaceType, distance_to_top); } void append(ExPolygons &&src, const Surface &surfaceTempl) { surfaces_append(this->surfaces, std::move(src), surfaceTempl); } void append(Surfaces &&src) { surfaces_append(this->surfaces, std::move(src)); } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ed4713737..d65dcdb9c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -569,7 +569,9 @@ const std::array(GCodeExtrusionRole::Count)> GCod { 0.12f, 0.12f, 1.00f, 1.0f }, // GCodeExtrusionRole::OverhangPerimeter { 0.69f, 0.19f, 0.16f, 1.0f }, // GCodeExtrusionRole::InternalInfill { 0.59f, 0.33f, 0.80f, 1.0f }, // GCodeExtrusionRole::SolidInfill + { 0.75f, 0.12f, 0.45f, 1.0f }, // GCodeExtrusionRole::SolidInfillNonplanar { 0.94f, 0.25f, 0.25f, 1.0f }, // GCodeExtrusionRole::TopSolidInfill + { 0.32f, 0.12f, 0.45f, 1.0f }, // GCodeExtrusionRole::TopSolidInfillNonplanar { 1.00f, 0.55f, 0.41f, 1.0f }, // GCodeExtrusionRole::Ironing { 0.30f, 0.50f, 0.73f, 1.0f }, // GCodeExtrusionRole::BridgeInfill { 1.00f, 1.00f, 1.00f, 1.0f }, // GCodeExtrusionRole::GapFill diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ad93afa87..35a08b70d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1459,6 +1459,12 @@ void TabPrint::build() optgroup->append_single_option_line("fuzzy_skin_thickness", category_path + "fuzzy-skin-thickness"); optgroup->append_single_option_line("fuzzy_skin_point_dist", category_path + "fuzzy-skin-point-distance"); + optgroup = page->new_optgroup(L("Nonplanar layers (experimental)")); + category_path = "nonplanar-layers_19282821/#"; + optgroup->append_single_option_line("use_nonplanar_layers", category_path + "use-nonplanar-layers"); + optgroup->append_single_option_line("nonplanar_layers_angle", category_path + "nonplanar-layers-height"); + optgroup->append_single_option_line("nonplanar_layers_height", category_path + "nonplanar-layers-height"); + page = add_options_page(L("Infill"), "infill"); category_path = "infill_42#"; optgroup = page->new_optgroup(L("Infill")); diff --git a/tests/data/nonplanar/wave_cube.stl b/tests/data/nonplanar/wave_cube.stl new file mode 100644 index 000000000..c9238b647 Binary files /dev/null and b/tests/data/nonplanar/wave_cube.stl differ