From 440e58573ed7ab82258adea44298d879459aa1d0 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 25 Jul 2018 16:11:31 +0200 Subject: [PATCH 01/63] Some documentation, C++11 conversion, code beautification, added some helper methods. --- xs/src/libslic3r/ExPolygonCollection.cpp | 9 ++-- xs/src/libslic3r/ExtrusionEntity.hpp | 40 ++++++++------ .../libslic3r/ExtrusionEntityCollection.hpp | 16 ++++-- xs/src/libslic3r/Flow.cpp | 9 ++-- xs/src/libslic3r/Layer.hpp | 54 ++++++++----------- xs/src/libslic3r/LayerRegion.cpp | 12 ++--- xs/src/libslic3r/MultiPoint.hpp | 4 +- xs/src/libslic3r/Polygon.hpp | 6 +++ xs/src/libslic3r/Polyline.cpp | 20 +++---- xs/src/libslic3r/Print.hpp | 3 ++ xs/src/libslic3r/PrintConfig.hpp | 8 +++ xs/src/libslic3r/PrintRegion.cpp | 5 ++ xs/src/libslic3r/SurfaceCollection.hpp | 5 ++ xs/xsp/Layer.xsp | 2 - 14 files changed, 110 insertions(+), 83 deletions(-) diff --git a/xs/src/libslic3r/ExPolygonCollection.cpp b/xs/src/libslic3r/ExPolygonCollection.cpp index e52498ecba7..6933544b6f7 100644 --- a/xs/src/libslic3r/ExPolygonCollection.cpp +++ b/xs/src/libslic3r/ExPolygonCollection.cpp @@ -61,12 +61,11 @@ ExPolygonCollection::rotate(double angle, const Point ¢er) } template -bool -ExPolygonCollection::contains(const T &item) const +bool ExPolygonCollection::contains(const T &item) const { - for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { - if (it->contains(item)) return true; - } + for (const ExPolygon &poly : this->expolygons) + if (poly.contains(item)) + return true; return false; } template bool ExPolygonCollection::contains(const Point &item) const; diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 15363e8edab..504d264fea4 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -91,6 +91,8 @@ class ExtrusionEntity // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. virtual double min_mm3_per_mm() const = 0; virtual Polyline as_polyline() const = 0; + virtual void collect_polylines(Polylines &dst) const = 0; + virtual Polylines as_polylines() const { Polylines dst; this->collect_polylines(dst); return dst; } virtual double length() const = 0; virtual double total_volume() const = 0; }; @@ -123,8 +125,11 @@ class ExtrusionPath : public ExtrusionEntity ExtrusionPath* clone() const { return new ExtrusionPath (*this); } void reverse() { this->polyline.reverse(); } - Point first_point() const { return this->polyline.points.front(); } - Point last_point() const { return this->polyline.points.back(); } + Point first_point() const override { return this->polyline.points.front(); } + Point last_point() const override { return this->polyline.points.back(); } + size_t size() const { return this->polyline.size(); } + bool empty() const { return this->polyline.empty(); } + bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); } // Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection. // Currently not used. void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; @@ -133,8 +138,8 @@ class ExtrusionPath : public ExtrusionEntity void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; void clip_end(double distance); void simplify(double tolerance); - virtual double length() const; - virtual ExtrusionRole role() const { return m_role; } + double length() const override; + ExtrusionRole role() const override { return m_role; } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; @@ -149,7 +154,8 @@ class ExtrusionPath : public ExtrusionEntity // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const { return this->mm3_per_mm; } Polyline as_polyline() const { return this->polyline; } - virtual double total_volume() const { return mm3_per_mm * unscale(length()); } + void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); } + double total_volume() const override { return mm3_per_mm * unscale(length()); } private: void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; @@ -178,10 +184,10 @@ class ExtrusionMultiPath : public ExtrusionEntity bool can_reverse() const { return true; } ExtrusionMultiPath* clone() const { return new ExtrusionMultiPath(*this); } void reverse(); - Point first_point() const { return this->paths.front().polyline.points.front(); } - Point last_point() const { return this->paths.back().polyline.points.back(); } - virtual double length() const; - virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); } + Point first_point() const override { return this->paths.front().polyline.points.front(); } + Point last_point() const override { return this->paths.back().polyline.points.back(); } + double length() const override; + ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; @@ -196,7 +202,8 @@ class ExtrusionMultiPath : public ExtrusionEntity // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const; Polyline as_polyline() const; - virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } + void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); } + double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } }; // Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging. @@ -218,18 +225,18 @@ class ExtrusionLoop : public ExtrusionEntity bool make_clockwise(); bool make_counter_clockwise(); void reverse(); - Point first_point() const { return this->paths.front().polyline.points.front(); } - Point last_point() const { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } + Point first_point() const override { return this->paths.front().polyline.points.front(); } + Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } Polygon polygon() const; - virtual double length() const; + double length() const override; bool split_at_vertex(const Point &point); void split_at(const Point &point, bool prefer_non_overhang); void clip_end(double distance, ExtrusionPaths* paths) const; // Test, whether the point is extruded by a bridging flow. // This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead. bool has_overhang_point(const Point &point) const; - virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); } - ExtrusionLoopRole loop_role() const { return m_loop_role; } + ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } + ExtrusionLoopRole loop_role() const { return m_loop_role; } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; @@ -244,7 +251,8 @@ class ExtrusionLoop : public ExtrusionEntity // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const; Polyline as_polyline() const { return this->polygon().split_at_first_point(); } - virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } + void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); } + double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } private: ExtrusionLoopRole m_loop_role; diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index 382455fe337..81582a94d01 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -24,7 +24,7 @@ class ExtrusionEntityCollection : public ExtrusionEntity explicit operator ExtrusionPaths() const; bool is_collection() const { return true; }; - virtual ExtrusionRole role() const { + ExtrusionRole role() const override { ExtrusionRole out = erNone; for (const ExtrusionEntity *ee : entities) { ExtrusionRole er = ee->role(); @@ -66,11 +66,11 @@ class ExtrusionEntityCollection : public ExtrusionEntity Point last_point() const { return this->entities.back()->last_point(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. - virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; + void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill. - virtual void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const; + void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override; Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const @@ -79,14 +79,20 @@ class ExtrusionEntityCollection : public ExtrusionEntity void flatten(ExtrusionEntityCollection* retval) const; ExtrusionEntityCollection flatten() const; double min_mm3_per_mm() const; - virtual double total_volume() const {double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } + double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } // Following methods shall never be called on an ExtrusionEntityCollection. Polyline as_polyline() const { CONFESS("Calling as_polyline() on a ExtrusionEntityCollection"); return Polyline(); }; - virtual double length() const { + + void collect_polylines(Polylines &dst) const override { + for (ExtrusionEntity* extrusion_entity : this->entities) + extrusion_entity->collect_polylines(dst); + } + + double length() const override { CONFESS("Calling length() on a ExtrusionEntityCollection"); return 0.; } diff --git a/xs/src/libslic3r/Flow.cpp b/xs/src/libslic3r/Flow.cpp index b60e26dcc21..e92674a171f 100644 --- a/xs/src/libslic3r/Flow.cpp +++ b/xs/src/libslic3r/Flow.cpp @@ -115,7 +115,8 @@ Flow support_material_flow(const PrintObject *object, float layer_height) // if object->config.support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), (layer_height > 0.f) ? layer_height : float(object->config.layer_height.value), - false); + // bridge_flow_ratio + 0.f); } Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) @@ -127,7 +128,8 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig (width.value > 0) ? width : object->config.extrusion_width, float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), (layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)), - false); + // bridge_flow_ratio + 0.f); } Flow support_material_interface_flow(const PrintObject *object, float layer_height) @@ -139,7 +141,8 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig // if object->config.support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_interface_extruder-1)), (layer_height > 0.f) ? layer_height : float(object->config.layer_height.value), - false); + // bridge_flow_ratio + 0.f); } } diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index f3b46044306..62058312854 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -21,45 +21,37 @@ class LayerRegion friend class Layer; public: - Layer* layer() { return this->_layer; } - const Layer* layer() const { return this->_layer; } - PrintRegion* region() { return this->_region; } - const PrintRegion* region() const { return this->_region; } + Layer* layer() { return this->_layer; } + const Layer* layer() const { return this->_layer; } + PrintRegion* region() { return this->_region; } + const PrintRegion* region() const { return this->_region; } - // collection of surfaces generated by slicing the original geometry - // divided by type top/bottom/internal - SurfaceCollection slices; - - // collection of extrusion paths/loops filling gaps - // These fills are generated by the perimeter generator. - // They are not printed on their own, but they are copied to this->fills during infill generation. - ExtrusionEntityCollection thin_fills; + // Collection of surfaces generated by slicing the original geometry, divided by type top/bottom/internal. + SurfaceCollection slices; // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") // and for re-starting of infills. - ExPolygons fill_expolygons; + ExPolygons fill_expolygons; // collection of surfaces for infill generation - SurfaceCollection fill_surfaces; - - // Collection of perimeter surfaces. This is a cached result of diff(slices, fill_surfaces). - // While not necessary, the memory consumption is meager and it speeds up calculation. - // The perimeter_surfaces keep the IDs of the slices (top/bottom/) - SurfaceCollection perimeter_surfaces; + SurfaceCollection fill_surfaces; + // Collection of extrusion paths/loops filling gaps. + // These fills are generated by the perimeter generator. + // They are not printed on their own, but they are copied to this->fills during infill generation. + ExtrusionEntityCollection thin_fills; - // collection of expolygons representing the bridged areas (thus not - // needing support material) - Polygons bridged; + // Collection of expolygons representing the bridged areas (thus not needing support material). + //FIXME Not used as of now. + Polygons bridged; // collection of polylines representing the unsupported bridge edges - PolylineCollection unsupported_bridge_edges; - - // ordered collection of extrusion paths/loops to build all perimeters - // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection perimeters; - - // ordered collection of extrusion paths to fill surfaces - // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection fills; + PolylineCollection unsupported_bridge_edges; + + // Ordered collection of extrusion paths/loops to build all perimeters. + // This collection contains only ExtrusionEntityCollection objects. + ExtrusionEntityCollection perimeters; + // Ordered collection of extrusion paths to fill surfaces. + // This collection contains only ExtrusionEntityCollection objects. + ExtrusionEntityCollection fills; Flow flow(FlowRole role, bool bridge = false, double width = -1) const; void slices_to_fill_surfaces_clipped(); diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index 68e17407e9d..12526f2eca5 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -15,8 +15,7 @@ namespace Slic3r { -Flow -LayerRegion::flow(FlowRole role, bool bridge, double width) const +Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const { return this->_region->flow( role, @@ -51,8 +50,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped() } } -void -LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) +void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) { this->perimeters.clear(); this->thin_fills.clear(); @@ -340,8 +338,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } -void -LayerRegion::prepare_fill_surfaces() +void LayerRegion::prepare_fill_surfaces() { #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); @@ -382,8 +379,7 @@ LayerRegion::prepare_fill_surfaces() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } -double -LayerRegion::infill_area_threshold() const +double LayerRegion::infill_area_threshold() const { double ss = this->flow(frSolidInfill).scaled_spacing(); return ss*ss; diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index 0970e9a6743..f4f82a353e0 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -34,8 +34,10 @@ class MultiPoint Point first_point() const; virtual Point last_point() const = 0; virtual Lines lines() const = 0; + size_t size() const { return points.size(); } + bool empty() const { return points.empty(); } double length() const; - bool is_valid() const { return this->points.size() >= 2; } + bool is_valid() const { return this->points.size() >= 2; } int find_point(const Point &point) const; bool has_boundary_point(const Point &point) const; diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index 1a02d78b718..2d624e71a9e 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -103,6 +103,12 @@ inline void polygons_rotate(Polygons &polys, double angle) p.rotate(cos_angle, sin_angle); } +inline void polygons_reverse(Polygons &polys) +{ + for (Polygon &p : polys) + p.reverse(); +} + inline Points to_points(const Polygon &poly) { return poly.points; diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index 3432506c66a..05bd8c7fb2b 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -193,23 +193,19 @@ Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const } } -bool -Polyline::is_straight() const +bool Polyline::is_straight() const { - /* Check that each segment's direction is equal to the line connecting - first point and last point. (Checking each line against the previous - one would cause the error to accumulate.) */ + // Check that each segment's direction is equal to the line connecting + // first point and last point. (Checking each line against the previous + // one would cause the error to accumulate.) double dir = Line(this->first_point(), this->last_point()).direction(); - - Lines lines = this->lines(); - for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { - if (!line->parallel_to(dir)) return false; - } + for (const auto &line: this->lines()) + if (! line.parallel_to(dir)) + return false; return true; } -std::string -Polyline::wkt() const +std::string Polyline::wkt() const { std::ostringstream wkt; wkt << "LINESTRING(("; diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index bcd61ea023c..67665fbcb29 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -80,7 +80,10 @@ class PrintRegion Print* print() { return this->_print; } Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const; + // Average diameter of nozzles participating on extruding this region. coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; + // Average diameter of nozzles participating on extruding this region. + coordf_t bridging_height_avg(const PrintConfig &print_config) const; private: Print* _print; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index c530868a12e..ba9c3fc8f5e 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -310,6 +310,7 @@ class PrintObjectConfig : public StaticPrintConfig ConfigOptionFloatOrPercent extrusion_width; ConfigOptionFloatOrPercent first_layer_height; ConfigOptionBool infill_only_where_needed; + // Force the generation of solid shells between adjacent materials/volumes. ConfigOptionBool interface_shells; ConfigOptionFloat layer_height; ConfigOptionInt raft_layers; @@ -317,6 +318,7 @@ class PrintObjectConfig : public StaticPrintConfig // ConfigOptionFloat seam_preferred_direction; // ConfigOptionFloat seam_preferred_direction_jitter; ConfigOptionBool support_material; + // Direction of the support pattern (in XY plane). ConfigOptionFloat support_material_angle; ConfigOptionBool support_material_buildplate_only; ConfigOptionFloat support_material_contact_distance; @@ -326,12 +328,15 @@ class PrintObjectConfig : public StaticPrintConfig ConfigOptionBool support_material_interface_contact_loops; ConfigOptionInt support_material_interface_extruder; ConfigOptionInt support_material_interface_layers; + // Spacing between interface lines (the hatching distance). Set zero to get a solid interface. ConfigOptionFloat support_material_interface_spacing; ConfigOptionFloatOrPercent support_material_interface_speed; ConfigOptionEnum support_material_pattern; + // Spacing between support material lines (the hatching distance). ConfigOptionFloat support_material_spacing; ConfigOptionFloat support_material_speed; ConfigOptionBool support_material_synchronize_layers; + // Overhang angle threshold. ConfigOptionInt support_material_threshold; ConfigOptionBool support_material_with_sheath; ConfigOptionFloatOrPercent support_material_xy_spacing; @@ -401,10 +406,12 @@ class PrintRegionConfig : public StaticPrintConfig ConfigOptionInt infill_every_layers; ConfigOptionFloatOrPercent infill_overlap; ConfigOptionFloat infill_speed; + // Detect bridging perimeters ConfigOptionBool overhangs; ConfigOptionInt perimeter_extruder; ConfigOptionFloatOrPercent perimeter_extrusion_width; ConfigOptionFloat perimeter_speed; + // Total number of perimeters. ConfigOptionInt perimeters; ConfigOptionFloatOrPercent small_perimeter_speed; ConfigOptionFloat solid_infill_below_area; @@ -412,6 +419,7 @@ class PrintRegionConfig : public StaticPrintConfig ConfigOptionFloatOrPercent solid_infill_extrusion_width; ConfigOptionInt solid_infill_every_layers; ConfigOptionFloatOrPercent solid_infill_speed; + // Detect thin walls. ConfigOptionBool thin_walls; ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionInt top_solid_layers; diff --git a/xs/src/libslic3r/PrintRegion.cpp b/xs/src/libslic3r/PrintRegion.cpp index 4874c71bcd0..71fcc876ae9 100644 --- a/xs/src/libslic3r/PrintRegion.cpp +++ b/xs/src/libslic3r/PrintRegion.cpp @@ -57,4 +57,9 @@ coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const print_config.nozzle_diameter.get_at(this->config.solid_infill_extruder.value - 1)) / 3.; } +coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const +{ + return this->nozzle_dmr_avg(print_config) * this->config.bridge_flow_ratio.value; +} + } diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp index 29cfeb1dba3..9544748e932 100644 --- a/xs/src/libslic3r/SurfaceCollection.hpp +++ b/xs/src/libslic3r/SurfaceCollection.hpp @@ -37,6 +37,11 @@ class SurfaceCollection void clear() { surfaces.clear(); } bool empty() const { return surfaces.empty(); } + bool has(SurfaceType type) const { + for (const Surface &surface : this->surfaces) + if (surface.surface_type == type) return true; + return false; + } void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; } void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); } diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index 4f09fb52140..efd6c9ae634 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -17,8 +17,6 @@ %code%{ RETVAL = &THIS->thin_fills; %}; Ref fill_surfaces() %code%{ RETVAL = &THIS->fill_surfaces; %}; - Ref perimeter_surfaces() - %code%{ RETVAL = &THIS->perimeter_surfaces; %}; Polygons bridged() %code%{ RETVAL = THIS->bridged; %}; Ref unsupported_bridge_edges() From a7fbb70bcf0a2a8546b539b6ac0e2adc5ba2693d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 25 Jul 2018 16:20:15 +0200 Subject: [PATCH 02/63] Fixed an incorrect offset between the obect bottom layer and the supports. (bridging_flow correction has not been considered, so the bridge was printed with a bridging flow, but an offset was calculated for a nozzle height). Fixed some missing interface layers (trim_support_layers_by_object() was too aggressive). Fixed trim_support_layers_by_object() to consider the bridging perimeters(). --- xs/src/libslic3r/SupportMaterial.cpp | 241 +++++++++++++++++---------- 1 file changed, 155 insertions(+), 86 deletions(-) diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 0cecf0014b2..538139c277b 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -248,10 +248,10 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) #ifdef SLIC3R_DEBUG static int iRun = 0; iRun ++; - for (MyLayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) + for (const MyLayer *layer : top_contacts) Slic3r::SVG::export_expolygons( - debug_out_path("support-top-contacts-%d-%lf.svg", iRun, (*it)->print_z), - union_ex((*it)->polygons, false)); + debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z), + union_ex(layer->polygons, false)); #endif /* SLIC3R_DEBUG */ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts"; @@ -282,7 +282,15 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( object, bottom_contacts, top_contacts, layer_storage); - this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); +// this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); + this->trim_support_layers_by_object(object, top_contacts, 0., 0., m_gap_xy); + +#ifdef SLIC3R_DEBUG + for (const MyLayer *layer : top_contacts) + Slic3r::SVG::export_expolygons( + debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z), + union_ex(layer->polygons, false)); +#endif BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers"; @@ -632,6 +640,69 @@ class SupportGridPattern Points m_island_samples; }; +namespace SupportMaterialInternal { + static inline bool has_bridging_perimeters(const ExtrusionLoop &loop) + { + for (const ExtrusionPath &ep : loop.paths) + if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) + return ep.size() >= (ep.is_closed() ? 3 : 2); + return false; + } + static bool has_bridging_perimeters(const ExtrusionEntityCollection &perimeters) + { + for (const ExtrusionEntity *ee : perimeters.entities) { + if (ee->is_collection()) { + for (const ExtrusionEntity *ee2 : static_cast(ee)->entities) { + assert(! ee2->is_collection()); + if (ee2->is_loop()) + if (has_bridging_perimeters(*static_cast(ee2))) + return true; + } + } else if (ee->is_loop() && has_bridging_perimeters(*static_cast(ee))) + return true; + } + return false; + } + + static inline void collect_bridging_perimeter_areas(const ExtrusionLoop &loop, const float expansion_scaled, Polygons &out) + { + assert(expansion_scaled >= 0.f); + for (const ExtrusionPath &ep : loop.paths) + if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) { + float exp = 0.5f * scale_(ep.width) + expansion_scaled; + if (ep.is_closed()) { + if (ep.size() >= 3) { + // This is a complete loop. + // Add the outer contour first. + Polygon poly; + poly.points = ep.polyline.points; + poly.points.pop_back(); + polygons_append(out, offset(poly, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + Polygons holes = offset(poly, - exp, SUPPORT_SURFACES_OFFSET_PARAMETERS); + polygons_reverse(holes); + polygons_append(out, holes); + } + } else if (ep.size() >= 2) { + // Offset the polyline. + offset(ep.polyline, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS); + } + } + } + static void collect_bridging_perimeter_areas(const ExtrusionEntityCollection &perimeters, const float expansion_scaled, Polygons &out) + { + for (const ExtrusionEntity *ee : perimeters.entities) { + if (ee->is_collection()) { + for (const ExtrusionEntity *ee2 : static_cast(ee)->entities) { + assert(! ee2->is_collection()); + if (ee2->is_loop()) + collect_bridging_perimeter_areas(*static_cast(ee2), expansion_scaled, out); + } + } else if (ee->is_loop()) + collect_bridging_perimeter_areas(*static_cast(ee), expansion_scaled, out); + } + } +} + // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. @@ -764,70 +835,60 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ if (this->m_object_config->dont_support_bridges) { // compute the area of bridging perimeters - // Note: this is duplicate code from GCode.pm, we need to refactor - if (true) { - Polygons bridged_perimeters; - { - Flow bridge_flow = layerm->flow(frPerimeter, true); - coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(layerm->region()->config.perimeter_extruder-1); - Polygons lower_grown_slices = offset(lower_layer_polygons, 0.5f*float(scale_(nozzle_diameter)), SUPPORT_SURFACES_OFFSET_PARAMETERS); - - // Collect perimeters of this layer. - // TODO: split_at_first_point() could split a bridge mid-way - Polylines overhang_perimeters; - for (ExtrusionEntity* extrusion_entity : layerm->perimeters.entities) { - const ExtrusionEntityCollection *island = dynamic_cast(extrusion_entity); - assert(island != NULL); - for (size_t i = 0; i < island->entities.size(); ++ i) { - ExtrusionEntity *entity = island->entities[i]; - ExtrusionLoop *loop = dynamic_cast(entity); - overhang_perimeters.push_back(loop ? - loop->as_polyline() : - dynamic_cast(entity)->polyline); - } + Polygons bridges; + { + // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. + Polygons lower_grown_slices = offset(lower_layer_polygons, + //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. + 0.5f * float(scale_(m_print_config->nozzle_diameter.get_at(layerm->region()->config.perimeter_extruder-1))), + SUPPORT_SURFACES_OFFSET_PARAMETERS); + // Collect perimeters of this layer. + //FIXME split_at_first_point() could split a bridge mid-way + #if 0 + Polylines overhang_perimeters = layerm->perimeters.as_polylines(); + // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() + for (Polyline &polyline : overhang_perimeters) + polyline.points[0].x += 1; + // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. + overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); + #else + Polylines overhang_perimeters = diff_pl(layerm->perimeters.as_polylines(), lower_grown_slices); + #endif + + // only consider straight overhangs + // only consider overhangs having endpoints inside layer's slices + // convert bridging polylines into polygons by inflating them with their thickness + // since we're dealing with bridges, we can't assume width is larger than spacing, + // so we take the largest value and also apply safety offset to be ensure no gaps + // are left in between + Flow bridge_flow = layerm->flow(frPerimeter, true); + float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); + for (Polyline &polyline : overhang_perimeters) + if (polyline.is_straight()) { + // This is a bridge + polyline.extend_start(fw); + polyline.extend_end(fw); + // Is the straight perimeter segment supported at both sides? + if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point())) + // Offset a polyline into a thick line. + polygons_append(bridges, offset(polyline, 0.5f * w + 10.f)); } - - // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() - for (Polyline &polyline : overhang_perimeters) - polyline.points[0].x += 1; - // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. - overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); - - // only consider straight overhangs - // only consider overhangs having endpoints inside layer's slices - // convert bridging polylines into polygons by inflating them with their thickness - // since we're dealing with bridges, we can't assume width is larger than spacing, - // so we take the largest value and also apply safety offset to be ensure no gaps - // are left in between - float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); - for (Polyline &polyline : overhang_perimeters) - if (polyline.is_straight()) { - // This is a bridge - polyline.extend_start(fw); - polyline.extend_end(fw); - // Is the straight perimeter segment supported at both sides? - if (layer.slices.contains(polyline.first_point()) && layer.slices.contains(polyline.last_point())) - // Offset a polyline into a thick line. - polygons_append(bridged_perimeters, offset(polyline, 0.5f * w + 10.f)); - } - bridged_perimeters = union_(bridged_perimeters); - } - // remove the entire bridges and only support the unsupported edges - Polygons bridges; - for (const Surface &surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) - polygons_append(bridges, surface.expolygon); - diff_polygons = diff(diff_polygons, bridges, true); - polygons_append(bridges, bridged_perimeters); - polygons_append(diff_polygons, - intersection( - // Offset unsupported edges into polygons. - offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), - bridges)); - } else { - // just remove bridged areas - diff_polygons = diff(diff_polygons, layerm->bridged, true); + bridges = union_(bridges); } + // remove the entire bridges and only support the unsupported edges + //FIXME the brided regions are already collected as layerm->bridged. Use it? + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) + polygons_append(bridges, surface.expolygon); + //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? + diff_polygons = diff(diff_polygons, bridges, true); + // Add the bridge anchors into the region. + //FIXME add supports at regular intervals to support long bridges! + polygons_append(diff_polygons, + intersection( + // Offset unsupported edges into polygons. + offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), + bridges)); } // if (m_objconfig->dont_support_bridges) if (diff_polygons.empty()) @@ -879,9 +940,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ } // for each layer.region } // end of Generate overhang/contact_polygons for non-raft layers. - // now apply the contact areas to the layer were they need to be made + // Now apply the contact areas to the layer where they need to be made. if (! contact_polygons.empty()) { - // get the average nozzle diameter used on this layer MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); new_layer.idx_object_layer_above = layer_id; if (m_slicing_params.soluble_interface) { @@ -903,11 +963,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ //FIXME Probably printing with the bridge flow? How about the unsupported perimeters? Are they printed with the bridging flow? // In the future we may switch to a normal extrusion flow for the supported bridges. // Get the average nozzle diameter used on this layer. - coordf_t nozzle_dmr = 0.; + coordf_t bridging_height = 0.; for (const LayerRegion *region : layer.regions) - nozzle_dmr += region->region()->nozzle_dmr_avg(*m_print_config); - nozzle_dmr /= coordf_t(layer.regions.size()); - new_layer.print_z = layer.print_z - nozzle_dmr - m_object_config->support_material_contact_distance; + bridging_height += region->region()->bridging_height_avg(*m_print_config); + bridging_height /= coordf_t(layer.regions.size()); + new_layer.print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance; new_layer.bottom_z = new_layer.print_z; new_layer.height = 0.; if (layer_id == 0) { @@ -996,7 +1056,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id; const Layer &layer = *object.get_layer(layer_id); // Collect projections of all contact areas above or at the same level as this top surface. - for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z >= layer.print_z; -- contact_idx) { + for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { + auto *l = top_contacts[contact_idx]; Polygons polygons_new; // Contact surfaces are expanded away from the object, trimmed by the object. // Use a slight positive offset to overlap the touching regions. @@ -1046,10 +1107,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta // Grow top surfaces so that interface and support generation are generated // with some spacing from object - it looks we don't need the actual // top shapes so this can be done here + //FIXME calculate layer height based on the actual thickness of the layer: + // If the layer is extruded with no bridging flow, support just the normal extrusions. layer_new.height = m_slicing_params.soluble_interface ? // Align the interface layer with the object's layer height. object.layers[layer_id + 1]->height : // Place a bridge flow interface layer over the top surface. + //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?) + // According to Jindrich the bottom surfaces work well. m_support_material_interface_flow.nozzle_diameter; layer_new.print_z = m_slicing_params.soluble_interface ? object.layers[layer_id + 1]->print_z : layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value; @@ -1185,7 +1250,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta task_group.wait(); } std::reverse(bottom_contacts.begin(), bottom_contacts.end()); - trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); + trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy); } // ! top_contacts.empty() return bottom_contacts; @@ -1608,7 +1673,7 @@ void PrintObjectSupportMaterial::generate_base_layers( ++ iRun; #endif /* SLIC3R_DEBUG */ - trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_gap_xy); + trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy); } void PrintObjectSupportMaterial::trim_support_layers_by_object( @@ -1660,12 +1725,16 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( for (; i < object.layers.size(); ++ i) { const Layer &object_layer = *object.layers[i]; bool some_region_overlaps = false; - for (LayerRegion* region : object_layer.regions) { - coordf_t nozzle_dmr = region->region()->nozzle_dmr_avg(*this->m_print_config); - if (object_layer.print_z - nozzle_dmr > support_layer.print_z + gap_extra_above - EPSILON) + for (LayerRegion *region : object_layer.regions) { + coordf_t bridging_height = region->region()->bridging_height_avg(*this->m_print_config); + if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON) break; some_region_overlaps = true; - polygons_append(polygons_trimming, to_polygons(region->slices.filter_by_type(stBottomBridge))); + polygons_append(polygons_trimming, + offset(to_expolygons(region->slices.filter_by_type(stBottomBridge)), + gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (region->region()->config.overhangs.value) + SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); } if (! some_region_overlaps) break; @@ -1675,9 +1744,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( // perimeter's width. $support contains the full shape of support // material, thus including the width of its foremost extrusion. // We leave a gap equal to a full extrusion width. - support_layer.polygons = diff( - support_layer.polygons, - offset(polygons_trimming, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + support_layer.polygons = diff(support_layer.polygons, polygons_trimming); } }); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; @@ -1861,8 +1928,8 @@ static inline void fill_expolygons_generate_paths( fill_params.density = density; fill_params.complete = true; fill_params.dont_adjust = true; - for (ExPolygons::const_iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { - Surface surface(stInternal, *it_expolygon); + for (const ExPolygon &expoly : expolygons) { + Surface surface(stInternal, expoly); extrusion_entities_append_paths( dst, filler->fill_surface(&surface, fill_params), @@ -1883,8 +1950,8 @@ static inline void fill_expolygons_generate_paths( fill_params.density = density; fill_params.complete = true; fill_params.dont_adjust = true; - for (ExPolygons::iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { - Surface surface(stInternal, std::move(*it_expolygon)); + for (ExPolygon &expoly : expolygons) { + Surface surface(stInternal, std::move(expoly)); extrusion_entities_append_paths( dst, filler->fill_surface(&surface, fill_params), @@ -2711,6 +2778,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( continue; //FIXME When paralellizing, each thread shall have its own copy of the fillers. bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0; + //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore + // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) Flow interface_flow( float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)), float(layer_ex.layer->height), From 9d0093e474ccecd2b4bb6a367d679bc1341700c7 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 26 Jul 2018 11:33:57 +0200 Subject: [PATCH 03/63] Support generator now produce two contact layers per object layer: One contact layer to support the bridging flow extrusions, and the other contact layer to support the non-bridging flow extrusions. --- xs/src/libslic3r/SupportMaterial.cpp | 295 +++++++++++++++++---------- xs/src/libslic3r/SupportMaterial.hpp | 15 ++ 2 files changed, 207 insertions(+), 103 deletions(-) diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 538139c277b..79bd66dfc06 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -663,6 +663,29 @@ namespace SupportMaterialInternal { } return false; } + static bool has_bridging_fills(const ExtrusionEntityCollection &fills) + { + for (const ExtrusionEntity *ee : fills.entities) { + assert(ee->is_collection()); + for (const ExtrusionEntity *ee2 : static_cast(ee)->entities) { + assert(! ee2->is_collection()); + assert(! ee2->is_loop()); + if (ee2->role() == erBridgeInfill) + return true; + } + } + return false; + } + static bool has_bridging_extrusions(const Layer &layer) + { + for (const LayerRegion *region : layer.regions) { + if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters)) + return true; + if (region->fill_surfaces.has(stBottomBridge) && has_bridging_fills(region->fills)) + return true; + } + return false; + } static inline void collect_bridging_perimeter_areas(const ExtrusionLoop &loop, const float expansion_scaled, Polygons &out) { @@ -677,6 +700,8 @@ namespace SupportMaterialInternal { Polygon poly; poly.points = ep.polyline.points; poly.points.pop_back(); + if (poly.area() < 0) + poly.reverse(); polygons_append(out, offset(poly, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS)); Polygons holes = offset(poly, - exp, SUPPORT_SURFACES_OFFSET_PARAMETERS); polygons_reverse(holes); @@ -684,7 +709,7 @@ namespace SupportMaterialInternal { } } else if (ep.size() >= 2) { // Offset the polyline. - offset(ep.polyline, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS); + polygons_append(out, offset(ep.polyline, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS)); } } } @@ -701,6 +726,71 @@ namespace SupportMaterialInternal { collect_bridging_perimeter_areas(*static_cast(ee), expansion_scaled, out); } } + + static void remove_bridges_from_contacts( + const PrintConfig &print_config, + const Layer &lower_layer, + const Polygons &lower_layer_polygons, + LayerRegion *layerm, + float fw, + Polygons &contact_polygons) + { + // compute the area of bridging perimeters + Polygons bridges; + { + // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. + Polygons lower_grown_slices = offset(lower_layer_polygons, + //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region()->config.perimeter_extruder-1))), + SUPPORT_SURFACES_OFFSET_PARAMETERS); + // Collect perimeters of this layer. + //FIXME split_at_first_point() could split a bridge mid-way + #if 0 + Polylines overhang_perimeters = layerm->perimeters.as_polylines(); + // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() + for (Polyline &polyline : overhang_perimeters) + polyline.points[0].x += 1; + // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. + overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); + #else + Polylines overhang_perimeters = diff_pl(layerm->perimeters.as_polylines(), lower_grown_slices); + #endif + + // only consider straight overhangs + // only consider overhangs having endpoints inside layer's slices + // convert bridging polylines into polygons by inflating them with their thickness + // since we're dealing with bridges, we can't assume width is larger than spacing, + // so we take the largest value and also apply safety offset to be ensure no gaps + // are left in between + Flow bridge_flow = layerm->flow(frPerimeter, true); + float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); + for (Polyline &polyline : overhang_perimeters) + if (polyline.is_straight()) { + // This is a bridge + polyline.extend_start(fw); + polyline.extend_end(fw); + // Is the straight perimeter segment supported at both sides? + if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point())) + // Offset a polyline into a thick line. + polygons_append(bridges, offset(polyline, 0.5f * w + 10.f)); + } + bridges = union_(bridges); + } + // remove the entire bridges and only support the unsupported edges + //FIXME the brided regions are already collected as layerm->bridged. Use it? + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) + polygons_append(bridges, surface.expolygon); + //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? + contact_polygons = diff(contact_polygons, bridges, true); + // Add the bridge anchors into the region. + //FIXME add supports at regular intervals to support long bridges! + polygons_append(contact_polygons, + intersection( + // Offset unsupported edges into polygons. + offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), + bridges)); + } } // Generate top contact layers supporting overhangs. @@ -751,7 +841,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers. // So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers. size_t num_layers = this->has_support() ? object.layer_count() : 1; - contact_out.assign(num_layers, nullptr); + contact_out.assign(num_layers * 2, nullptr); tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(this->has_raft() ? 0 : 1, num_layers), [this, &object, &buildplate_covered, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out](const tbb::blocked_range& range) { @@ -833,63 +923,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ } #endif /* SLIC3R_DEBUG */ - if (this->m_object_config->dont_support_bridges) { - // compute the area of bridging perimeters - Polygons bridges; - { - // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. - Polygons lower_grown_slices = offset(lower_layer_polygons, - //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. - 0.5f * float(scale_(m_print_config->nozzle_diameter.get_at(layerm->region()->config.perimeter_extruder-1))), - SUPPORT_SURFACES_OFFSET_PARAMETERS); - // Collect perimeters of this layer. - //FIXME split_at_first_point() could split a bridge mid-way - #if 0 - Polylines overhang_perimeters = layerm->perimeters.as_polylines(); - // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() - for (Polyline &polyline : overhang_perimeters) - polyline.points[0].x += 1; - // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. - overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); - #else - Polylines overhang_perimeters = diff_pl(layerm->perimeters.as_polylines(), lower_grown_slices); - #endif - - // only consider straight overhangs - // only consider overhangs having endpoints inside layer's slices - // convert bridging polylines into polygons by inflating them with their thickness - // since we're dealing with bridges, we can't assume width is larger than spacing, - // so we take the largest value and also apply safety offset to be ensure no gaps - // are left in between - Flow bridge_flow = layerm->flow(frPerimeter, true); - float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); - for (Polyline &polyline : overhang_perimeters) - if (polyline.is_straight()) { - // This is a bridge - polyline.extend_start(fw); - polyline.extend_end(fw); - // Is the straight perimeter segment supported at both sides? - if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point())) - // Offset a polyline into a thick line. - polygons_append(bridges, offset(polyline, 0.5f * w + 10.f)); - } - bridges = union_(bridges); - } - // remove the entire bridges and only support the unsupported edges - //FIXME the brided regions are already collected as layerm->bridged. Use it? - for (const Surface &surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) - polygons_append(bridges, surface.expolygon); - //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? - diff_polygons = diff(diff_polygons, bridges, true); - // Add the bridge anchors into the region. - //FIXME add supports at regular intervals to support long bridges! - polygons_append(diff_polygons, - intersection( - // Offset unsupported edges into polygons. - offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), - bridges)); - } // if (m_objconfig->dont_support_bridges) + if (this->m_object_config->dont_support_bridges) + SupportMaterialInternal::remove_bridges_from_contacts( + *m_print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons); if (diff_polygons.empty()) continue; @@ -944,53 +980,68 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ if (! contact_polygons.empty()) { MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); new_layer.idx_object_layer_above = layer_id; - if (m_slicing_params.soluble_interface) { + MyLayer *bridging_layer = nullptr; + if (layer_id == 0) { + // This is a raft contact layer sitting directly on the print bed. + assert(this->has_raft()); + new_layer.print_z = m_slicing_params.raft_contact_top_z; + new_layer.bottom_z = m_slicing_params.raft_interface_top_z; + new_layer.height = m_slicing_params.contact_raft_layer_height; + } else if (m_slicing_params.soluble_interface) { // Align the contact surface height with a layer immediately below the supported layer. - new_layer.print_z = layer.print_z - layer.height; - if (layer_id == 0) { - // This is a raft contact layer sitting directly on the print bed. - new_layer.height = m_slicing_params.contact_raft_layer_height; - new_layer.bottom_z = m_slicing_params.raft_interface_top_z; - } else { - // Interface layer will be synchronized with the object. - assert(layer_id > 0); - new_layer.height = object.layers[layer_id - 1]->height; - new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers[layer_id - 2]->print_z; - } + // Interface layer will be synchronized with the object. + new_layer.print_z = layer.print_z - layer.height; + new_layer.height = object.layers[layer_id - 1]->height; + new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers[layer_id - 2]->print_z; } else { - // Contact layer will be printed with a normal flow, but - // it will support layers printed with a bridging flow. - //FIXME Probably printing with the bridge flow? How about the unsupported perimeters? Are they printed with the bridging flow? - // In the future we may switch to a normal extrusion flow for the supported bridges. - // Get the average nozzle diameter used on this layer. - coordf_t bridging_height = 0.; - for (const LayerRegion *region : layer.regions) - bridging_height += region->region()->bridging_height_avg(*m_print_config); - bridging_height /= coordf_t(layer.regions.size()); - new_layer.print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance; + new_layer.print_z = layer.print_z - layer.height - m_object_config->support_material_contact_distance; new_layer.bottom_z = new_layer.print_z; new_layer.height = 0.; - if (layer_id == 0) { - // This is a raft contact layer sitting directly on the print bed. - assert(this->has_raft()); - new_layer.bottom_z = m_slicing_params.raft_interface_top_z; - new_layer.height = m_slicing_params.contact_raft_layer_height; + // Ignore this contact area if it's too low. + // Don't want to print a layer below the first layer height as it may not stick well. + //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact + // and it may actually make sense to do it with a thinner layer than the first layer height. + if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) { + // This contact layer is below the first layer height, therefore not printable. Don't support this surface. + continue; + } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { + // Align the layer with the 1st layer height. + new_layer.print_z = m_slicing_params.first_print_layer_height; + new_layer.bottom_z = 0; + new_layer.height = m_slicing_params.first_print_layer_height; } else { - // Ignore this contact area if it's too low. - // Don't want to print a layer below the first layer height as it may not stick well. - //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact - // and it may actually make sense to do it with a thinner layer than the first layer height. - if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) { - // This contact layer is below the first layer height, therefore not printable. Don't support this surface. - continue; - } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - new_layer.print_z = m_slicing_params.first_print_layer_height; - new_layer.bottom_z = 0; - new_layer.height = m_slicing_params.first_print_layer_height; - } else { - // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and - // its height will be set adaptively later on. + // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and + // its height will be set adaptively later on. + } + + // Contact layer will be printed with a normal flow, but + // it will support layers printed with a bridging flow. + if (SupportMaterialInternal::has_bridging_extrusions(layer)) { + coordf_t bridging_height = 0.; + for (const LayerRegion *region : layer.regions) + bridging_height += region->region()->bridging_height_avg(*m_print_config); + bridging_height /= coordf_t(layer.regions.size()); + coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance; + if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) { + // Not below the first layer height means this layer is printable. + if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { + // Align the layer with the 1st layer height. + bridging_print_z = m_slicing_params.first_print_layer_height; + } + if (bridging_print_z != new_layer.print_z) { + // Allocate the new layer. + bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); + bridging_layer->idx_object_layer_above = layer_id; + bridging_layer->print_z = bridging_print_z; + if (bridging_print_z == m_slicing_params.first_print_layer_height) { + bridging_layer->bottom_z = 0; + bridging_layer->height = m_slicing_params.first_print_layer_height; + } else { + // Don't know the height yet. + bridging_layer->bottom_z = bridging_print_z; + bridging_layer->height = 0; + } + } } } } @@ -1015,12 +1066,50 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // The overhang polygons are used in the path generator for planning of the contact loops. // if (this->has_contact_loops()) new_layer.overhang_polygons = new Polygons(std::move(overhang_polygons)); - contact_out[layer_id] = &new_layer; + contact_out[layer_id * 2] = &new_layer; + if (bridging_layer != nullptr) { + bridging_layer->polygons = new_layer.polygons; + bridging_layer->contact_polygons = new Polygons(*new_layer.contact_polygons); + bridging_layer->overhang_polygons = new Polygons(*new_layer.overhang_polygons); + contact_out[layer_id * 2 + 1] = bridging_layer; + } } } }); + // Compress contact_out, remove the nullptr items. remove_nulls(contact_out); + // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z. + std::sort(contact_out.begin(), contact_out.end(), [](const MyLayer *l1, const MyLayer *l2) { return l1->print_z < l2->print_z; }); + + // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), + // the top contact layer is merged into the bottom contact layer. + { + int k = 0; + for (int i = 0; i < int(contact_out.size()); ++ k) { + // Find the span of layers closer than m_support_layer_height_min. + int j = i + 1; + coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min - EPSILON; + for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; + if (i + 1 < j) { + // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i]. + MyLayer &dst = *contact_out[i]; + for (int u = i + 1; u < j; ++ u) { + MyLayer &src = *contact_out[u]; + // The union_() does not support move semantic yet, but maybe one day it will. + dst.polygons = union_(dst.polygons, std::move(src.polygons)); + *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); + *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); + // Source polygon is no more needed, it will not be refrenced. Release its data. + src.reset(); + } + } + if (k < i) + contact_out[k] = contact_out[i]; + i = j; + } + } + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; return contact_out; @@ -1731,7 +1820,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( break; some_region_overlaps = true; polygons_append(polygons_trimming, - offset(to_expolygons(region->slices.filter_by_type(stBottomBridge)), + offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (region->region()->config.overhangs.value) SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); diff --git a/xs/src/libslic3r/SupportMaterial.hpp b/xs/src/libslic3r/SupportMaterial.hpp index 968763446b1..da11cd82c35 100644 --- a/xs/src/libslic3r/SupportMaterial.hpp +++ b/xs/src/libslic3r/SupportMaterial.hpp @@ -71,6 +71,21 @@ class PrintObjectSupportMaterial overhang_polygons = nullptr; } + void reset() { + layer_type = sltUnknown; + print_z = 0.; + bottom_z = 0.; + height = 0.; + idx_object_layer_above = size_t(-1); + idx_object_layer_below = size_t(-1); + bridging = false; + polygons.clear(); + delete contact_polygons; + contact_polygons = nullptr; + delete overhang_polygons; + overhang_polygons = nullptr; + } + bool operator==(const MyLayer &layer2) const { return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; } From 53a7d05dcb3d35342c5f4fbefeda5ec305fba571 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 26 Jul 2018 19:24:39 +0200 Subject: [PATCH 04/63] Starting logging SublimeCodeIntel v2.2.0 rev GIT-unknown (1481616156) on Thu Jul 26 19:24:37 2018 ================================================================================================= --- xs/src/libslic3r/SupportMaterial.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 79bd66dfc06..1bf9675df0a 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -1108,6 +1108,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ contact_out[k] = contact_out[i]; i = j; } + if (k < contact_out.size()) + contact_out.erase(contact_out.begin() + k, contact_out.end()); } BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; From 8da8ecb4157aefa24e3ff4d13367bf94d0f1952e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 3 Aug 2018 14:49:26 +0200 Subject: [PATCH 05/63] Bed shape detection in progress --- xs/src/libslic3r/ModelArrange.hpp | 164 ++++++++++++++++++++++++++++-- xs/src/slic3r/AppController.cpp | 7 +- 2 files changed, 161 insertions(+), 10 deletions(-) diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index f2d399ac66a..5b7a2a32583 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -292,6 +292,7 @@ class _ArrBase { template inline IndexedPackGroup operator()(Args&&...args) { areacache_.clear(); + rtree_.clear(); return pck_.arrangeIndexed(std::forward(args)...); } }; @@ -440,16 +441,157 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { return ret; } -enum BedShapeHint { +class Circle { + Point center_; + double radius_; +public: + + inline Circle(): center_(0, 0), radius_(std::nan("")) {} + inline Circle(const Point& c, double r): center_(c), radius_(r) {} + + inline double radius() const { return radius_; } + inline const Point& center() const { return center_; } + inline operator bool() { return std::isnan(radius_); } +}; + + +Circle circle(std::array P) { + + using Coord = Point::coord_type; + using std::pow; + using std::abs; + using std::round; + using std::nan; + + auto getX = [](const Point& p) { return p.x; }; + auto getY = [](const Point& p) { return p.y; }; + + auto distance = [](const Point& p1, const Point& p2) { + return abs(p1.distance_to(p2)); + }; + + static const auto E = 10.0/SCALING_FACTOR; + + auto x1 = getX(P[0]), y1 = getY(P[0]); + auto x2 = getX(P[1]), y2 = getY(P[1]); + auto x3 = getX(P[2]), y3 = getY(P[2]); + + + auto A_div = (x2 - x1); + auto B_div = (x3 - x2); + if(A_div == 0 || B_div == 0) return Circle(); + + auto A = (y2 - y1)/A_div; + auto B = (y2 - y3)/B_div; + auto C = (-pow(x1, 2) - pow(y1, 2) + pow(x2, 2) + pow(y2, 2))/(2*(x2 - x1)); + auto D = (pow(x2, 2) + pow(y1, 2) - pow(x3, 2) - pow(y3, 2))/(2*(x3 - x2)); + + auto cy = (C + D)/(A + B); + auto cx = B*cy - D; + + Point cc = {Coord(round(cx)), Coord(round(cy))}; + auto d = distance(cc, P[0]); + auto d2 = distance(cc, P[1]); + auto d3 = distance(cc, P[2]); + + auto e1 = abs(d - d2); + auto e2 = abs(d - d3); + + if(e1 > E || e2 > E) return Circle(); + + return { cc, d }; +} + +Circle isCircle(const Polyline& p) { + + using std::abs; + + auto& pp = p.points; + static const double E = 10/SCALING_FACTOR; + double radius = 0; + bool ret = true; + Circle c; + for(auto i = 0; i < pp.size() - 3 && ret; i += 3) { + c = circle({pp[i], pp[i+1], pp[i+2]}); + if(c || abs(radius - c.radius()) >= E) ret = false; + else radius = c.radius(); + } + +// auto rem = pp.size() % 3; + +// if(ret && rem > 0) { +// std::array remarr; + +// auto i = 0; +// for(i = 0; i < rem; i++) remarr[i] = *(pp.rbegin() - i); +// while(i < 3) remarr[i] = pp[i++]; +// c = circle(remarr); +// if(c || abs(radius - c.radius()) >= E) ret = false; +// } + + if(!ret) c = Circle(); + + return c; +} + +enum class BedShapeType { BOX, CIRCLE, IRREGULAR, WHO_KNOWS }; -BedShapeHint bedShape(const Slic3r::Polyline& /*bed*/) { +struct BedShapeHint { + BedShapeType type; + /*union*/ struct { // I know but who cares... + Circle circ; + BoundingBox box; + Polyline polygon; + } shape; +}; + +BedShapeHint bedShape(const Polyline& bed) { + static const double E = 10/SCALING_FACTOR; + + BedShapeHint ret; + + auto width = [](const BoundingBox& box) { + return box.max.x - box.min.x; + }; + + auto height = [](const BoundingBox& box) { + return box.max.y - box.min.y; + }; + + auto area = [&width, &height](const BoundingBox& box) { + return width(box) * height(box); + }; + + auto poly_area = [](Polyline p) { + Polygon pp; pp.points.reserve(p.points.size() + 1); + pp.points = std::move(p.points); + pp.points.emplace_back(pp.points.front()); + return std::abs(pp.area()); + }; + + auto bb = bed.bounding_box(); + if(std::abs(area(bb) - poly_area(bed)) < E) { + ret.type = BedShapeType::BOX; + ret.shape.box = bb; + std::cout << "BOX" << std::endl; + } + else if(auto c = isCircle(bed)) { + ret.type = BedShapeType::CIRCLE; + ret.shape.circ = c; + std::cout << "Circle" << std::endl; + } else { + std::cout << "Polygon" << std::endl; + ret.type = BedShapeType::IRREGULAR; + ret.shape.polygon = bed; + } + // Determine the bed shape by hand - return BOX; + return ret; } void applyResult( @@ -525,7 +667,10 @@ bool arrange(Model &model, coordf_t min_obj_distance, }); IndexedPackGroup result; - BoundingBox bbb(bed.points); + + if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); + + BoundingBox bbb(bed); auto binbb = Box({ static_cast(bbb.min.x), @@ -536,8 +681,8 @@ bool arrange(Model &model, coordf_t min_obj_distance, static_cast(bbb.max.y) }); - switch(bedhint) { - case BOX: { + switch(bedhint.type) { + case BedShapeType::BOX: { // Create the arranger for the box shaped bed AutoArranger arrange(binbb, min_obj_distance, progressind); @@ -547,10 +692,11 @@ bool arrange(Model &model, coordf_t min_obj_distance, result = arrange(shapes.begin(), shapes.end()); break; } - case CIRCLE: + case BedShapeType::CIRCLE: break; - case IRREGULAR: - case WHO_KNOWS: { + case BedShapeType::IRREGULAR: + case BedShapeType::WHO_KNOWS: { + using P = libnest2d::PolygonImpl; auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); diff --git a/xs/src/slic3r/AppController.cpp b/xs/src/slic3r/AppController.cpp index 58858f5fc4c..9394df3637c 100644 --- a/xs/src/slic3r/AppController.cpp +++ b/xs/src/slic3r/AppController.cpp @@ -288,6 +288,7 @@ const PrintConfig &PrintController::config() const return print_->config; } + void AppController::arrange_model() { auto ftr = std::async( @@ -324,10 +325,14 @@ void AppController::arrange_model() if(pind) pind->update(0, _(L("Arranging objects..."))); try { + arr::BedShapeHint hint; + // TODO: from Sasha from GUI + hint.type = arr::BedShapeType::WHO_KNOWS; + arr::arrange(*model_, min_obj_distance, bed, - arr::BOX, + hint, false, // create many piles not just one pile [pind, count](unsigned rem) { if(pind) From db8762a93cefaa7790198c6ef8d2781abcb4f5e8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 6 Aug 2018 11:30:10 +0200 Subject: [PATCH 06/63] bed shape detection works and circle shaped bed now supported with limited arrange quality. --- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 7 +- xs/src/libslic3r/ModelArrange.hpp | 178 ++++++++---------- 2 files changed, 86 insertions(+), 99 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 5d09a61fcb2..a3429bf4845 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -505,13 +505,18 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer& bin) { + return sl::isInside(bb, bin); } bool static inline wouldFit(const RawShape& chull, const _Circle& bin) { - return sl::isInside(chull, bin); + auto bb = sl::boundingBox(chull); + auto d = bin.center() - bb.center(); + auto chullcpy = chull; + sl::translate(chullcpy, d); + return sl::isInside(chullcpy, bin); } PackResult trypack(Item& item) { diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 5b7a2a32583..53b99b78176 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -329,6 +329,45 @@ class AutoArranger: public _ArrBase { } }; +using lnCircle = libnest2d::_Circle; + +template<> +class AutoArranger: public _ArrBase { +public: + + AutoArranger(const lnCircle& bin, Distance dist, + std::function progressind): + _ArrBase(bin, dist, progressind) { + + pconf_.object_function = [this, &bin] ( + Pile& pile, + const Item &item, + double pile_area, + double norm, + double /*penality*/) { + + auto result = objfunc(bin.center(), bin_area_, pile, + pile_area, item, norm, areacache_, rtree_); + double score = std::get<0>(result); + + // Circle fitting detection is very rough at the moment but + // we still need something that tells how badly the arrangement + // misses the print bed. + auto& fullbb = std::get<1>(result); + auto bbr = 0.5*PointLike::distance(fullbb.minCorner(), + fullbb.maxCorner()); + auto diff = bbr - bin.radius(); + + if(diff > 0) score += std::pow(diff, 2) / norm; + + + return score; + }; + + pck_.configure(pconf_); + } +}; + template<> class AutoArranger: public _ArrBase { public: @@ -348,15 +387,6 @@ class AutoArranger: public _ArrBase { pile_area, item, norm, areacache_, rtree_); double score = std::get<0>(result); - pile.emplace_back(item.transformedShape()); - auto chull = ShapeLike::convexHull(pile); - pile.pop_back(); - - // If it does not fit into the print bed we will beat it with a - // large penality. If we would not do this, there would be only one - // big pile that doesn't care whether it fits onto the print bed. - if(!Placer::wouldFit(chull, bin)) score += norm; - return score; }; @@ -451,89 +481,9 @@ class Circle { inline double radius() const { return radius_; } inline const Point& center() const { return center_; } - inline operator bool() { return std::isnan(radius_); } + inline operator bool() { return !std::isnan(radius_); } }; - -Circle circle(std::array P) { - - using Coord = Point::coord_type; - using std::pow; - using std::abs; - using std::round; - using std::nan; - - auto getX = [](const Point& p) { return p.x; }; - auto getY = [](const Point& p) { return p.y; }; - - auto distance = [](const Point& p1, const Point& p2) { - return abs(p1.distance_to(p2)); - }; - - static const auto E = 10.0/SCALING_FACTOR; - - auto x1 = getX(P[0]), y1 = getY(P[0]); - auto x2 = getX(P[1]), y2 = getY(P[1]); - auto x3 = getX(P[2]), y3 = getY(P[2]); - - - auto A_div = (x2 - x1); - auto B_div = (x3 - x2); - if(A_div == 0 || B_div == 0) return Circle(); - - auto A = (y2 - y1)/A_div; - auto B = (y2 - y3)/B_div; - auto C = (-pow(x1, 2) - pow(y1, 2) + pow(x2, 2) + pow(y2, 2))/(2*(x2 - x1)); - auto D = (pow(x2, 2) + pow(y1, 2) - pow(x3, 2) - pow(y3, 2))/(2*(x3 - x2)); - - auto cy = (C + D)/(A + B); - auto cx = B*cy - D; - - Point cc = {Coord(round(cx)), Coord(round(cy))}; - auto d = distance(cc, P[0]); - auto d2 = distance(cc, P[1]); - auto d3 = distance(cc, P[2]); - - auto e1 = abs(d - d2); - auto e2 = abs(d - d3); - - if(e1 > E || e2 > E) return Circle(); - - return { cc, d }; -} - -Circle isCircle(const Polyline& p) { - - using std::abs; - - auto& pp = p.points; - static const double E = 10/SCALING_FACTOR; - double radius = 0; - bool ret = true; - Circle c; - for(auto i = 0; i < pp.size() - 3 && ret; i += 3) { - c = circle({pp[i], pp[i+1], pp[i+2]}); - if(c || abs(radius - c.radius()) >= E) ret = false; - else radius = c.radius(); - } - -// auto rem = pp.size() % 3; - -// if(ret && rem > 0) { -// std::array remarr; - -// auto i = 0; -// for(i = 0; i < rem; i++) remarr[i] = *(pp.rbegin() - i); -// while(i < 3) remarr[i] = pp[i++]; -// c = circle(remarr); -// if(c || abs(radius - c.radius()) >= E) ret = false; -// } - - if(!ret) c = Circle(); - - return c; -} - enum class BedShapeType { BOX, CIRCLE, @@ -564,7 +514,9 @@ BedShapeHint bedShape(const Polyline& bed) { }; auto area = [&width, &height](const BoundingBox& box) { - return width(box) * height(box); + double w = width(box); + double h = height(box); + return w*h; }; auto poly_area = [](Polyline p) { @@ -574,18 +526,43 @@ BedShapeHint bedShape(const Polyline& bed) { return std::abs(pp.area()); }; + auto bb = bed.bounding_box(); - if(std::abs(area(bb) - poly_area(bed)) < E) { + + auto isCircle = [bb](const Polyline& polygon) { + auto center = bb.center(); + std::vector vertex_distances; + double avg_dist = 0; + for (auto pt: polygon.points) + { + double distance = center.distance_to(pt); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + + Circle ret(center, avg_dist); + for (auto el: vertex_distances) + { + if (abs(el - avg_dist) > 10 * SCALED_EPSILON) + ret = Circle(); + break; + } + + return ret; + }; + + auto parea = poly_area(bed); + + if( (1.0 - parea/area(bb)) < 1e-3 ) { ret.type = BedShapeType::BOX; ret.shape.box = bb; - std::cout << "BOX" << std::endl; } else if(auto c = isCircle(bed)) { ret.type = BedShapeType::CIRCLE; ret.shape.circ = c; - std::cout << "Circle" << std::endl; } else { - std::cout << "Polygon" << std::endl; ret.type = BedShapeType::IRREGULAR; ret.shape.polygon = bed; } @@ -692,8 +669,15 @@ bool arrange(Model &model, coordf_t min_obj_distance, result = arrange(shapes.begin(), shapes.end()); break; } - case BedShapeType::CIRCLE: + case BedShapeType::CIRCLE: { + + auto c = bedhint.shape.circ; + auto cc = lnCircle({c.center().x, c.center().y} , c.radius()); + + AutoArranger arrange(cc, min_obj_distance, progressind); + result = arrange(shapes.begin(), shapes.end()); break; + } case BedShapeType::IRREGULAR: case BedShapeType::WHO_KNOWS: { @@ -702,8 +686,6 @@ bool arrange(Model &model, coordf_t min_obj_distance, auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); P irrbed = ShapeLike::create(std::move(ctour)); -// std::cout << ShapeLike::toString(irrbed) << std::endl; - AutoArranger

arrange(irrbed, min_obj_distance, progressind); // Arrange and return the items with their respective indices within the From e1edb05bbbd95bf0f15d3540545d9bf1cf63356e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 6 Aug 2018 20:13:04 +0200 Subject: [PATCH 07/63] Better support for circular bed. --- xs/src/libnest2d/examples/main.cpp | 501 +----------------- .../libnest2d/libnest2d/geometry_traits.hpp | 2 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 110 +++- xs/src/libnest2d/tests/test.cpp | 37 ++ xs/src/libslic3r/ModelArrange.hpp | 52 +- 5 files changed, 172 insertions(+), 530 deletions(-) diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index d6b2ccc3485..02be465a888 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -50,492 +50,12 @@ void arrangeRectangles() { using namespace libnest2d; const int SCALE = 1000000; -// const int SCALE = 1; - std::vector rects = { - {80*SCALE, 80*SCALE}, - {60*SCALE, 90*SCALE}, - {70*SCALE, 30*SCALE}, - {80*SCALE, 60*SCALE}, - {60*SCALE, 60*SCALE}, - {60*SCALE, 40*SCALE}, - {40*SCALE, 40*SCALE}, - {10*SCALE, 10*SCALE}, - {10*SCALE, 10*SCALE}, - {10*SCALE, 10*SCALE}, - {10*SCALE, 10*SCALE}, - {10*SCALE, 10*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {20*SCALE, 20*SCALE} - }; - -// std::vector rects = { -// {20*SCALE, 10*SCALE}, -// {20*SCALE, 10*SCALE}, -// {20*SCALE, 20*SCALE}, -// }; -// std::vector input { -// {{0, 0}, {0, 20*SCALE}, {10*SCALE, 0}, {0, 0}} -// }; - - std::vector crasher = - { - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-18000000, -1000000}, - {-15000000, 22000000}, - {-11000000, 26000000}, - {11000000, 26000000}, - {15000000, 22000000}, - {18000000, -1000000}, - {18000000, -26000000}, - {-18000000, -26000000}, - {-18000000, -1000000}, - }, - }; - - std::vector proba = { - { - Rectangle(100, 2) - }, - { - Rectangle(100, 2) - }, - { - Rectangle(100, 2) - }, - { - Rectangle(10, 10) - }, + std::vector rects = { + {60*SCALE, 200*SCALE}, + {60*SCALE, 200*SCALE} }; - proba[0].rotate(Pi/3); - proba[1].rotate(Pi-Pi/3); - -// std::vector input(25, Rectangle(70*SCALE, 10*SCALE)); std::vector input; input.insert(input.end(), prusaParts().begin(), prusaParts().end()); // input.insert(input.end(), prusaExParts().begin(), prusaExParts().end()); @@ -544,7 +64,7 @@ void arrangeRectangles() { // input.insert(input.end(), proba.begin(), proba.end()); // input.insert(input.end(), crasher.begin(), crasher.end()); - Box bin(250*SCALE, 210*SCALE); +// Box bin(250*SCALE, 210*SCALE); // PolygonImpl bin = { // { // {25*SCALE, 0}, @@ -560,9 +80,11 @@ void arrangeRectangles() { // {} // }; + _Circle bin({0, 0}, 125*SCALE); + auto min_obj_distance = static_cast(0*SCALE); - using Placer = strategies::_NofitPolyPlacer; + using Placer = strategies::_NofitPolyPlacer; using Packer = Arranger; Packer arrange(bin, min_obj_distance); @@ -571,9 +93,9 @@ void arrangeRectangles() { pconf.alignment = Placer::Config::Alignment::CENTER; pconf.starting_point = Placer::Config::Alignment::CENTER; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; - pconf.accuracy = 0.5f; + pconf.accuracy = 1.0f; -// auto bincenter = ShapeLike::boundingBox(bin).center(); +// auto bincenter = ShapeLike::boundingBox(bin).center(); // pconf.object_function = [&bin, bincenter]( // Placer::Pile pile, const Item& item, // double /*area*/, double norm, double penality) { @@ -660,10 +182,7 @@ void arrangeRectangles() { // score = pl::distance(ibb.center(), bigbb.center()) / norm; // } -// // If it does not fit into the print bed we will beat it -// // with a large penality. If we would not do this, there would be only -// // one big pile that doesn't care whether it fits onto the print bed. -// if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score; +// if(!Placer::wouldFit(fullbb, bin)) score += norm; // return score; // }; diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 058c47cd436..83064313005 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -109,7 +109,7 @@ class _Circle { inline void radius(double r) { radius_ = r; } inline double area() const BP2D_NOEXCEPT { - return 2.0*Pi*radius_; + return 2.0*Pi*radius_*radius_; } }; diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index a3429bf4845..c506a5d5a43 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -1,16 +1,19 @@ #ifndef NOFITPOLY_HPP #define NOFITPOLY_HPP +#include +#include + #ifndef NDEBUG #include #endif #include "placer_boilerplate.hpp" #include "../geometry_traits_nfp.hpp" #include "libnest2d/optimizer.hpp" -#include #include "tools/svgtools.hpp" + namespace libnest2d { namespace strategies { template @@ -161,12 +164,11 @@ template class EdgeCache { } size_t stride(const size_t N) const { - using std::ceil; using std::round; using std::pow; return static_cast( - std::round(N/std::pow(N, std::pow(accuracy_, 1.0/3.0))) + round(N/pow(N, pow(accuracy_, 1.0/3.0))) ); } @@ -177,6 +179,7 @@ template class EdgeCache { const auto S = stride(N); contour_.corners.reserve(N / S + 1); + contour_.corners.emplace_back(0.0); auto N_1 = N-1; contour_.corners.emplace_back(0.0); for(size_t i = 0; i < N_1; i += S) { @@ -190,8 +193,8 @@ template class EdgeCache { if(!hc.corners.empty()) return; const auto N = hc.distances.size(); - const auto S = stride(N); auto N_1 = N-1; + const auto S = stride(N); hc.corners.reserve(N / S + 1); hc.corners.emplace_back(0.0); for(size_t i = 0; i < N_1; i += S) { @@ -339,7 +342,7 @@ Nfp::Shapes nfp( const Container& polygons, Nfp::Shapes nfps; - //int pi = 0; +// int pi = 0; for(Item& sh : polygons) { auto subnfp_r = Nfp::noFitPolygon( sh.transformedShape(), trsh.transformedShape()); @@ -441,6 +444,63 @@ Nfp::Shapes nfp( const Container& polygons, // return nfps; } +template +_Circle> minimizeCircle(const RawShape& sh) { + using sl = ShapeLike; using pl = PointLike; + using Point = TPoint; + using Coord = TCoord; + + auto bb = sl::boundingBox(sh); + auto capprx = bb.center(); + auto rapprx = pl::distance(bb.minCorner(), bb.maxCorner()); + + auto& ctr = sl::getContour(sh); + + opt::StopCriteria stopcr; + stopcr.max_iterations = 100; + stopcr.relative_score_difference = 1e-3; + opt::TOptimizer solver(stopcr); + + std::vector dists(ctr.size(), 0); + + auto result = solver.optimize_min( + [capprx, rapprx, &ctr, &dists](double xf, double yf) { + auto xt = Coord( std::round(getX(capprx) + rapprx*xf) ); + auto yt = Coord( std::round(getY(capprx) + rapprx*yf) ); + + Point centr(xt, yt); + + unsigned i = 0; + for(auto v : ctr) { + dists[i++] = pl::distance(v, centr); + } + + auto mit = std::max_element(dists.begin(), dists.end()); + + assert(mit != dists.end()); + + return *mit; + }, + opt::initvals(0.0, 0.0), + opt::bound(-1.0, 1.0), opt::bound(-1.0, 1.0) + ); + + double oxf = std::get<0>(result.optimum); + double oyf = std::get<1>(result.optimum); + auto xt = Coord( std::round(getX(capprx) + rapprx*oxf) ); + auto yt = Coord( std::round(getY(capprx) + rapprx*oyf) ); + + Point cc(xt, yt); + auto r = result.score; + + return {cc, r}; +} + +template +_Circle> boundingCircle(const RawShape& sh) { + return minimizeCircle(sh); +} + template>> class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, RawShape, TBin, NfpPConfig> { @@ -512,11 +572,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer& bin) { - auto bb = sl::boundingBox(chull); - auto d = bin.center() - bb.center(); - auto chullcpy = chull; - sl::translate(chullcpy, d); - return sl::isInside(chullcpy, bin); + return boundingCircle(chull).radius() < bin.radius(); } PackResult trypack(Item& item) { @@ -574,8 +630,9 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer pile; @@ -595,7 +652,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer& /*pile*/, const Item& item, - double occupied_area, + double occupied_area, double norm, double /*penality*/) { @@ -751,14 +808,37 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer> cbin) { + if(items_.empty()) return; + Nfp::Shapes m; m.reserve(items_.size()); + for(Item& item : items_) m.emplace_back(item.transformedShape()); + + auto c = boundingCircle(sl::convexHull(m)); + + auto d = cbin.center() - c.center(); + for(Item& item : items_) item.translate(d); + } + inline void finalAlign(Box bbin) { + Nfp::Shapes m; + m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); auto&& bb = sl::boundingBox(m); Vertex ci, cb; - auto bbin = sl::boundingBox(bin_); switch(config_.alignment) { case Config::Alignment::CENTER: { @@ -790,12 +870,8 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer c = boundingCircle(p); + + ASSERT_EQ(c.center().X, 0); + ASSERT_EQ(c.center().Y, 0); + ASSERT_DOUBLE_EQ(c.radius(), 10); + + ShapeLike::translate(p, PointImpl{10, 10}); + c = boundingCircle(p); + + ASSERT_EQ(c.center().X, 10); + ASSERT_EQ(c.center().Y, 10); + ASSERT_DOUBLE_EQ(c.radius(), 10); + + auto parts = prusaParts(); + + int i = 0; + for(auto& part : parts) { + c = boundingCircle(part.transformedShape()); + if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl; + + else for(auto v : ShapeLike::getContour(part.transformedShape()) ) { + auto d = PointLike::distance(v, c.center()); + if(d > c.radius() ) { + auto e = std::abs( 1.0 - d/c.radius()); + ASSERT_LE(e, 1e-3); + } + } + i++; + } + +} + TEST(GeometryAlgorithms, Distance) { using namespace libnest2d; diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 53b99b78176..57a16920591 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -102,9 +102,9 @@ using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; std::tuple objfunc(const PointImpl& bincenter, - double /*bin_area*/, + double bin_area, ShapeLike::Shapes& pile, // The currently arranged pile - double /*pile_area*/, + double pile_area, const Item &item, double norm, // A norming factor for physical dimensions std::vector& areacache, // pile item areas will be cached @@ -115,12 +115,16 @@ objfunc(const PointImpl& bincenter, using pl = PointLike; using sl = ShapeLike; - static const double BIG_ITEM_TRESHOLD = 0.2; + static const double BIG_ITEM_TRESHOLD = 0.04; static const double ROUNDNESS_RATIO = 0.5; static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; // We will treat big items (compared to the print bed) differently - auto normarea = [norm](double area) { return std::sqrt(area)/norm; }; + + auto isBig = [&areacache, bin_area](double a) { + bool t = areacache.empty() ? true : a > 0.5*areacache.front(); + return a/bin_area > BIG_ITEM_TRESHOLD || t; + }; // If a new bin has been created: if(pile.size() < areacache.size()) { @@ -133,7 +137,7 @@ objfunc(const PointImpl& bincenter, for(auto& p : pile) { if(idx == areacache.size()) { areacache.emplace_back(sl::area(p)); - if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) + if(isBig(areacache[idx])) spatindex.insert({sl::boundingBox(p), idx}); } @@ -157,14 +161,10 @@ objfunc(const PointImpl& bincenter, boost::geometry::convert(boostbb, bigbb); } - // The size indicator of the candidate item. This is not the area, - // but almost... - double item_normarea = normarea(item.area()); - // Will hold the resulting score double score = 0; - if(item_normarea > BIG_ITEM_TRESHOLD) { + if(isBig(item.area())) { // This branch is for the bigger items.. // Here we will use the closest point of the item bounding box to // the already arranged pile. So not the bb center nor the a choosen @@ -223,10 +223,9 @@ objfunc(const PointImpl& bincenter, // The final mix of the score is the balance between the distance // from the full pile center, the pack density and the // alignment with the neigbours - auto C = 0.33; - score = C * dist + C * density + C * alignment_score; + score = 0.4 * dist + 0.4 * density + 0.2 * alignment_score; - } else if( item_normarea < BIG_ITEM_TRESHOLD && spatindex.empty()) { + } else if( !isBig(item.area()) && spatindex.empty()) { // If there are no big items, only small, we should consider the // density here as well to not get silly results auto bindist = pl::distance(ibb.center(), bincenter) / norm; @@ -349,17 +348,26 @@ class AutoArranger: public _ArrBase { auto result = objfunc(bin.center(), bin_area_, pile, pile_area, item, norm, areacache_, rtree_); double score = std::get<0>(result); - - // Circle fitting detection is very rough at the moment but - // we still need something that tells how badly the arrangement - // misses the print bed. auto& fullbb = std::get<1>(result); - auto bbr = 0.5*PointLike::distance(fullbb.minCorner(), - fullbb.maxCorner()); - auto diff = bbr - bin.radius(); - if(diff > 0) score += std::pow(diff, 2) / norm; + auto d = PointLike::distance(fullbb.minCorner(), + fullbb.maxCorner()); + auto diff = d - 2*bin.radius(); + + if(diff > 0) { + if( item.area() > 0.01*bin_area_ && item.vertexCount() < 20) { + pile.emplace_back(item.transformedShape()); + auto chull = ShapeLike::convexHull(pile); + pile.pop_back(); + auto C = strategies::boundingCircle(chull); + auto rdiff = C.radius() - bin.radius(); + + if(rdiff > 0) { + score += std::pow(rdiff, 3) / norm; + } + } + } return score; }; @@ -695,6 +703,8 @@ bool arrange(Model &model, coordf_t min_obj_distance, } }; + if(result.empty()) return false; + if(first_bin_only) { applyResult(result.front(), 0, shapemap); } else { From 224c0e74eab48b9bc117d1af60ddcd7f1362e837 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 7 Aug 2018 10:57:22 +0200 Subject: [PATCH 08/63] Precision raised and big item classification improved --- xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp | 2 +- xs/src/libslic3r/ModelArrange.hpp | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index c506a5d5a43..110c05f0ced 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -706,7 +706,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer solver(stopcr); Optimum optimum(0, 0); diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 57a16920591..118bff17f29 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -115,15 +115,18 @@ objfunc(const PointImpl& bincenter, using pl = PointLike; using sl = ShapeLike; - static const double BIG_ITEM_TRESHOLD = 0.04; + static const double BIG_ITEM_TRESHOLD = 0.02; static const double ROUNDNESS_RATIO = 0.5; static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; // We will treat big items (compared to the print bed) differently auto isBig = [&areacache, bin_area](double a) { - bool t = areacache.empty() ? true : a > 0.5*areacache.front(); - return a/bin_area > BIG_ITEM_TRESHOLD || t; + double farea = areacache.empty() ? 0 : areacache.front(); + bool fbig = farea / bin_area > BIG_ITEM_TRESHOLD; + bool abig = a/bin_area > BIG_ITEM_TRESHOLD; + bool rbig = fbig && a > 0.5*farea; + return abig || rbig; }; // If a new bin has been created: @@ -258,7 +261,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.6f; + pcfg.accuracy = 1.0f; } template @@ -355,7 +358,7 @@ class AutoArranger: public _ArrBase { auto diff = d - 2*bin.radius(); if(diff > 0) { - if( item.area() > 0.01*bin_area_ && item.vertexCount() < 20) { + if( item.area() > 0.01*bin_area_ && item.vertexCount() < 30) { pile.emplace_back(item.transformedShape()); auto chull = ShapeLike::convexHull(pile); pile.pop_back(); From 08fb677583518382f3ff4a42fadc431d0a9870f0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 7 Aug 2018 14:23:57 +0200 Subject: [PATCH 09/63] Fine tuning of precision. --- xs/src/libslic3r/ModelArrange.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 118bff17f29..f4ce0daca75 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -226,7 +226,7 @@ objfunc(const PointImpl& bincenter, // The final mix of the score is the balance between the distance // from the full pile center, the pack density and the // alignment with the neigbours - score = 0.4 * dist + 0.4 * density + 0.2 * alignment_score; + score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; } else if( !isBig(item.area()) && spatindex.empty()) { // If there are no big items, only small, we should consider the @@ -261,7 +261,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 1.0f; + pcfg.accuracy = 0.6f; } template From 20b7aad6d1cbb4b282586a3844fb66843c81f10d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 7 Aug 2018 19:48:00 +0200 Subject: [PATCH 10/63] Bug fixes for the neighborhood detection --- xs/src/libnest2d/README.md | 18 ++- xs/src/libnest2d/examples/main.cpp | 92 ------------- .../libnest2d/libnest2d/geometry_traits.hpp | 6 +- xs/src/libnest2d/libnest2d/libnest2d.hpp | 36 +++-- .../libnest2d/placers/bottomleftplacer.hpp | 7 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 65 +++++---- .../libnest2d/placers/placer_boilerplate.hpp | 7 +- .../libnest2d/selections/djd_heuristic.hpp | 32 ++--- .../libnest2d/libnest2d/selections/filler.hpp | 2 +- .../libnest2d/selections/firstfit.hpp | 9 +- xs/src/libnest2d/tests/test.cpp | 4 +- xs/src/libslic3r/ModelArrange.hpp | 126 ++++++++++++------ 12 files changed, 194 insertions(+), 210 deletions(-) diff --git a/xs/src/libnest2d/README.md b/xs/src/libnest2d/README.md index 3508801a83d..61a7ac7d059 100644 --- a/xs/src/libnest2d/README.md +++ b/xs/src/libnest2d/README.md @@ -9,18 +9,28 @@ with templated geometry types. These geometries can have custom or already existing implementation to avoid copying or having unnecessary dependencies. A default backend is provided if the user of the library just wants to use it -out of the box without additional integration. The default backend is reasonably +out of the box without additional integration. This backend is reasonably fast and robust, being built on top of boost geometry and the [polyclipping](http://www.angusj.com/delphi/clipper.php) library. Usage of -this default backend implies the dependency on these packages as well as the -compilation of the backend itself (The default backend is not yet header only). +this default backend implies the dependency on these packages but its header +only as well. This software is currently under construction and lacks a throughout documentation and some essential algorithms as well. At this stage it works well for rectangles and convex closed polygons without considering holes and concavities. -Holes and non-convex polygons will be usable in the near future as well. +Holes and non-convex polygons will be usable in the near future as well. The +no fit polygon based placer module combined with the first fit selection +strategy is now used in the [Slic3r](https://github.com/prusa3d/Slic3r) +application's arrangement feature. It uses local optimization techniques to find +the best placement of each new item based on some features of the arrangement. + +In the near future I would like to use machine learning to evaluate the +placements and (or) the order if items in which they are placed and see what +results can be obtained. This is a different approach than that of SVGnest which +uses genetic algorithms to find better and better selection orders. Maybe the +two approaches can be combined as well. # References - [SVGNest](https://github.com/Jack000/SVGnest) diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 02be465a888..11f8f50cf7a 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -95,98 +95,6 @@ void arrangeRectangles() { pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; pconf.accuracy = 1.0f; -// auto bincenter = ShapeLike::boundingBox(bin).center(); -// pconf.object_function = [&bin, bincenter]( -// Placer::Pile pile, const Item& item, -// double /*area*/, double norm, double penality) { - -// using pl = PointLike; - -// static const double BIG_ITEM_TRESHOLD = 0.2; -// static const double GRAVITY_RATIO = 0.5; -// static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO; - -// // We will treat big items (compared to the print bed) differently -// NfpPlacer::Pile bigs; -// bigs.reserve(pile.size()); -// for(auto& p : pile) { -// auto pbb = ShapeLike::boundingBox(p); -// auto na = std::sqrt(pbb.width()*pbb.height())/norm; -// if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); -// } - -// // Candidate item bounding box -// auto ibb = item.boundingBox(); - -// // Calculate the full bounding box of the pile with the candidate item -// pile.emplace_back(item.transformedShape()); -// auto fullbb = ShapeLike::boundingBox(pile); -// pile.pop_back(); - -// // The bounding box of the big items (they will accumulate in the center -// // of the pile -// auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); - -// // The size indicator of the candidate item. This is not the area, -// // but almost... -// auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; - -// // Will hold the resulting score -// double score = 0; - -// if(itemnormarea > BIG_ITEM_TRESHOLD) { -// // This branch is for the bigger items.. -// // Here we will use the closest point of the item bounding box to -// // the already arranged pile. So not the bb center nor the a choosen -// // corner but whichever is the closest to the center. This will -// // prevent unwanted strange arrangements. - -// auto minc = ibb.minCorner(); // bottom left corner -// auto maxc = ibb.maxCorner(); // top right corner - -// // top left and bottom right corners -// auto top_left = PointImpl{getX(minc), getY(maxc)}; -// auto bottom_right = PointImpl{getX(maxc), getY(minc)}; - -// auto cc = fullbb.center(); // The gravity center - -// // Now the distnce of the gravity center will be calculated to the -// // five anchor points and the smallest will be chosen. -// std::array dists; -// dists[0] = pl::distance(minc, cc); -// dists[1] = pl::distance(maxc, cc); -// dists[2] = pl::distance(ibb.center(), cc); -// dists[3] = pl::distance(top_left, cc); -// dists[4] = pl::distance(bottom_right, cc); - -// auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; - -// // Density is the pack density: how big is the arranged pile -// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; - -// // The score is a weighted sum of the distance from pile center -// // and the pile size -// score = GRAVITY_RATIO * dist + DENSITY_RATIO * density; - -// } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { -// // If there are no big items, only small, we should consider the -// // density here as well to not get silly results -// auto bindist = pl::distance(ibb.center(), bincenter) / norm; -// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; -// score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density; -// } else { -// // Here there are the small items that should be placed around the -// // already processed bigger items. -// // No need to play around with the anchor points, the center will be -// // just fine for small items -// score = pl::distance(ibb.center(), bigbb.center()) / norm; -// } - -// if(!Placer::wouldFit(fullbb, bin)) score += norm; - -// return score; -// }; - Packer::SelectionConfig sconf; // sconf.allow_parallel = false; // sconf.force_parallel = false; diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 83064313005..1c0d44c9fde 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -313,9 +313,9 @@ inline RawPoint _Box::center() const BP2D_NOEXCEPT { using Coord = TCoord; - RawPoint ret = { - static_cast( std::round((getX(minc) + getX(maxc))/2.0) ), - static_cast( std::round((getY(minc) + getY(maxc))/2.0) ) + RawPoint ret = { // No rounding here, we dont know if these are int coords + static_cast( (getX(minc) + getX(maxc))/2.0 ), + static_cast( (getY(minc) + getY(maxc))/2.0 ) }; return ret; diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 7f23de35836..eadd1e110bf 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -541,21 +541,20 @@ class PlacementStrategyLike { inline void configure(const Config& config) { impl_.configure(config); } /** - * @brief A method that tries to pack an item and returns an object - * describing the pack result. + * Try to pack an item with a result object that contains the packing + * information for later accepting it. * - * The result can be casted to bool and used as an argument to the accept - * method to accept a succesfully packed item. This way the next packing - * will consider the accepted item as well. The PackResult should carry the - * transformation info so that if the tried item is later modified or tried - * multiple times, the result object should set it to the originally - * determied position. An implementation can be found in the - * strategies::PlacerBoilerplate::PackResult class. - * - * @param item Ithe item to be packed. - * @return The PackResult object that can be implicitly casted to bool. + * \param item_store A container of items */ - inline PackResult trypack(Item& item) { return impl_.trypack(item); } + template + inline PackResult trypack(Container& item_store, + typename Container::iterator from, + unsigned count = 1) { + using V = typename Container::value_type; + static_assert(std::is_convertible::value, + "Invalid Item container!"); + return impl_.trypack(item_store, from, count); + } /** * @brief A method to accept a previously tried item. @@ -578,7 +577,16 @@ class PlacementStrategyLike { * @return Returns true if the item was packed or false if it could not be * packed. */ - inline bool pack(Item& item) { return impl_.pack(item); } + template + inline bool pack(Container& item_store, + typename Container::iterator from, + unsigned count = 1) + { + using V = typename Container::value_type; + static_assert(std::is_convertible::value, + "Invalid Item container!"); + return impl_.pack(item_store, from, count); + } /// Unpack the last element (remove it from the list of packed items). inline void unpackLast() { impl_.unpackLast(); } diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp index 775e44e0906..71573e34ddb 100644 --- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp @@ -27,9 +27,14 @@ class _BottomLeftPlacer: public PlacerBoilerplate< explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {} - PackResult trypack(Item& item) { + template + PackResult trypack(Store& /*s*/, typename Store::iterator from, + unsigned /*count*/ = 1) + { + Item& item = *from; auto r = _trypack(item); if(!r && Base::config_.allow_rotations) { + item.rotate(Degrees(90)); r =_trypack(item); } diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 110c05f0ced..d74fe2b1d35 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -19,6 +19,8 @@ namespace libnest2d { namespace strategies { template struct NfpPConfig { + using ItemGroup = std::vector>>; + enum class Alignment { CENTER, BOTTOM_LEFT, @@ -57,8 +59,8 @@ struct NfpPConfig { * \param item The second parameter is the candidate item. * * \param occupied_area The third parameter is the sum of areas of the - * items in the first parameter so you don't have to iterate through them - * if you only need their area. + * items in the first parameter (no candidate item there) so you don't have + * to iterate through them if you only need their accumulated area. * * \param norm A norming factor for physical dimensions. E.g. if your score * is the distance between the item and the bin center, you should divide @@ -66,21 +68,21 @@ struct NfpPConfig { * divide it with the square of the norming factor. Imagine it as a unit of * distance. * - * \param penality The fifth parameter is the amount of minimum penality if - * the arranged pile would't fit into the bin. You can use the wouldFit() - * function to check this. Note that the pile can be outside the bin's - * boundaries while the placement algorithm is running. Your job is only to - * check if the pile could be translated into a position in the bin where - * all the items would be inside. For a box shaped bin you can use the - * pile's bounding box to check whether it's width and height is small - * enough. If the pile would not fit, you have to make sure that the - * resulting score will be higher then the penality value. A good solution - * would be to set score = 2*penality-score in case the pile wouldn't fit - * into the bin. + * \param remaining A container with the remaining items waiting to be + * placed. You can use some features about the remaining items to alter to + * score of the current placement. If you know that you have to leave place + * for other items as well, that might influence your decision about where + * the current candidate should be placed. E.g. imagine three big circles + * which you want to place into a box: you might place them in a triangle + * shape which has the maximum pack density. But if there is a 4th big + * circle than you won't be able to pack it. If you knew apriori that + * there four circles are to be placed, you would have placed the first 3 + * into an L shape. This parameter can be used to make these kind of + * decisions (for you or a more intelligent AI). * */ std::function&, const _Item&, - double, double, double)> + double, double, const ItemGroup&)> object_function; /** @@ -450,11 +452,13 @@ _Circle> minimizeCircle(const RawShape& sh) { using Point = TPoint; using Coord = TCoord; + auto& ctr = sl::getContour(sh); + if(ctr.empty()) return {{0, 0}, 0}; + auto bb = sl::boundingBox(sh); auto capprx = bb.center(); auto rapprx = pl::distance(bb.minCorner(), bb.maxCorner()); - auto& ctr = sl::getContour(sh); opt::StopCriteria stopcr; stopcr.max_iterations = 100; @@ -513,7 +517,6 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer>; const double norm_; - const double penality_; using MaxNfpLevel = Nfp::MaxNfpLevel; using sl = ShapeLike; @@ -524,8 +527,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer(bin))), - penality_(1e6*norm_) {} + norm_(std::sqrt(sl::area(bin))) {} _NofitPolyPlacer(const _NofitPolyPlacer&) = default; _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default; @@ -575,7 +577,15 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer + PackResult trypack(Container& items, + typename Container::iterator from, + unsigned /*count*/ = 1) + { + return trypack(*from, {std::next(from), items.end()}); + } + + PackResult trypack(Item& item, ItemGroup remaining) { PackResult ret; @@ -586,7 +596,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer::max(); auto initial_tr = item.translation(); auto initial_rot = item.rotation(); @@ -630,9 +640,8 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer pile; @@ -654,7 +663,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer solver(stopcr); Optimum optimum(0, 0); - double best_score = penality_; + double best_score = std::numeric_limits::max(); // Local optimization with the four polygon corners as // starting points @@ -821,7 +830,6 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer> cbin) { if(items_.empty()) return; - Nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); @@ -833,6 +841,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp index 9d2cb626bea..f31a9343c12 100644 --- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp @@ -56,8 +56,11 @@ class PlacerBoilerplate { config_ = config; } - bool pack(Item& item) { - auto&& r = static_cast(this)->trypack(item); + template + bool pack(Container& items, + typename Container::iterator from, + unsigned count = 1) { + auto&& r = static_cast(this)->trypack(items, from, count); if(r) { items_.push_back(*(r.item_ptr_)); farea_valid_ = false; diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index e3ad97c1005..34d6d05c5c2 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -230,7 +230,7 @@ class _DJDHeuristic: public SelectionBoilerplate { while(it != not_packed.end() && !ret && free_area - (item_area = it->get().area()) <= waste) { - if(item_area <= free_area && placer.pack(*it) ) { + if(item_area <= free_area && placer.pack(not_packed, it) ) { free_area -= item_area; filled_area = bin_area - free_area; ret = true; @@ -278,7 +278,7 @@ class _DJDHeuristic: public SelectionBoilerplate { if(item_area + smallestPiece(it, not_packed)->get().area() > free_area ) { it++; continue; } - auto pr = placer.trypack(*it); + auto pr = placer.trypack(not_packed, it); // First would fit it2 = not_packed.begin(); @@ -294,14 +294,14 @@ class _DJDHeuristic: public SelectionBoilerplate { } placer.accept(pr); - auto pr2 = placer.trypack(*it2); + auto pr2 = placer.trypack(not_packed, it2); if(!pr2) { placer.unpackLast(); // remove first if(try_reverse) { - pr2 = placer.trypack(*it2); + pr2 = placer.trypack(not_packed, it2); if(pr2) { placer.accept(pr2); - auto pr12 = placer.trypack(*it); + auto pr12 = placer.trypack(not_packed, it); if(pr12) { placer.accept(pr12); ret = true; @@ -394,7 +394,7 @@ class _DJDHeuristic: public SelectionBoilerplate { it++; continue; } - auto pr = placer.trypack(*it); + auto pr = placer.trypack(not_packed, it); // Check for free area and try to pack the 1st item... if(!pr) { it++; continue; } @@ -420,15 +420,15 @@ class _DJDHeuristic: public SelectionBoilerplate { bool can_pack2 = false; placer.accept(pr); - auto pr2 = placer.trypack(*it2); + auto pr2 = placer.trypack(not_packed, it2); auto pr12 = pr; if(!pr2) { placer.unpackLast(); // remove first if(try_reverse) { - pr2 = placer.trypack(*it2); + pr2 = placer.trypack(not_packed, it2); if(pr2) { placer.accept(pr2); - pr12 = placer.trypack(*it); + pr12 = placer.trypack(not_packed, it); if(pr12) can_pack2 = true; placer.unpackLast(); } @@ -463,7 +463,7 @@ class _DJDHeuristic: public SelectionBoilerplate { if(a3_sum > free_area) { it3++; continue; } placer.accept(pr12); placer.accept(pr2); - bool can_pack3 = placer.pack(*it3); + bool can_pack3 = placer.pack(not_packed, it3); if(!can_pack3) { placer.unpackLast(); @@ -473,16 +473,16 @@ class _DJDHeuristic: public SelectionBoilerplate { if(!can_pack3 && try_reverse) { std::array indices = {0, 1, 2}; - std::array - candidates = {*it, *it2, *it3}; + std::array + candidates = {it, it2, it3}; - auto tryPack = [&placer, &candidates]( + auto tryPack = [&placer, &candidates, ¬_packed]( const decltype(indices)& idx) { std::array packed = {false}; for(auto id : idx) packed.at(id) = - placer.pack(candidates[id]); + placer.pack(not_packed, candidates[id]); bool check = std::all_of(packed.begin(), @@ -536,7 +536,7 @@ class _DJDHeuristic: public SelectionBoilerplate { { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); p.configure(pconfig); - if(!p.pack(*it)) { + if(!p.pack(store_, it)) { it = store_.erase(it); } else it++; } @@ -601,7 +601,7 @@ class _DJDHeuristic: public SelectionBoilerplate { while(it != not_packed.end() && filled_area < INITIAL_FILL_AREA) { - if(placer.pack(*it)) { + if(placer.pack(not_packed, it)) { filled_area += it->get().area(); free_area = bin_area - filled_area; it = not_packed.erase(it); diff --git a/xs/src/libnest2d/libnest2d/selections/filler.hpp b/xs/src/libnest2d/libnest2d/selections/filler.hpp index d0018dc73de..ca1281fe6f3 100644 --- a/xs/src/libnest2d/libnest2d/selections/filler.hpp +++ b/xs/src/libnest2d/libnest2d/selections/filler.hpp @@ -65,7 +65,7 @@ class _FillerSelection: public SelectionBoilerplate { auto it = store_.begin(); while(it != store_.end()) { - if(!placer.pack(*it)) { + if(!placer.pack(store_, it)) { if(packed_bins_.back().empty()) ++it; // makeProgress(placer); placer.clearItems(); diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 665b9da9f22..93ca02b1e65 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -40,6 +40,7 @@ class _FirstFitSelection: public SelectionBoilerplate { packed_bins_.clear(); std::vector placers; + placers.reserve(last-first); std::copy(first, last, std::back_inserter(store_)); @@ -60,18 +61,19 @@ class _FirstFitSelection: public SelectionBoilerplate { { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); p.configure(pconfig); - if(!p.pack(*it)) { + if(!p.pack(store_, it)) { it = store_.erase(it); } else it++; } } - for(auto& item : store_ ) { + auto it = store_.begin(); + while(it != store_.end()) { bool was_packed = false; while(!was_packed) { for(size_t j = 0; j < placers.size() && !was_packed; j++) { - if((was_packed = placers[j].pack(item))) + if((was_packed = placers[j].pack(store_, it))) makeProgress(placers[j], j); } @@ -81,6 +83,7 @@ class _FirstFitSelection: public SelectionBoilerplate { packed_bins_.emplace_back(); } } + ++it; } } diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index 1e030c0566c..79832b6830f 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -471,8 +471,8 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { auto next = it; int i = 0; while(it != input.end() && ++next != input.end()) { - placer.pack(*it); - placer.pack(*next); + placer.pack(input, it); + placer.pack(input, next); auto result = placer.getItems(); bool valid = true; diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index f4ce0daca75..952a9e3a641 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -99,6 +99,7 @@ namespace bgi = boost::geometry::index; using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; +using ItemGroup = std::vector>; std::tuple objfunc(const PointImpl& bincenter, @@ -109,24 +110,21 @@ objfunc(const PointImpl& bincenter, double norm, // A norming factor for physical dimensions std::vector& areacache, // pile item areas will be cached // a spatial index to quickly get neighbors of the candidate item - SpatIndex& spatindex + SpatIndex& spatindex, + const ItemGroup& remaining ) { using pl = PointLike; using sl = ShapeLike; + using Coord = TCoord; static const double BIG_ITEM_TRESHOLD = 0.02; static const double ROUNDNESS_RATIO = 0.5; static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; // We will treat big items (compared to the print bed) differently - auto isBig = [&areacache, bin_area](double a) { - double farea = areacache.empty() ? 0 : areacache.front(); - bool fbig = farea / bin_area > BIG_ITEM_TRESHOLD; - bool abig = a/bin_area > BIG_ITEM_TRESHOLD; - bool rbig = fbig && a > 0.5*farea; - return abig || rbig; + return a/bin_area > BIG_ITEM_TRESHOLD ; }; // If a new bin has been created: @@ -195,39 +193,74 @@ objfunc(const PointImpl& bincenter, auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; // Density is the pack density: how big is the arranged pile - auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; - - // Prepare a variable for the alignment score. - // This will indicate: how well is the candidate item aligned with - // its neighbors. We will check the aligment with all neighbors and - // return the score for the best alignment. So it is enough for the - // candidate to be aligned with only one item. - auto alignment_score = std::numeric_limits::max(); - - auto& trsh = item.transformedShape(); - - auto querybb = item.boundingBox(); - - // Query the spatial index for the neigbours - std::vector result; - spatindex.query(bgi::intersects(querybb), std::back_inserter(result)); + double density = 0; + + if(remaining.empty()) { + pile.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(pile); + pile.pop_back(); + strategies::EdgeCache ec(chull); + + double circ = ec.circumference() / norm; + double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; + score = 0.5*circ + 0.5*bcirc; + + } else { + // Prepare a variable for the alignment score. + // This will indicate: how well is the candidate item aligned with + // its neighbors. We will check the aligment with all neighbors and + // return the score for the best alignment. So it is enough for the + // candidate to be aligned with only one item. + auto alignment_score = std::numeric_limits::max(); + + density = (fullbb.width()*fullbb.height()) / (norm*norm); + auto& trsh = item.transformedShape(); + auto querybb = item.boundingBox(); + auto wp = querybb.width()*0.2; + auto hp = querybb.height()*0.2; + auto pad = PointImpl( Coord(wp), Coord(hp)); + querybb = Box({ querybb.minCorner() - pad, + querybb.maxCorner() + pad + }); + + // Query the spatial index for the neigbours + std::vector result; + result.reserve(spatindex.size()); + spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + +// if(result.empty()) { +// std::cout << "Error while arranging!" << std::endl; +// std::cout << spatindex.size() << " " << pile.size() << std::endl; + +// auto ib = spatindex.bounds(); +// Box ibb; +// boost::geometry::convert(ib, ibb); +// std::cout << "Inside: " << (sl::isInside(querybb, ibb) || +// boost::geometry::intersects(querybb, ibb)) << std::endl; +// } + + for(auto& e : result) { // now get the score for the best alignment + auto idx = e.second; + auto& p = pile[idx]; + auto parea = areacache[idx]; + auto bb = sl::boundingBox(sl::Shapes{p, trsh}); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; + + if(ascore < alignment_score) alignment_score = ascore; + } - for(auto& e : result) { // now get the score for the best alignment - auto idx = e.second; - auto& p = pile[idx]; - auto parea = areacache[idx]; - auto bb = sl::boundingBox(sl::Shapes{p, trsh}); - auto bbarea = bb.area(); - auto ascore = 1.0 - (item.area() + parea)/bbarea; + // The final mix of the score is the balance between the distance + // from the full pile center, the pack density and the + // alignment with the neigbours + if(result.empty()) + score = 0.5 * dist + 0.5 * density; + else + score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; - if(ascore < alignment_score) alignment_score = ascore; } - // The final mix of the score is the balance between the distance - // from the full pile center, the pack density and the - // alignment with the neigbours - score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; - } else if( !isBig(item.area()) && spatindex.empty()) { // If there are no big items, only small, we should consider the // density here as well to not get silly results @@ -312,10 +345,12 @@ class AutoArranger: public _ArrBase { const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_, rtree_); + pile_area, item, norm, areacache_, + rtree_, + rem); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); @@ -346,10 +381,11 @@ class AutoArranger: public _ArrBase { const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_, rtree_); + pile_area, item, norm, areacache_, + rtree_, rem); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); @@ -391,11 +427,12 @@ class AutoArranger: public _ArrBase { const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto binbb = ShapeLike::boundingBox(bin); auto result = objfunc(binbb.center(), bin_area_, pile, - pile_area, item, norm, areacache_, rtree_); + pile_area, item, norm, areacache_, + rtree_, rem); double score = std::get<0>(result); return score; @@ -417,10 +454,11 @@ class AutoArranger: public _ArrBase { const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto result = objfunc({0, 0}, 0, pile, pile_area, - item, norm, areacache_, rtree_); + item, norm, areacache_, + rtree_, rem); return std::get<0>(result); }; From ad92aa74865fcaf522f936b5962988753b7bdac3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 8 Aug 2018 12:51:17 +0200 Subject: [PATCH 11/63] Solution for stupid arrangement of rotated items and some fine tuning. --- xs/src/libslic3r/ModelArrange.hpp | 44 ++++++++----------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 952a9e3a641..79371cdb27a 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -167,10 +167,6 @@ objfunc(const PointImpl& bincenter, if(isBig(item.area())) { // This branch is for the bigger items.. - // Here we will use the closest point of the item bounding box to - // the already arranged pile. So not the bb center nor the a choosen - // corner but whichever is the closest to the center. This will - // prevent some unwanted strange arrangements. auto minc = ibb.minCorner(); // bottom left corner auto maxc = ibb.maxCorner(); // top right corner @@ -211,17 +207,11 @@ objfunc(const PointImpl& bincenter, // its neighbors. We will check the aligment with all neighbors and // return the score for the best alignment. So it is enough for the // candidate to be aligned with only one item. - auto alignment_score = std::numeric_limits::max(); + auto alignment_score = 1.0; density = (fullbb.width()*fullbb.height()) / (norm*norm); auto& trsh = item.transformedShape(); auto querybb = item.boundingBox(); - auto wp = querybb.width()*0.2; - auto hp = querybb.height()*0.2; - auto pad = PointImpl( Coord(wp), Coord(hp)); - querybb = Box({ querybb.minCorner() - pad, - querybb.maxCorner() + pad - }); // Query the spatial index for the neigbours std::vector result; @@ -229,26 +219,17 @@ objfunc(const PointImpl& bincenter, spatindex.query(bgi::intersects(querybb), std::back_inserter(result)); -// if(result.empty()) { -// std::cout << "Error while arranging!" << std::endl; -// std::cout << spatindex.size() << " " << pile.size() << std::endl; - -// auto ib = spatindex.bounds(); -// Box ibb; -// boost::geometry::convert(ib, ibb); -// std::cout << "Inside: " << (sl::isInside(querybb, ibb) || -// boost::geometry::intersects(querybb, ibb)) << std::endl; -// } - for(auto& e : result) { // now get the score for the best alignment auto idx = e.second; auto& p = pile[idx]; auto parea = areacache[idx]; - auto bb = sl::boundingBox(sl::Shapes{p, trsh}); - auto bbarea = bb.area(); - auto ascore = 1.0 - (item.area() + parea)/bbarea; + if(std::abs(1.0 - parea/item.area()) < 1e-6) { + auto bb = sl::boundingBox(sl::Shapes{p, trsh}); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; - if(ascore < alignment_score) alignment_score = ascore; + if(ascore < alignment_score) alignment_score = ascore; + } } // The final mix of the score is the balance between the distance @@ -258,15 +239,12 @@ objfunc(const PointImpl& bincenter, score = 0.5 * dist + 0.5 * density; else score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; - } - } else if( !isBig(item.area()) && spatindex.empty()) { - // If there are no big items, only small, we should consider the - // density here as well to not get silly results auto bindist = pl::distance(ibb.center(), bincenter) / norm; - auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; - score = ROUNDNESS_RATIO * bindist + DENSITY_RATIO * density; + + // Bindist is surprisingly enough... + score = bindist; } else { // Here there are the small items that should be placed around the // already processed bigger items. @@ -294,7 +272,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.6f; + pcfg.accuracy = 0.65f; } template From 00e9f07a03042de25438ec5922618dbb87f86be6 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 8 Aug 2018 16:24:10 +0200 Subject: [PATCH 12/63] Improved robustness of slicing when the slicing plane crosses a horizontal plane of an object exactly. Should improve Github issues #831, #895, #1102 --- xs/src/libslic3r/Fill/FillHoneycomb.cpp | 4 +- xs/src/libslic3r/Model.cpp | 1 + xs/src/libslic3r/Polyline.hpp | 2 + xs/src/libslic3r/TriangleMesh.cpp | 236 ++++++++++++++++-------- xs/src/libslic3r/TriangleMesh.hpp | 27 ++- 5 files changed, 186 insertions(+), 84 deletions(-) diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.cpp b/xs/src/libslic3r/Fill/FillHoneycomb.cpp index aa0e0f6b0d1..aa52856ae1a 100644 --- a/xs/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/FillHoneycomb.cpp @@ -86,8 +86,8 @@ void FillHoneycomb::_fill_surface_single( Polylines paths; { Polylines p; - for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it) - p.push_back((Polyline)(*it)); + for (Polygon &poly : polygons) + p.emplace_back(poly.points); paths = intersection_pl(p, to_polygons(expolygon)); } diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index bceeea2582a..f08736f4e14 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -613,6 +613,7 @@ const BoundingBoxf3& ModelObject::bounding_box() const BoundingBoxf3 raw_bbox; for (const ModelVolume *v : this->volumes) if (! v->modifier) + // mesh.bounding_box() returns a cached value. raw_bbox.merge(v->mesh.bounding_box()); BoundingBoxf3 bb; for (const ModelInstance *i : this->instances) diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index b64743d84ba..123ca5d2c41 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -19,6 +19,8 @@ class Polyline : public MultiPoint { Polyline() {}; Polyline(const Polyline &other) : MultiPoint(other.points) {} Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {} + explicit Polyline(const Points &points) : MultiPoint(points) {} + explicit Polyline(Points &&points) : MultiPoint(std::move(points)) {} Polyline& operator=(const Polyline &other) { points = other.points; return *this; } Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; } static Polyline new_scale(std::vector points) { diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 45e4b6f5dca..b83e0dae648 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -696,8 +696,7 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : } } -void -TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const +void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const { BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice"; @@ -800,35 +799,37 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { std::vector::size_type layer_idx = it - z.begin(); IntersectionLine il; - if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il)) { + if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) { boost::lock_guard l(*lines_mutex); if (il.edge_type == feHorizontal) { - // Insert all three edges of the face. - const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; - const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0; - for (int j = 0; j < 3; ++ j) { - int a_id = vertices[j % 3]; - int b_id = vertices[(j+1) % 3]; - if (reverse) - std::swap(a_id, b_id); - const stl_vertex *a = &this->v_scaled_shared[a_id]; - const stl_vertex *b = &this->v_scaled_shared[b_id]; - il.a.x = a->x; - il.a.y = a->y; - il.b.x = b->x; - il.b.y = b->y; - il.a_id = a_id; - il.b_id = b_id; - (*lines)[layer_idx].push_back(il); - } + // Insert all marked edges of the face. The marked edges do not share an edge with another horizontal face + // (they may not have a nighbor, or their neighbor is vertical) + const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; + const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0; + uint32_t edge_mask = IntersectionLine::EDGE0; + for (int j = 0; j < 3; ++ j, edge_mask <<= 1) + if (il.flags & edge_mask) { + int a_id = vertices[j % 3]; + int b_id = vertices[(j+1) % 3]; + if (reverse) + std::swap(a_id, b_id); + const stl_vertex *a = &this->v_scaled_shared[a_id]; + const stl_vertex *b = &this->v_scaled_shared[b_id]; + il.a.x = a->x; + il.a.y = a->y; + il.b.x = b->x; + il.b.y = b->y; + il.a_id = a_id; + il.b_id = b_id; + (*lines)[layer_idx].emplace_back(il); + } } else - (*lines)[layer_idx].push_back(il); + (*lines)[layer_idx].emplace_back(il); } } } -void -TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const +void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const { std::vector layers_p; this->slice(z, &layers_p); @@ -848,8 +849,18 @@ TriangleMeshSlicer::slice(const std::vector &z, std::vector* BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end"; } +static inline float cross_product(const stl_vertex *a, const stl_vertex *b, const stl_vertex *c) +{ + float v1_x = b->x - a->x; + float v1_y = b->y - a->y; + float v2_x = c->x - a->x; + float v2_y = c->y - a->y; + float dir = (b->x - a->x) * (c->y - a->y) - (b->y - a->y) * (c->x - a->x); + return dir; +} + // Return true, if the facet has been sliced and line_out has been filled. -bool TriangleMeshSlicer::slice_facet( +TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( float slice_z, const stl_facet &facet, const int facet_idx, const float min_z, const float max_z, IntersectionLine *line_out) const @@ -877,22 +888,61 @@ bool TriangleMeshSlicer::slice_facet( const stl_vertex &v0 = this->v_scaled_shared[vertices[0]]; const stl_vertex &v1 = this->v_scaled_shared[vertices[1]]; const stl_vertex &v2 = this->v_scaled_shared[vertices[2]]; + // We may ignore this edge for slicing purposes, but we may still use it for object cutting. + FacetSliceType result = Slicing; + const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx]; if (min_z == max_z) { // All three vertices are aligned with slice_z. line_out->edge_type = feHorizontal; + // Mark neighbor edges, which do not have a neighbor. + uint32_t edges = 0; + uint32_t mask = IntersectionLine::EDGE0; + for (int nbr_idx = 2; nbr_idx != 5; ++ nbr_idx, mask <<= 1) + // If the neighbor with an edge starting with a vertex idx (nbr_idx - 2) shares no + // opposite face, add it to the edges to process when slicing. + if (nbr.neighbor[nbr_idx % 3] == -1) + // Mark this edge. + edges |= mask; + // Use some edges of this triangle for slicing only if at least one of its edge does not have an opposite face. + result = (edges == 0) ? Cutting : Slicing; + line_out->flags |= edges; if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) { // If normal points downwards this is a bottom horizontal facet so we reverse its point order. std::swap(a, b); std::swap(a_id, b_id); } - } else if (v0.z < slice_z || v1.z < slice_z || v2.z < slice_z) { - // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. - line_out->edge_type = feTop; - std::swap(a, b); - std::swap(a_id, b_id); } else { - // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. - line_out->edge_type = feBottom; + // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane. + int nbr_idx = (j + 2) % 3; + int nbr_face = nbr.neighbor[nbr_idx]; + // Is the third vertex below the cutting plane? + bool third_below = v0.z < slice_z || v1.z < slice_z || v2.z < slice_z; + // Is this a concave corner? + if (nbr_face == -1) { +#ifdef _DEBUG + printf("Face has no neighbor!\n"); +#endif + } else { + int idx_vertex_opposite = this->mesh->stl.v_indices[nbr_face].vertex[nbr.which_vertex_not[nbr_idx]]; + const stl_vertex *c = &this->v_scaled_shared[idx_vertex_opposite]; + if (c->z > slice_z) { + // If an edge resides on a cutting plane, and none of the two triangles are coplanar with the cutting plane, + // igore the lower triangle. + if (third_below) + result = Cutting; + } else if (c->z == slice_z) { + // A vertical face shares edge with a horizontal face. Verify, whether the shared corner is convex or concave. + float dir = cross_product(a, b, c); + if (third_below ? (dir < 0.) : (dir > 0.)) + result = Cutting; + } + } + if (third_below) { + line_out->edge_type = feTop; + std::swap(a, b); + std::swap(a_id, b_id); + } else + line_out->edge_type = feBottom; } line_out->a.x = a->x; line_out->a.y = a->y; @@ -900,7 +950,7 @@ bool TriangleMeshSlicer::slice_facet( line_out->b.y = b->y; line_out->a_id = a_id; line_out->b_id = b_id; - return true; + return result; } if (a->z == slice_z) { @@ -935,7 +985,7 @@ bool TriangleMeshSlicer::slice_facet( assert(num_points == 2 || num_points == 3); if (num_points < 3) // This triangle touches the cutting plane with a single vertex. Ignore it. - return false; + return NoSlice; // Erase one of the duplicate points. -- num_points; for (int i = points_on_layer[1]; i < num_points; ++ i) @@ -945,52 +995,86 @@ bool TriangleMeshSlicer::slice_facet( // Facets must intersect each plane 0 or 2 times. assert(num_points == 0 || num_points == 2); if (num_points == 2) { - line_out->edge_type = feNone; + line_out->edge_type = feGeneral; line_out->a = (Point)points[1]; line_out->b = (Point)points[0]; line_out->a_id = points[1].point_id; line_out->b_id = points[0].point_id; line_out->edge_a_id = points[1].edge_id; line_out->edge_b_id = points[0].edge_id; - return true; + // General slicing position, use the segment for both slicing and object cutting. + return Slicing; } - return false; + return NoSlice; } -void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const +//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing +// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces. +// So the following code makes only sense now to handle degenerate meshes with more than two faces +// sharing a single edge. +static inline void remove_tangent_edges(std::vector &lines) { - // Remove tangent edges. - //FIXME This is O(n^2) in rare cases when many faces intersect the cutting plane. - for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line) - if (! line->skip && line->edge_type != feNone) { - // This line is af facet edge. There may be a duplicate line with the same end vertices. - // If the line is is an edge connecting two facets, find another facet edge - // having the same endpoints but in reverse order. - for (IntersectionLines::iterator line2 = line + 1; line2 != lines.end(); ++ line2) - if (! line2->skip && line2->edge_type != feNone) { - // Are these facets adjacent? (sharing a common edge on this layer) - if (line->a_id == line2->a_id && line->b_id == line2->b_id) { - line2->skip = true; - /* if they are both oriented upwards or downwards (like a 'V') - then we can remove both edges from this layer since it won't - affect the sliced shape */ - /* if one of them is oriented upwards and the other is oriented - downwards, let's only keep one of them (it doesn't matter which - one since all 'top' lines were reversed at slicing) */ - if (line->edge_type == line2->edge_type) { - line->skip = true; - break; - } - } else if (line->a_id == line2->b_id && line->b_id == line2->a_id) { - /* if this edge joins two horizontal facets, remove both of them */ - if (line->edge_type == feHorizontal && line2->edge_type == feHorizontal) { - line->skip = true; - line2->skip = true; - break; - } + std::vector by_vertex_pair; + by_vertex_pair.reserve(lines.size()); + for (IntersectionLine& line : lines) + if (line.edge_type != feGeneral && line.a_id != -1) + // This is a face edge. Check whether there is its neighbor stored in lines. + by_vertex_pair.emplace_back(&line); + auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) { + // Sort vertices of l1, l2 lexicographically + int l1a = l1->a_id; + int l1b = l1->b_id; + int l2a = l2->a_id; + int l2b = l2->b_id; + if (l1a > l1b) + std::swap(l1a, l1b); + if (l2a > l2b) + std::swap(l2a, l2b); + // Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored. + return l1a < l2a || (l1a == l2a && l1b < l2b); + }; + std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted); + for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) { + IntersectionLine &l1 = **line; + if (! l1.skip()) { + // Iterate as long as line and line2 edges share the same end points. + for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) { + // Lines must share the end points. + assert(! edges_lower_sorted(*line, *line2)); + assert(! edges_lower_sorted(*line2, *line)); + IntersectionLine &l2 = **line2; + if (l2.skip()) + continue; + if (l1.a_id == l2.a_id) { + assert(l1.b_id == l2.b_id); + l2.set_skip(); + // If they are both oriented upwards or downwards (like a 'V'), + // then we can remove both edges from this layer since it won't + // affect the sliced shape. + // If one of them is oriented upwards and the other is oriented + // downwards, let's only keep one of them (it doesn't matter which + // one since all 'top' lines were reversed at slicing). + if (l1.edge_type == l2.edge_type) { + l1.set_skip(); + break; + } + } else { + assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id); + // If this edge joins two horizontal facets, remove both of them. + if (l1.edge_type == feHorizontal && l2.edge_type == feHorizontal) { + l1.set_skip(); + l2.set_skip(); + break; } } + } } + } +} + +void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const +{ + remove_tangent_edges(lines); struct OpenPolyline { OpenPolyline() {}; @@ -1013,7 +1097,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo by_edge_a_id.reserve(lines.size()); by_a_id.reserve(lines.size()); for (IntersectionLine &line : lines) { - if (! line.skip) { + if (! line.skip()) { if (line.edge_a_id != -1) by_edge_a_id.emplace_back(&line); if (line.a_id != -1) @@ -1030,13 +1114,13 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // take first spare line and start a new loop IntersectionLine *first_line = nullptr; for (; it_line_seed != lines.end(); ++ it_line_seed) - if (! it_line_seed->skip) { + if (! it_line_seed->skip()) { first_line = &(*it_line_seed ++); break; } if (first_line == nullptr) break; - first_line->skip = true; + first_line->set_skip(); Points loop_pts; loop_pts.emplace_back(first_line->a); IntersectionLine *last_line = first_line; @@ -1057,7 +1141,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo if (it_begin != by_edge_a_id.end()) { auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip) { + if (! (*it_line)->skip()) { next_line = *it_line; break; } @@ -1069,7 +1153,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo if (it_begin != by_a_id.end()) { auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip) { + if (! (*it_line)->skip()) { next_line = *it_line; break; } @@ -1100,7 +1184,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo */ loop_pts.emplace_back(next_line->a); last_line = next_line; - next_line->skip = true; + next_line->set_skip(); } } } @@ -1192,8 +1276,8 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) || (ip1.point_id != -1 && ip1.point_id == ip2.point_id)) { // The current loop is complete. Add it to the output. - assert(opl.points.front().point_id == opl.points.back().point_id); - assert(opl.points.front().edge_id == opl.points.back().edge_id); + //assert(opl.points.front().point_id == opl.points.back().point_id); + //assert(opl.points.front().edge_id == opl.points.back().edge_id); // Remove the duplicate last point. opl.points.pop_back(); if (opl.points.size() >= 3) { @@ -1393,7 +1477,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // intersect facet with cutting plane IntersectionLine line; - if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line)) { + if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line) != TriangleMeshSlicer::NoSlice) { // Save intersection lines for generating correct triangulations. if (line.edge_type == feTop) { lower_lines.push_back(line); diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index c700784a51f..3714f5e9e05 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -76,7 +76,7 @@ class TriangleMesh enum FacetEdgeType { // A general case, the cutting plane intersect a face at two different edges. - feNone, + feGeneral, // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. feTop, // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. @@ -110,6 +110,11 @@ class IntersectionPoint : public Point, public IntersectionReference class IntersectionLine : public Line { public: + IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feGeneral), flags(0) {} + + bool skip() const { return (this->flags & SKIP) != 0; } + void set_skip() { this->flags |= SKIP; } + // Inherits Point a, b // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. // Vertex indices of the line end points. @@ -118,11 +123,16 @@ class IntersectionLine : public Line // Source mesh edges of the line end points. int edge_a_id; int edge_b_id; - // feNone, feTop, feBottom, feHorizontal + // feGeneral, feTop, feBottom, feHorizontal FacetEdgeType edge_type; - // Used by TriangleMeshSlicer::make_loops() to skip duplicate edges. - bool skip; - IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feNone), skip(false) {}; + // Used by TriangleMeshSlicer::slice() to skip duplicate edges. + enum { + EDGE0 = 1, + EDGE1 = 2, + EDGE2 = 4, + SKIP = 8, + }; + uint32_t flags; }; typedef std::vector IntersectionLines; typedef std::vector IntersectionLinePtrs; @@ -133,7 +143,12 @@ class TriangleMeshSlicer TriangleMeshSlicer(TriangleMesh* _mesh); void slice(const std::vector &z, std::vector* layers) const; void slice(const std::vector &z, std::vector* layers) const; - bool slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, + enum FacetSliceType { + NoSlice = 0, + Slicing = 1, + Cutting = 2 + }; + FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, const float min_z, const float max_z, IntersectionLine *line_out) const; void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; From e678368b233670ab6a707f87b42306964f12d463 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 9 Aug 2018 09:59:05 +0200 Subject: [PATCH 13/63] fix compilation on linux and mac --- xs/src/libnest2d/CMakeLists.txt | 1 + xs/src/libnest2d/examples/main.cpp | 20 +- xs/src/libnest2d/libnest2d.h | 4 +- xs/src/libnest2d/libnest2d/boost_alg.hpp | 98 ++++----- .../clipper_backend/clipper_backend.hpp | 105 +++++---- .../libnest2d/libnest2d/geometry_traits.hpp | 208 ++++++++++-------- .../libnest2d/geometry_traits_nfp.hpp | 113 +++++----- xs/src/libnest2d/libnest2d/libnest2d.hpp | 143 ++++++------ .../libnest2d/placers/bottomleftplacer.hpp | 63 +++--- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 125 +++++------ .../libnest2d/placers/placer_boilerplate.hpp | 25 +-- xs/src/libnest2d/libnest2d/rotfinder.hpp | 41 ++++ .../libnest2d/selections/djd_heuristic.hpp | 48 ++-- .../libnest2d/libnest2d/selections/filler.hpp | 10 +- .../libnest2d/selections/firstfit.hpp | 7 +- xs/src/libnest2d/tests/test.cpp | 58 ++--- xs/src/libnest2d/tools/svgtools.hpp | 6 +- xs/src/libslic3r/ModelArrange.hpp | 58 ++--- 18 files changed, 590 insertions(+), 543 deletions(-) create mode 100644 xs/src/libnest2d/libnest2d/rotfinder.hpp diff --git a/xs/src/libnest2d/CMakeLists.txt b/xs/src/libnest2d/CMakeLists.txt index 835e8311d3b..0a181f4ab8e 100644 --- a/xs/src/libnest2d/CMakeLists.txt +++ b/xs/src/libnest2d/CMakeLists.txt @@ -31,6 +31,7 @@ set(LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/metaloop.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/rotfinder.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 11f8f50cf7a..37096019d34 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -9,6 +9,8 @@ #include "tests/printer_parts.h" #include "tools/benchmark.h" #include "tools/svgtools.hpp" +#include "libnest2d/rotfinder.hpp" + //#include "tools/libnfpglue.hpp" using namespace libnest2d; @@ -64,7 +66,7 @@ void arrangeRectangles() { // input.insert(input.end(), proba.begin(), proba.end()); // input.insert(input.end(), crasher.begin(), crasher.end()); -// Box bin(250*SCALE, 210*SCALE); + Box bin(250*SCALE, 210*SCALE); // PolygonImpl bin = { // { // {25*SCALE, 0}, @@ -80,12 +82,12 @@ void arrangeRectangles() { // {} // }; - _Circle bin({0, 0}, 125*SCALE); +// _Circle bin({0, 0}, 125*SCALE); auto min_obj_distance = static_cast(0*SCALE); using Placer = strategies::_NofitPolyPlacer; - using Packer = Arranger; + using Packer = Nester; Packer arrange(bin, min_obj_distance); @@ -112,7 +114,9 @@ void arrangeRectangles() { // svgw.writePackGroup(arrange.lastResult()); // svgw.save("debout"); std::cout << "Remaining items: " << r << std::endl; - })/*.useMinimumBoundigBoxRotation()*/; + }); + +// findMinimumBoundingBoxRotations(input.begin(), input.end()); Benchmark bench; @@ -120,7 +124,7 @@ void arrangeRectangles() { Packer::ResultType result; try { - result = arrange.arrange(input.begin(), input.end()); + result = arrange.execute(input.begin(), input.end()); } catch(GeometryException& ge) { std::cerr << "Geometry error: " << ge.what() << std::endl; return ; @@ -134,7 +138,7 @@ void arrangeRectangles() { std::vector eff; eff.reserve(result.size()); - auto bin_area = ShapeLike::area(bin); + auto bin_area = sl::area(bin); for(auto& r : result) { double a = 0; std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); }); @@ -156,7 +160,7 @@ void arrangeRectangles() { std::cout << ") Total: " << total << std::endl; for(auto& it : input) { - auto ret = ShapeLike::isValid(it.transformedShape()); + auto ret = sl::isValid(it.transformedShape()); std::cout << ret.second << std::endl; } @@ -177,7 +181,7 @@ void arrangeRectangles() { int main(void /*int argc, char **argv*/) { arrangeRectangles(); -// findDegenerateCase(); +//// findDegenerateCase(); return EXIT_SUCCESS; } diff --git a/xs/src/libnest2d/libnest2d.h b/xs/src/libnest2d/libnest2d.h index c9e21ecfb3c..05677afd7cf 100644 --- a/xs/src/libnest2d/libnest2d.h +++ b/xs/src/libnest2d/libnest2d.h @@ -22,6 +22,7 @@ using Point = PointImpl; using Coord = TCoord; using Box = _Box; using Segment = _Segment; +using Circle = _Circle; using Item = _Item; using Rectangle = _Rectangle; @@ -36,9 +37,6 @@ using DJDHeuristic = strategies::_DJDHeuristic; using NfpPlacer = strategies::_NofitPolyPlacer; using BottomLeftPlacer = strategies::_BottomLeftPlacer; -//template -//using NofitPolyPlacer = strategies::_NofitPolyPlacer; - } #endif // LIBNEST2D_H diff --git a/xs/src/libnest2d/libnest2d/boost_alg.hpp b/xs/src/libnest2d/libnest2d/boost_alg.hpp index 67e19fcbdd2..7da1036f011 100644 --- a/xs/src/libnest2d/libnest2d/boost_alg.hpp +++ b/xs/src/libnest2d/libnest2d/boost_alg.hpp @@ -36,7 +36,7 @@ using libnest2d::setX; using libnest2d::setY; using Box = libnest2d::_Box; using Segment = libnest2d::_Segment; -using Shapes = libnest2d::Nfp::Shapes; +using Shapes = libnest2d::nfp::Shapes; } @@ -241,11 +241,11 @@ template<> struct tag { template<> struct exterior_ring { static inline bp2d::PathImpl& get(bp2d::PolygonImpl& p) { - return libnest2d::ShapeLike::getContour(p); + return libnest2d::shapelike::getContour(p); } static inline bp2d::PathImpl const& get(bp2d::PolygonImpl const& p) { - return libnest2d::ShapeLike::getContour(p); + return libnest2d::shapelike::getContour(p); } }; @@ -271,13 +271,13 @@ struct interior_rings { static inline libnest2d::THolesContainer& get( bp2d::PolygonImpl& p) { - return libnest2d::ShapeLike::holes(p); + return libnest2d::shapelike::holes(p); } static inline const libnest2d::THolesContainer& get( bp2d::PolygonImpl const& p) { - return libnest2d::ShapeLike::holes(p); + return libnest2d::shapelike::holes(p); } }; @@ -311,83 +311,78 @@ struct range_value { namespace libnest2d { // Now the algorithms that boost can provide... +namespace pointlike { template<> -inline double PointLike::distance(const PointImpl& p1, - const PointImpl& p2 ) +inline double distance(const PointImpl& p1, const PointImpl& p2 ) { return boost::geometry::distance(p1, p2); } template<> -inline double PointLike::distance(const PointImpl& p, - const bp2d::Segment& seg ) +inline double distance(const PointImpl& p, const bp2d::Segment& seg ) { return boost::geometry::distance(p, seg); } +} +namespace shapelike { // Tell libnest2d how to make string out of a ClipperPolygon object template<> -inline bool ShapeLike::intersects(const PathImpl& sh1, - const PathImpl& sh2) +inline bool intersects(const PathImpl& sh1, const PathImpl& sh2) { return boost::geometry::intersects(sh1, sh2); } // Tell libnest2d how to make string out of a ClipperPolygon object template<> -inline bool ShapeLike::intersects(const PolygonImpl& sh1, - const PolygonImpl& sh2) +inline bool intersects(const PolygonImpl& sh1, const PolygonImpl& sh2) { return boost::geometry::intersects(sh1, sh2); } // Tell libnest2d how to make string out of a ClipperPolygon object template<> -inline bool ShapeLike::intersects(const bp2d::Segment& s1, - const bp2d::Segment& s2) +inline bool intersects(const bp2d::Segment& s1, const bp2d::Segment& s2) { return boost::geometry::intersects(s1, s2); } #ifndef DISABLE_BOOST_AREA template<> -inline double ShapeLike::area(const PolygonImpl& shape) +inline double area(const PolygonImpl& shape, const PolygonTag&) { return boost::geometry::area(shape); } #endif template<> -inline bool ShapeLike::isInside(const PointImpl& point, - const PolygonImpl& shape) +inline bool isInside(const PointImpl& point, + const PolygonImpl& shape) { return boost::geometry::within(point, shape); } template<> -inline bool ShapeLike::isInside(const PolygonImpl& sh1, - const PolygonImpl& sh2) +inline bool isInside(const PolygonImpl& sh1, const PolygonImpl& sh2) { return boost::geometry::within(sh1, sh2); } template<> -inline bool ShapeLike::touches( const PolygonImpl& sh1, - const PolygonImpl& sh2) +inline bool touches(const PolygonImpl& sh1, const PolygonImpl& sh2) { return boost::geometry::touches(sh1, sh2); } template<> -inline bool ShapeLike::touches( const PointImpl& point, - const PolygonImpl& shape) +inline bool touches( const PointImpl& point, const PolygonImpl& shape) { return boost::geometry::touches(point, shape); } #ifndef DISABLE_BOOST_BOUNDING_BOX template<> -inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) +inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&) { bp2d::Box b; boost::geometry::envelope(sh, b); @@ -395,7 +390,7 @@ inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) } template<> -inline bp2d::Box ShapeLike::boundingBox(const bp2d::Shapes& shapes) +inline bp2d::Box boundingBox(const bp2d::Shapes& shapes) { bp2d::Box b; boost::geometry::envelope(shapes, b); @@ -405,7 +400,7 @@ inline bp2d::Box ShapeLike::boundingBox(const bp2d::Shapes& shapes) #ifndef DISABLE_BOOST_CONVEX_HULL template<> -inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh) +inline PolygonImpl convexHull(const PolygonImpl& sh) { PolygonImpl ret; boost::geometry::convex_hull(sh, ret); @@ -413,7 +408,7 @@ inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh) } template<> -inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes) +inline PolygonImpl convexHull(const bp2d::Shapes& shapes) { PolygonImpl ret; boost::geometry::convex_hull(shapes, ret); @@ -423,7 +418,7 @@ inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes) #ifndef DISABLE_BOOST_ROTATE template<> -inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) +inline void rotate(PolygonImpl& sh, const Radians& rads) { namespace trans = boost::geometry::strategy::transform; @@ -437,7 +432,7 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) #ifndef DISABLE_BOOST_TRANSLATE template<> -inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) +inline void translate(PolygonImpl& sh, const PointImpl& offs) { namespace trans = boost::geometry::strategy::transform; @@ -451,26 +446,15 @@ inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) #ifndef DISABLE_BOOST_OFFSET template<> -inline void ShapeLike::offset(PolygonImpl& sh, bp2d::Coord distance) +inline void offset(PolygonImpl& sh, bp2d::Coord distance) { PolygonImpl cpy = sh; boost::geometry::buffer(cpy, sh, distance); } #endif -#ifndef DISABLE_BOOST_NFP_MERGE -template<> -inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes, - const PolygonImpl& sh) -{ - bp2d::Shapes retv; - boost::geometry::union_(shapes, sh, retv); - return retv; -} -#endif - #ifndef DISABLE_BOOST_SERIALIZE -template<> inline std::string ShapeLike::serialize( +template<> inline std::string serialize( const PolygonImpl& sh, double scale) { std::stringstream ss; @@ -482,14 +466,14 @@ template<> inline std::string ShapeLike::serialize( Polygonf::ring_type ring; Polygonf::inner_container_type holes; - ring.reserve(ShapeLike::contourVertexCount(sh)); + ring.reserve(shapelike::contourVertexCount(sh)); - for(auto it = ShapeLike::cbegin(sh); it != ShapeLike::cend(sh); it++) { + for(auto it = shapelike::cbegin(sh); it != shapelike::cend(sh); it++) { auto& v = *it; ring.emplace_back(getX(v)*scale, getY(v)*scale); }; - auto H = ShapeLike::holes(sh); + auto H = shapelike::holes(sh); for(PathImpl& h : H ) { Polygonf::ring_type hf; for(auto it = h.begin(); it != h.end(); it++) { @@ -512,21 +496,37 @@ template<> inline std::string ShapeLike::serialize( #ifndef DISABLE_BOOST_UNSERIALIZE template<> -inline void ShapeLike::unserialize( +inline void unserialize( PolygonImpl& sh, const std::string& str) { } #endif -template<> inline std::pair -ShapeLike::isValid(const PolygonImpl& sh) +template<> inline std::pair isValid(const PolygonImpl& sh) { std::string message; bool ret = boost::geometry::is_valid(sh, message); return {ret, message}; } +} + +namespace nfp { + +#ifndef DISABLE_BOOST_NFP_MERGE +template<> +inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes, + const PolygonImpl& sh) +{ + bp2d::Shapes retv; + boost::geometry::union_(shapes, sh, retv); + return retv; +} +#endif + +} + } diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp index 15ceb157678..4238212ad20 100644 --- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp +++ b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp @@ -21,6 +21,9 @@ struct PolygonImpl { PathImpl Contour; HoleStore Holes; + using Tag = libnest2d::PolygonTag; + using PointType = PointImpl; + inline PolygonImpl() = default; inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {} @@ -113,35 +116,32 @@ template<> struct CountourType { using Type = PathImpl; }; +namespace pointlike { + // Tell binpack2d how to extract the X coord from a ClipperPoint object -template<> inline TCoord PointLike::x(const PointImpl& p) +template<> inline TCoord x(const PointImpl& p) { return p.X; } // Tell binpack2d how to extract the Y coord from a ClipperPoint object -template<> inline TCoord PointLike::y(const PointImpl& p) +template<> inline TCoord y(const PointImpl& p) { return p.Y; } // Tell binpack2d how to extract the X coord from a ClipperPoint object -template<> inline TCoord& PointLike::x(PointImpl& p) +template<> inline TCoord& x(PointImpl& p) { return p.X; } // Tell binpack2d how to extract the Y coord from a ClipperPoint object -template<> -inline TCoord& PointLike::y(PointImpl& p) +template<> inline TCoord& y(PointImpl& p) { return p.Y; } -template<> -inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity) -{ - return sh.Contour.reserve(vertex_capacity); } #define DISABLE_BOOST_AREA @@ -175,16 +175,28 @@ inline double area(const PolygonImpl& sh) { return ClipperLib::Area(sh.Contour) + a; } + +} + +template<> struct HolesContainer { + using Type = ClipperLib::Paths; +}; + +namespace shapelike { + +template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity) +{ + return sh.Contour.reserve(vertex_capacity); } // Tell binpack2d how to make string out of a ClipperPolygon object -template<> -inline double ShapeLike::area(const PolygonImpl& sh) { +template<> inline double area(const PolygonImpl& sh, const PolygonTag&) +{ return _smartarea::area::Value>(sh); } -template<> -inline void ShapeLike::offset(PolygonImpl& sh, TCoord distance) { +template<> inline void offset(PolygonImpl& sh, TCoord distance) +{ #define DISABLE_BOOST_OFFSET using ClipperLib::ClipperOffset; @@ -234,7 +246,8 @@ inline void ShapeLike::offset(PolygonImpl& sh, TCoord distance) { } // Tell libnest2d how to make string out of a ClipperPolygon object -template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) { +template<> inline std::string toString(const PolygonImpl& sh) +{ std::stringstream ss; ss << "Contour {\n"; @@ -256,38 +269,31 @@ template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) { return ss.str(); } -template<> -inline TVertexIterator ShapeLike::begin(PolygonImpl& sh) +template<> inline TVertexIterator begin(PolygonImpl& sh) { return sh.Contour.begin(); } -template<> -inline TVertexIterator ShapeLike::end(PolygonImpl& sh) +template<> inline TVertexIterator end(PolygonImpl& sh) { return sh.Contour.end(); } template<> -inline TVertexConstIterator ShapeLike::cbegin( - const PolygonImpl& sh) +inline TVertexConstIterator cbegin(const PolygonImpl& sh) { return sh.Contour.cbegin(); } -template<> -inline TVertexConstIterator ShapeLike::cend( +template<> inline TVertexConstIterator cend( const PolygonImpl& sh) { return sh.Contour.cend(); } -template<> struct HolesContainer { - using Type = ClipperLib::Paths; -}; - -template<> inline PolygonImpl ShapeLike::create(const PathImpl& path, - const HoleStore& holes) { +template<> +inline PolygonImpl create(const PathImpl& path, const HoleStore& holes) +{ PolygonImpl p; p.Contour = path; @@ -308,8 +314,7 @@ template<> inline PolygonImpl ShapeLike::create(const PathImpl& path, return p; } -template<> inline PolygonImpl ShapeLike::create( PathImpl&& path, - HoleStore&& holes) { +template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) { PolygonImpl p; p.Contour.swap(path); @@ -331,49 +336,49 @@ template<> inline PolygonImpl ShapeLike::create( PathImpl&& path, return p; } -template<> inline const THolesContainer& -ShapeLike::holes(const PolygonImpl& sh) +template<> +inline const THolesContainer& holes(const PolygonImpl& sh) { return sh.Holes; } -template<> inline THolesContainer& -ShapeLike::holes(PolygonImpl& sh) +template<> inline THolesContainer& holes(PolygonImpl& sh) { return sh.Holes; } -template<> inline TContour& -ShapeLike::getHole(PolygonImpl& sh, unsigned long idx) +template<> +inline TContour& getHole(PolygonImpl& sh, unsigned long idx) { return sh.Holes[idx]; } -template<> inline const TContour& -ShapeLike::getHole(const PolygonImpl& sh, unsigned long idx) +template<> +inline const TContour& getHole(const PolygonImpl& sh, + unsigned long idx) { return sh.Holes[idx]; } -template<> inline size_t ShapeLike::holeCount(const PolygonImpl& sh) +template<> inline size_t holeCount(const PolygonImpl& sh) { return sh.Holes.size(); } -template<> inline PathImpl& ShapeLike::getContour(PolygonImpl& sh) +template<> inline PathImpl& getContour(PolygonImpl& sh) { return sh.Contour; } template<> -inline const PathImpl& ShapeLike::getContour(const PolygonImpl& sh) +inline const PathImpl& getContour(const PolygonImpl& sh) { return sh.Contour; } #define DISABLE_BOOST_TRANSLATE template<> -inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) +inline void translate(PolygonImpl& sh, const PointImpl& offs) { for(auto& p : sh.Contour) { p += offs; } for(auto& hole : sh.Holes) for(auto& p : hole) { p += offs; } @@ -381,7 +386,7 @@ inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) #define DISABLE_BOOST_ROTATE template<> -inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) +inline void rotate(PolygonImpl& sh, const Radians& rads) { using Coord = TCoord; @@ -402,9 +407,11 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) } } +} // namespace shapelike + #define DISABLE_BOOST_NFP_MERGE -inline Nfp::Shapes _merge(ClipperLib::Clipper& clipper) { - Nfp::Shapes retv; +inline nfp::Shapes _merge(ClipperLib::Clipper& clipper) { + nfp::Shapes retv; ClipperLib::PolyTree result; clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative); @@ -438,8 +445,10 @@ inline Nfp::Shapes _merge(ClipperLib::Clipper& clipper) { return retv; } -template<> inline Nfp::Shapes -Nfp::merge(const Nfp::Shapes& shapes) +namespace nfp { + +template<> inline nfp::Shapes +merge(const nfp::Shapes& shapes) { ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); @@ -461,6 +470,8 @@ Nfp::merge(const Nfp::Shapes& shapes) } +} + //#define DISABLE_BOOST_SERIALIZE //#define DISABLE_BOOST_UNSERIALIZE diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 1c0d44c9fde..0826cbd4bcb 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -60,6 +60,10 @@ struct PointPair { RawPoint p2; }; +struct PolygonTag {}; +struct BoxTag {}; +struct CircleTag {}; + /** * \brief An abstraction of a box; */ @@ -69,6 +73,9 @@ class _Box: PointPair { using PointPair::p2; public: + using Tag = BoxTag; + using PointType = RawPoint; + inline _Box() = default; inline _Box(const RawPoint& p, const RawPoint& pp): PointPair({p, pp}) {} @@ -98,6 +105,9 @@ class _Circle { double radius_ = 0; public: + using Tag = CircleTag; + using PointType = RawPoint; + _Circle() = default; _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} @@ -123,6 +133,8 @@ class _Segment: PointPair { mutable Radians angletox_ = std::nan(""); public: + using PointType = RawPoint; + inline _Segment() = default; inline _Segment(const RawPoint& p, const RawPoint& pp): @@ -156,36 +168,36 @@ class _Segment: PointPair { inline double length(); }; -// This struct serves as a namespace. The only difference is that is can be +// This struct serves almost as a namespace. The only difference is that is can // used in friend declarations. -struct PointLike { +namespace pointlike { template - static TCoord x(const RawPoint& p) + inline TCoord x(const RawPoint& p) { return p.x(); } template - static TCoord y(const RawPoint& p) + inline TCoord y(const RawPoint& p) { return p.y(); } template - static TCoord& x(RawPoint& p) + inline TCoord& x(RawPoint& p) { return p.x(); } template - static TCoord& y(RawPoint& p) + inline TCoord& y(RawPoint& p) { return p.y(); } template - static double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) + inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) { static_assert(always_false::value, "PointLike::distance(point, point) unimplemented!"); @@ -193,7 +205,7 @@ struct PointLike { } template - static double distance(const RawPoint& /*p1*/, + inline double distance(const RawPoint& /*p1*/, const _Segment& /*s*/) { static_assert(always_false::value, @@ -202,13 +214,13 @@ struct PointLike { } template - static std::pair, bool> horizontalDistance( + inline std::pair, bool> horizontalDistance( const RawPoint& p, const _Segment& s) { using Unit = TCoord; - auto x = PointLike::x(p), y = PointLike::y(p); - auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first()); - auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second()); + auto x = pointlike::x(p), y = pointlike::y(p); + auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); + auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); TCoord ret; @@ -228,13 +240,13 @@ struct PointLike { } template - static std::pair, bool> verticalDistance( + inline std::pair, bool> verticalDistance( const RawPoint& p, const _Segment& s) { using Unit = TCoord; - auto x = PointLike::x(p), y = PointLike::y(p); - auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first()); - auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second()); + auto x = pointlike::x(p), y = pointlike::y(p); + auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); + auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); TCoord ret; @@ -252,36 +264,36 @@ struct PointLike { return {ret, true}; } -}; +} template TCoord _Box::width() const BP2D_NOEXCEPT { - return PointLike::x(maxCorner()) - PointLike::x(minCorner()); + return pointlike::x(maxCorner()) - pointlike::x(minCorner()); } template TCoord _Box::height() const BP2D_NOEXCEPT { - return PointLike::y(maxCorner()) - PointLike::y(minCorner()); + return pointlike::y(maxCorner()) - pointlike::y(minCorner()); } template -TCoord getX(const RawPoint& p) { return PointLike::x(p); } +TCoord getX(const RawPoint& p) { return pointlike::x(p); } template -TCoord getY(const RawPoint& p) { return PointLike::y(p); } +TCoord getY(const RawPoint& p) { return pointlike::y(p); } template void setX(RawPoint& p, const TCoord& val) { - PointLike::x(p) = val; + pointlike::x(p) = val; } template void setY(RawPoint& p, const TCoord& val) { - PointLike::y(p) = val; + pointlike::y(p) = val; } template @@ -303,7 +315,7 @@ inline Radians _Segment::angleToXaxis() const template inline double _Segment::length() { - return PointLike::distance(first(), second()); + return pointlike::distance(first(), second()); } template @@ -356,124 +368,124 @@ enum class Formats { // This struct serves as a namespace. The only difference is that it can be // used in friend declarations and can be aliased at class scope. -struct ShapeLike { +namespace shapelike { template using Shapes = std::vector; template - static RawShape create(const TContour& contour, + inline RawShape create(const TContour& contour, const THolesContainer& holes) { return RawShape(contour, holes); } template - static RawShape create(TContour&& contour, + inline RawShape create(TContour&& contour, THolesContainer&& holes) { return RawShape(contour, holes); } template - static RawShape create(const TContour& contour) + inline RawShape create(const TContour& contour) { return create(contour, {}); } template - static RawShape create(TContour&& contour) + inline RawShape create(TContour&& contour) { return create(contour, {}); } template - static THolesContainer& holes(RawShape& /*sh*/) + inline THolesContainer& holes(RawShape& /*sh*/) { static THolesContainer empty; return empty; } template - static const THolesContainer& holes(const RawShape& /*sh*/) + inline const THolesContainer& holes(const RawShape& /*sh*/) { static THolesContainer empty; return empty; } template - static TContour& getHole(RawShape& sh, unsigned long idx) + inline TContour& getHole(RawShape& sh, unsigned long idx) { return holes(sh)[idx]; } template - static const TContour& getHole(const RawShape& sh, + inline const TContour& getHole(const RawShape& sh, unsigned long idx) { return holes(sh)[idx]; } template - static size_t holeCount(const RawShape& sh) + inline size_t holeCount(const RawShape& sh) { return holes(sh).size(); } template - static TContour& getContour(RawShape& sh) + inline TContour& getContour(RawShape& sh) { return sh; } template - static const TContour& getContour(const RawShape& sh) + inline const TContour& getContour(const RawShape& sh) { return sh; } // Optional, does nothing by default template - static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {} + inline void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {} template - static void addVertex(RawShape& sh, Args...args) + inline void addVertex(RawShape& sh, Args...args) { return getContour(sh).emplace_back(std::forward(args)...); } template - static TVertexIterator begin(RawShape& sh) + inline TVertexIterator begin(RawShape& sh) { return sh.begin(); } template - static TVertexIterator end(RawShape& sh) + inline TVertexIterator end(RawShape& sh) { return sh.end(); } template - static TVertexConstIterator cbegin(const RawShape& sh) + inline TVertexConstIterator cbegin(const RawShape& sh) { return sh.cbegin(); } template - static TVertexConstIterator cend(const RawShape& sh) + inline TVertexConstIterator cend(const RawShape& sh) { return sh.cend(); } template - static std::string toString(const RawShape& /*sh*/) + inline std::string toString(const RawShape& /*sh*/) { return ""; } template - static std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) + inline std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) { static_assert(always_false::value, "ShapeLike::serialize() unimplemented!"); @@ -481,14 +493,14 @@ struct ShapeLike { } template - static void unserialize(RawShape& /*sh*/, const std::string& /*str*/) + inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) { static_assert(always_false::value, "ShapeLike::unserialize() unimplemented!"); } template - static double area(const RawShape& /*sh*/) + inline double area(const RawShape& /*sh*/, const PolygonTag&) { static_assert(always_false::value, "ShapeLike::area() unimplemented!"); @@ -496,7 +508,7 @@ struct ShapeLike { } template - static bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) + inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) { static_assert(always_false::value, "ShapeLike::intersects() unimplemented!"); @@ -504,7 +516,7 @@ struct ShapeLike { } template - static bool isInside(const TPoint& /*point*/, + inline bool isInside(const TPoint& /*point*/, const RawShape& /*shape*/) { static_assert(always_false::value, @@ -513,7 +525,7 @@ struct ShapeLike { } template - static bool isInside(const RawShape& /*shape*/, + inline bool isInside(const RawShape& /*shape*/, const RawShape& /*shape*/) { static_assert(always_false::value, @@ -522,7 +534,7 @@ struct ShapeLike { } template - static bool touches( const RawShape& /*shape*/, + inline bool touches( const RawShape& /*shape*/, const RawShape& /*shape*/) { static_assert(always_false::value, @@ -531,7 +543,7 @@ struct ShapeLike { } template - static bool touches( const TPoint& /*point*/, + inline bool touches( const TPoint& /*point*/, const RawShape& /*shape*/) { static_assert(always_false::value, @@ -540,21 +552,22 @@ struct ShapeLike { } template - static _Box> boundingBox(const RawShape& /*sh*/) + inline _Box> boundingBox(const RawShape& /*sh*/, + const PolygonTag&) { static_assert(always_false::value, "ShapeLike::boundingBox(shape) unimplemented!"); } template - static _Box> boundingBox(const Shapes& /*sh*/) + inline _Box> boundingBox(const Shapes& /*sh*/) { static_assert(always_false::value, "ShapeLike::boundingBox(shapes) unimplemented!"); } template - static RawShape convexHull(const RawShape& /*sh*/) + inline RawShape convexHull(const RawShape& /*sh*/) { static_assert(always_false::value, "ShapeLike::convexHull(shape) unimplemented!"); @@ -562,7 +575,7 @@ struct ShapeLike { } template - static RawShape convexHull(const Shapes& /*sh*/) + inline RawShape convexHull(const Shapes& /*sh*/) { static_assert(always_false::value, "ShapeLike::convexHull(shapes) unimplemented!"); @@ -570,34 +583,34 @@ struct ShapeLike { } template - static void rotate(RawShape& /*sh*/, const Radians& /*rads*/) + inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) { static_assert(always_false::value, "ShapeLike::rotate() unimplemented!"); } template - static void translate(RawShape& /*sh*/, const RawPoint& /*offs*/) + inline void translate(RawShape& /*sh*/, const RawPoint& /*offs*/) { static_assert(always_false::value, "ShapeLike::translate() unimplemented!"); } template - static void offset(RawShape& /*sh*/, TCoord> /*distance*/) + inline void offset(RawShape& /*sh*/, TCoord> /*distance*/) { static_assert(always_false::value, "ShapeLike::offset() unimplemented!"); } template - static std::pair isValid(const RawShape& /*sh*/) + inline std::pair isValid(const RawShape& /*sh*/) { return {false, "ShapeLike::isValid() unimplemented!"}; } template - static inline bool isConvex(const TContour& sh) + inline bool isConvex(const TContour& sh) { using Vertex = TPoint; auto first = sh.begin(); @@ -633,43 +646,55 @@ struct ShapeLike { // No need to implement these // ************************************************************************* - template - static inline _Box> boundingBox( - const _Box>& box) + template + inline Box boundingBox(const Box& box, const BoxTag& ) { return box; } - template - static inline _Box> boundingBox( - const _Circle>& circ) + template + inline _Box boundingBox( + const Circle& circ, const CircleTag&) { - using Coord = TCoord>; - TPoint pmin = { + using Point = typename Circle::PointType; + using Coord = TCoord; + Point pmin = { static_cast(getX(circ.center()) - circ.radius()), static_cast(getY(circ.center()) - circ.radius()) }; - TPoint pmax = { + Point pmax = { static_cast(getX(circ.center()) + circ.radius()), static_cast(getY(circ.center()) + circ.radius()) }; return {pmin, pmax}; } - template - static inline double area(const _Box>& box) + template // Dispatch function + inline _Box boundingBox(const S& sh) { - return static_cast(box.width() * box.height()); + return boundingBox(sh, typename S::Tag()); } - template - static inline double area(const _Circle>& circ) + template + inline double area(const Box& box, const BoxTag& ) + { + return box.area(); + } + + template + inline double area(const Circle& circ, const CircleTag& ) { return circ.area(); } + template // Dispatching function + inline double area(const RawShape& sh) + { + return area(sh, typename RawShape::Tag()); + } + template - static inline double area(const Shapes& shapes) + inline double area(const Shapes& shapes) { return std::accumulate(shapes.begin(), shapes.end(), 0.0, [](double a, const RawShape& b) { @@ -678,14 +703,14 @@ struct ShapeLike { } template - static bool isInside(const TPoint& point, + inline bool isInside(const TPoint& point, const _Circle>& circ) { - return PointLike::distance(point, circ.center()) < circ.radius(); + return pointlike::distance(point, circ.center()) < circ.radius(); } template - static bool isInside(const TPoint& point, + inline bool isInside(const TPoint& point, const _Box>& box) { auto px = getX(point); @@ -699,7 +724,7 @@ struct ShapeLike { } template - static bool isInside(const RawShape& sh, + inline bool isInside(const RawShape& sh, const _Circle>& circ) { return std::all_of(cbegin(sh), cend(sh), @@ -709,7 +734,7 @@ struct ShapeLike { } template - static bool isInside(const _Box>& box, + inline bool isInside(const _Box>& box, const _Circle>& circ) { return isInside(box.minCorner(), circ) && @@ -717,7 +742,7 @@ struct ShapeLike { } template - static bool isInside(const _Box>& ibb, + inline bool isInside(const _Box>& ibb, const _Box>& box) { auto iminX = getX(ibb.minCorner()); @@ -734,31 +759,31 @@ struct ShapeLike { } template // Potential O(1) implementation may exist - static inline TPoint& vertex(RawShape& sh, unsigned long idx) + inline TPoint& vertex(RawShape& sh, unsigned long idx) { return *(begin(sh) + idx); } template // Potential O(1) implementation may exist - static inline const TPoint& vertex(const RawShape& sh, + inline const TPoint& vertex(const RawShape& sh, unsigned long idx) { return *(cbegin(sh) + idx); } template - static inline size_t contourVertexCount(const RawShape& sh) + inline size_t contourVertexCount(const RawShape& sh) { return cend(sh) - cbegin(sh); } template - static inline void foreachContourVertex(RawShape& sh, Fn fn) { + inline void foreachContourVertex(RawShape& sh, Fn fn) { for(auto it = begin(sh); it != end(sh); ++it) fn(*it); } template - static inline void foreachHoleVertex(RawShape& sh, Fn fn) { + inline void foreachHoleVertex(RawShape& sh, Fn fn) { for(int i = 0; i < holeCount(sh); ++i) { auto& h = getHole(sh, i); for(auto it = begin(h); it != end(h); ++it) fn(*it); @@ -766,12 +791,12 @@ struct ShapeLike { } template - static inline void foreachContourVertex(const RawShape& sh, Fn fn) { + inline void foreachContourVertex(const RawShape& sh, Fn fn) { for(auto it = cbegin(sh); it != cend(sh); ++it) fn(*it); } template - static inline void foreachHoleVertex(const RawShape& sh, Fn fn) { + inline void foreachHoleVertex(const RawShape& sh, Fn fn) { for(int i = 0; i < holeCount(sh); ++i) { auto& h = getHole(sh, i); for(auto it = cbegin(h); it != cend(h); ++it) fn(*it); @@ -779,18 +804,17 @@ struct ShapeLike { } template - static inline void foreachVertex(RawShape& sh, Fn fn) { + inline void foreachVertex(RawShape& sh, Fn fn) { foreachContourVertex(sh, fn); foreachHoleVertex(sh, fn); } template - static inline void foreachVertex(const RawShape& sh, Fn fn) { + inline void foreachVertex(const RawShape& sh, Fn fn) { foreachContourVertex(sh, fn); foreachHoleVertex(sh, fn); } - -}; +} } diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp index 90cf21be5b2..6cac374aef7 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp @@ -9,6 +9,27 @@ namespace libnest2d { +namespace __nfp { +// Do not specialize this... +template +inline bool _vsort(const TPoint& v1, const TPoint& v2) +{ + using Coord = TCoord>; + Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); + auto diff = y1 - y2; + if(std::abs(diff) <= std::numeric_limits::epsilon()) + return x1 < x2; + + return diff < 0; +} +} + +/// A collection of static methods for handling the no fit polygon creation. +namespace nfp { + +namespace sl = shapelike; +namespace pl = pointlike; + /// The complexity level of a polygon that an NFP implementation can handle. enum class NfpLevel: unsigned { CONVEX_ONLY, @@ -18,12 +39,17 @@ enum class NfpLevel: unsigned { BOTH_CONCAVE_WITH_HOLES }; -/// A collection of static methods for handling the no fit polygon creation. -struct Nfp { +template +using NfpResult = std::pair>; + +template struct MaxNfpLevel { + static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; +}; + // Shorthand for a pile of polygons template -using Shapes = typename ShapeLike::Shapes; +using Shapes = typename shapelike::Shapes; /** * Merge a bunch of polygons with the specified additional polygon. @@ -37,7 +63,7 @@ using Shapes = typename ShapeLike::Shapes; * polygons are disjuct than the resulting set will contain more polygons. */ template -static Shapes merge(const Shapes& /*shc*/) +inline Shapes merge(const Shapes& /*shc*/) { static_assert(always_false::value, "Nfp::merge(shapes, shape) unimplemented!"); @@ -55,7 +81,7 @@ static Shapes merge(const Shapes& /*shc*/) * polygons are disjuct than the resulting set will contain more polygons. */ template -static Shapes merge(const Shapes& shc, +inline Shapes merge(const Shapes& shc, const RawShape& sh) { auto m = merge(shc); @@ -63,31 +89,18 @@ static Shapes merge(const Shapes& shc, return merge(m); } -/** - * A method to get a vertex from a polygon that always maintains a relative - * position to the coordinate system: It is always the rightmost top vertex. - * - * This way it does not matter in what order the vertices are stored, the - * reference will be always the same for the same polygon. - */ -template -inline static TPoint referenceVertex(const RawShape& sh) -{ - return rightmostUpVertex(sh); -} - /** * Get the vertex of the polygon that is at the lowest values (bottom) in the Y * axis and if there are more than one vertices on the same Y coordinate than * the result will be the leftmost (with the highest X coordinate). */ template -static TPoint leftmostDownVertex(const RawShape& sh) +inline TPoint leftmostDownVertex(const RawShape& sh) { // find min x and min y vertex - auto it = std::min_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh), - _vsort); + auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh), + __nfp::_vsort); return *it; } @@ -98,26 +111,27 @@ static TPoint leftmostDownVertex(const RawShape& sh) * the result will be the rightmost (with the lowest X coordinate). */ template -static TPoint rightmostUpVertex(const RawShape& sh) +TPoint rightmostUpVertex(const RawShape& sh) { // find max x and max y vertex - auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh), - _vsort); + auto it = std::max_element(shapelike::cbegin(sh), shapelike::cend(sh), + __nfp::_vsort); return *it; } +/** + * A method to get a vertex from a polygon that always maintains a relative + * position to the coordinate system: It is always the rightmost top vertex. + * + * This way it does not matter in what order the vertices are stored, the + * reference will be always the same for the same polygon. + */ template -using NfpResult = std::pair>; - -/// Helper function to get the NFP -template -static NfpResult noFitPolygon(const RawShape& sh, - const RawShape& other) +inline TPoint referenceVertex(const RawShape& sh) { - NfpImpl nfp; - return nfp(sh, other); + return rightmostUpVertex(sh); } /** @@ -139,11 +153,11 @@ static NfpResult noFitPolygon(const RawShape& sh, * */ template -static NfpResult nfpConvexOnly(const RawShape& sh, +inline NfpResult nfpConvexOnly(const RawShape& sh, const RawShape& other) { using Vertex = TPoint; using Edge = _Segment; - using sl = ShapeLike; + namespace sl = shapelike; RawShape rsh; // Final nfp placeholder Vertex top_nfp; @@ -187,7 +201,7 @@ static NfpResult nfpConvexOnly(const RawShape& sh, sl::addVertex(rsh, edgelist.front().second()); // Sorting function for the nfp reference vertex search - auto& cmp = _vsort; + auto& cmp = __nfp::_vsort; // the reference (rightmost top) vertex so far top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp ); @@ -214,7 +228,7 @@ static NfpResult nfpConvexOnly(const RawShape& sh, } template -static NfpResult nfpSimpleSimple(const RawShape& cstationary, +NfpResult nfpSimpleSimple(const RawShape& cstationary, const RawShape& cother) { @@ -233,7 +247,7 @@ static NfpResult nfpSimpleSimple(const RawShape& cstationary, using Vertex = TPoint; using Coord = TCoord; using Edge = _Segment; - using sl = ShapeLike; + namespace sl = shapelike; using std::signbit; using std::sort; using std::vector; @@ -528,27 +542,16 @@ struct NfpImpl { } }; -template struct MaxNfpLevel { - static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; -}; - -private: - -// Do not specialize this... -template -static inline bool _vsort(const TPoint& v1, - const TPoint& v2) +/// Helper function to get the NFP +template +inline NfpResult noFitPolygon(const RawShape& sh, + const RawShape& other) { - using Coord = TCoord>; - Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); - auto diff = y1 - y2; - if(std::abs(diff) <= std::numeric_limits::epsilon()) - return x1 < x2; - - return diff < 0; + NfpImpl nfps; + return nfps(sh, other); } -}; +} } diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index eadd1e110bf..d2850d4eda4 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -9,10 +9,12 @@ #include #include "geometry_traits.hpp" -#include "optimizer.hpp" namespace libnest2d { +namespace sl = shapelike; +namespace pl = pointlike; + /** * \brief An item to be placed on a bin. * @@ -28,7 +30,6 @@ class _Item { using Coord = TCoord>; using Vertex = TPoint; using Box = _Box; - using sl = ShapeLike; // The original shape that gets encapsulated. RawShape sh_; @@ -438,7 +439,7 @@ class _Rectangle: public _Item { inline _Rectangle(Unit width, Unit height, // disable this ctor if o != CLOCKWISE enable_if_t< o == TO::CLOCKWISE, int> = 0 ): - _Item( ShapeLike::create( { + _Item( sl::create( { {0, 0}, {0, height}, {width, height}, @@ -452,7 +453,7 @@ class _Rectangle: public _Item { inline _Rectangle(Unit width, Unit height, // disable this ctor if o != COUNTER_CLOCKWISE enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ): - _Item( ShapeLike::create( { + _Item( sl::create( { {0, 0}, {width, 0}, {width, height}, @@ -473,12 +474,32 @@ class _Rectangle: public _Item { template inline bool _Item::isInside(const _Box>& box) const { - return ShapeLike::isInside(boundingBox(), box); + return sl::isInside(boundingBox(), box); } template inline bool _Item::isInside(const _Circle>& circ) const { - return ShapeLike::isInside(transformedShape(), circ); + return sl::isInside(transformedShape(), circ); +} + + +template using _ItemRef = std::reference_wrapper; +template using _ItemGroup = std::vector<_ItemRef>; + +template +struct ConstItemRange { + Iterator from; + Iterator to; + bool valid = false; + + ConstItemRange() = default; + ConstItemRange(Iterator f, Iterator t): from(f), to(t), valid(true) {} +}; + +template +inline ConstItemRange +rem(typename Container::const_iterator it, const Container& cont) { + return {std::next(it), cont.end()}; } /** @@ -515,8 +536,9 @@ class PlacementStrategyLike { */ using PackResult = typename PlacementStrategy::PackResult; - using ItemRef = std::reference_wrapper; - using ItemGroup = std::vector; + using ItemRef = _ItemRef; + using ItemGroup = _ItemGroup; + using DefaultIterator = typename ItemGroup::const_iterator; /** * @brief Constructor taking the bin and an optional configuration. @@ -544,20 +566,24 @@ class PlacementStrategyLike { * Try to pack an item with a result object that contains the packing * information for later accepting it. * - * \param item_store A container of items + * \param item_store A container of items that are intended to be packed + * later. Can be used by the placer to switch tactics. When it's knows that + * many items will come a greedy startegy may not be the best. + * \param from The iterator to the item from which the packing should start, + * including the pointed item + * \param count How many items should be packed. If the value is 1, than + * just the item pointed to by "from" argument should be packed. */ - template - inline PackResult trypack(Container& item_store, - typename Container::iterator from, - unsigned count = 1) { - using V = typename Container::value_type; - static_assert(std::is_convertible::value, - "Invalid Item container!"); - return impl_.trypack(item_store, from, count); + template + inline PackResult trypack( + Item& item, + const ConstItemRange& remaining = ConstItemRange()) + { + return impl_.trypack(item, remaining); } /** - * @brief A method to accept a previously tried item. + * @brief A method to accept a previously tried item (or items). * * If the pack result is a failure the method should ignore it. * @param r The result of a previous trypack call. @@ -565,10 +591,10 @@ class PlacementStrategyLike { inline void accept(PackResult& r) { impl_.accept(r); } /** - * @brief pack Try to pack an item and immediately accept it on success. + * @brief pack Try to pack and immediately accept it on success. * * A default implementation would be to call - * { auto&& r = trypack(item); accept(r); return r; } but we should let the + * { auto&& r = trypack(...); accept(r); return r; } but we should let the * implementor of the placement strategy to harvest any optimizations from * the absence of an intermadiate step. The above version can still be used * in the implementation. @@ -577,15 +603,12 @@ class PlacementStrategyLike { * @return Returns true if the item was packed or false if it could not be * packed. */ - template - inline bool pack(Container& item_store, - typename Container::iterator from, - unsigned count = 1) + template> + inline bool pack( + Item& item, + const Range& remaining = Range()) { - using V = typename Container::value_type; - static_assert(std::is_convertible::value, - "Invalid Item container!"); - return impl_.pack(item_store, from, count); + return impl_.pack(item, remaining); } /// Unpack the last element (remove it from the list of packed items). @@ -736,10 +759,9 @@ using _IndexedPackGroup = std::vector< * inside the provided bin. */ template -class Arranger { +class Nester { using TSel = SelectionStrategyLike; TSel selector_; - bool use_min_bb_rotation_ = false; public: using Item = typename PlacementStrategy::Item; using ItemRef = std::reference_wrapper; @@ -777,7 +799,7 @@ class Arranger { template - Arranger( TBinType&& bin, + Nester( TBinType&& bin, Unit min_obj_distance = 0, PConf&& pconfig = PConf(), SConf&& sconfig = SConf()): @@ -810,9 +832,9 @@ class Arranger { * the selection algorithm. */ template - inline PackGroup arrange(TIterator from, TIterator to) + inline PackGroup execute(TIterator from, TIterator to) { - return _arrange(from, to); + return _execute(from, to); } /** @@ -823,20 +845,20 @@ class Arranger { * input sequence size. */ template - inline IndexedPackGroup arrangeIndexed(TIterator from, TIterator to) + inline IndexedPackGroup executeIndexed(TIterator from, TIterator to) { - return _arrangeIndexed(from, to); + return _executeIndexed(from, to); } /// Shorthand to normal arrange method. template inline PackGroup operator() (TIterator from, TIterator to) { - return _arrange(from, to); + return _execute(from, to); } /// Set a progress indicatior function object for the selector. - inline Arranger& progressIndicator(ProgressFunction func) + inline Nester& progressIndicator(ProgressFunction func) { selector_.progressIndicator(func); return *this; } @@ -850,10 +872,6 @@ class Arranger { return ret; } - inline Arranger& useMinimumBoundigBoxRotation(bool s = true) { - use_min_bb_rotation_ = s; return *this; - } - private: template::value, IT> > - inline PackGroup _arrange(TIterator from, TIterator to, bool = false) + inline PackGroup _execute(TIterator from, TIterator to, bool = false) { - __arrange(from, to); + __execute(from, to); return lastResult(); } @@ -875,11 +893,11 @@ class Arranger { class IT = remove_cvref_t, class T = enable_if_t::value, IT> > - inline PackGroup _arrange(TIterator from, TIterator to, int = false) + inline PackGroup _execute(TIterator from, TIterator to, int = false) { item_cache_ = {from, to}; - __arrange(item_cache_.begin(), item_cache_.end()); + __execute(item_cache_.begin(), item_cache_.end()); return lastResult(); } @@ -892,11 +910,11 @@ class Arranger { // have to exist for the lifetime of this call. class T = enable_if_t< std::is_convertible::value, IT> > - inline IndexedPackGroup _arrangeIndexed(TIterator from, + inline IndexedPackGroup _executeIndexed(TIterator from, TIterator to, bool = false) { - __arrange(from, to); + __execute(from, to); return createIndexedPackGroup(from, to, selector_); } @@ -904,12 +922,12 @@ class Arranger { class IT = remove_cvref_t, class T = enable_if_t::value, IT> > - inline IndexedPackGroup _arrangeIndexed(TIterator from, + inline IndexedPackGroup _executeIndexed(TIterator from, TIterator to, int = false) { item_cache_ = {from, to}; - __arrange(item_cache_.begin(), item_cache_.end()); + __execute(item_cache_.begin(), item_cache_.end()); return createIndexedPackGroup(from, to, selector_); } @@ -941,37 +959,12 @@ class Arranger { return pg; } - Radians findBestRotation(Item& item) { - opt::StopCriteria stopcr; - stopcr.absolute_score_difference = 0.01; - stopcr.max_iterations = 10000; - opt::TOptimizer solver(stopcr); - - auto orig_rot = item.rotation(); - - auto result = solver.optimize_min([&item, &orig_rot](Radians rot){ - item.rotation(orig_rot + rot); - auto bb = item.boundingBox(); - return std::sqrt(bb.height()*bb.width()); - }, opt::initvals(Radians(0)), opt::bound(-Pi/2, Pi/2)); - - item.rotation(orig_rot); - - return std::get<0>(result.optimum); - } - - template inline void __arrange(TIter from, TIter to) + template inline void __execute(TIter from, TIter to) { if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { item.addOffset(static_cast(std::ceil(min_obj_distance_/2.0))); }); - if(use_min_bb_rotation_) - std::for_each(from, to, [this](Item& item){ - Radians rot = findBestRotation(item); - item.rotate(rot); - }); - selector_.template packItems( from, to, bin_, pconfig_); diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp index 71573e34ddb..af167837218 100644 --- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp @@ -27,11 +27,10 @@ class _BottomLeftPlacer: public PlacerBoilerplate< explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {} - template - PackResult trypack(Store& /*s*/, typename Store::iterator from, - unsigned /*count*/ = 1) + template> + PackResult trypack(Item& item, + const Range& = Range()) { - Item& item = *from; auto r = _trypack(item); if(!r && Base::config_.allow_rotations) { @@ -117,10 +116,10 @@ class _BottomLeftPlacer: public PlacerBoilerplate< const RawShape& scanpoly) { auto tsh = other.transformedShape(); - return ( ShapeLike::intersects(tsh, scanpoly) || - ShapeLike::isInside(tsh, scanpoly) ) && - ( !ShapeLike::intersects(tsh, item.rawShape()) && - !ShapeLike::isInside(tsh, item.rawShape()) ); + return ( sl::intersects(tsh, scanpoly) || + sl::isInside(tsh, scanpoly) ) && + ( !sl::intersects(tsh, item.rawShape()) && + !sl::isInside(tsh, item.rawShape()) ); } template @@ -131,25 +130,25 @@ class _BottomLeftPlacer: public PlacerBoilerplate< { auto tsh = other.transformedShape(); - bool inters_scanpoly = ShapeLike::intersects(tsh, scanpoly) && - !ShapeLike::touches(tsh, scanpoly); - bool inters_item = ShapeLike::intersects(tsh, item.rawShape()) && - !ShapeLike::touches(tsh, item.rawShape()); + bool inters_scanpoly = sl::intersects(tsh, scanpoly) && + !sl::touches(tsh, scanpoly); + bool inters_item = sl::intersects(tsh, item.rawShape()) && + !sl::touches(tsh, item.rawShape()); return ( inters_scanpoly || - ShapeLike::isInside(tsh, scanpoly)) && + sl::isInside(tsh, scanpoly)) && ( !inters_item && - !ShapeLike::isInside(tsh, item.rawShape()) + !sl::isInside(tsh, item.rawShape()) ); } - Container itemsInTheWayOf(const Item& item, const Dir dir) { + ItemGroup itemsInTheWayOf(const Item& item, const Dir dir) { // Get the left or down polygon, that has the same area as the shadow // of input item reflected to the left or downwards auto&& scanpoly = dir == Dir::LEFT? leftPoly(item) : downPoly(item); - Container ret; // packed items 'in the way' of item + ItemGroup ret; // packed items 'in the way' of item ret.reserve(items_.size()); // Predicate to find items that are 'in the way' for left (down) move @@ -178,18 +177,18 @@ class _BottomLeftPlacer: public PlacerBoilerplate< if(dir == Dir::LEFT) { getCoord = [](const Vertex& v) { return getX(v); }; - availableDistance = PointLike::horizontalDistance; + availableDistance = pointlike::horizontalDistance; availableDistanceSV = [](const Segment& s, const Vertex& v) { - auto ret = PointLike::horizontalDistance(v, s); + auto ret = pointlike::horizontalDistance(v, s); if(ret.second) ret.first = -ret.first; return ret; }; } else { getCoord = [](const Vertex& v) { return getY(v); }; - availableDistance = PointLike::verticalDistance; + availableDistance = pointlike::verticalDistance; availableDistanceSV = [](const Segment& s, const Vertex& v) { - auto ret = PointLike::verticalDistance(v, s); + auto ret = pointlike::verticalDistance(v, s); if(ret.second) ret.first = -ret.first; return ret; }; @@ -219,9 +218,9 @@ class _BottomLeftPlacer: public PlacerBoilerplate< assert(pleft.vertexCount() > 0); auto trpleft = pleft.transformedShape(); - auto first = ShapeLike::begin(trpleft); + auto first = sl::begin(trpleft); auto next = first + 1; - auto endit = ShapeLike::end(trpleft); + auto endit = sl::end(trpleft); while(next != endit) { Segment seg(*(first++), *(next++)); @@ -345,16 +344,16 @@ class _BottomLeftPlacer: public PlacerBoilerplate< // reserve for all vertices plus 2 for the left horizontal wall, 2 for // the additional vertices for maintaning min object distance - ShapeLike::reserve(rsh, finish-start+4); + sl::reserve(rsh, finish-start+4); /*auto addOthers = [&rsh, finish, start, &item](){ for(size_t i = start+1; i < finish; i++) - ShapeLike::addVertex(rsh, item.vertex(i)); + sl::addVertex(rsh, item.vertex(i)); };*/ auto reverseAddOthers = [&rsh, finish, start, &item](){ for(auto i = finish-1; i > start; i--) - ShapeLike::addVertex(rsh, item.vertex( + sl::addVertex(rsh, item.vertex( static_cast(i))); }; @@ -366,25 +365,25 @@ class _BottomLeftPlacer: public PlacerBoilerplate< // Clockwise polygon construction - ShapeLike::addVertex(rsh, topleft_vertex); + sl::addVertex(rsh, topleft_vertex); if(dir == Dir::LEFT) reverseAddOthers(); else { - ShapeLike::addVertex(rsh, getX(topleft_vertex), 0); - ShapeLike::addVertex(rsh, getX(bottomleft_vertex), 0); + sl::addVertex(rsh, getX(topleft_vertex), 0); + sl::addVertex(rsh, getX(bottomleft_vertex), 0); } - ShapeLike::addVertex(rsh, bottomleft_vertex); + sl::addVertex(rsh, bottomleft_vertex); if(dir == Dir::LEFT) { - ShapeLike::addVertex(rsh, 0, getY(bottomleft_vertex)); - ShapeLike::addVertex(rsh, 0, getY(topleft_vertex)); + sl::addVertex(rsh, 0, getY(bottomleft_vertex)); + sl::addVertex(rsh, 0, getY(topleft_vertex)); } else reverseAddOthers(); // Close the polygon - ShapeLike::addVertex(rsh, topleft_vertex); + sl::addVertex(rsh, topleft_vertex); return rsh; } diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index d74fe2b1d35..638d606e0f1 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -19,7 +19,7 @@ namespace libnest2d { namespace strategies { template struct NfpPConfig { - using ItemGroup = std::vector>>; + using ItemGroup = _ItemGroup<_Item>; enum class Alignment { CENTER, @@ -58,16 +58,6 @@ struct NfpPConfig { * * \param item The second parameter is the candidate item. * - * \param occupied_area The third parameter is the sum of areas of the - * items in the first parameter (no candidate item there) so you don't have - * to iterate through them if you only need their accumulated area. - * - * \param norm A norming factor for physical dimensions. E.g. if your score - * is the distance between the item and the bin center, you should divide - * that distance with the norming factor. If the score is an area than - * divide it with the square of the norming factor. Imagine it as a unit of - * distance. - * * \param remaining A container with the remaining items waiting to be * placed. You can use some features about the remaining items to alter to * score of the current placement. If you know that you have to leave place @@ -81,8 +71,8 @@ struct NfpPConfig { * decisions (for you or a more intelligent AI). * */ - std::function&, const _Item&, - double, double, const ItemGroup&)> + std::function&, const _Item&, + const ItemGroup&)> object_function; /** @@ -134,11 +124,11 @@ template class EdgeCache { void createCache(const RawShape& sh) { { // For the contour - auto first = ShapeLike::cbegin(sh); + auto first = shapelike::cbegin(sh); auto next = std::next(first); - auto endit = ShapeLike::cend(sh); + auto endit = shapelike::cend(sh); - contour_.distances.reserve(ShapeLike::contourVertexCount(sh)); + contour_.distances.reserve(shapelike::contourVertexCount(sh)); while(next != endit) { contour_.emap.emplace_back(*(first++), *(next++)); @@ -147,7 +137,7 @@ template class EdgeCache { } } - for(auto& h : ShapeLike::holes(sh)) { // For the holes + for(auto& h : shapelike::holes(sh)) { // For the holes auto first = h.begin(); auto next = std::next(first); auto endit = h.end(); @@ -295,11 +285,11 @@ template class EdgeCache { }; -template -struct Lvl { static const NfpLevel value = lvl; }; +template +struct Lvl { static const nfp::NfpLevel value = lvl; }; template -inline void correctNfpPosition(Nfp::NfpResult& nfp, +inline void correctNfpPosition(nfp::NfpResult& nfp, const _Item& stationary, const _Item& orbiter) { @@ -319,46 +309,47 @@ inline void correctNfpPosition(Nfp::NfpResult& nfp, auto dtouch = touch_sh - touch_other; auto top_other = orbiter.rightmostTopVertex() + dtouch; auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point - ShapeLike::translate(nfp.first, dnfp); + shapelike::translate(nfp.first, dnfp); } template -inline void correctNfpPosition(Nfp::NfpResult& nfp, +inline void correctNfpPosition(nfp::NfpResult& nfp, const RawShape& stationary, const _Item& orbiter) { - auto touch_sh = Nfp::rightmostUpVertex(stationary); + auto touch_sh = nfp::rightmostUpVertex(stationary); auto touch_other = orbiter.leftmostBottomVertex(); auto dtouch = touch_sh - touch_other; auto top_other = orbiter.rightmostTopVertex() + dtouch; auto dnfp = top_other - nfp.second; - ShapeLike::translate(nfp.first, dnfp); + shapelike::translate(nfp.first, dnfp); } template -Nfp::Shapes nfp( const Container& polygons, +nfp::Shapes calcnfp( const Container& polygons, const _Item& trsh, - Lvl) + Lvl) { using Item = _Item; + using namespace nfp; - Nfp::Shapes nfps; + nfp::Shapes nfps; // int pi = 0; for(Item& sh : polygons) { - auto subnfp_r = Nfp::noFitPolygon( + auto subnfp_r = noFitPolygon( sh.transformedShape(), trsh.transformedShape()); #ifndef NDEBUG - auto vv = ShapeLike::isValid(sh.transformedShape()); + auto vv = sl::isValid(sh.transformedShape()); assert(vv.first); - auto vnfp = ShapeLike::isValid(subnfp_r.first); + auto vnfp = sl::isValid(subnfp_r.first); assert(vnfp.first); #endif correctNfpPosition(subnfp_r, sh, trsh); - nfps = Nfp::merge(nfps, subnfp_r.first); + nfps = nfp::merge(nfps, subnfp_r.first); // double SCALE = 1000000; // using SVGWriter = svg::SVGWriter; @@ -379,31 +370,32 @@ Nfp::Shapes nfp( const Container& polygons, } template -Nfp::Shapes nfp( const Container& polygons, +nfp::Shapes calcnfp( const Container& polygons, const _Item& trsh, Level) { + using namespace nfp; using Item = _Item; - Nfp::Shapes nfps; + Shapes nfps; auto& orb = trsh.transformedShape(); bool orbconvex = trsh.isContourConvex(); for(Item& sh : polygons) { - Nfp::NfpResult subnfp; + nfp::NfpResult subnfp; auto& stat = sh.transformedShape(); if(sh.isContourConvex() && orbconvex) - subnfp = Nfp::noFitPolygon(stat, orb); + subnfp = nfp::noFitPolygon(stat, orb); else if(orbconvex) - subnfp = Nfp::noFitPolygon(stat, orb); + subnfp = nfp::noFitPolygon(stat, orb); else - subnfp = Nfp::noFitPolygon(stat, orb); + subnfp = nfp::noFitPolygon(stat, orb); correctNfpPosition(subnfp, sh, trsh); - nfps = Nfp::merge(nfps, subnfp.first); + nfps = nfp::merge(nfps, subnfp.first); } return nfps; @@ -448,7 +440,6 @@ Nfp::Shapes nfp( const Container& polygons, template _Circle> minimizeCircle(const RawShape& sh) { - using sl = ShapeLike; using pl = PointLike; using Point = TPoint; using Coord = TCoord; @@ -518,16 +509,14 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer; - using sl = ShapeLike; - + using MaxNfpLevel = nfp::MaxNfpLevel; public: - using Pile = Nfp::Shapes; + using Pile = nfp::Shapes; inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin), - norm_(std::sqrt(sl::area(bin))) {} + norm_(std::sqrt(sl::area(bin))) {} _NofitPolyPlacer(const _NofitPolyPlacer&) = default; _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default; @@ -577,20 +566,17 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer - PackResult trypack(Container& items, - typename Container::iterator from, - unsigned /*count*/ = 1) - { - return trypack(*from, {std::next(from), items.end()}); - } - - PackResult trypack(Item& item, ItemGroup remaining) { + template> + PackResult trypack( + Item& item, + const Range& remaining = Range()) { PackResult ret; bool can_pack = false; + auto remlist = ItemGroup(remaining.from, remaining.to); + if(items_.empty()) { setInitialPosition(item); can_pack = item.isInside(bin_); @@ -602,7 +588,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer nfps; + nfp::Shapes nfps; for(auto rot : config_.rotations) { @@ -615,8 +601,8 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer()); - auto iv = Nfp::referenceVertex(trsh); + nfps = calcnfp(items_, item, Lvl()); + auto iv = nfp::referenceVertex(trsh); auto startpos = item.translation(); @@ -644,7 +630,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer pile; + nfp::Shapes pile; pile.reserve(items_.size()+1); double pile_area = 0; for(Item& mitem : items_) { @@ -652,17 +638,15 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer& /*pile*/, + [this, &merged_pile, &pile_area]( + nfp::Shapes& /*pile*/, const Item& item, - double occupied_area, - double norm, const ItemGroup& /*remaining*/) { merged_pile.emplace_back(item.transformedShape()); @@ -670,7 +654,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer> cbin) { if(items_.empty()) return; - Nfp::Shapes m; + nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); @@ -842,7 +823,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer m; + nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); auto&& bb = sl::boundingBox(m); @@ -884,7 +865,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer(bin_); + auto bbin = sl::boundingBox(bin_); switch(config_.starting_point) { case Config::Alignment::CENTER: { @@ -920,7 +901,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer(bin_); + Box binbb = sl::boundingBox(bin_); Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) }; diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp index f31a9343c12..1a0730d8800 100644 --- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp @@ -7,10 +7,7 @@ namespace libnest2d { namespace strategies { struct EmptyConfig {}; -template>> - > +template class PlacerBoilerplate { mutable bool farea_valid_ = false; mutable double farea_ = 0.0; @@ -22,7 +19,8 @@ class PlacerBoilerplate { using Coord = TCoord; using Unit = Coord; using Config = Cfg; - using Container = Store; + using ItemGroup = _ItemGroup; + using DefaultIter = typename ItemGroup::const_iterator; class PackResult { Item *item_ptr_; @@ -39,8 +37,6 @@ class PlacerBoilerplate { operator bool() { return item_ptr_ != nullptr; } }; - using ItemGroup = const Container&; - inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin) { items_.reserve(cap); @@ -56,11 +52,10 @@ class PlacerBoilerplate { config_ = config; } - template - bool pack(Container& items, - typename Container::iterator from, - unsigned count = 1) { - auto&& r = static_cast(this)->trypack(items, from, count); + template> + bool pack(Item& item, + const Range& rem = Range()) { + auto&& r = static_cast(this)->trypack(item, rem); if(r) { items_.push_back(*(r.item_ptr_)); farea_valid_ = false; @@ -82,7 +77,7 @@ class PlacerBoilerplate { farea_valid_ = false; } - inline ItemGroup getItems() const { return items_; } + inline const ItemGroup& getItems() const { return items_; } inline void clearItems() { items_.clear(); @@ -113,7 +108,7 @@ class PlacerBoilerplate { protected: BinType bin_; - Container items_; + ItemGroup items_; Cfg config_; }; @@ -124,6 +119,7 @@ using Base::items_; \ using Base::config_; \ public: \ using typename Base::Item; \ +using typename Base::ItemGroup; \ using typename Base::BinType; \ using typename Base::Config; \ using typename Base::Vertex; \ @@ -131,7 +127,6 @@ using typename Base::Segment; \ using typename Base::PackResult; \ using typename Base::Coord; \ using typename Base::Unit; \ -using typename Base::Container; \ private: } diff --git a/xs/src/libnest2d/libnest2d/rotfinder.hpp b/xs/src/libnest2d/libnest2d/rotfinder.hpp new file mode 100644 index 00000000000..525fd875959 --- /dev/null +++ b/xs/src/libnest2d/libnest2d/rotfinder.hpp @@ -0,0 +1,41 @@ +#ifndef ROTFINDER_HPP +#define ROTFINDER_HPP + +#include +#include +#include + +namespace libnest2d { + +template +Radians findBestRotation(_Item& item) { + opt::StopCriteria stopcr; + stopcr.absolute_score_difference = 0.01; + stopcr.max_iterations = 10000; + opt::TOptimizer solver(stopcr); + + auto orig_rot = item.rotation(); + + auto result = solver.optimize_min([&item, &orig_rot](Radians rot){ + item.rotation(orig_rot + rot); + auto bb = item.boundingBox(); + return std::sqrt(bb.height()*bb.width()); + }, opt::initvals(Radians(0)), opt::bound(-Pi/2, Pi/2)); + + item.rotation(orig_rot); + + return std::get<0>(result.optimum); +} + +template +void findMinimumBoundingBoxRotations(Iterator from, Iterator to) { + using V = typename std::iterator_traits::value_type; + std::for_each(from, to, [](V& item){ + Radians rot = findBestRotation(item); + item.rotate(rot); + }); +} + +} + +#endif // ROTFINDER_HPP diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 34d6d05c5c2..8c02dc37332 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -118,7 +118,7 @@ class _DJDHeuristic: public SelectionBoilerplate { using Placer = PlacementStrategyLike; using ItemList = std::list; - const double bin_area = ShapeLike::area(bin); + const double bin_area = sl::area(bin); const double w = bin_area * config_.waste_increment; const double INITIAL_FILL_PROPORTION = config_.initial_fill_proportion; @@ -227,10 +227,14 @@ class _DJDHeuristic: public SelectionBoilerplate { bool ret = false; auto it = not_packed.begin(); + auto pack = [&placer, ¬_packed](ItemListIt it) { + return placer.pack(*it, rem(it, not_packed)); + }; + while(it != not_packed.end() && !ret && free_area - (item_area = it->get().area()) <= waste) { - if(item_area <= free_area && placer.pack(not_packed, it) ) { + if(item_area <= free_area && pack(it) ) { free_area -= item_area; filled_area = bin_area - free_area; ret = true; @@ -270,6 +274,11 @@ class _DJDHeuristic: public SelectionBoilerplate { auto it2 = it; std::vector wrong_pairs; + using std::placeholders::_1; + + auto trypack = [&placer, ¬_packed](ItemListIt it) { + return placer.trypack(*it, rem(it, not_packed)); + }; while(it != endit && !ret && free_area - (item_area = it->get().area()) - @@ -278,7 +287,7 @@ class _DJDHeuristic: public SelectionBoilerplate { if(item_area + smallestPiece(it, not_packed)->get().area() > free_area ) { it++; continue; } - auto pr = placer.trypack(not_packed, it); + auto pr = trypack(it); // First would fit it2 = not_packed.begin(); @@ -294,14 +303,14 @@ class _DJDHeuristic: public SelectionBoilerplate { } placer.accept(pr); - auto pr2 = placer.trypack(not_packed, it2); + auto pr2 = trypack(it2); if(!pr2) { placer.unpackLast(); // remove first if(try_reverse) { - pr2 = placer.trypack(not_packed, it2); + pr2 = trypack(it2); if(pr2) { placer.accept(pr2); - auto pr12 = placer.trypack(not_packed, it); + auto pr12 = trypack(it); if(pr12) { placer.accept(pr12); ret = true; @@ -365,6 +374,14 @@ class _DJDHeuristic: public SelectionBoilerplate { return it->get().area(); }; + auto trypack = [&placer, ¬_packed](ItemListIt it) { + return placer.trypack(*it, rem(it, not_packed)); + }; + + auto pack = [&placer, ¬_packed](ItemListIt it) { + return placer.pack(*it, rem(it, not_packed)); + }; + while (it != endit && !ret) { // drill down 1st level // We need to determine in each iteration the largest, second @@ -394,7 +411,7 @@ class _DJDHeuristic: public SelectionBoilerplate { it++; continue; } - auto pr = placer.trypack(not_packed, it); + auto pr = trypack(it); // Check for free area and try to pack the 1st item... if(!pr) { it++; continue; } @@ -420,15 +437,15 @@ class _DJDHeuristic: public SelectionBoilerplate { bool can_pack2 = false; placer.accept(pr); - auto pr2 = placer.trypack(not_packed, it2); + auto pr2 = trypack(it2); auto pr12 = pr; if(!pr2) { placer.unpackLast(); // remove first if(try_reverse) { - pr2 = placer.trypack(not_packed, it2); + pr2 = trypack(it2); if(pr2) { placer.accept(pr2); - pr12 = placer.trypack(not_packed, it); + pr12 = trypack(it); if(pr12) can_pack2 = true; placer.unpackLast(); } @@ -463,7 +480,7 @@ class _DJDHeuristic: public SelectionBoilerplate { if(a3_sum > free_area) { it3++; continue; } placer.accept(pr12); placer.accept(pr2); - bool can_pack3 = placer.pack(not_packed, it3); + bool can_pack3 = pack(it3); if(!can_pack3) { placer.unpackLast(); @@ -476,13 +493,14 @@ class _DJDHeuristic: public SelectionBoilerplate { std::array candidates = {it, it2, it3}; - auto tryPack = [&placer, &candidates, ¬_packed]( + auto tryPack = [&placer, &candidates, ¬_packed, + &pack]( const decltype(indices)& idx) { std::array packed = {false}; for(auto id : idx) packed.at(id) = - placer.pack(not_packed, candidates[id]); + pack(candidates[id]); bool check = std::all_of(packed.begin(), @@ -536,7 +554,7 @@ class _DJDHeuristic: public SelectionBoilerplate { { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); p.configure(pconfig); - if(!p.pack(store_, it)) { + if(!p.pack(*it, rem(it, store_))) { it = store_.erase(it); } else it++; } @@ -601,7 +619,7 @@ class _DJDHeuristic: public SelectionBoilerplate { while(it != not_packed.end() && filled_area < INITIAL_FILL_AREA) { - if(placer.pack(not_packed, it)) { + if(placer.pack(*it, rem(it, not_packed))) { filled_area += it->get().area(); free_area = bin_area - filled_area; it = not_packed.erase(it); diff --git a/xs/src/libnest2d/libnest2d/selections/filler.hpp b/xs/src/libnest2d/libnest2d/selections/filler.hpp index ca1281fe6f3..b20455b0e4b 100644 --- a/xs/src/libnest2d/libnest2d/selections/filler.hpp +++ b/xs/src/libnest2d/libnest2d/selections/filler.hpp @@ -56,18 +56,13 @@ class _FillerSelection: public SelectionBoilerplate { std::sort(store_.begin(), store_.end(), sortfunc); -// Container a = {store_[0], store_[1], store_[4], store_[5] }; -//// a.insert(a.end(), store_.end()-10, store_.end()); -// store_ = a; - PlacementStrategyLike placer(bin); placer.configure(pconfig); auto it = store_.begin(); while(it != store_.end()) { - if(!placer.pack(store_, it)) { + if(!placer.pack(*it, {std::next(it), store_.end()})) { if(packed_bins_.back().empty()) ++it; -// makeProgress(placer); placer.clearItems(); packed_bins_.emplace_back(); } else { @@ -76,9 +71,6 @@ class _FillerSelection: public SelectionBoilerplate { } } -// if(was_packed) { -// packed_bins_.push_back(placer.getItems()); -// } } }; diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 93ca02b1e65..1312f9874de 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -61,7 +61,7 @@ class _FirstFitSelection: public SelectionBoilerplate { { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); p.configure(pconfig); - if(!p.pack(store_, it)) { + if(!p.pack(*it)) { it = store_.erase(it); } else it++; } @@ -73,8 +73,9 @@ class _FirstFitSelection: public SelectionBoilerplate { while(!was_packed) { for(size_t j = 0; j < placers.size() && !was_packed; j++) { - if((was_packed = placers[j].pack(store_, it))) - makeProgress(placers[j], j); + if((was_packed = + placers[j].pack(*it, rem(it, store_) ))) + makeProgress(placers[j], j); } if(!was_packed) { diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index 79832b6830f..197ff659813 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -110,7 +110,7 @@ TEST(GeometryAlgorithms, boundingCircle) { ASSERT_EQ(c.center().Y, 0); ASSERT_DOUBLE_EQ(c.radius(), 10); - ShapeLike::translate(p, PointImpl{10, 10}); + shapelike::translate(p, PointImpl{10, 10}); c = boundingCircle(p); ASSERT_EQ(c.center().X, 10); @@ -124,8 +124,8 @@ TEST(GeometryAlgorithms, boundingCircle) { c = boundingCircle(part.transformedShape()); if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl; - else for(auto v : ShapeLike::getContour(part.transformedShape()) ) { - auto d = PointLike::distance(v, c.center()); + else for(auto v : shapelike::getContour(part.transformedShape()) ) { + auto d = pointlike::distance(v, c.center()); if(d > c.radius() ) { auto e = std::abs( 1.0 - d/c.radius()); ASSERT_LE(e, 1e-3); @@ -144,14 +144,14 @@ TEST(GeometryAlgorithms, Distance) { Point p2 = {10, 0}; Point p3 = {10, 10}; - ASSERT_DOUBLE_EQ(PointLike::distance(p1, p2), 10); - ASSERT_DOUBLE_EQ(PointLike::distance(p1, p3), sqrt(200)); + ASSERT_DOUBLE_EQ(pointlike::distance(p1, p2), 10); + ASSERT_DOUBLE_EQ(pointlike::distance(p1, p3), sqrt(200)); Segment seg(p1, p3); - ASSERT_DOUBLE_EQ(PointLike::distance(p2, seg), 7.0710678118654755); + ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); - auto result = PointLike::horizontalDistance(p2, seg); + auto result = pointlike::horizontalDistance(p2, seg); auto check = [](Coord val, Coord expected) { if(std::is_floating_point::value) @@ -164,11 +164,11 @@ TEST(GeometryAlgorithms, Distance) { ASSERT_TRUE(result.second); check(result.first, 10); - result = PointLike::verticalDistance(p2, seg); + result = pointlike::verticalDistance(p2, seg); ASSERT_TRUE(result.second); check(result.first, -10); - result = PointLike::verticalDistance(Point{10, 20}, seg); + result = pointlike::verticalDistance(Point{10, 20}, seg); ASSERT_TRUE(result.second); check(result.first, 10); @@ -176,12 +176,12 @@ TEST(GeometryAlgorithms, Distance) { Point p4 = {80, 0}; Segment seg2 = { {0, 0}, {0, 40} }; - result = PointLike::horizontalDistance(p4, seg2); + result = pointlike::horizontalDistance(p4, seg2); ASSERT_TRUE(result.second); check(result.first, 80); - result = PointLike::verticalDistance(p4, seg2); + result = pointlike::verticalDistance(p4, seg2); // Point should not be related to the segment ASSERT_FALSE(result.second); @@ -209,7 +209,7 @@ TEST(GeometryAlgorithms, Area) { {61, 97} }; - ASSERT_TRUE(ShapeLike::area(item.transformedShape()) > 0 ); + ASSERT_TRUE(shapelike::area(item.transformedShape()) > 0 ); } TEST(GeometryAlgorithms, IsPointInsidePolygon) { @@ -287,7 +287,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) Item leftp(placer.leftPoly(item)); - ASSERT_TRUE(ShapeLike::isValid(leftp.rawShape()).first); + ASSERT_TRUE(shapelike::isValid(leftp.rawShape()).first); ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount()); for(unsigned long i = 0; i < leftControl.vertexCount(); i++) { @@ -297,7 +297,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) Item downp(placer.downPoly(item)); - ASSERT_TRUE(ShapeLike::isValid(downp.rawShape()).first); + ASSERT_TRUE(shapelike::isValid(downp.rawShape()).first); ASSERT_EQ(downp.vertexCount(), downControl.vertexCount()); for(unsigned long i = 0; i < downControl.vertexCount(); i++) { @@ -334,7 +334,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) {20, 20} }; - Arranger arrange(Box(210, 250)); + Nester arrange(Box(210, 250)); auto groups = arrange(rects.begin(), rects.end()); @@ -387,7 +387,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) Coord min_obj_distance = 5; - Arranger arrange(Box(210, 250), + Nester arrange(Box(210, 250), min_obj_distance); auto groups = arrange(rects.begin(), rects.end()); @@ -438,7 +438,7 @@ R"raw( setX(v, getX(v)/SCALE); rbin.setVertex(i, v); } - out << ShapeLike::serialize(rbin.rawShape()) << std::endl; + out << shapelike::serialize(rbin.rawShape()) << std::endl; for(Item& sh : r) { Item tsh(sh.transformedShape()); for(unsigned i = 0; i < tsh.vertexCount(); i++) { @@ -447,7 +447,7 @@ R"raw( setX(v, getX(v)/SCALE); tsh.setVertex(i, v); } - out << ShapeLike::serialize(tsh.rawShape()) << std::endl; + out << shapelike::serialize(tsh.rawShape()) << std::endl; } out << "\n" << std::endl; } @@ -471,8 +471,8 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { auto next = it; int i = 0; while(it != input.end() && ++next != input.end()) { - placer.pack(input, it); - placer.pack(input, next); + placer.pack(*it); + placer.pack(*next); auto result = placer.getItems(); bool valid = true; @@ -701,7 +701,7 @@ std::vector nfp_concave_testdata = { } }; -template +template void testNfp(const std::vector& testdata) { using namespace libnest2d; @@ -716,12 +716,12 @@ void testNfp(const std::vector& testdata) { orbiter.translate({210*SCALE, 0}); - auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(), + auto&& nfp = nfp::noFitPolygon(stationary.rawShape(), orbiter.transformedShape()); strategies::correctNfpPosition(nfp, stationary, orbiter); - auto v = ShapeLike::isValid(nfp.first); + auto v = shapelike::isValid(nfp.first); if(!v.first) { std::cout << v.second << std::endl; @@ -733,7 +733,7 @@ void testNfp(const std::vector& testdata) { int i = 0; auto rorbiter = orbiter.transformedShape(); - auto vo = Nfp::referenceVertex(rorbiter); + auto vo = nfp::referenceVertex(rorbiter); ASSERT_TRUE(stationary.isInside(infp)); @@ -774,7 +774,7 @@ void testNfp(const std::vector& testdata) { } TEST(GeometryAlgorithms, nfpConvexConvex) { - testNfp(nfp_testdata); + testNfp(nfp_testdata); } //TEST(GeometryAlgorithms, nfpConcaveConcave) { @@ -807,7 +807,7 @@ TEST(GeometryAlgorithms, pointOnPolygonContour) { for(int i = 0; i <= 100; i++) { auto v = ecache.coords(i*(0.01)); - ASSERT_TRUE(ShapeLike::touches(v, input.transformedShape())); + ASSERT_TRUE(shapelike::touches(v, input.transformedShape())); } } @@ -821,17 +821,17 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) { rect2.translate({10, 0}); rect3.translate({25, 0}); - ShapeLike::Shapes pile; + shapelike::Shapes pile; pile.push_back(rect1.transformedShape()); pile.push_back(rect2.transformedShape()); - auto result = Nfp::merge(pile, rect3.transformedShape()); + auto result = nfp::merge(pile, rect3.transformedShape()); ASSERT_EQ(result.size(), 1); Rectangle ref(45, 15); - ASSERT_EQ(ShapeLike::area(result.front()), ref.area()); + ASSERT_EQ(shapelike::area(result.front()), ref.area()); } int main(int argc, char **argv) { diff --git a/xs/src/libnest2d/tools/svgtools.hpp b/xs/src/libnest2d/tools/svgtools.hpp index 3a83caa7072..776dd5a1a8a 100644 --- a/xs/src/libnest2d/tools/svgtools.hpp +++ b/xs/src/libnest2d/tools/svgtools.hpp @@ -56,14 +56,14 @@ class SVGWriter { auto d = static_cast( std::round(conf_.height*conf_.mm_in_coord_units) ); - auto& contour = ShapeLike::getContour(tsh); + auto& contour = shapelike::getContour(tsh); for(auto& v : contour) setY(v, -getY(v) + d); - auto& holes = ShapeLike::holes(tsh); + auto& holes = shapelike::holes(tsh); for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d); } - currentLayer() += ShapeLike::serialize(tsh, + currentLayer() += shapelike::serialize(tsh, 1.0/conf_.mm_in_coord_units) + "\n"; } diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 79371cdb27a..cc4bfff0f6c 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -104,8 +104,7 @@ using ItemGroup = std::vector>; std::tuple objfunc(const PointImpl& bincenter, double bin_area, - ShapeLike::Shapes& pile, // The currently arranged pile - double pile_area, + sl::Shapes& pile, // The currently arranged pile const Item &item, double norm, // A norming factor for physical dimensions std::vector& areacache, // pile item areas will be cached @@ -114,8 +113,6 @@ objfunc(const PointImpl& bincenter, const ItemGroup& remaining ) { - using pl = PointLike; - using sl = ShapeLike; using Coord = TCoord; static const double BIG_ITEM_TRESHOLD = 0.02; @@ -150,7 +147,7 @@ objfunc(const PointImpl& bincenter, // Calculate the full bounding box of the pile with the candidate item pile.emplace_back(item.transformedShape()); - auto fullbb = ShapeLike::boundingBox(pile); + auto fullbb = sl::boundingBox(pile); pile.pop_back(); // The bounding box of the big items (they will accumulate in the center @@ -283,21 +280,23 @@ class _ArrBase { protected: using Placer = strategies::_NofitPolyPlacer; using Selector = FirstFitSelection; - using Packer = Arranger; + using Packer = Nester; using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; - using Pile = ShapeLike::Shapes; + using Pile = sl::Shapes; Packer pck_; PConfig pconf_; // Placement configuration double bin_area_; std::vector areacache_; SpatIndex rtree_; + double norm_; public: _ArrBase(const TBin& bin, Distance dist, std::function progressind): - pck_(bin, dist), bin_area_(ShapeLike::area(bin)) + pck_(bin, dist), bin_area_(sl::area(bin)), + norm_(std::sqrt(sl::area(bin))) { fillConfig(pconf_); pck_.progressIndicator(progressind); @@ -306,7 +305,7 @@ class _ArrBase { template inline IndexedPackGroup operator()(Args&&...args) { areacache_.clear(); rtree_.clear(); - return pck_.arrangeIndexed(std::forward(args)...); + return pck_.executeIndexed(std::forward(args)...); } }; @@ -321,21 +320,17 @@ class AutoArranger: public _ArrBase { pconf_.object_function = [this, bin] ( Pile& pile, const Item &item, - double pile_area, - double norm, const ItemGroup& rem) { auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_, - rtree_, - rem); + item, norm_, areacache_, rtree_, rem); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); auto wdiff = fullbb.width() - bin.width(); auto hdiff = fullbb.height() - bin.height(); - if(wdiff > 0) score += std::pow(wdiff, 2) / norm; - if(hdiff > 0) score += std::pow(hdiff, 2) / norm; + if(wdiff > 0) score += std::pow(wdiff, 2) / norm_; + if(hdiff > 0) score += std::pow(hdiff, 2) / norm_; return score; }; @@ -357,31 +352,28 @@ class AutoArranger: public _ArrBase { pconf_.object_function = [this, &bin] ( Pile& pile, const Item &item, - double pile_area, - double norm, const ItemGroup& rem) { - auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_, - rtree_, rem); + auto result = objfunc(bin.center(), bin_area_, pile, item, norm_, + areacache_, rtree_, rem); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); - auto d = PointLike::distance(fullbb.minCorner(), + auto d = pl::distance(fullbb.minCorner(), fullbb.maxCorner()); auto diff = d - 2*bin.radius(); if(diff > 0) { if( item.area() > 0.01*bin_area_ && item.vertexCount() < 30) { pile.emplace_back(item.transformedShape()); - auto chull = ShapeLike::convexHull(pile); + auto chull = sl::convexHull(pile); pile.pop_back(); auto C = strategies::boundingCircle(chull); auto rdiff = C.radius() - bin.radius(); if(rdiff > 0) { - score += std::pow(rdiff, 3) / norm; + score += std::pow(rdiff, 3) / norm_; } } } @@ -403,14 +395,11 @@ class AutoArranger: public _ArrBase { pconf_.object_function = [this, &bin] ( Pile& pile, const Item &item, - double pile_area, - double norm, const ItemGroup& rem) { - auto binbb = ShapeLike::boundingBox(bin); - auto result = objfunc(binbb.center(), bin_area_, pile, - pile_area, item, norm, areacache_, - rtree_, rem); + auto binbb = sl::boundingBox(bin); + auto result = objfunc(binbb.center(), bin_area_, pile, item, norm_, + areacache_, rtree_, rem); double score = std::get<0>(result); return score; @@ -430,13 +419,10 @@ class AutoArranger: public _ArrBase { this->pconf_.object_function = [this] ( Pile& pile, const Item &item, - double pile_area, - double norm, const ItemGroup& rem) { - auto result = objfunc({0, 0}, 0, pile, pile_area, - item, norm, areacache_, - rtree_, rem); + auto result = objfunc({0, 0}, 0, pile, item, norm_, + areacache_, rtree_, rem); return std::get<0>(result); }; @@ -711,7 +697,7 @@ bool arrange(Model &model, coordf_t min_obj_distance, using P = libnest2d::PolygonImpl; auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - P irrbed = ShapeLike::create(std::move(ctour)); + P irrbed = sl::create(std::move(ctour)); AutoArranger

arrange(irrbed, min_obj_distance, progressind); From 0ea45576327fc974655dccc2234e269f6ac60bb8 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 9 Aug 2018 21:15:49 +0200 Subject: [PATCH 14/63] Improved accuracy of slicing (triangle cutting) code, improved debugging outputs and asserts of the slicing code. Disabled detection of concave corners with horizontal faces, as too often there were found models with badly triangulated faces, see for example GH issue #895. --- xs/src/libslic3r/TriangleMesh.cpp | 97 ++++++++++++++++++++----------- xs/src/libslic3r/Utils.hpp | 1 + xs/src/libslic3r/utils.cpp | 10 ++++ xs/xsp/XS.xsp | 5 ++ 4 files changed, 78 insertions(+), 35 deletions(-) diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index b83e0dae648..0941c11ef13 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -15,16 +15,20 @@ #include +// for SLIC3R_DEBUG_SLICE_PROCESSING +#include "libslic3r.h" + #if 0 #define DEBUG #define _DEBUG #undef NDEBUG + #define SLIC3R_DEBUG +// #define SLIC3R_TRIANGLEMESH_DEBUG #endif #include -#ifdef SLIC3R_DEBUG -// #define SLIC3R_TRIANGLEMESH_DEBUG +#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING) #include "SVG.hpp" #endif @@ -758,8 +762,22 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vectory; il.a_id = a_id; il.b_id = b_id; + assert(il.a != il.b); (*lines)[layer_idx].emplace_back(il); } } else @@ -849,16 +868,6 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vectorx - a->x; - float v1_y = b->y - a->y; - float v2_x = c->x - a->x; - float v2_y = c->y - a->y; - float dir = (b->x - a->x) * (c->y - a->y) - (b->y - a->y) * (c->x - a->x); - return dir; -} - // Return true, if the facet has been sliced and line_out has been filled. TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( float slice_z, const stl_facet &facet, const int facet_idx, @@ -873,10 +882,10 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( // Reorder vertices so that the first one is the one with lowest Z. // This is needed to get all intersection lines in a consistent order // (external on the right of the line) - int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); - for (int j = i; j - i < 3; ++ j) { // loop through facet edges + const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; + int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); + for (int j = i; j - i < 3; ++j) { // loop through facet edges int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; - const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; const stl_vertex *a = &this->v_scaled_shared[a_id]; @@ -888,6 +897,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( const stl_vertex &v0 = this->v_scaled_shared[vertices[0]]; const stl_vertex &v1 = this->v_scaled_shared[vertices[1]]; const stl_vertex &v2 = this->v_scaled_shared[vertices[2]]; + const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal; // We may ignore this edge for slicing purposes, but we may still use it for object cutting. FacetSliceType result = Slicing; const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx]; @@ -897,23 +907,23 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( // Mark neighbor edges, which do not have a neighbor. uint32_t edges = 0; uint32_t mask = IntersectionLine::EDGE0; - for (int nbr_idx = 2; nbr_idx != 5; ++ nbr_idx, mask <<= 1) + for (int nbr_idx = 0; nbr_idx != 3; ++ nbr_idx, mask <<= 1) // If the neighbor with an edge starting with a vertex idx (nbr_idx - 2) shares no // opposite face, add it to the edges to process when slicing. - if (nbr.neighbor[nbr_idx % 3] == -1) + if (nbr.neighbor[nbr_idx] == -1) // Mark this edge. edges |= mask; // Use some edges of this triangle for slicing only if at least one of its edge does not have an opposite face. result = (edges == 0) ? Cutting : Slicing; line_out->flags |= edges; - if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) { + if (normal.z < 0) { // If normal points downwards this is a bottom horizontal facet so we reverse its point order. std::swap(a, b); std::swap(a_id, b_id); } } else { // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane. - int nbr_idx = (j + 2) % 3; + int nbr_idx = j % 3; int nbr_face = nbr.neighbor[nbr_idx]; // Is the third vertex below the cutting plane? bool third_below = v0.z < slice_z || v1.z < slice_z || v2.z < slice_z; @@ -923,19 +933,25 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( printf("Face has no neighbor!\n"); #endif } else { + assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id); + assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id); int idx_vertex_opposite = this->mesh->stl.v_indices[nbr_face].vertex[nbr.which_vertex_not[nbr_idx]]; const stl_vertex *c = &this->v_scaled_shared[idx_vertex_opposite]; - if (c->z > slice_z) { - // If an edge resides on a cutting plane, and none of the two triangles are coplanar with the cutting plane, - // igore the lower triangle. - if (third_below) - result = Cutting; - } else if (c->z == slice_z) { - // A vertical face shares edge with a horizontal face. Verify, whether the shared corner is convex or concave. - float dir = cross_product(a, b, c); - if (third_below ? (dir < 0.) : (dir > 0.)) - result = Cutting; - } +// double side = double(normal.x) * (double(c->x) - double(a->x)) + double(normal.y) * (double(c->y) - double(a->y)); +// assert(c->z != slice_z || side != 0.); +// double normal_nbr = (double(c->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c->y) - double(a->y)) * (double(b->x) - double(a->x)); + result = + (c->z == slice_z) ? + // A vertical face shares edge with a horizontal face. Verify, whether the shared edge makes a convex or concave corner. + // Unfortunately too often there are flipped normals, which brake our assumption. Let's rather return every edge, + // and leth the code downstream hopefully handle it. + Slicing : + // Failing tests: Ignore concave corners for slicing. + // (((normal_nbr < 0) == third_below) ? Cutting : Slicing) : + // or + // (((this->mesh->stl.facet_start[nbr_face].normal.z < 0) == third_below) ? Cutting : Slicing) : + // For a pair of faces touching exactly at the cutting plane, ignore the face with a higher index. + (facet_idx < nbr_face) ? Slicing : Cutting; } if (third_below) { line_out->edge_type = feTop; @@ -950,6 +966,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( line_out->b.y = b->y; line_out->a_id = a_id; line_out->b_id = b_id; + assert(line_out->a != line_out->b); return result; } @@ -970,8 +987,9 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { // A general case. The face edge intersects the cutting plane. Calculate the intersection point. IntersectionPoint &point = points[num_points ++]; - point.x = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z); - point.y = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z); + double t = (double(slice_z) - double(b->z)) / (double(a->z) - double(b->z)); + point.x = float(double(b->x) + (double(a->x) - double(b->x)) * t); + point.y = float(double(b->y) + (double(a->y) - double(b->y)) * t); point.edge_id = edge_id; } } @@ -1003,7 +1021,11 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( line_out->edge_a_id = points[1].edge_id; line_out->edge_b_id = points[0].edge_id; // General slicing position, use the segment for both slicing and object cutting. - return Slicing; + // In a degenerate case where a plane cuts the triangle very close to its vertex, it is possible, that + // a zero length edge is created. In that case the zero length edge could be safely ignored + // as the polyline will still be connected, because both the sliced edges of the triangle will be + // sliced the same way at the neighbor triangles. + return (line_out->a == line_out->b) ? NoSlice : Slicing; } return NoSlice; } @@ -1074,6 +1096,11 @@ static inline void remove_tangent_edges(std::vector &lines) void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { +#ifdef _DEBUG + for (const Line &l : lines_src) + assert(l.a != l.b); +#endif /* _DEBUG */ + remove_tangent_edges(lines); struct OpenPolyline { diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index 34922285432..fd63d412a6a 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -9,6 +9,7 @@ namespace Slic3r { extern void set_logging_level(unsigned int level); extern void trace(unsigned int level, const char *message); +extern void disable_multi_threading(); // Set a path with GUI resource files. void set_var_dir(const std::string &path); diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 55164bbdd62..4ca4f69fd8f 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -24,6 +24,8 @@ #include #include +#include + namespace Slic3r { static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error; @@ -82,6 +84,14 @@ void trace(unsigned int level, const char *message) (::boost::log::keywords::severity = severity)) << message; } +void disable_multi_threading() +{ + // Disable parallelization so the Shiny profiler works + static tbb::task_scheduler_init *tbb_init = nullptr; + if (tbb_init == nullptr) + tbb_init = new tbb::task_scheduler_init(1); +} + static std::string g_var_dir; void set_var_dir(const std::string &dir) diff --git a/xs/xsp/XS.xsp b/xs/xsp/XS.xsp index e900532aaf2..04969a7f929 100644 --- a/xs/xsp/XS.xsp +++ b/xs/xsp/XS.xsp @@ -48,6 +48,11 @@ trace(level, message) CODE: Slic3r::trace(level, message); +void +disable_multi_threading() + CODE: + Slic3r::disable_multi_threading(); + void set_var_dir(dir) char *dir; From b67f32a94d83619f7b2056c22a0e55233238160f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 10 Aug 2018 14:10:28 +0200 Subject: [PATCH 15/63] Slicing improvement for slicing degenerated meshes: Add edges of parallel folded horizontal faces into the output contours, but ignore them when taking seeds for contour extraction. FIXME: Single vertices touching a plane are not handled correctly, they create zero length edges. --- xs/src/libslic3r/TriangleMesh.cpp | 145 +++++++++++++++++++++--------- xs/src/libslic3r/TriangleMesh.hpp | 18 +++- 2 files changed, 118 insertions(+), 45 deletions(-) diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 0941c11ef13..be554aef3ea 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -223,7 +223,6 @@ TriangleMesh::repair() { BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; } - float TriangleMesh::volume() { if (this->stl.stats.volume == -1) @@ -427,7 +426,7 @@ bool TriangleMesh::has_multiple_patches() const facet_visited[facet_idx] = true; for (int j = 0; j < 3; ++ j) { int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; - if (! facet_visited[neighbor_idx]) + if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) facet_queue[facet_queue_cnt ++] = neighbor_idx; } } @@ -479,8 +478,7 @@ size_t TriangleMesh::number_of_patches() const return num_bodies; } -TriangleMeshPtrs -TriangleMesh::split() const +TriangleMeshPtrs TriangleMesh::split() const { TriangleMeshPtrs meshes; std::set seen_facets; @@ -532,8 +530,7 @@ TriangleMesh::split() const return meshes; } -void -TriangleMesh::merge(const TriangleMesh &mesh) +void TriangleMesh::merge(const TriangleMesh &mesh) { // reset stats and metadata int number_of_facets = this->stl.stats.number_of_facets; @@ -587,8 +584,7 @@ Polygon TriangleMesh::convex_hull() return Slic3r::Geometry::convex_hull(pp); } -BoundingBoxf3 -TriangleMesh::bounding_box() const +BoundingBoxf3 TriangleMesh::bounding_box() const { BoundingBoxf3 bb; bb.defined = true; @@ -601,8 +597,7 @@ TriangleMesh::bounding_box() const return bb; } -void -TriangleMesh::require_shared_vertices() +void TriangleMesh::require_shared_vertices() { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start"; if (!this->repaired) @@ -611,10 +606,23 @@ TriangleMesh::require_shared_vertices() BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; stl_generate_shared_vertices(&(this->stl)); } +#ifdef _DEBUG + // Verify validity of neighborship data. + for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) { + const stl_neighbors &nbr = stl.neighbors_start[facet_idx]; + const int *vertices = stl.v_indices[facet_idx].vertex; + for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) { + int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx]; + if (nbr_face != -1) { + assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3]); + assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]); + } + } + } +#endif /* _DEBUG */ BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; } - TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : mesh(_mesh) { @@ -778,11 +786,14 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vector FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0].z, facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1].z, facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2].z); printf("z: min = %.2f, max = %.2f\n", min_z, max_z); - #endif + #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ // find layer extents std::vector::const_iterator min_layer, max_layer; min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z - #ifdef SLIC3R_DEBUG + #ifdef SLIC3R_TRIANGLEMESH_DEBUG printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); - #endif + #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { std::vector::size_type layer_idx = it - z.begin(); @@ -824,9 +835,8 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vectormesh->stl.v_indices[facet_idx].vertex; const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0; - uint32_t edge_mask = IntersectionLine::EDGE0; - for (int j = 0; j < 3; ++ j, edge_mask <<= 1) - if (il.flags & edge_mask) { + for (int j = 0; j < 3; ++ j) + if (il.flags & ((IntersectionLine::EDGE0_NO_NEIGHBOR | IntersectionLine::EDGE0_FOLD) << j)) { int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; if (reverse) @@ -840,6 +850,8 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vectoredge_type = feHorizontal; // Mark neighbor edges, which do not have a neighbor. uint32_t edges = 0; - uint32_t mask = IntersectionLine::EDGE0; - for (int nbr_idx = 0; nbr_idx != 3; ++ nbr_idx, mask <<= 1) + for (int nbr_idx = 0; nbr_idx != 3; ++ nbr_idx) { // If the neighbor with an edge starting with a vertex idx (nbr_idx - 2) shares no // opposite face, add it to the edges to process when slicing. - if (nbr.neighbor[nbr_idx] == -1) - // Mark this edge. - edges |= mask; + if (nbr.neighbor[nbr_idx] == -1) { + // Mark this edge to be added to the slice. + edges |= (IntersectionLine::EDGE0_NO_NEIGHBOR << nbr_idx); + } +#if 1 + else if (normal.z > 0) { + // Produce edges for opposite faced overlapping horizontal faces aka folds. + // This method often produces connecting lines (noise) at the cutting plane. + // Produce the edges for the top facing face of the pair of top / bottom facing faces. + + // Index of a neighbor face. + const int nbr_face = nbr.neighbor[nbr_idx]; + const int *nbr_vertices = this->mesh->stl.v_indices[nbr_face].vertex; + int idx_vertex_opposite = nbr_vertices[nbr.which_vertex_not[nbr_idx]]; + const stl_vertex *c2 = &this->v_scaled_shared[idx_vertex_opposite]; + if (c2->z == slice_z) { + // Edge shared by facet_idx and nbr_face. + int a_id = vertices[nbr_idx]; + int b_id = vertices[(nbr_idx + 1) % 3]; + int c1_id = vertices[(nbr_idx + 2) % 3]; + const stl_vertex *a = &this->v_scaled_shared[a_id]; + const stl_vertex *b = &this->v_scaled_shared[b_id]; + const stl_vertex *c1 = &this->v_scaled_shared[c1_id]; + // Verify that the two neighbor faces share a common edge. + assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id); + assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id); + double n1 = (double(c1->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c1->y) - double(a->y)) * (double(b->x) - double(a->x)); + double n2 = (double(c2->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c2->y) - double(a->y)) * (double(b->x) - double(a->x)); + if (n1 * n2 > 0) + // The two faces overlap. This indicates an invalid mesh geometry (non-manifold), + // but these are the real world objects, and leaving out these edges leads to missing contours. + edges |= (IntersectionLine::EDGE0_FOLD << nbr_idx); + } + } +#endif + } // Use some edges of this triangle for slicing only if at least one of its edge does not have an opposite face. result = (edges == 0) ? Cutting : Slicing; line_out->flags |= edges; @@ -937,21 +981,31 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id); int idx_vertex_opposite = this->mesh->stl.v_indices[nbr_face].vertex[nbr.which_vertex_not[nbr_idx]]; const stl_vertex *c = &this->v_scaled_shared[idx_vertex_opposite]; -// double side = double(normal.x) * (double(c->x) - double(a->x)) + double(normal.y) * (double(c->y) - double(a->y)); -// assert(c->z != slice_z || side != 0.); -// double normal_nbr = (double(c->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c->y) - double(a->y)) * (double(b->x) - double(a->x)); - result = - (c->z == slice_z) ? - // A vertical face shares edge with a horizontal face. Verify, whether the shared edge makes a convex or concave corner. - // Unfortunately too often there are flipped normals, which brake our assumption. Let's rather return every edge, - // and leth the code downstream hopefully handle it. - Slicing : - // Failing tests: Ignore concave corners for slicing. - // (((normal_nbr < 0) == third_below) ? Cutting : Slicing) : - // or - // (((this->mesh->stl.facet_start[nbr_face].normal.z < 0) == third_below) ? Cutting : Slicing) : - // For a pair of faces touching exactly at the cutting plane, ignore the face with a higher index. - (facet_idx < nbr_face) ? Slicing : Cutting; + if (c->z == slice_z) { + double normal_nbr = (double(c->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c->y) - double(a->y)) * (double(b->x) - double(a->x)); +#if 0 + if ((normal_nbr < 0) == third_below) { + printf("Flipped normal?\n"); + } +#endif + result = + // A vertical face shares edge with a horizontal face. Verify, whether the shared edge makes a convex or concave corner. + // Unfortunately too often there are flipped normals, which brake our assumption. Let's rather return every edge, + // and leth the code downstream hopefully handle it. + #if 1 + // Ignore concave corners for slicing. + // This method has the unfortunate property, that folds in a horizontal plane create concave corners, + // leading to broken contours, if these concave corners are not replaced by edges of the folds, see above. + ((normal_nbr < 0) == third_below) ? Cutting : Slicing; + #else + // Use concave corners for slicing. This leads to the test 01_trianglemesh.t "slicing a top tangent plane includes its area" failing, + // and rightly so. + Slicing; + #endif + } else { + // For a pair of faces touching exactly at the cutting plane, ignore one of them. An arbitrary rule is to ignore the face with a higher index. + result = (facet_idx < nbr_face) ? Slicing : Cutting; + } } if (third_below) { line_out->edge_type = feTop; @@ -1021,11 +1075,17 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( line_out->edge_a_id = points[1].edge_id; line_out->edge_b_id = points[0].edge_id; // General slicing position, use the segment for both slicing and object cutting. +#if 0 // In a degenerate case where a plane cuts the triangle very close to its vertex, it is possible, that // a zero length edge is created. In that case the zero length edge could be safely ignored // as the polyline will still be connected, because both the sliced edges of the triangle will be // sliced the same way at the neighbor triangles. return (line_out->a == line_out->b) ? NoSlice : Slicing; +#else + // The chaining code primarily relies on the IDs of the edges. + // Even though there may be a zero length edge generated, it is still important, + return Slicing; +#endif } return NoSlice; } @@ -1096,8 +1156,10 @@ static inline void remove_tangent_edges(std::vector &lines) void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { -#ifdef _DEBUG - for (const Line &l : lines_src) +#if 0 +//FIXME the slice_facet() creates zero length edges. +//#ifdef _DEBUG + for (const Line &l : lines) assert(l.a != l.b); #endif /* _DEBUG */ @@ -1141,7 +1203,8 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // take first spare line and start a new loop IntersectionLine *first_line = nullptr; for (; it_line_seed != lines.end(); ++ it_line_seed) - if (! it_line_seed->skip()) { + if (it_line_seed->is_seed_candidate()) { + //if (! it_line_seed->skip()) { first_line = &(*it_line_seed ++); break; } diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index 3714f5e9e05..89742fd4c86 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -114,6 +114,9 @@ class IntersectionLine : public Line bool skip() const { return (this->flags & SKIP) != 0; } void set_skip() { this->flags |= SKIP; } + + bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); } + void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; } // Inherits Point a, b // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. @@ -127,10 +130,17 @@ class IntersectionLine : public Line FacetEdgeType edge_type; // Used by TriangleMeshSlicer::slice() to skip duplicate edges. enum { - EDGE0 = 1, - EDGE1 = 2, - EDGE2 = 4, - SKIP = 8, + // Triangle edge added, because it has no neighbor. + EDGE0_NO_NEIGHBOR = 0x001, + EDGE1_NO_NEIGHBOR = 0x002, + EDGE2_NO_NEIGHBOR = 0x004, + // Triangle edge added, because it makes a fold with another horizontal edge. + EDGE0_FOLD = 0x010, + EDGE1_FOLD = 0x020, + EDGE2_FOLD = 0x040, + // The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds). + NO_SEED = 0x100, + SKIP = 0x200, }; uint32_t flags; }; From 13ce087606838d66a65f6f0a1db41ea71f8a623f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 10 Aug 2018 17:37:09 +0200 Subject: [PATCH 16/63] Another improvement in robustness of mesh slicing. --- xs/src/libslic3r/TriangleMesh.cpp | 117 +++++++++++++++++------------- 1 file changed, 68 insertions(+), 49 deletions(-) diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index be554aef3ea..65a31fada64 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -469,7 +469,7 @@ size_t TriangleMesh::number_of_patches() const facet_visited[facet_idx] = true; for (int j = 0; j < 3; ++ j) { int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; - if (! facet_visited[neighbor_idx]) + if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) facet_queue[facet_queue_cnt ++] = neighbor_idx; } } @@ -787,7 +787,7 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vectormesh->stl.v_indices[facet_idx].vertex; - int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); - for (int j = i; j - i < 3; ++j) { // loop through facet edges + int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); + for (int j = i; j - i < 3; ++j) { // loop through facet edges int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; @@ -1026,46 +1025,57 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( if (a->z == slice_z) { // Only point a alings with the cutting plane. - points_on_layer[num_points_on_layer ++] = num_points; - IntersectionPoint &point = points[num_points ++]; - point.x = a->x; - point.y = a->y; - point.point_id = a_id; + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { + point_on_layer = num_points; + IntersectionPoint &point = points[num_points ++]; + point.x = a->x; + point.y = a->y; + point.point_id = a_id; + } } else if (b->z == slice_z) { // Only point b alings with the cutting plane. - points_on_layer[num_points_on_layer ++] = num_points; - IntersectionPoint &point = points[num_points ++]; - point.x = b->x; - point.y = b->y; - point.point_id = b_id; + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { + point_on_layer = num_points; + IntersectionPoint &point = points[num_points ++]; + point.x = b->x; + point.y = b->y; + point.point_id = b_id; + } } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { // A general case. The face edge intersects the cutting plane. Calculate the intersection point. - IntersectionPoint &point = points[num_points ++]; - double t = (double(slice_z) - double(b->z)) / (double(a->z) - double(b->z)); - point.x = float(double(b->x) + (double(a->x) - double(b->x)) * t); - point.y = float(double(b->y) + (double(a->y) - double(b->y)) * t); - point.edge_id = edge_id; + assert(a_id != b_id); + // Sort the edge to give a consistent answer. + if (a_id > b_id) { + std::swap(a_id, b_id); + std::swap(a, b); + } + IntersectionPoint &point = points[num_points]; + double t = (double(slice_z) - double(b->z)) / (double(a->z) - double(b->z)); + if (t <= 0.) { + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { + point.x = a->x; + point.y = a->y; + point_on_layer = num_points ++; + point.point_id = a_id; + } + } else if (t >= 1.) { + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { + point.x = b->x; + point.y = b->y; + point_on_layer = num_points ++; + point.point_id = b_id; + } + } else { + point.x = coord_t(floor(double(b->x) + (double(a->x) - double(b->x)) * t + 0.5)); + point.y = coord_t(floor(double(b->y) + (double(a->y) - double(b->y)) * t + 0.5)); + point.edge_id = edge_id; + ++ num_points; + } } } - // We can't have only one point on layer because each vertex gets detected - // twice (once for each edge), and we can't have three points on layer, - // because we assume this code is not getting called for horizontal facets. - assert(num_points_on_layer == 0 || num_points_on_layer == 2); - if (num_points_on_layer > 0) { - assert(points[points_on_layer[0]].point_id == points[points_on_layer[1]].point_id); - assert(num_points == 2 || num_points == 3); - if (num_points < 3) - // This triangle touches the cutting plane with a single vertex. Ignore it. - return NoSlice; - // Erase one of the duplicate points. - -- num_points; - for (int i = points_on_layer[1]; i < num_points; ++ i) - points[i] = points[i + 1]; - } - - // Facets must intersect each plane 0 or 2 times. - assert(num_points == 0 || num_points == 2); + // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only. + assert(num_points < 3); if (num_points == 2) { line_out->edge_type = feGeneral; line_out->a = (Point)points[1]; @@ -1074,18 +1084,27 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( line_out->b_id = points[0].point_id; line_out->edge_a_id = points[1].edge_id; line_out->edge_b_id = points[0].edge_id; + // Not a zero lenght edge. + //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. + //assert(line_out->a != line_out->b); + // The plane cuts at least one edge in a general position. + assert(line_out->a_id == -1 || line_out->b_id == -1); + assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1); // General slicing position, use the segment for both slicing and object cutting. #if 0 - // In a degenerate case where a plane cuts the triangle very close to its vertex, it is possible, that - // a zero length edge is created. In that case the zero length edge could be safely ignored - // as the polyline will still be connected, because both the sliced edges of the triangle will be - // sliced the same way at the neighbor triangles. - return (line_out->a == line_out->b) ? NoSlice : Slicing; -#else - // The chaining code primarily relies on the IDs of the edges. - // Even though there may be a zero length edge generated, it is still important, - return Slicing; + if (line_out->a_id != -1 && line_out->b_id != -1) { + // Solving a degenerate case, where both the intersections snapped to an edge. + // Correctly classify the face as below or above based on the position of the 3rd point. + int i = vertices[0]; + if (i == line_out->a_id || i == line_out->b_id) + i = vertices[1]; + if (i == line_out->a_id || i == line_out->b_id) + i = vertices[2]; + assert(i != line_out->a_id && i != line_out->b_id); + line_out->edge_type = (this->v_scaled_shared[i].z < slice_z) ? feTop : feBottom; + } #endif + return Slicing; } return NoSlice; } @@ -1157,7 +1176,7 @@ static inline void remove_tangent_edges(std::vector &lines) void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { #if 0 -//FIXME the slice_facet() creates zero length edges. +//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. //#ifdef _DEBUG for (const Line &l : lines) assert(l.a != l.b); From 8617b0a409ba0cbb682c5cd0078fa23be66ab440 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 20 Aug 2018 16:34:35 +0200 Subject: [PATCH 17/63] parallel nesting can be enabled but fails with the current objectfunction. --- xs/src/libnest2d/CMakeLists.txt | 2 + xs/src/libnest2d/examples/main.cpp | 59 +- xs/src/libnest2d/libnest2d/boost_alg.hpp | 51 +- .../clipper_backend/clipper_backend.hpp | 71 +- .../libnest2d/libnest2d/geometry_traits.hpp | 94 +- .../libnest2d/geometry_traits_nfp.hpp | 20 +- xs/src/libnest2d/libnest2d/libnest2d.hpp | 53 +- xs/src/libnest2d/libnest2d/metaloop.hpp | 8 +- xs/src/libnest2d/libnest2d/optimizer.hpp | 3 + .../optimizers/nlopt_boilerplate.hpp | 6 +- .../libnest2d/placers/bottomleftplacer.hpp | 28 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 696 ++++++++---- .../libnest2d/placers/placer_boilerplate.hpp | 15 +- .../libnest2d/selections/djd_heuristic.hpp | 9 +- .../libnest2d/selections/firstfit.hpp | 9 +- xs/src/libnest2d/tests/test.cpp | 9 +- xs/src/libnest2d/tools/libnfpglue.cpp | 16 +- xs/src/libnest2d/tools/libnfpglue.hpp | 10 +- xs/src/libnest2d/tools/nfp_svgnest.hpp | 1004 +++++++++++++++++ xs/src/libnest2d/tools/nfp_svgnest_glue.hpp | 77 ++ xs/src/libslic3r/ModelArrange.hpp | 49 +- 21 files changed, 1816 insertions(+), 473 deletions(-) create mode 100644 xs/src/libnest2d/tools/nfp_svgnest.hpp create mode 100644 xs/src/libnest2d/tools/nfp_svgnest_glue.hpp diff --git a/xs/src/libnest2d/CMakeLists.txt b/xs/src/libnest2d/CMakeLists.txt index 0a181f4ab8e..cd3e35b978b 100644 --- a/xs/src/libnest2d/CMakeLists.txt +++ b/xs/src/libnest2d/CMakeLists.txt @@ -93,6 +93,8 @@ if(LIBNEST2D_BUILD_EXAMPLES) add_executable(example examples/main.cpp # tools/libnfpglue.hpp # tools/libnfpglue.cpp + tools/nfp_svgnest.hpp + tools/nfp_svgnest_glue.hpp tools/svgtools.hpp tests/printer_parts.cpp tests/printer_parts.h diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 37096019d34..57be7a20808 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -1,7 +1,6 @@ #include #include #include - //#define DEBUG_EXPORT_NFP #include @@ -12,6 +11,8 @@ #include "libnest2d/rotfinder.hpp" //#include "tools/libnfpglue.hpp" +//#include "tools/nfp_svgnest_glue.hpp" + using namespace libnest2d; using ItemGroup = std::vector>; @@ -53,10 +54,50 @@ void arrangeRectangles() { const int SCALE = 1000000; - std::vector rects = { - {60*SCALE, 200*SCALE}, - {60*SCALE, 200*SCALE} - }; + std::vector rects(100, { + {-9945219, -3065619}, + {-9781479, -2031780}, + {-9510560, -1020730}, + {-9135450, -43529}, + {-2099999, 14110899}, + {2099999, 14110899}, + {9135450, -43529}, + {9510560, -1020730}, + {9781479, -2031780}, + {9945219, -3065619}, + {10000000, -4110899}, + {9945219, -5156179}, + {9781479, -6190019}, + {9510560, -7201069}, + {9135450, -8178270}, + {8660249, -9110899}, + {8090169, -9988750}, + {7431449, -10802209}, + {6691309, -11542349}, + {5877850, -12201069}, + {5000000, -12771149}, + {4067369, -13246350}, + {3090169, -13621459}, + {2079119, -13892379}, + {1045279, -14056119}, + {0, -14110899}, + {-1045279, -14056119}, + {-2079119, -13892379}, + {-3090169, -13621459}, + {-4067369, -13246350}, + {-5000000, -12771149}, + {-5877850, -12201069}, + {-6691309, -11542349}, + {-7431449, -10802209}, + {-8090169, -9988750}, + {-8660249, -9110899}, + {-9135450, -8178270}, + {-9510560, -7201069}, + {-9781479, -6190019}, + {-9945219, -5156179}, + {-10000000, -4110899}, + {-9945219, -3065619}, + }); std::vector input; input.insert(input.end(), prusaParts().begin(), prusaParts().end()); @@ -84,7 +125,7 @@ void arrangeRectangles() { // _Circle bin({0, 0}, 125*SCALE); - auto min_obj_distance = static_cast(0*SCALE); + auto min_obj_distance = static_cast(1.5*SCALE); using Placer = strategies::_NofitPolyPlacer; using Packer = Nester; @@ -95,14 +136,15 @@ void arrangeRectangles() { pconf.alignment = Placer::Config::Alignment::CENTER; pconf.starting_point = Placer::Config::Alignment::CENTER; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; - pconf.accuracy = 1.0f; + pconf.accuracy = 0.5f; + pconf.parallel = true; Packer::SelectionConfig sconf; // sconf.allow_parallel = false; // sconf.force_parallel = false; // sconf.try_triplets = true; // sconf.try_reverse_order = true; -// sconf.waste_increment = 0.005; +// sconf.waste_increment = 0.01; arrange.configure(pconf, sconf); @@ -175,7 +217,6 @@ void arrangeRectangles() { SVGWriter svgw(conf); svgw.setSize(Box(250*SCALE, 210*SCALE)); svgw.writePackGroup(result); -// std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);}); svgw.save("out"); } diff --git a/xs/src/libnest2d/libnest2d/boost_alg.hpp b/xs/src/libnest2d/libnest2d/boost_alg.hpp index 7da1036f011..bb0403b0667 100644 --- a/xs/src/libnest2d/libnest2d/boost_alg.hpp +++ b/xs/src/libnest2d/libnest2d/boost_alg.hpp @@ -356,8 +356,7 @@ inline double area(const PolygonImpl& shape, const PolygonTag&) #endif template<> -inline bool isInside(const PointImpl& point, - const PolygonImpl& shape) +inline bool isInside(const PointImpl& point, const PolygonImpl& shape) { return boost::geometry::within(point, shape); } @@ -390,7 +389,8 @@ inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&) } template<> -inline bp2d::Box boundingBox(const bp2d::Shapes& shapes) +inline bp2d::Box boundingBox(const bp2d::Shapes& shapes, + const MultiPolygonTag&) { bp2d::Box b; boost::geometry::envelope(shapes, b); @@ -400,7 +400,7 @@ inline bp2d::Box boundingBox(const bp2d::Shapes& shapes) #ifndef DISABLE_BOOST_CONVEX_HULL template<> -inline PolygonImpl convexHull(const PolygonImpl& sh) +inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&) { PolygonImpl ret; boost::geometry::convex_hull(sh, ret); @@ -408,7 +408,8 @@ inline PolygonImpl convexHull(const PolygonImpl& sh) } template<> -inline PolygonImpl convexHull(const bp2d::Shapes& shapes) +inline PolygonImpl convexHull(const TMultiShape& shapes, + const MultiPolygonTag&) { PolygonImpl ret; boost::geometry::convex_hull(shapes, ret); @@ -416,34 +417,6 @@ inline PolygonImpl convexHull(const bp2d::Shapes& shapes) } #endif -#ifndef DISABLE_BOOST_ROTATE -template<> -inline void rotate(PolygonImpl& sh, const Radians& rads) -{ - namespace trans = boost::geometry::strategy::transform; - - PolygonImpl cpy = sh; - trans::rotate_transformer - rotate(rads); - - boost::geometry::transform(cpy, sh, rotate); -} -#endif - -#ifndef DISABLE_BOOST_TRANSLATE -template<> -inline void translate(PolygonImpl& sh, const PointImpl& offs) -{ - namespace trans = boost::geometry::strategy::transform; - - PolygonImpl cpy = sh; - trans::translate_transformer translate( - bp2d::getX(offs), bp2d::getY(offs)); - - boost::geometry::transform(cpy, sh, translate); -} -#endif - #ifndef DISABLE_BOOST_OFFSET template<> inline void offset(PolygonImpl& sh, bp2d::Coord distance) @@ -515,14 +488,24 @@ template<> inline std::pair isValid(const PolygonImpl& sh) namespace nfp { #ifndef DISABLE_BOOST_NFP_MERGE + +// Warning: I could not get boost union_ to work. Geometries will overlap. template<> -inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes, +inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes, const PolygonImpl& sh) { bp2d::Shapes retv; boost::geometry::union_(shapes, sh, retv); return retv; } + +template<> +inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes) +{ + bp2d::Shapes retv; + boost::geometry::union_(shapes, shapes.back(), retv); + return retv; +} #endif } diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp index 4238212ad20..745fd2108d1 100644 --- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp +++ b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp @@ -21,9 +21,6 @@ struct PolygonImpl { PathImpl Contour; HoleStore Holes; - using Tag = libnest2d::PolygonTag; - using PointType = PointImpl; - inline PolygonImpl() = default; inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {} @@ -102,41 +99,49 @@ template<> struct PointType { using Type = PointImpl; }; -// Type of vertex iterator used by Clipper -template<> struct VertexIteratorType { - using Type = ClipperLib::Path::iterator; -}; - -// Type of vertex iterator used by Clipper -template<> struct VertexConstIteratorType { - using Type = ClipperLib::Path::const_iterator; +template<> struct PointType { + using Type = PointImpl; }; template<> struct CountourType { using Type = PathImpl; }; +template<> struct ShapeTag { using Type = PolygonTag; }; + +template<> struct ShapeTag> { + using Type = MultiPolygonTag; +}; + +template<> struct PointType> { + using Type = PointImpl; +}; + +template<> struct HolesContainer { + using Type = ClipperLib::Paths; +}; + namespace pointlike { -// Tell binpack2d how to extract the X coord from a ClipperPoint object +// Tell libnest2d how to extract the X coord from a ClipperPoint object template<> inline TCoord x(const PointImpl& p) { return p.X; } -// Tell binpack2d how to extract the Y coord from a ClipperPoint object +// Tell libnest2d how to extract the Y coord from a ClipperPoint object template<> inline TCoord y(const PointImpl& p) { return p.Y; } -// Tell binpack2d how to extract the X coord from a ClipperPoint object +// Tell libnest2d how to extract the X coord from a ClipperPoint object template<> inline TCoord& x(PointImpl& p) { return p.X; } -// Tell binpack2d how to extract the Y coord from a ClipperPoint object +// Tell libnest2d how to extract the Y coord from a ClipperPoint object template<> inline TCoord& y(PointImpl& p) { return p.Y; @@ -178,10 +183,6 @@ inline double area(const PolygonImpl& sh) { } -template<> struct HolesContainer { - using Type = ClipperLib::Paths; -}; - namespace shapelike { template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity) @@ -189,7 +190,7 @@ template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity) return sh.Contour.reserve(vertex_capacity); } -// Tell binpack2d how to make string out of a ClipperPolygon object +// Tell libnest2d how to make string out of a ClipperPolygon object template<> inline double area(const PolygonImpl& sh, const PolygonTag&) { return _smartarea::area::Value>(sh); @@ -269,28 +270,6 @@ template<> inline std::string toString(const PolygonImpl& sh) return ss.str(); } -template<> inline TVertexIterator begin(PolygonImpl& sh) -{ - return sh.Contour.begin(); -} - -template<> inline TVertexIterator end(PolygonImpl& sh) -{ - return sh.Contour.end(); -} - -template<> -inline TVertexConstIterator cbegin(const PolygonImpl& sh) -{ - return sh.Contour.cbegin(); -} - -template<> inline TVertexConstIterator cend( - const PolygonImpl& sh) -{ - return sh.Contour.cend(); -} - template<> inline PolygonImpl create(const PathImpl& path, const HoleStore& holes) { @@ -410,8 +389,8 @@ inline void rotate(PolygonImpl& sh, const Radians& rads) } // namespace shapelike #define DISABLE_BOOST_NFP_MERGE -inline nfp::Shapes _merge(ClipperLib::Clipper& clipper) { - nfp::Shapes retv; +inline std::vector _merge(ClipperLib::Clipper& clipper) { + shapelike::Shapes retv; ClipperLib::PolyTree result; clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative); @@ -447,8 +426,8 @@ inline nfp::Shapes _merge(ClipperLib::Clipper& clipper) { namespace nfp { -template<> inline nfp::Shapes -merge(const nfp::Shapes& shapes) +template<> inline std::vector +merge(const std::vector& shapes) { ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 0826cbd4bcb..d1625773151 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -22,34 +22,12 @@ template using TCoord = typename CoordType>::Type; /// Getting the type of point structure used by a shape. -template struct PointType { /*using Type = void;*/ }; +template struct PointType { using Type = typename Sh::PointType; }; /// TPoint as shorthand for `typename PointType::Type`. template using TPoint = typename PointType>::Type; -/// Getting the VertexIterator type of a shape class. -template struct VertexIteratorType { /*using Type = void;*/ }; - -/// Getting the const vertex iterator for a shape class. -template struct VertexConstIteratorType {/* using Type = void;*/ }; - -/** - * TVertexIterator as shorthand for - * `typename VertexIteratorType::Type` - */ -template -using TVertexIterator = -typename VertexIteratorType>::Type; - -/** - * \brief TVertexConstIterator as shorthand for - * `typename VertexConstIteratorType::Type` - */ -template -using TVertexConstIterator = -typename VertexConstIteratorType>::Type; - /** * \brief A point pair base class for other point pairs (segment, box, ...). * \tparam RawPoint The actual point type to use. @@ -61,9 +39,16 @@ struct PointPair { }; struct PolygonTag {}; +struct MultiPolygonTag {}; struct BoxTag {}; struct CircleTag {}; +template struct ShapeTag { using Type = typename Shape::Tag; }; +template using Tag = typename ShapeTag::Type; + +template struct MultiShape { using Type = std::vector; }; +template using TMultiShape = typename MultiShape::Type; + /** * \brief An abstraction of a box; */ @@ -371,7 +356,7 @@ enum class Formats { namespace shapelike { template - using Shapes = std::vector; + using Shapes = TMultiShape; template inline RawShape create(const TContour& contour, @@ -455,27 +440,28 @@ namespace shapelike { } template - inline TVertexIterator begin(RawShape& sh) + inline typename TContour::iterator begin(RawShape& sh) { - return sh.begin(); + return getContour(sh).begin(); } template - inline TVertexIterator end(RawShape& sh) + inline typename TContour::iterator end(RawShape& sh) { - return sh.end(); + return getContour(sh).end(); } template - inline TVertexConstIterator cbegin(const RawShape& sh) + inline typename TContour::const_iterator + cbegin(const RawShape& sh) { - return sh.cbegin(); + return getContour(sh).cbegin(); } template - inline TVertexConstIterator cend(const RawShape& sh) + inline typename TContour::const_iterator cend(const RawShape& sh) { - return sh.cend(); + return getContour(sh).cend(); } template @@ -559,27 +545,29 @@ namespace shapelike { "ShapeLike::boundingBox(shape) unimplemented!"); } - template - inline _Box> boundingBox(const Shapes& /*sh*/) + template + inline _Box> + boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) { - static_assert(always_false::value, + static_assert(always_false::value, "ShapeLike::boundingBox(shapes) unimplemented!"); } template - inline RawShape convexHull(const RawShape& /*sh*/) + inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) { static_assert(always_false::value, "ShapeLike::convexHull(shape) unimplemented!"); return RawShape(); } - template - inline RawShape convexHull(const Shapes& /*sh*/) + template + inline typename RawShapes::value_type + convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&) { - static_assert(always_false::value, + static_assert(always_false::value, "ShapeLike::convexHull(shapes) unimplemented!"); - return RawShape(); + return typename RawShapes::value_type(); } template @@ -599,8 +587,7 @@ namespace shapelike { template inline void offset(RawShape& /*sh*/, TCoord> /*distance*/) { - static_assert(always_false::value, - "ShapeLike::offset() unimplemented!"); + dout() << "The current geometry backend does not support offsetting!\n"; } template @@ -670,9 +657,9 @@ namespace shapelike { } template // Dispatch function - inline _Box boundingBox(const S& sh) + inline _Box> boundingBox(const S& sh) { - return boundingBox(sh, typename S::Tag()); + return boundingBox(sh, Tag() ); } template @@ -690,7 +677,7 @@ namespace shapelike { template // Dispatching function inline double area(const RawShape& sh) { - return area(sh, typename RawShape::Tag()); + return area(sh, Tag()); } template @@ -702,6 +689,13 @@ namespace shapelike { }); } + template + inline auto convexHull(const RawShape& sh) + -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce + { + return convexHull(sh, Tag()); + } + template inline bool isInside(const TPoint& point, const _Circle>& circ) @@ -816,6 +810,16 @@ namespace shapelike { } } +#define DECLARE_MAIN_TYPES(T) \ + using Polygon = T; \ + using Point = TPoint; \ + using Coord = TCoord; \ + using Contour = TContour; \ + using Box = _Box; \ + using Circle = _Circle; \ + using Segment = _Segment; \ + using Polygons = TMultiShape + } #endif // GEOMETRY_TRAITS_HPP diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp index 6cac374aef7..b9dfd21854d 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp @@ -27,8 +27,8 @@ inline bool _vsort(const TPoint& v1, const TPoint& v2) /// A collection of static methods for handling the no fit polygon creation. namespace nfp { -namespace sl = shapelike; -namespace pl = pointlike; +//namespace sl = shapelike; +//namespace pl = pointlike; /// The complexity level of a polygon that an NFP implementation can handle. enum class NfpLevel: unsigned { @@ -49,7 +49,7 @@ template struct MaxNfpLevel { // Shorthand for a pile of polygons template -using Shapes = typename shapelike::Shapes; +using Shapes = TMultiShape; /** * Merge a bunch of polygons with the specified additional polygon. @@ -62,10 +62,10 @@ using Shapes = typename shapelike::Shapes; * mostly it will be a set containing only one big polygon but if the input * polygons are disjuct than the resulting set will contain more polygons. */ -template -inline Shapes merge(const Shapes& /*shc*/) +template +inline RawShapes merge(const RawShapes& /*shc*/) { - static_assert(always_false::value, + static_assert(always_false::value, "Nfp::merge(shapes, shape) unimplemented!"); } @@ -81,12 +81,12 @@ inline Shapes merge(const Shapes& /*shc*/) * polygons are disjuct than the resulting set will contain more polygons. */ template -inline Shapes merge(const Shapes& shc, - const RawShape& sh) +inline TMultiShape merge(const TMultiShape& shc, + const RawShape& sh) { - auto m = merge(shc); + auto m = nfp::merge(shc); m.push_back(sh); - return merge(m); + return nfp::merge(m); } /** diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index d2850d4eda4..42255cbb48e 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -31,6 +31,8 @@ class _Item { using Vertex = TPoint; using Box = _Box; + using VertexConstIterator = typename TContour::const_iterator; + // The original shape that gets encapsulated. RawShape sh_; @@ -39,7 +41,7 @@ class _Item { Radians rotation_; Coord offset_distance_; - // Info about whether the tranformations will have to take place + // Info about whether the transformations will have to take place // This is needed because if floating point is used, it is hard to say // that a zero angle is not a rotation because of testing for equality. bool has_rotation_ = false, has_translation_ = false, has_offset_ = false; @@ -59,8 +61,8 @@ class _Item { }; mutable Convexity convexity_ = Convexity::UNCHECKED; - mutable TVertexConstIterator rmt_; // rightmost top vertex - mutable TVertexConstIterator lmb_; // leftmost bottom vertex + mutable VertexConstIterator rmt_; // rightmost top vertex + mutable VertexConstIterator lmb_; // leftmost bottom vertex mutable bool rmt_valid_ = false, lmb_valid_ = false; mutable struct BBCache { Box bb; bool valid; Vertex tr; @@ -81,7 +83,7 @@ class _Item { * supports. Giving out a non const iterator would make it impossible to * perform correct cache invalidation. */ - using Iterator = TVertexConstIterator; + using Iterator = VertexConstIterator; /** * @brief Get the orientation of the polygon. @@ -110,7 +112,7 @@ class _Item { explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {} /** - * @brief Create an item from an initilizer list. + * @brief Create an item from an initializer list. * @param il The initializer list of vertices. */ inline _Item(const std::initializer_list< Vertex >& il): @@ -160,7 +162,7 @@ class _Item { } /** - * @brief Get a copy of an outer vertex whithin the carried shape. + * @brief Get a copy of an outer vertex within the carried shape. * * Note that the vertex considered here is taken from the original shape * that this item is constructed from. This means that no transformation is @@ -245,7 +247,7 @@ class _Item { * @param p * @return */ - inline bool isPointInside(const Vertex& p) const + inline bool isInside(const Vertex& p) const { return sl::isInside(p, transformedShape()); } @@ -505,7 +507,7 @@ rem(typename Container::const_iterator it, const Container& cont) { /** * \brief A wrapper interface (trait) class for any placement strategy provider. * - * If a client want's to use its own placement algorithm, all it has to do is to + * If a client wants to use its own placement algorithm, all it has to do is to * specialize this class template and define all the ten methods it has. It can * use the strategies::PlacerBoilerplace class for creating a new placement * strategy where only the constructor and the trypack method has to be provided @@ -558,7 +560,7 @@ class PlacementStrategyLike { * Note that it depends on the particular placer implementation how it * reacts to config changes in the middle of a calculation. * - * @param config The configuration object defined by the placement startegy. + * @param config The configuration object defined by the placement strategy. */ inline void configure(const Config& config) { impl_.configure(config); } @@ -568,7 +570,7 @@ class PlacementStrategyLike { * * \param item_store A container of items that are intended to be packed * later. Can be used by the placer to switch tactics. When it's knows that - * many items will come a greedy startegy may not be the best. + * many items will come a greedy strategy may not be the best. * \param from The iterator to the item from which the packing should start, * including the pointed item * \param count How many items should be packed. If the value is 1, than @@ -596,7 +598,7 @@ class PlacementStrategyLike { * A default implementation would be to call * { auto&& r = trypack(...); accept(r); return r; } but we should let the * implementor of the placement strategy to harvest any optimizations from - * the absence of an intermadiate step. The above version can still be used + * the absence of an intermediate step. The above version can still be used * in the implementation. * * @param item The item to pack. @@ -628,13 +630,6 @@ class PlacementStrategyLike { inline double filledArea() const { return impl_.filledArea(); } -#ifndef NDEBUG - inline auto getDebugItems() -> decltype(impl_.debug_items_)& - { - return impl_.debug_items_; - } -#endif - }; // The progress function will be called with the number of placed items @@ -659,15 +654,15 @@ class SelectionStrategyLike { * Note that it depends on the particular placer implementation how it * reacts to config changes in the middle of a calculation. * - * @param config The configuration object defined by the selection startegy. + * @param config The configuration object defined by the selection strategy. */ inline void configure(const Config& config) { impl_.configure(config); } /** - * @brief A function callback which should be called whenewer an item or - * a group of items where succesfully packed. + * @brief A function callback which should be called whenever an item or + * a group of items where successfully packed. * @param fn A function callback object taking one unsigned integer as the * number of the remaining items to pack. */ @@ -680,7 +675,7 @@ class SelectionStrategyLike { * placer compatible with the PlacementStrategyLike interface. * * \param first, last The first and last iterator if the input sequence. It - * can be only an iterator of a type converitible to Item. + * can be only an iterator of a type convertible to Item. * \param bin. The shape of the bin. It has to be supported by the placement * strategy. * \param An optional config object for the placer. @@ -712,7 +707,7 @@ class SelectionStrategyLike { /** * @brief Get the items for a particular bin. * @param binIndex The index of the requested bin. - * @return Returns a list of allitems packed into the requested bin. + * @return Returns a list of all items packed into the requested bin. */ inline ItemGroup itemsForBin(size_t binIndex) { return impl_.itemsForBin(binIndex); @@ -754,7 +749,7 @@ using _IndexedPackGroup = std::vector< >; /** - * The Arranger is the frontend class for the binpack2d library. It takes the + * The Arranger is the front-end class for the libnest2d library. It takes the * input items and outputs the items with the proper transformations to be * inside the provided bin. */ @@ -857,7 +852,7 @@ class Nester { return _execute(from, to); } - /// Set a progress indicatior function object for the selector. + /// Set a progress indicator function object for the selector. inline Nester& progressIndicator(ProgressFunction func) { selector_.progressIndicator(func); return *this; @@ -877,8 +872,8 @@ class Nester { template, - // This funtion will be used only if the iterators are pointing to - // a type compatible with the binpack2d::_Item template. + // This function will be used only if the iterators are pointing to + // a type compatible with the libnets2d::_Item template. // This way we can use references to input elements as they will // have to exist for the lifetime of this call. class T = enable_if_t< std::is_convertible::value, IT> @@ -904,8 +899,8 @@ class Nester { template, - // This funtion will be used only if the iterators are pointing to - // a type compatible with the binpack2d::_Item template. + // This function will be used only if the iterators are pointing to + // a type compatible with the libnest2d::_Item template. // This way we can use references to input elements as they will // have to exist for the lifetime of this call. class T = enable_if_t< std::is_convertible::value, IT> diff --git a/xs/src/libnest2d/libnest2d/metaloop.hpp b/xs/src/libnest2d/libnest2d/metaloop.hpp index 18755525c3f..d88988ba155 100644 --- a/xs/src/libnest2d/libnest2d/metaloop.hpp +++ b/xs/src/libnest2d/libnest2d/metaloop.hpp @@ -67,11 +67,11 @@ class metaloop { // need to wrap that in a type (see metaloop::Int). /* - * A helper alias to create integer values wrapped as a type. It is nessecary + * A helper alias to create integer values wrapped as a type. It is necessary * because a non type template parameter (such as int) would be prohibited in * a partial specialization. Also for the same reason we have to use a class * _Metaloop instead of a simple function as a functor. A function cannot be - * partially specialized in a way that is neccesary for this trick. + * partially specialized in a way that is necessary for this trick. */ template using Int = std::integral_constant; @@ -88,7 +88,7 @@ template class MapFn { // It takes the real functor that can be specified in-place but only // with C++14 because the second parameter's type will depend on the // type of the parameter pack element that is processed. In C++14 we can - // specify this second parameter type as auto in the lamda parameter list. + // specify this second parameter type as auto in the lambda parameter list. inline MapFn(Fn&& fn): fn_(forward(fn)) {} template void operator ()(T&& pack_element) { @@ -146,7 +146,7 @@ class _MetaLoop, Args...> { * version of run is called which does not call itself anymore. * * If you are utterly annoyed, at least you have learned a super crazy - * functional metaprogramming pattern. + * functional meta-programming pattern. */ template using MetaLoop = _MetaLoop, Args...>; diff --git a/xs/src/libnest2d/libnest2d/optimizer.hpp b/xs/src/libnest2d/libnest2d/optimizer.hpp index c8ed2e378a4..90d2f2ff998 100644 --- a/xs/src/libnest2d/libnest2d/optimizer.hpp +++ b/xs/src/libnest2d/libnest2d/optimizer.hpp @@ -102,6 +102,9 @@ struct StopCriteria { /// If the relative value difference between two scores. double relative_score_difference = std::nan(""); + /// Stop if this value or better is found. + double stop_score = std::nan(""); + unsigned max_iterations = 0; }; diff --git a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp index 737ca6e3c0b..1a0f06e028b 100644 --- a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp @@ -142,10 +142,12 @@ class NloptOptimizer: public Optimizer { default: ; } - auto abs_diff = stopcr_.absolute_score_difference; - auto rel_diff = stopcr_.relative_score_difference; + double abs_diff = stopcr_.absolute_score_difference; + double rel_diff = stopcr_.relative_score_difference; + double stopval = stopcr_.stop_score; if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff); if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff); + if(!std::isnan(stopval)) opt_.set_stopval(stopval); if(this->stopcr_.max_iterations > 0) opt_.set_maxeval(this->stopcr_.max_iterations ); diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp index af167837218..0ba9eb3c060 100644 --- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp @@ -7,9 +7,24 @@ namespace libnest2d { namespace strategies { +template struct Epsilon {}; + +template +struct Epsilon::value, T> > { + static const T Value = 1; +}; + +template +struct Epsilon::value, T> > { + static const T Value = 1e-3; +}; + template struct BLConfig { - TCoord> min_obj_distance = 0; + DECLARE_MAIN_TYPES(RawShape); + + Coord min_obj_distance = 0; + Coord epsilon = Epsilon::Value; bool allow_rotations = false; }; @@ -69,20 +84,21 @@ class _BottomLeftPlacer: public PlacerBoilerplate< setInitialPosition(item); Unit d = availableSpaceDown(item); - bool can_move = d > 1 /*std::numeric_limits::epsilon()*/; + auto eps = config_.epsilon; + bool can_move = d > eps; bool can_be_packed = can_move; bool left = true; while(can_move) { if(left) { // write previous down move and go down - item.translate({0, -d+1}); + item.translate({0, -d+eps}); d = availableSpaceLeft(item); - can_move = d > 1/*std::numeric_limits::epsilon()*/; + can_move = d > eps; left = false; } else { // write previous left move and go down - item.translate({-d+1, 0}); + item.translate({-d+eps, 0}); d = availableSpaceDown(item); - can_move = d > 1/*std::numeric_limits::epsilon()*/; + can_move = d > eps; left = true; } } diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 638d606e0f1..b9e0ba8f160 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -2,7 +2,15 @@ #define NOFITPOLY_HPP #include -#include + +// For caching nfps +#include + +// For parallel for +#include +#include +#include +#include #ifndef NDEBUG #include @@ -13,8 +21,84 @@ #include "tools/svgtools.hpp" +namespace libnest2d { + +namespace __parallel { + +using std::function; +using std::iterator_traits; +template +using TIteratorValue = typename iterator_traits::value_type; -namespace libnest2d { namespace strategies { +template +inline void enumerate( + Iterator from, Iterator to, + function, unsigned)> fn, + std::launch policy = std::launch::deferred | std::launch::async) +{ + auto N = to-from; + std::vector> rets(N); + + auto it = from; + for(unsigned b = 0; b < N; b++) { + rets[b] = std::async(policy, fn, *it++, b); + } + + for(unsigned fi = 0; fi < rets.size(); ++fi) rets[fi].wait(); +} + +} + +namespace __itemhash { + +using Key = size_t; + +template +Key hash(const _Item& item) { + using Point = TPoint; + using Segment = _Segment; + + static const int N = 26; + static const int M = N*N - 1; + + std::string ret; + auto& rhs = item.rawShape(); + auto& ctr = sl::getContour(rhs); + auto it = ctr.begin(); + auto nx = std::next(it); + + double circ = 0; + while(nx != ctr.end()) { + Segment seg(*it++, *nx++); + Radians a = seg.angleToXaxis(); + double deg = Degrees(a); + int ms = 'A', ls = 'A'; + while(deg > N) { ms++; deg -= N; } + ls += int(deg); + ret.push_back(char(ms)); ret.push_back(char(ls)); + circ += seg.length(); + } + + it = ctr.begin(); nx = std::next(it); + + while(nx != ctr.end()) { + Segment seg(*it++, *nx++); + auto l = int(M * seg.length() / circ); + int ms = 'A', ls = 'A'; + while(l > N) { ms++; l -= N; } + ls += l; + ret.push_back(char(ms)); ret.push_back(char(ls)); + } + + return std::hash()(ret); +} + +template +using Hash = std::unordered_map>; + +} + +namespace strategies { template struct NfpPConfig { @@ -71,7 +155,7 @@ struct NfpPConfig { * decisions (for you or a more intelligent AI). * */ - std::function&, const _Item&, + std::function&, const _Item&, const ItemGroup&)> object_function; @@ -80,7 +164,7 @@ struct NfpPConfig { * This is a compromise slider between quality and speed. Zero is the * fast and poor solution while 1.0 is the slowest but most accurate. */ - float accuracy = 1.0; + float accuracy = 0.65f; /** * @brief If you want to see items inside other item's holes, you have to @@ -91,6 +175,11 @@ struct NfpPConfig { */ bool explore_holes = false; + /** + * @brief If true, use all CPUs available. Run on a single core otherwise. + */ + bool parallel = true; + NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}), alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {} }; @@ -325,121 +414,8 @@ inline void correctNfpPosition(nfp::NfpResult& nfp, shapelike::translate(nfp.first, dnfp); } -template -nfp::Shapes calcnfp( const Container& polygons, - const _Item& trsh, - Lvl) -{ - using Item = _Item; - using namespace nfp; - - nfp::Shapes nfps; - -// int pi = 0; - for(Item& sh : polygons) { - auto subnfp_r = noFitPolygon( - sh.transformedShape(), trsh.transformedShape()); - #ifndef NDEBUG - auto vv = sl::isValid(sh.transformedShape()); - assert(vv.first); - - auto vnfp = sl::isValid(subnfp_r.first); - assert(vnfp.first); - #endif - - correctNfpPosition(subnfp_r, sh, trsh); - - nfps = nfp::merge(nfps, subnfp_r.first); - -// double SCALE = 1000000; -// using SVGWriter = svg::SVGWriter; -// SVGWriter::Config conf; -// conf.mm_in_coord_units = SCALE; -// SVGWriter svgw(conf); -// Box bin(250*SCALE, 210*SCALE); -// svgw.setSize(bin); -// for(int i = 0; i <= pi; i++) svgw.writeItem(polygons[i]); -// svgw.writeItem(trsh); -//// svgw.writeItem(Item(subnfp_r.first)); -// for(auto& n : nfps) svgw.writeItem(Item(n)); -// svgw.save("nfpout"); -// pi++; - } - - return nfps; -} - -template -nfp::Shapes calcnfp( const Container& polygons, - const _Item& trsh, - Level) -{ - using namespace nfp; - using Item = _Item; - - Shapes nfps; - - auto& orb = trsh.transformedShape(); - bool orbconvex = trsh.isContourConvex(); - - for(Item& sh : polygons) { - nfp::NfpResult subnfp; - auto& stat = sh.transformedShape(); - - if(sh.isContourConvex() && orbconvex) - subnfp = nfp::noFitPolygon(stat, orb); - else if(orbconvex) - subnfp = nfp::noFitPolygon(stat, orb); - else - subnfp = nfp::noFitPolygon(stat, orb); - - correctNfpPosition(subnfp, sh, trsh); - - nfps = nfp::merge(nfps, subnfp.first); - } - - return nfps; - - -// using Item = _Item; -// using sl = ShapeLike; - -// Nfp::Shapes nfps, stationary; - -// for(Item& sh : polygons) { -// stationary = Nfp::merge(stationary, sh.transformedShape()); -// } - -// for(RawShape& sh : stationary) { - -//// auto vv = sl::isValid(sh); -//// std::cout << vv.second << std::endl; - - -// Nfp::NfpResult subnfp; -// bool shconvex = sl::isConvex(sl::getContour(sh)); -// if(shconvex && trsh.isContourConvex()) { -// subnfp = Nfp::noFitPolygon( -// sh, trsh.transformedShape()); -// } else if(trsh.isContourConvex()) { -// subnfp = Nfp::noFitPolygon( -// sh, trsh.transformedShape()); -// } -// else { -// subnfp = Nfp::noFitPolygon( sh, -// trsh.transformedShape()); -// } - -// correctNfpPosition(subnfp, sh, trsh); - -// nfps = Nfp::merge(nfps, subnfp.first); -// } - -// return nfps; -} - -template -_Circle> minimizeCircle(const RawShape& sh) { +template> > +Circle minimizeCircle(const RawShape& sh) { using Point = TPoint; using Coord = TCoord; @@ -507,9 +483,19 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer>; + using MaxNfpLevel = nfp::MaxNfpLevel; + + using ItemKeys = std::vector<__itemhash::Key>; + + // Norming factor for the optimization function const double norm_; - using MaxNfpLevel = nfp::MaxNfpLevel; + // Caching calculated nfps + __itemhash::Hash nfpcache_; + + // Storing item hash keys + ItemKeys item_keys_; + public: using Pile = nfp::Shapes; @@ -526,60 +512,290 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer(bin); + static inline double overfit(const Box& bb, const RawShape& bin) { + auto bbin = sl::boundingBox(bin); auto d = bbin.center() - bb.center(); _Rectangle rect(bb.width(), bb.height()); rect.translate(bb.minCorner() + d); - return sl::isInside(rect.transformedShape(), bin); + return sl::isInside(rect.transformedShape(), bin) ? -1.0 : 1; } - bool static inline wouldFit(const RawShape& chull, const RawShape& bin) { - auto bbch = sl::boundingBox(chull); - auto bbin = sl::boundingBox(bin); + static inline double overfit(const RawShape& chull, const RawShape& bin) { + auto bbch = sl::boundingBox(chull); + auto bbin = sl::boundingBox(bin); auto d = bbch.center() - bbin.center(); auto chullcpy = chull; sl::translate(chullcpy, d); - return sl::isInside(chullcpy, bin); + return sl::isInside(chullcpy, bin) ? -1.0 : 1.0; } - bool static inline wouldFit(const RawShape& chull, const Box& bin) + static inline double overfit(const RawShape& chull, const Box& bin) { - auto bbch = sl::boundingBox(chull); - return wouldFit(bbch, bin); + auto bbch = sl::boundingBox(chull); + return overfit(bbch, bin); } - bool static inline wouldFit(const Box& bb, const Box& bin) + static inline double overfit(const Box& bb, const Box& bin) { - return bb.width() <= bin.width() && bb.height() <= bin.height(); + auto wdiff = double(bb.width() - bin.width()); + auto hdiff = double(bb.height() - bin.height()); + double diff = 0; + if(wdiff > 0) diff += wdiff; + if(hdiff > 0) diff += hdiff; + return diff; } - bool static inline wouldFit(const Box& bb, const _Circle& bin) + static inline double overfit(const Box& bb, const _Circle& bin) { - - return sl::isInside(bb, bin); + double boxr = 0.5*pl::distance(bb.minCorner(), bb.maxCorner()); + double diff = boxr - bin.radius(); + return diff; } - bool static inline wouldFit(const RawShape& chull, + static inline double overfit(const RawShape& chull, const _Circle& bin) { - return boundingCircle(chull).radius() < bin.radius(); + double r = boundingCircle(chull).radius(); + double diff = r - bin.radius(); + return diff; } template> - PackResult trypack( + PackResult trypack(Item& item, + const Range& remaining = Range()) { + auto result = _trypack(item, remaining); + + // Experimental + // if(!result) repack(item, result); + + return result; + } + + ~_NofitPolyPlacer() { + clearItems(); + } + + inline void clearItems() { + finalAlign(bin_); + Base::clearItems(); + } + +private: + + using Shapes = TMultiShape; + using ItemRef = std::reference_wrapper; + using ItemWithHash = const std::pair; + + Shapes calcnfp(const ItemWithHash itsh, Lvl) + { + using namespace nfp; + + Shapes nfps; + const Item& trsh = itsh.first; + // nfps.reserve(polygons.size()); + +// unsigned idx = 0; + for(Item& sh : items_) { + +// auto ik = item_keys_[idx++] + itsh.second; +// auto fnd = nfpcache_.find(ik); + +// nfp::NfpResult subnfp_r; +// if(fnd == nfpcache_.end()) { + + auto subnfp_r = noFitPolygon( + sh.transformedShape(), trsh.transformedShape()); +// nfpcache_[ik] = subnfp_r; +// } else { +// subnfp_r = fnd->second; +// } + + correctNfpPosition(subnfp_r, sh, trsh); + + // nfps.emplace_back(subnfp_r.first); + nfps = nfp::merge(nfps, subnfp_r.first); + } + + // nfps = nfp::merge(nfps); + + return nfps; + } + + template + Shapes calcnfp( const ItemWithHash itsh, Level) + { // Function for arbitrary level of nfp implementation + using namespace nfp; + + Shapes nfps; + const Item& trsh = itsh.first; + + auto& orb = trsh.transformedShape(); + bool orbconvex = trsh.isContourConvex(); + + for(Item& sh : items_) { + nfp::NfpResult subnfp; + auto& stat = sh.transformedShape(); + + if(sh.isContourConvex() && orbconvex) + subnfp = nfp::noFitPolygon(stat, orb); + else if(orbconvex) + subnfp = nfp::noFitPolygon(stat, orb); + else + subnfp = nfp::noFitPolygon(stat, orb); + + correctNfpPosition(subnfp, sh, trsh); + + nfps = nfp::merge(nfps, subnfp.first); + } + + return nfps; + } + + // Very much experimental + void repack(Item& item, PackResult& result) { + + if((sl::area(bin_) - this->filledArea()) >= item.area()) { + auto prev_func = config_.object_function; + + unsigned iter = 0; + ItemGroup backup_rf = items_; + std::vector backup_cpy; + for(Item& itm : items_) backup_cpy.emplace_back(itm); + + auto ofn = [this, &item, &result, &iter, &backup_cpy, &backup_rf] + (double ratio) + { + auto& bin = bin_; + iter++; + config_.object_function = [bin, ratio]( + nfp::Shapes& pile, + const Item& item, + const ItemGroup& /*remaining*/) + { + pile.emplace_back(item.transformedShape()); + auto ch = sl::convexHull(pile); + auto pbb = sl::boundingBox(pile); + pile.pop_back(); + + double parea = 0.5*(sl::area(ch) + sl::area(pbb)); + + double pile_area = std::accumulate( + pile.begin(), pile.end(), item.area(), + [](double sum, const RawShape& sh){ + return sum + sl::area(sh); + }); + + // The pack ratio -- how much is the convex hull occupied + double pack_rate = (pile_area)/parea; + + // ratio of waste + double waste = 1.0 - pack_rate; + + // Score is the square root of waste. This will extend the + // range of good (lower) values and shrink the range of bad + // (larger) values. + auto wscore = std::sqrt(waste); + + + auto ibb = item.boundingBox(); + auto bbb = sl::boundingBox(bin); + auto c = ibb.center(); + double norm = 0.5*pl::distance(bbb.minCorner(), + bbb.maxCorner()); + + double dscore = pl::distance(c, pbb.center()) / norm; + + return ratio*wscore + (1.0 - ratio) * dscore; + }; + + auto bb = sl::boundingBox(bin); + double norm = bb.width() + bb.height(); + + auto items = items_; + clearItems(); + auto it = items.begin(); + while(auto pr = _trypack(*it++)) { + this->accept(pr); if(it == items.end()) break; + } + + auto count_diff = items.size() - items_.size(); + double score = count_diff; + + if(count_diff == 0) { + result = _trypack(item); + + if(result) { + std::cout << "Success" << std::endl; + score = 0.0; + } else { + score += result.overfit() / norm; + } + } else { + result = PackResult(); + items_ = backup_rf; + for(unsigned i = 0; i < items_.size(); i++) { + items_[i].get() = backup_cpy[i]; + } + } + + std::cout << iter << " repack result: " << score << " " + << ratio << " " << count_diff << std::endl; + + return score; + }; + + opt::StopCriteria stopcr; + stopcr.max_iterations = 30; + stopcr.stop_score = 1e-20; + opt::TOptimizer solver(stopcr); + solver.optimize_min(ofn, opt::initvals(0.5), + opt::bound(0.0, 1.0)); + + // optimize + config_.object_function = prev_func; + } + + } + + struct Optimum { + double relpos; + unsigned nfpidx; + int hidx; + Optimum(double pos, unsigned nidx): + relpos(pos), nfpidx(nidx), hidx(-1) {} + Optimum(double pos, unsigned nidx, int holeidx): + relpos(pos), nfpidx(nidx), hidx(holeidx) {} + }; + + class Optimizer: public opt::TOptimizer { + public: + Optimizer() { + opt::StopCriteria stopcr; + stopcr.max_iterations = 200; + stopcr.relative_score_difference = 1e-20; + this->stopcr_ = stopcr; + } + }; + + using Edges = EdgeCache; + + template> + PackResult _trypack( Item& item, const Range& remaining = Range()) { PackResult ret; bool can_pack = false; + double best_overfit = std::numeric_limits::max(); auto remlist = ItemGroup(remaining.from, remaining.to); + size_t itemhash = __itemhash::hash(item); if(items_.empty()) { setInitialPosition(item); - can_pack = item.isInside(bin_); + best_overfit = overfit(item.transformedShape(), bin_); + can_pack = best_overfit <= 0; } else { double global_score = std::numeric_limits::max(); @@ -588,7 +804,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer nfps; + Shapes nfps; for(auto rot : config_.rotations) { @@ -596,17 +812,16 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer()); - nfps = calcnfp(items_, item, Lvl()); - auto iv = nfp::referenceVertex(trsh); + auto iv = item.referenceVertex(); auto startpos = item.translation(); - std::vector> ecache; + std::vector ecache; ecache.reserve(nfps.size()); for(auto& nfp : nfps ) { @@ -614,74 +829,66 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer pile; + Shapes pile; pile.reserve(items_.size()+1); - double pile_area = 0; + // double pile_area = 0; for(Item& mitem : items_) { pile.emplace_back(mitem.transformedShape()); - pile_area += mitem.area(); + // pile_area += mitem.area(); } auto merged_pile = nfp::merge(pile); + auto& bin = bin_; + double norm = norm_; // This is the kernel part of the object function that is // customizable by the library client auto _objfunc = config_.object_function? config_.object_function : - [this, &merged_pile, &pile_area]( - nfp::Shapes& /*pile*/, - const Item& item, - const ItemGroup& /*remaining*/) + [norm, /*pile_area,*/ bin, merged_pile]( + const Pile& /*pile*/, + const Item& item, + const ItemGroup& /*remaining*/) { - merged_pile.emplace_back(item.transformedShape()); - auto ch = sl::convexHull(merged_pile); - merged_pile.pop_back(); - - // The pack ratio -- how much is the convex hull occupied - double pack_rate = (pile_area + item.area())/sl::area(ch); - - // ratio of waste - double waste = 1.0 - pack_rate; + auto ibb = item.boundingBox(); + auto binbb = sl::boundingBox(bin); + auto mp = merged_pile; + mp.emplace_back(item.transformedShape()); + auto fullbb = sl::boundingBox(mp); - // Score is the square root of waste. This will extend the - // range of good (lower) values and shring the range of bad - // (larger) values. - auto score = std::sqrt(waste); + double score = pl::distance(ibb.center(), binbb.center()); + score /= norm; - if(!wouldFit(ch, bin_)) score += norm_; + double miss = overfit(fullbb, bin); + miss = miss > 0? miss : 0; + score += std::pow(miss, 2); return score; }; // Our object function for placement - auto rawobjfunc = [&] (Vertex v) + auto rawobjfunc = + [item, _objfunc, iv, + startpos, remlist, pile] (Vertex v) { auto d = v - iv; d += startpos; - item.translation(d); + Item itm = item; + itm.translation(d); - double score = _objfunc(pile, item, remlist); + return _objfunc(pile, itm, remlist); + }; - return score; + auto getNfpPoint = [&ecache](const Optimum& opt) + { + return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : + ecache[opt.nfpidx].coords(opt.hidx, opt.relpos); }; - auto boundaryCheck = [&](const Optimum& o) { + auto boundaryCheck = + [&merged_pile, &getNfpPoint, &item, &bin, &iv, &startpos] + (const Optimum& o) + { auto v = getNfpPoint(o); auto d = v - iv; d += startpos; @@ -691,84 +898,111 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer solver(stopcr); - Optimum optimum(0, 0); double best_score = std::numeric_limits::max(); + std::launch policy = std::launch::deferred; + if(config_.parallel) policy |= std::launch::async; + + using OptResult = opt::Result; + using OptResults = std::vector; // Local optimization with the four polygon corners as // starting points for(unsigned ch = 0; ch < ecache.size(); ch++) { auto& cache = ecache[ch]; - auto contour_ofn = [&rawobjfunc, &getNfpPoint, ch] + auto contour_ofn = [rawobjfunc, getNfpPoint, ch] (double relpos) { return rawobjfunc(getNfpPoint(Optimum(relpos, ch))); }; - std::for_each(cache.corners().begin(), - cache.corners().end(), - [ch, &contour_ofn, &solver, &best_score, - &optimum, &boundaryCheck] (double pos) + OptResults results(cache.corners().size()); + + __parallel::enumerate( + cache.corners().begin(), + cache.corners().end(), + [&contour_ofn, &results] + (double pos, unsigned n) { + Optimizer solver; try { - auto result = solver.optimize_min(contour_ofn, + results[n] = solver.optimize_min(contour_ofn, opt::initvals(pos), opt::bound(0, 1.0) ); - - if(result.score < best_score) { - Optimum o(std::get<0>(result.optimum), ch, -1); - if(boundaryCheck(o)) { - best_score = result.score; - optimum = o; - } - } } catch(std::exception& e) { derr() << "ERROR: " << e.what() << "\n"; } - }); + }, policy); + + auto resultcomp = + []( const OptResult& r1, const OptResult& r2 ) { + return r1.score < r2.score; + }; + + auto mr = *std::min_element(results.begin(), results.end(), + resultcomp); + + if(mr.score < best_score) { + Optimum o(std::get<0>(mr.optimum), ch, -1); + double miss = boundaryCheck(o); + if(miss <= 0) { + best_score = mr.score; + optimum = o; + } else { + best_overfit = std::min(miss, best_overfit); + } + } for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) { auto hole_ofn = - [&rawobjfunc, &getNfpPoint, ch, hidx] + [rawobjfunc, getNfpPoint, ch, hidx] (double pos) { Optimum opt(pos, ch, hidx); return rawobjfunc(getNfpPoint(opt)); }; - std::for_each(cache.corners(hidx).begin(), + results.clear(); + results.resize(cache.corners(hidx).size()); + + // TODO : use parallel for + __parallel::enumerate(cache.corners(hidx).begin(), cache.corners(hidx).end(), - [&hole_ofn, &solver, &best_score, - &optimum, ch, hidx, &boundaryCheck] - (double pos) + [&hole_ofn, &results] + (double pos, unsigned n) { + Optimizer solver; try { - auto result = solver.optimize_min(hole_ofn, + results[n] = solver.optimize_min(hole_ofn, opt::initvals(pos), opt::bound(0, 1.0) ); - if(result.score < best_score) { - Optimum o(std::get<0>(result.optimum), - ch, hidx); - if(boundaryCheck(o)) { - best_score = result.score; - optimum = o; - } - } } catch(std::exception& e) { derr() << "ERROR: " << e.what() << "\n"; } - }); + }, policy); + + auto hmr = *std::min_element(results.begin(), + results.end(), + resultcomp); + + if(hmr.score < best_score) { + Optimum o(std::get<0>(hmr.optimum), + ch, hidx); + double miss = boundaryCheck(o); + if(miss <= 0.0) { + best_score = hmr.score; + optimum = o; + } else { + best_overfit = std::min(miss, best_overfit); + } + } } } @@ -788,22 +1022,14 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); - auto&& bb = sl::boundingBox(m); + auto&& bb = sl::boundingBox(m); Vertex ci, cb; diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp index 1a0730d8800..44e2bc1b004 100644 --- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp @@ -26,15 +26,21 @@ class PlacerBoilerplate { Item *item_ptr_; Vertex move_; Radians rot_; + double overfit_; friend class PlacerBoilerplate; friend Subclass; + PackResult(Item& item): item_ptr_(&item), move_(item.translation()), rot_(item.rotation()) {} - PackResult(): item_ptr_(nullptr) {} + + PackResult(double overfit = 1.0): + item_ptr_(nullptr), overfit_(overfit) {} + public: operator bool() { return item_ptr_ != nullptr; } + double overfit() const { return overfit_; } }; inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin) @@ -82,9 +88,6 @@ class PlacerBoilerplate { inline void clearItems() { items_.clear(); farea_valid_ = false; -#ifndef NDEBUG - debug_items_.clear(); -#endif } inline double filledArea() const { @@ -101,10 +104,6 @@ class PlacerBoilerplate { return farea_; } -#ifndef NDEBUG - std::vector debug_items_; -#endif - protected: BinType bin_; diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 8c02dc37332..846b00badfb 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -493,8 +493,7 @@ class _DJDHeuristic: public SelectionBoilerplate { std::array candidates = {it, it2, it3}; - auto tryPack = [&placer, &candidates, ¬_packed, - &pack]( + auto tryPack = [&placer, &candidates, &pack]( const decltype(indices)& idx) { std::array packed = {false}; @@ -569,11 +568,7 @@ class _DJDHeuristic: public SelectionBoilerplate { { packed_bins_[idx] = placer.getItems(); -#ifndef NDEBUG - packed_bins_[idx].insert(packed_bins_[idx].end(), - placer.getDebugItems().begin(), - placer.getDebugItems().end()); -#endif + // TODO here should be a spinlock slock.lock(); acounter -= packednum; diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 1312f9874de..eb820a51842 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -68,13 +68,13 @@ class _FirstFitSelection: public SelectionBoilerplate { } auto it = store_.begin(); + while(it != store_.end()) { bool was_packed = false; + size_t j = 0; while(!was_packed) { - - for(size_t j = 0; j < placers.size() && !was_packed; j++) { - if((was_packed = - placers[j].pack(*it, rem(it, store_) ))) + for(; j < placers.size() && !was_packed; j++) { + if((was_packed = placers[j].pack(*it, rem(it, store_) ))) makeProgress(placers[j], j); } @@ -82,6 +82,7 @@ class _FirstFitSelection: public SelectionBoilerplate { placers.emplace_back(bin); placers.back().configure(pconfig); packed_bins_.emplace_back(); + j = placers.size() - 1; } } ++it; diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index 197ff659813..b85bbc11125 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -5,6 +5,7 @@ #include "printer_parts.h" #include //#include "../tools/libnfpglue.hpp" +//#include "../tools/nfp_svgnest_glue.hpp" std::vector& prusaParts() { static std::vector ret; @@ -219,21 +220,21 @@ TEST(GeometryAlgorithms, IsPointInsidePolygon) { Point p = {1, 1}; - ASSERT_TRUE(rect.isPointInside(p)); + ASSERT_TRUE(rect.isInside(p)); p = {11, 11}; - ASSERT_FALSE(rect.isPointInside(p)); + ASSERT_FALSE(rect.isInside(p)); p = {11, 12}; - ASSERT_FALSE(rect.isPointInside(p)); + ASSERT_FALSE(rect.isInside(p)); p = {3, 3}; - ASSERT_TRUE(rect.isPointInside(p)); + ASSERT_TRUE(rect.isInside(p)); } diff --git a/xs/src/libnest2d/tools/libnfpglue.cpp b/xs/src/libnest2d/tools/libnfpglue.cpp index 18656fd402f..31733acf97e 100644 --- a/xs/src/libnest2d/tools/libnfpglue.cpp +++ b/xs/src/libnest2d/tools/libnfpglue.cpp @@ -56,7 +56,7 @@ libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) { NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) { - using Vertex = PointImpl; + namespace sl = shapelike; NfpR ret; @@ -85,7 +85,7 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) // this can throw auto nfp = libnfporb::generateNFP(pstat, porb, true); - auto &ct = ShapeLike::getContour(ret.first); + auto &ct = sl::getContour(ret.first); ct.reserve(nfp.front().size()+1); for(auto v : nfp.front()) { v = scale(v, refactor); @@ -94,7 +94,7 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) ct.push_back(ct.front()); std::reverse(ct.begin(), ct.end()); - auto &rholes = ShapeLike::holes(ret.first); + auto &rholes = sl::holes(ret.first); for(size_t hidx = 1; hidx < nfp.size(); ++hidx) { if(nfp[hidx].size() >= 3) { rholes.emplace_back(); @@ -110,31 +110,31 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) } } - ret.second = Nfp::referenceVertex(ret.first); + ret.second = nfp::referenceVertex(ret.first); } catch(std::exception& e) { std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl; // auto ch_stat = ShapeLike::convexHull(sh); // auto ch_orb = ShapeLike::convexHull(cother); - ret = Nfp::nfpConvexOnly(sh, cother); + ret = nfp::nfpConvexOnly(sh, cother); } return ret; } -NfpR Nfp::NfpImpl::operator()( +NfpR nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother);//nfpConvexOnly(sh, cother); } -NfpR Nfp::NfpImpl::operator()( +NfpR nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother); } -NfpR Nfp::NfpImpl::operator()( +NfpR nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother); diff --git a/xs/src/libnest2d/tools/libnfpglue.hpp b/xs/src/libnest2d/tools/libnfpglue.hpp index 75f6394451f..1ff033cb92b 100644 --- a/xs/src/libnest2d/tools/libnfpglue.hpp +++ b/xs/src/libnest2d/tools/libnfpglue.hpp @@ -5,22 +5,22 @@ namespace libnest2d { -using NfpR = Nfp::NfpResult; +using NfpR = nfp::NfpResult; NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother); template<> -struct Nfp::NfpImpl { +struct nfp::NfpImpl { NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; template<> -struct Nfp::NfpImpl { +struct nfp::NfpImpl { NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; template<> -struct Nfp::NfpImpl { +struct nfp::NfpImpl { NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; @@ -34,7 +34,7 @@ struct Nfp::NfpImpl { // NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); //}; -template<> struct Nfp::MaxNfpLevel { +template<> struct nfp::MaxNfpLevel { static const BP2D_CONSTEXPR NfpLevel value = // NfpLevel::CONVEX_ONLY; NfpLevel::BOTH_CONCAVE; diff --git a/xs/src/libnest2d/tools/nfp_svgnest.hpp b/xs/src/libnest2d/tools/nfp_svgnest.hpp new file mode 100644 index 00000000000..8ab571c003e --- /dev/null +++ b/xs/src/libnest2d/tools/nfp_svgnest.hpp @@ -0,0 +1,1004 @@ +#ifndef NFP_SVGNEST_HPP +#define NFP_SVGNEST_HPP + +#include +#include + +#include + +namespace libnest2d { + +namespace __svgnest { + +using std::sqrt; +using std::min; +using std::max; +using std::abs; +using std::isnan; + +//template struct _Scale { +// static const BP2D_CONSTEXPR long long Value = 1000000; +//}; + +template struct _alg { + using Contour = TContour; + using Point = TPoint; + using iCoord = TCoord; + using Coord = double; + using Shapes = nfp::Shapes; + + static const Coord TOL; + +#define dNAN std::nan("") + + struct Vector { + Coord x, y; + bool marked = false; + Vector() = default; + Vector(Coord X, Coord Y): x(X), y(Y) {} + Vector(const Point& p): x(getX(p)), y(getY(p)) {} + operator Point() const { return {iCoord(x), iCoord(y)}; } + Vector& operator=(const Point& p) { + x = getX(p), y = getY(p); return *this; + } + Vector(std::initializer_list il): + x(*il.begin()), y(*std::next(il.begin())) {} + }; + + static inline Coord x(const Point& p) { return Coord(getX(p)); } + static inline Coord y(const Point& p) { return Coord(getY(p)); } + + static inline Coord x(const Vector& p) { return p.x; } + static inline Coord y(const Vector& p) { return p.y; } + + class Cntr { + std::vector v_; + public: + Cntr(const Contour& c) { + v_.reserve(c.size()); + std::transform(c.begin(), c.end(), std::back_inserter(v_), + [](const Point& p) { + return Vector(double(x(p))/1e6, double(y(p))/1e6); + }); + } + Cntr() = default; + + Coord offsetx = 0; + Coord offsety = 0; + size_t size() const { return v_.size(); } + bool empty() const { return v_.empty(); } + typename Contour::const_iterator begin() const { return v_.cbegin(); } + typename Contour::const_iterator end() const { return v_.cend(); } + Vector& operator[](size_t idx) { return v_[idx]; } + const Vector& operator[](size_t idx) const { return v_[idx]; } + template + void emplace_back(Args&&...args) { + v_.emplace_back(std::forward(args)...); + } + template + void push(Args&&...args) { + v_.emplace_back(std::forward(args)...); + } + void clear() { v_.clear(); } + + operator Contour() const { + Contour cnt; + cnt.reserve(v_.size()); + std::transform(v_.begin(), v_.end(), std::back_inserter(cnt), + [](const Vector& vertex) { + return Point(iCoord(vertex.x*1e6), iCoord(vertex.y*1e6)); + }); + return cnt; + } + }; + + inline static bool _almostEqual(Coord a, Coord b, + Coord tolerance = TOL) + { + return std::abs(a - b) < tolerance; + } + + // returns true if p lies on the line segment defined by AB, + // but not at any endpoints may need work! + static bool _onSegment(const Vector& A, const Vector& B, const Vector& p) { + + // vertical line + if(_almostEqual(A.x, B.x) && _almostEqual(p.x, A.x)) { + if(!_almostEqual(p.y, B.y) && !_almostEqual(p.y, A.y) && + p.y < max(B.y, A.y) && p.y > min(B.y, A.y)){ + return true; + } + else{ + return false; + } + } + + // horizontal line + if(_almostEqual(A.y, B.y) && _almostEqual(p.y, A.y)){ + if(!_almostEqual(p.x, B.x) && !_almostEqual(p.x, A.x) && + p.x < max(B.x, A.x) && p.x > min(B.x, A.x)){ + return true; + } + else{ + return false; + } + } + + //range check + if((p.x < A.x && p.x < B.x) || (p.x > A.x && p.x > B.x) || + (p.y < A.y && p.y < B.y) || (p.y > A.y && p.y > B.y)) + return false; + + // exclude end points + if((_almostEqual(p.x, A.x) && _almostEqual(p.y, A.y)) || + (_almostEqual(p.x, B.x) && _almostEqual(p.y, B.y))) + return false; + + + double cross = (p.y - A.y) * (B.x - A.x) - (p.x - A.x) * (B.y - A.y); + + if(abs(cross) > TOL) return false; + + double dot = (p.x - A.x) * (B.x - A.x) + (p.y - A.y)*(B.y - A.y); + + if(dot < 0 || _almostEqual(dot, 0)) return false; + + double len2 = (B.x - A.x)*(B.x - A.x) + (B.y - A.y)*(B.y - A.y); + + if(dot > len2 || _almostEqual(dot, len2)) return false; + + return true; + } + + // return true if point is in the polygon, false if outside, and null if exactly on a point or edge + static int pointInPolygon(const Vector& point, const Cntr& polygon) { + if(polygon.size() < 3){ + return 0; + } + + bool inside = false; + Coord offsetx = polygon.offsetx; + Coord offsety = polygon.offsety; + + for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j=i++) { + auto xi = polygon[i].x + offsetx; + auto yi = polygon[i].y + offsety; + auto xj = polygon[j].x + offsetx; + auto yj = polygon[j].y + offsety; + + if(_almostEqual(xi, point.x) && _almostEqual(yi, point.y)){ + return 0; // no result + } + + if(_onSegment({xi, yi}, {xj, yj}, point)){ + return 0; // exactly on the segment + } + + if(_almostEqual(xi, xj) && _almostEqual(yi, yj)){ // ignore very small lines + continue; + } + + bool intersect = ((yi > point.y) != (yj > point.y)) && + (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi); + if (intersect) inside = !inside; + } + + return inside? 1 : -1; + } + + static bool intersect(const Cntr& A, const Cntr& B){ + Contour a = A, b = B; + return shapelike::intersects(shapelike::create(a), shapelike::create(b)); + } + + static Vector _normalizeVector(const Vector& v) { + if(_almostEqual(v.x*v.x + v.y*v.y, Coord(1))){ + return Point(v); // given vector was already a unit vector + } + auto len = sqrt(v.x*v.x + v.y*v.y); + auto inverse = 1/len; + + return { Coord(v.x*inverse), Coord(v.y*inverse) }; + } + + static double pointDistance( const Vector& p, + const Vector& s1, + const Vector& s2, + Vector normal, + bool infinite = false) + { + normal = _normalizeVector(normal); + + Vector dir = { + normal.y, + -normal.x + }; + + auto pdot = p.x*dir.x + p.y*dir.y; + auto s1dot = s1.x*dir.x + s1.y*dir.y; + auto s2dot = s2.x*dir.x + s2.y*dir.y; + + auto pdotnorm = p.x*normal.x + p.y*normal.y; + auto s1dotnorm = s1.x*normal.x + s1.y*normal.y; + auto s2dotnorm = s2.x*normal.x + s2.y*normal.y; + + if(!infinite){ + if (((pdots1dot || _almostEqual(pdot, s1dot)) && + (pdot>s2dot || _almostEqual(pdot, s2dot)))) + { + // dot doesn't collide with segment, + // or lies directly on the vertex + return dNAN; + } + if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && + (pdotnorm>s1dotnorm && pdotnorm>s2dotnorm)) + { + return double(min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm)); + } + if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && + (pdotnorm EFmax){ + return dNAN; + } + + double overlap = 0; + + if((ABmax > EFmax && ABmin < EFmin) || (EFmax > ABmax && EFmin < ABmin)) + { + overlap = 1; + } + else{ + auto minMax = min(ABmax, EFmax); + auto maxMin = max(ABmin, EFmin); + + auto maxMax = max(ABmax, EFmax); + auto minMin = min(ABmin, EFmin); + + overlap = (minMax-maxMin)/(maxMax-minMin); + } + + auto crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y); + auto crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y); + + // lines are colinear + if(_almostEqual(crossABE,0) && _almostEqual(crossABF,0)){ + + Vector ABnorm = {B.y-A.y, A.x-B.x}; + Vector EFnorm = {F.y-E.y, E.x-F.x}; + + auto ABnormlength = sqrt(ABnorm.x*ABnorm.x + ABnorm.y*ABnorm.y); + ABnorm.x /= ABnormlength; + ABnorm.y /= ABnormlength; + + auto EFnormlength = sqrt(EFnorm.x*EFnorm.x + EFnorm.y*EFnorm.y); + EFnorm.x /= EFnormlength; + EFnorm.y /= EFnormlength; + + // segment normals must point in opposite directions + if(abs(ABnorm.y * EFnorm.x - ABnorm.x * EFnorm.y) < TOL && + ABnorm.y * EFnorm.y + ABnorm.x * EFnorm.x < 0){ + // normal of AB segment must point in same direction as + // given direction vector + auto normdot = ABnorm.y * direction.y + ABnorm.x * direction.x; + // the segments merely slide along eachother + if(_almostEqual(normdot,0, TOL)){ + return dNAN; + } + if(normdot < 0){ + return 0.0; + } + } + return dNAN; + } + + std::vector distances; distances.reserve(10); + + // coincident points + if(_almostEqual(dotA, dotE)){ + distances.emplace_back(crossA-crossE); + } + else if(_almostEqual(dotA, dotF)){ + distances.emplace_back(crossA-crossF); + } + else if(dotA > EFmin && dotA < EFmax){ + auto d = pointDistance(A,E,F,reverse); + if(!isnan(d) && _almostEqual(d, 0)) + { // A currently touches EF, but AB is moving away from EF + auto dB = pointDistance(B,E,F,reverse,true); + if(dB < 0 || _almostEqual(dB*overlap,0)){ + d = dNAN; + } + } + if(isnan(d)){ + distances.emplace_back(d); + } + } + + if(_almostEqual(dotB, dotE)){ + distances.emplace_back(crossB-crossE); + } + else if(_almostEqual(dotB, dotF)){ + distances.emplace_back(crossB-crossF); + } + else if(dotB > EFmin && dotB < EFmax){ + auto d = pointDistance(B,E,F,reverse); + + if(!isnan(d) && _almostEqual(d, 0)) + { // crossA>crossB A currently touches EF, but AB is moving away from EF + double dA = pointDistance(A,E,F,reverse,true); + if(dA < 0 || _almostEqual(dA*overlap,0)){ + d = dNAN; + } + } + if(!isnan(d)){ + distances.emplace_back(d); + } + } + + if(dotE > ABmin && dotE < ABmax){ + auto d = pointDistance(E,A,B,direction); + if(!isnan(d) && _almostEqual(d, 0)) + { // crossF ABmin && dotF < ABmax){ + auto d = pointDistance(F,A,B,direction); + if(!isnan(d) && _almostEqual(d, 0)) + { // && crossE 0 || _almostEqual(d, 0)){ + distance = d; + } + } + } + } + return distance; + } + + static double polygonProjectionDistance(const Cntr& A, + const Cntr& B, + Vector direction) + { + auto Boffsetx = B.offsetx; + auto Boffsety = B.offsety; + auto Aoffsetx = A.offsetx; + auto Aoffsety = A.offsety; + + // close the loop for polygons + /*if(A[0] != A[A.length-1]){ + A.push(A[0]); + } + + if(B[0] != B[B.length-1]){ + B.push(B[0]); + }*/ + + auto& edgeA = A; + auto& edgeB = B; + + double distance = dNAN, d; +// Vector p, s1, s2; + + for(size_t i = 0; i < edgeB.size(); i++) { + // the shortest/most negative projection of B onto A + double minprojection = dNAN; + Vector minp; + for(size_t j = 0; j < edgeA.size() - 1; j++){ + Vector p = {x(edgeB[i]) + Boffsetx, y(edgeB[i]) + Boffsety }; + Vector s1 = {x(edgeA[j]) + Aoffsetx, y(edgeA[j]) + Aoffsety }; + Vector s2 = {x(edgeA[j+1]) + Aoffsetx, y(edgeA[j+1]) + Aoffsety }; + + if(abs((s2.y-s1.y) * direction.x - + (s2.x-s1.x) * direction.y) < TOL) continue; + + // project point, ignore edge boundaries + d = pointDistance(p, s1, s2, direction); + + if(!isnan(d) && (isnan(minprojection) || d < minprojection)) { + minprojection = d; + minp = p; + } + } + + if(!isnan(minprojection) && (isnan(distance) || + minprojection > distance)){ + distance = minprojection; + } + } + + return distance; + } + + static std::pair searchStartPoint( + const Cntr& AA, const Cntr& BB, bool inside, const std::vector& NFP = {}) + { + // clone arrays + auto A = AA; + auto B = BB; + +// // close the loop for polygons +// if(A[0] != A[A.size()-1]){ +// A.push(A[0]); +// } + +// if(B[0] != B[B.size()-1]){ +// B.push(B[0]); +// } + + // returns true if point already exists in the given nfp + auto inNfp = [](const Vector& p, const std::vector& nfp){ + if(nfp.empty()){ + return false; + } + + for(size_t i=0; i < nfp.size(); i++){ + for(size_t j = 0; j< nfp[i].size(); j++){ + if(_almostEqual(p.x, nfp[i][j].x) && + _almostEqual(p.y, nfp[i][j].y)){ + return true; + } + } + } + + return false; + }; + + for(size_t i = 0; i < A.size() - 1; i++){ + if(!A[i].marked) { + A[i].marked = true; + for(size_t j = 0; j < B.size(); j++){ + B.offsetx = A[i].x - B[j].x; + B.offsety = A[i].y - B[j].y; + + int Binside = 0; + for(size_t k = 0; k < B.size(); k++){ + int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A); + if(inpoly != 0){ + Binside = inpoly; + break; + } + } + + if(Binside == 0){ // A and B are the same + return {false, {}}; + } + + auto startPoint = std::make_pair(true, Vector(B.offsetx, B.offsety)); + if(((Binside && inside) || (!Binside && !inside)) && + !intersect(A,B) && !inNfp(startPoint.second, NFP)){ + return startPoint; + } + + // slide B along vector + auto vx = A[i+1].x - A[i].x; + auto vy = A[i+1].y - A[i].y; + + double d1 = polygonProjectionDistance(A,B,{vx, vy}); + double d2 = polygonProjectionDistance(B,A,{-vx, -vy}); + + double d = dNAN; + + // todo: clean this up + if(isnan(d1) && isnan(d2)){ + // nothin + } + else if(isnan(d1)){ + d = d2; + } + else if(isnan(d2)){ + d = d1; + } + else{ + d = min(d1,d2); + } + + // only slide until no longer negative + // todo: clean this up + if(!isnan(d) && !_almostEqual(d,0) && d > 0){ + + } + else{ + continue; + } + + auto vd2 = vx*vx + vy*vy; + + if(d*d < vd2 && !_almostEqual(d*d, vd2)){ + auto vd = sqrt(vx*vx + vy*vy); + vx *= d/vd; + vy *= d/vd; + } + + B.offsetx += vx; + B.offsety += vy; + + for(size_t k = 0; k < B.size(); k++){ + int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A); + if(inpoly != 0){ + Binside = inpoly; + break; + } + } + startPoint = std::make_pair(true, Vector{B.offsetx, B.offsety}); + if(((Binside && inside) || (!Binside && !inside)) && + !intersect(A,B) && !inNfp(startPoint.second, NFP)){ + return startPoint; + } + } + } + } + + return {false, Vector(0, 0)}; + } + + static std::vector noFitPolygon(Cntr A, + Cntr B, + bool inside, + bool searchEdges) + { + if(A.size() < 3 || B.size() < 3) { + throw GeometryException(GeomErr::NFP); + return {}; + } + + A.offsetx = 0; + A.offsety = 0; + + unsigned i = 0, j = 0; + + auto minA = y(A[0]); + long minAindex = 0; + + auto maxB = y(B[0]); + long maxBindex = 0; + + for(i = 1; i < A.size(); i++){ + A[i].marked = false; + if(y(A[i]) < minA){ + minA = y(A[i]); + minAindex = i; + } + } + + for(i = 1; i < B.size(); i++){ + B[i].marked = false; + if(y(B[i]) > maxB){ + maxB = y(B[i]); + maxBindex = i; + } + } + + std::pair startpoint; + + if(!inside){ + // shift B such that the bottom-most point of B is at the top-most + // point of A. This guarantees an initial placement with no + // intersections + startpoint = { true, + { x(A[minAindex]) - x(B[maxBindex]), + y(A[minAindex]) - y(B[maxBindex]) } + }; + } + else { + // no reliable heuristic for inside + startpoint = searchStartPoint(A, B, true); + } + + std::vector NFPlist; + + struct Touch { + int type; + long A, B; + Touch(int t, long a, long b): type(t), A(a), B(b) {} + }; + + while(startpoint.first) { + + B.offsetx = startpoint.second.x; + B.offsety = startpoint.second.y; + + // maintain a list of touching points/edges + std::vector touching; + + Cntr NFP; + NFP.emplace_back(x(B[0]) + B.offsetx, y(B[0]) + B.offsety); + + auto referencex = x(B[0]) + B.offsetx; + auto referencey = y(B[0]) + B.offsety; + auto startx = referencex; + auto starty = referencey; + unsigned counter = 0; + + // sanity check, prevent infinite loop + while(counter < 10*(A.size() + B.size())){ + touching.clear(); + + // find touching vertices/edges + for(i = 0; i < A.size(); i++){ + auto nexti = (i == A.size() - 1) ? 0 : i + 1; + for(j = 0; j < B.size(); j++){ + + auto nextj = (j == B.size() - 1) ? 0 : j + 1; + + if( _almostEqual(A[i].x, B[j].x+B.offsetx) && + _almostEqual(A[i].y, B[j].y+B.offsety)) + { + touching.emplace_back(0, i, j); + } + else if( _onSegment( + A[i], A[nexti], + { B[j].x+B.offsetx, B[j].y + B.offsety}) ) + { + touching.emplace_back(1, nexti, j); + } + else if( _onSegment( + {B[j].x+B.offsetx, B[j].y + B.offsety}, + {B[nextj].x+B.offsetx, B[nextj].y + B.offsety}, + A[i]) ) + { + touching.emplace_back(2, i, nextj); + } + } + } + + struct V { + Coord x, y; + Vector *start, *end; + operator bool() { + return start != nullptr && end != nullptr; + } + operator Vector() const { return {x, y}; } + }; + + // generate translation vectors from touching vertices/edges + std::vector vectors; + for(i=0; i < touching.size(); i++){ + auto& vertexA = A[touching[i].A]; + vertexA.marked = true; + + // adjacent A vertices + auto prevAindex = touching[i].A - 1; + auto nextAindex = touching[i].A + 1; + + prevAindex = (prevAindex < 0) ? A.size() - 1 : prevAindex; // loop + nextAindex = (nextAindex >= A.size()) ? 0 : nextAindex; // loop + + auto& prevA = A[prevAindex]; + auto& nextA = A[nextAindex]; + + // adjacent B vertices + auto& vertexB = B[touching[i].B]; + + auto prevBindex = touching[i].B-1; + auto nextBindex = touching[i].B+1; + + prevBindex = (prevBindex < 0) ? B.size() - 1 : prevBindex; // loop + nextBindex = (nextBindex >= B.size()) ? 0 : nextBindex; // loop + + auto& prevB = B[prevBindex]; + auto& nextB = B[nextBindex]; + + if(touching[i].type == 0){ + + V vA1 = { + prevA.x - vertexA.x, + prevA.y - vertexA.y, + &vertexA, + &prevA + }; + + V vA2 = { + nextA.x - vertexA.x, + nextA.y - vertexA.y, + &vertexA, + &nextA + }; + + // B vectors need to be inverted + V vB1 = { + vertexB.x - prevB.x, + vertexB.y - prevB.y, + &prevB, + &vertexB + }; + + V vB2 = { + vertexB.x - nextB.x, + vertexB.y - nextB.y, + &nextB, + &vertexB + }; + + vectors.emplace_back(vA1); + vectors.emplace_back(vA2); + vectors.emplace_back(vB1); + vectors.emplace_back(vB2); + } + else if(touching[i].type == 1){ + vectors.emplace_back(V{ + vertexA.x-(vertexB.x+B.offsetx), + vertexA.y-(vertexB.y+B.offsety), + &prevA, + &vertexA + }); + + vectors.emplace_back(V{ + prevA.x-(vertexB.x+B.offsetx), + prevA.y-(vertexB.y+B.offsety), + &vertexA, + &prevA + }); + } + else if(touching[i].type == 2){ + vectors.emplace_back(V{ + vertexA.x-(vertexB.x+B.offsetx), + vertexA.y-(vertexB.y+B.offsety), + &prevB, + &vertexB + }); + + vectors.emplace_back(V{ + vertexA.x-(prevB.x+B.offsetx), + vertexA.y-(prevB.y+B.offsety), + &vertexB, + &prevB + }); + } + } + + // TODO: there should be a faster way to reject vectors that + // will cause immediate intersection. For now just check them all + + V translate = {0, 0, nullptr, nullptr}; + V prevvector = {0, 0, nullptr, nullptr}; + double maxd = 0; + + for(i = 0; i < vectors.size(); i++) { + if(vectors[i].x == 0 && vectors[i].y == 0){ + continue; + } + + // if this vector points us back to where we came from, ignore it. + // ie cross product = 0, dot product < 0 + if(prevvector && vectors[i].y * prevvector.y + vectors[i].x * prevvector.x < 0){ + + // compare magnitude with unit vectors + double vectorlength = sqrt(vectors[i].x*vectors[i].x+vectors[i].y*vectors[i].y); + Vector unitv = {Coord(vectors[i].x/vectorlength), + Coord(vectors[i].y/vectorlength)}; + + double prevlength = sqrt(prevvector.x*prevvector.x+prevvector.y*prevvector.y); + Vector prevunit = { prevvector.x/prevlength, prevvector.y/prevlength}; + + // we need to scale down to unit vectors to normalize vector length. Could also just do a tan here + if(abs(unitv.y * prevunit.x - unitv.x * prevunit.y) < 0.0001){ + continue; + } + } + + double d = polygonSlideDistance(A, B, vectors[i], true); + double vecd2 = vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y; + + if(isnan(d) || d*d > vecd2){ + double vecd = sqrt(vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y); + d = vecd; + } + + if(!isnan(d) && d > maxd){ + maxd = d; + translate = vectors[i]; + } + } + + if(!translate || _almostEqual(maxd, 0)) + { + // didn't close the loop, something went wrong here + NFP.clear(); + break; + } + + translate.start->marked = true; + translate.end->marked = true; + + prevvector = translate; + + // trim + double vlength2 = translate.x*translate.x + translate.y*translate.y; + if(maxd*maxd < vlength2 && !_almostEqual(maxd*maxd, vlength2)){ + double scale = sqrt((maxd*maxd)/vlength2); + translate.x *= scale; + translate.y *= scale; + } + + referencex += translate.x; + referencey += translate.y; + + if(_almostEqual(referencex, startx) && + _almostEqual(referencey, starty)) { + // we've made a full loop + break; + } + + // if A and B start on a touching horizontal line, + // the end point may not be the start point + bool looped = false; + if(NFP.size() > 0) { + for(i = 0; i < NFP.size() - 1; i++) { + if(_almostEqual(referencex, NFP[i].x) && + _almostEqual(referencey, NFP[i].y)){ + looped = true; + } + } + } + + if(looped){ + // we've made a full loop + break; + } + + NFP.emplace_back(referencex, referencey); + + B.offsetx += translate.x; + B.offsety += translate.y; + + counter++; + } + + if(NFP.size() > 0){ + NFPlist.emplace_back(NFP); + } + + if(!searchEdges){ + // only get outer NFP or first inner NFP + break; + } + + startpoint = + searchStartPoint(A, B, inside, NFPlist); + + } + + return NFPlist; + } +}; + +template const double _alg::TOL = std::pow(10, -9); + +//template +//nfp::NfpResult nfpSimpleSimple(const S& stat, const S& orb) { +//// using Cntr = TContour; +// using Point = TPoint; +//// using Coord = TCoord; +//// using Shapes = nfp::Shapes; + +// namespace sl = shapelike; + +// noFitPolygon(sl::getContour(stat), sl::getContour(orb), true, true); +// return {S(), Point()}; +//} + +} +} + +#endif // NFP_SVGNEST_HPP diff --git a/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp new file mode 100644 index 00000000000..7ceb2d24dc2 --- /dev/null +++ b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp @@ -0,0 +1,77 @@ +#ifndef NFP_SVGNEST_GLUE_HPP +#define NFP_SVGNEST_GLUE_HPP + +#include "nfp_svgnest.hpp" + +#include + +namespace libnest2d { + +namespace __svgnest { + +//template<> struct _Tol { +// static const BP2D_CONSTEXPR TCoord Value = 1000000; +//}; + +} + +namespace nfp { + +using NfpR = NfpResult; + +template<> struct NfpImpl { + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { +// return nfpConvexOnly(sh, cother); + namespace sl = shapelike; + using alg = __svgnest::_alg; + + std::cout << "Itt vagyok" << std::endl; + auto nfp_p = alg::noFitPolygon(sl::getContour(sh), + sl::getContour(cother), false, false); + + PolygonImpl nfp_cntr; + nfp_cntr.Contour = nfp_p.front(); + std::cout << "Contour size: " << nfp_cntr.Contour.size() << std::endl; + return {nfp_cntr, referenceVertex(nfp_cntr)}; + } +}; + +template<> struct NfpImpl { + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { +// return nfpConvexOnly(sh, cother); + namespace sl = shapelike; + using alg = __svgnest::_alg; + + std::cout << "Itt vagyok" << std::endl; + auto nfp_p = alg::noFitPolygon(sl::getContour(sh), + sl::getContour(cother), false, false); + + PolygonImpl nfp_cntr; + nfp_cntr.Contour = nfp_p.front(); + return {nfp_cntr, referenceVertex(nfp_cntr)}; + } +}; + +template<> +struct NfpImpl { + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { + namespace sl = shapelike; + using alg = __svgnest::_alg; + + auto nfp_p = alg::noFitPolygon(sl::getContour(sh), + sl::getContour(cother), true, false); + + PolygonImpl nfp_cntr; + nfp_cntr.Contour = nfp_p.front(); + return {nfp_cntr, referenceVertex(nfp_cntr)}; + } +}; + +template<> struct MaxNfpLevel { +// static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::BOTH_CONCAVE; + static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; +}; + +}} + +#endif // NFP_SVGNEST_GLUE_HPP diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index cc4bfff0f6c..dcb0da9e5dd 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -270,6 +270,8 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well pcfg.accuracy = 0.65f; + + pcfg.parallel = false; } template @@ -291,6 +293,7 @@ class _ArrBase { std::vector areacache_; SpatIndex rtree_; double norm_; + Pile pile_cache_; public: _ArrBase(const TBin& bin, Distance dist, @@ -317,23 +320,26 @@ class AutoArranger: public _ArrBase { std::function progressind): _ArrBase(bin, dist, progressind) { - pconf_.object_function = [this, bin] ( - Pile& pile, - const Item &item, - const ItemGroup& rem) { +// pconf_.object_function = [this, bin] ( +// const Pile& pile_c, +// const Item &item, +// const ItemGroup& rem) { - auto result = objfunc(bin.center(), bin_area_, pile, - item, norm_, areacache_, rtree_, rem); - double score = std::get<0>(result); - auto& fullbb = std::get<1>(result); +// auto& pile = pile_cache_; +// if(pile.size() != pile_c.size()) pile = pile_c; - auto wdiff = fullbb.width() - bin.width(); - auto hdiff = fullbb.height() - bin.height(); - if(wdiff > 0) score += std::pow(wdiff, 2) / norm_; - if(hdiff > 0) score += std::pow(hdiff, 2) / norm_; +// auto result = objfunc(bin.center(), bin_area_, pile, +// item, norm_, areacache_, rtree_, rem); +// double score = std::get<0>(result); +// auto& fullbb = std::get<1>(result); - return score; - }; +// auto wdiff = fullbb.width() - bin.width(); +// auto hdiff = fullbb.height() - bin.height(); +// if(wdiff > 0) score += std::pow(wdiff, 2) / norm_; +// if(hdiff > 0) score += std::pow(hdiff, 2) / norm_; + +// return score; +// }; pck_.configure(pconf_); } @@ -350,10 +356,13 @@ class AutoArranger: public _ArrBase { _ArrBase(bin, dist, progressind) { pconf_.object_function = [this, &bin] ( - Pile& pile, + const Pile& pile_c, const Item &item, const ItemGroup& rem) { + auto& pile = pile_cache_; + if(pile.size() != pile_c.size()) pile = pile_c; + auto result = objfunc(bin.center(), bin_area_, pile, item, norm_, areacache_, rtree_, rem); double score = std::get<0>(result); @@ -393,10 +402,13 @@ class AutoArranger: public _ArrBase { _ArrBase(bin, dist, progressind) { pconf_.object_function = [this, &bin] ( - Pile& pile, + const Pile& pile_c, const Item &item, const ItemGroup& rem) { + auto& pile = pile_cache_; + if(pile.size() != pile_c.size()) pile = pile_c; + auto binbb = sl::boundingBox(bin); auto result = objfunc(binbb.center(), bin_area_, pile, item, norm_, areacache_, rtree_, rem); @@ -417,10 +429,13 @@ class AutoArranger: public _ArrBase { _ArrBase(Box(0, 0), dist, progressind) { this->pconf_.object_function = [this] ( - Pile& pile, + const Pile& pile_c, const Item &item, const ItemGroup& rem) { + auto& pile = pile_cache_; + if(pile.size() != pile_c.size()) pile = pile_c; + auto result = objfunc({0, 0}, 0, pile, item, norm_, areacache_, rtree_, rem); return std::get<0>(result); From e522ad1a00cfb85b6cd21c664b213d5d35dca2bd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 22 Aug 2018 13:52:41 +0200 Subject: [PATCH 18/63] Parallel placer now works with the custom Slic3r object function. Works an order of magnitude faster. --- xs/CMakeLists.txt | 1 + xs/src/libnest2d/CMakeLists.txt | 27 +- xs/src/libnest2d/cmake_modules/FindTBB.cmake | 322 ++++++++++++++++++ xs/src/libnest2d/examples/main.cpp | 28 +- xs/src/libnest2d/libnest2d.h | 10 +- .../libnest2d/geometry_traits_nfp.hpp | 4 +- xs/src/libnest2d/libnest2d/libnest2d.hpp | 20 +- .../libnest2d/placers/bottomleftplacer.hpp | 2 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 227 +++++++----- .../libnest2d/placers/placer_boilerplate.hpp | 2 +- .../libnest2d/selections/djd_heuristic.hpp | 2 +- .../libnest2d/libnest2d/selections/filler.hpp | 2 +- .../libnest2d/selections/firstfit.hpp | 2 +- .../selections/selection_boilerplate.hpp | 3 +- xs/src/libnest2d/tests/test.cpp | 34 +- xs/src/libnest2d/tools/nfp_svgnest.hpp | 114 ++++--- xs/src/libnest2d/tools/nfp_svgnest_glue.hpp | 4 +- xs/src/libslic3r/ModelArrange.hpp | 237 +++++++------ 18 files changed, 742 insertions(+), 299 deletions(-) create mode 100644 xs/src/libnest2d/cmake_modules/FindTBB.cmake diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index d41b4c13a9d..70e9330580e 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -729,6 +729,7 @@ add_custom_target(pot set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d") add_subdirectory(${LIBDIR}/libnest2d) +target_compile_definitions(libslic3r PUBLIC -DUSE_TBB) target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES}) target_include_directories(libslic3r_gui PUBLIC BEFORE ${LIBNEST2D_INCLUDES}) diff --git a/xs/src/libnest2d/CMakeLists.txt b/xs/src/libnest2d/CMakeLists.txt index cd3e35b978b..f8135501234 100644 --- a/xs/src/libnest2d/CMakeLists.txt +++ b/xs/src/libnest2d/CMakeLists.txt @@ -90,6 +90,7 @@ if(LIBNEST2D_UNITTESTS) endif() if(LIBNEST2D_BUILD_EXAMPLES) + add_executable(example examples/main.cpp # tools/libnfpglue.hpp # tools/libnfpglue.cpp @@ -98,8 +99,30 @@ if(LIBNEST2D_BUILD_EXAMPLES) tools/svgtools.hpp tests/printer_parts.cpp tests/printer_parts.h - ${LIBNEST2D_SRCFILES}) - + ${LIBNEST2D_SRCFILES} + ) + set(TBB_STATIC ON) + find_package(TBB QUIET) + if(TBB_FOUND) + message(STATUS "Parallelization with Intel TBB") + target_include_directories(example PUBLIC ${TBB_INCLUDE_DIRS}) + target_compile_definitions(example PUBLIC ${TBB_DEFINITIONS} -DUSE_TBB) + if(MSVC) + # Suppress implicit linking of the TBB libraries by the Visual Studio compiler. + target_compile_definitions(example PUBLIC -D__TBB_NO_IMPLICIT_LINKAGE) + endif() + # The Intel TBB library will use the std::exception_ptr feature of C++11. + target_compile_definitions(example PUBLIC -DTBB_USE_CAPTURED_EXCEPTION=1) + + target_link_libraries(example ${TBB_LIBRARIES}) + else() + find_package(OpenMP QUIET) + if(OpenMP_CXX_FOUND) + message(STATUS "Parallelization with OpenMP") + target_include_directories(example PUBLIC OpenMP::OpenMP_CXX) + target_link_libraries(example OpenMP::OpenMP_CXX) + endif() + endif() target_link_libraries(example ${LIBNEST2D_LIBRARIES}) target_include_directories(example PUBLIC ${LIBNEST2D_HEADERS}) diff --git a/xs/src/libnest2d/cmake_modules/FindTBB.cmake b/xs/src/libnest2d/cmake_modules/FindTBB.cmake new file mode 100644 index 00000000000..8b498d3ab11 --- /dev/null +++ b/xs/src/libnest2d/cmake_modules/FindTBB.cmake @@ -0,0 +1,322 @@ +# The MIT License (MIT) +# +# Copyright (c) 2015 Justus Calvin +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# +# FindTBB +# ------- +# +# Find TBB include directories and libraries. +# +# Usage: +# +# find_package(TBB [major[.minor]] [EXACT] +# [QUIET] [REQUIRED] +# [[COMPONENTS] [components...]] +# [OPTIONAL_COMPONENTS components...]) +# +# where the allowed components are tbbmalloc and tbb_preview. Users may modify +# the behavior of this module with the following variables: +# +# * TBB_ROOT_DIR - The base directory the of TBB installation. +# * TBB_INCLUDE_DIR - The directory that contains the TBB headers files. +# * TBB_LIBRARY - The directory that contains the TBB library files. +# * TBB__LIBRARY - The path of the TBB the corresponding TBB library. +# These libraries, if specified, override the +# corresponding library search results, where +# may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug, +# tbb_preview, or tbb_preview_debug. +# * TBB_USE_DEBUG_BUILD - The debug version of tbb libraries, if present, will +# be used instead of the release version. +# * TBB_STATIC - Static linking of libraries with a _static suffix. +# For example, on Windows a tbb_static.lib will be searched for +# instead of tbb.lib. +# +# Users may modify the behavior of this module with the following environment +# variables: +# +# * TBB_INSTALL_DIR +# * TBBROOT +# * LIBRARY_PATH +# +# This module will set the following variables: +# +# * TBB_FOUND - Set to false, or undefined, if we haven’t found, or +# don’t want to use TBB. +# * TBB__FOUND - If False, optional part of TBB sytem is +# not available. +# * TBB_VERSION - The full version string +# * TBB_VERSION_MAJOR - The major version +# * TBB_VERSION_MINOR - The minor version +# * TBB_INTERFACE_VERSION - The interface version number defined in +# tbb/tbb_stddef.h. +# * TBB__LIBRARY_RELEASE - The path of the TBB release version of +# , where may be tbb, tbb_debug, +# tbbmalloc, tbbmalloc_debug, tbb_preview, or +# tbb_preview_debug. +# * TBB__LIBRARY_DEGUG - The path of the TBB release version of +# , where may be tbb, tbb_debug, +# tbbmalloc, tbbmalloc_debug, tbb_preview, or +# tbb_preview_debug. +# +# The following varibles should be used to build and link with TBB: +# +# * TBB_INCLUDE_DIRS - The include directory for TBB. +# * TBB_LIBRARIES - The libraries to link against to use TBB. +# * TBB_LIBRARIES_RELEASE - The release libraries to link against to use TBB. +# * TBB_LIBRARIES_DEBUG - The debug libraries to link against to use TBB. +# * TBB_DEFINITIONS - Definitions to use when compiling code that uses +# TBB. +# * TBB_DEFINITIONS_RELEASE - Definitions to use when compiling release code that +# uses TBB. +# * TBB_DEFINITIONS_DEBUG - Definitions to use when compiling debug code that +# uses TBB. +# +# This module will also create the "tbb" target that may be used when building +# executables and libraries. + +include(FindPackageHandleStandardArgs) + +if(NOT TBB_FOUND) + + ################################## + # Check the build type + ################################## + + if(NOT DEFINED TBB_USE_DEBUG_BUILD) + if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)") + set(TBB_BUILD_TYPE DEBUG) + else() + set(TBB_BUILD_TYPE RELEASE) + endif() + elseif(TBB_USE_DEBUG_BUILD) + set(TBB_BUILD_TYPE DEBUG) + else() + set(TBB_BUILD_TYPE RELEASE) + endif() + + ################################## + # Set the TBB search directories + ################################## + + # Define search paths based on user input and environment variables + set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT}) + + # Define the search directories based on the current platform + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB" + "C:/Program Files (x86)/Intel/TBB") + + # Set the target architecture + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(TBB_ARCHITECTURE "intel64") + else() + set(TBB_ARCHITECTURE "ia32") + endif() + + # Set the TBB search library path search suffix based on the version of VC + if(WINDOWS_STORE) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui") + elseif(MSVC14) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc14") + elseif(MSVC12) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc12") + elseif(MSVC11) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11") + elseif(MSVC10) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10") + endif() + + # Add the library path search suffix for the VC independent version of TBB + list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt") + + elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # OS X + set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") + + # TODO: Check to see which C++ library is being used by the compiler. + if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0) + # The default C++ library on OS X 10.9 and later is libc++ + set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib") + else() + set(TBB_LIB_PATH_SUFFIX "lib") + endif() + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # Linux + set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") + + # TODO: Check compiler version to see the suffix should be /gcc4.1 or + # /gcc4.1. For now, assume that the compiler is more recent than + # gcc 4.4.x or later. + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") + set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4") + endif() + endif() + + ################################## + # Find the TBB include dir + ################################## + + find_path(TBB_INCLUDE_DIRS tbb/tbb.h + HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} + PATH_SUFFIXES include) + + ################################## + # Set version strings + ################################## + + if(TBB_INCLUDE_DIRS) + file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file) + string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1" + TBB_VERSION_MAJOR "${_tbb_version_file}") + string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1" + TBB_VERSION_MINOR "${_tbb_version_file}") + string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1" + TBB_INTERFACE_VERSION "${_tbb_version_file}") + set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}") + endif() + + ################################## + # Find TBB components + ################################## + + if(TBB_VERSION VERSION_LESS 4.3) + set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb) + else() + set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb) + endif() + + if(TBB_STATIC) + set(TBB_STATIC_SUFFIX "_static") + endif() + + # Find each component + foreach(_comp ${TBB_SEARCH_COMPOMPONENTS}) + if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};") + + # Search for the libraries + find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX} + HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH + PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) + + find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug + HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH + PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) + + if(TBB_${_comp}_LIBRARY_DEBUG) + list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}") + endif() + if(TBB_${_comp}_LIBRARY_RELEASE) + list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}") + endif() + if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY) + set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}") + endif() + + if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}") + set(TBB_${_comp}_FOUND TRUE) + else() + set(TBB_${_comp}_FOUND FALSE) + endif() + + # Mark internal variables as advanced + mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE) + mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG) + mark_as_advanced(TBB_${_comp}_LIBRARY) + + endif() + endforeach() + + unset(TBB_STATIC_SUFFIX) + + ################################## + # Set compile flags and libraries + ################################## + + set(TBB_DEFINITIONS_RELEASE "") + set(TBB_DEFINITIONS_DEBUG "-DTBB_USE_DEBUG=1") + + if(TBB_LIBRARIES_${TBB_BUILD_TYPE}) + set(TBB_DEFINITIONS "${TBB_DEFINITIONS_${TBB_BUILD_TYPE}}") + set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}") + elseif(TBB_LIBRARIES_RELEASE) + set(TBB_DEFINITIONS "${TBB_DEFINITIONS_RELEASE}") + set(TBB_LIBRARIES "${TBB_LIBRARIES_RELEASE}") + elseif(TBB_LIBRARIES_DEBUG) + set(TBB_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}") + set(TBB_LIBRARIES "${TBB_LIBRARIES_DEBUG}") + endif() + + find_package_handle_standard_args(TBB + REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES + HANDLE_COMPONENTS + VERSION_VAR TBB_VERSION) + + ################################## + # Create targets + ################################## + + if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) + add_library(tbb SHARED IMPORTED) + set_target_properties(tbb PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS} + IMPORTED_LOCATION ${TBB_LIBRARIES}) + if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG) + set_target_properties(tbb PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "$<$,$>:TBB_USE_DEBUG=1>" + IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG} + IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_DEBUG} + IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE} + IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE} + ) + elseif(TBB_LIBRARIES_RELEASE) + set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ${TBB_LIBRARIES_RELEASE}) + else() + set_target_properties(tbb PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}" + IMPORTED_LOCATION ${TBB_LIBRARIES_DEBUG} + ) + endif() + endif() + + mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES) + + unset(TBB_ARCHITECTURE) + unset(TBB_BUILD_TYPE) + unset(TBB_LIB_PATH_SUFFIX) + unset(TBB_DEFAULT_SEARCH_DIR) + + if(TBB_DEBUG) + message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}") + message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}") + message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}") + message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}") + message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}") + message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}") + message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}") + endif() + +endif() diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 57be7a20808..ebc3fb15c5e 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -54,7 +54,7 @@ void arrangeRectangles() { const int SCALE = 1000000; - std::vector rects(100, { + std::vector rects(202, { {-9945219, -3065619}, {-9781479, -2031780}, {-9510560, -1020730}, @@ -104,8 +104,6 @@ void arrangeRectangles() { // input.insert(input.end(), prusaExParts().begin(), prusaExParts().end()); // input.insert(input.end(), stegoParts().begin(), stegoParts().end()); // input.insert(input.end(), rects.begin(), rects.end()); -// input.insert(input.end(), proba.begin(), proba.end()); -// input.insert(input.end(), crasher.begin(), crasher.end()); Box bin(250*SCALE, 210*SCALE); // PolygonImpl bin = { @@ -123,11 +121,11 @@ void arrangeRectangles() { // {} // }; -// _Circle bin({0, 0}, 125*SCALE); +// Circle bin({0, 0}, 125*SCALE); - auto min_obj_distance = static_cast(1.5*SCALE); + auto min_obj_distance = static_cast(6*SCALE); - using Placer = strategies::_NofitPolyPlacer; + using Placer = placers::_NofitPolyPlacer; using Packer = Nester; Packer arrange(bin, min_obj_distance); @@ -136,7 +134,7 @@ void arrangeRectangles() { pconf.alignment = Placer::Config::Alignment::CENTER; pconf.starting_point = Placer::Config::Alignment::CENTER; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; - pconf.accuracy = 0.5f; + pconf.accuracy = 0.65f; pconf.parallel = true; Packer::SelectionConfig sconf; @@ -149,12 +147,6 @@ void arrangeRectangles() { arrange.configure(pconf, sconf); arrange.progressIndicator([&](unsigned r){ -// svg::SVGWriter::Config conf; -// conf.mm_in_coord_units = SCALE; -// svg::SVGWriter svgw(conf); -// svgw.setSize(bin); -// svgw.writePackGroup(arrange.lastResult()); -// svgw.save("debout"); std::cout << "Remaining items: " << r << std::endl; }); @@ -201,10 +193,10 @@ void arrangeRectangles() { for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); } std::cout << ") Total: " << total << std::endl; - for(auto& it : input) { - auto ret = sl::isValid(it.transformedShape()); - std::cout << ret.second << std::endl; - } +// for(auto& it : input) { +// auto ret = sl::isValid(it.transformedShape()); +// std::cout << ret.second << std::endl; +// } if(total != input.size()) std::cout << "ERROR " << "could not pack " << input.size() - total << " elements!" @@ -222,7 +214,5 @@ void arrangeRectangles() { int main(void /*int argc, char **argv*/) { arrangeRectangles(); -//// findDegenerateCase(); - return EXIT_SUCCESS; } diff --git a/xs/src/libnest2d/libnest2d.h b/xs/src/libnest2d/libnest2d.h index 05677afd7cf..bfd88f4f5d1 100644 --- a/xs/src/libnest2d/libnest2d.h +++ b/xs/src/libnest2d/libnest2d.h @@ -30,12 +30,12 @@ using Rectangle = _Rectangle; using PackGroup = _PackGroup; using IndexedPackGroup = _IndexedPackGroup; -using FillerSelection = strategies::_FillerSelection; -using FirstFitSelection = strategies::_FirstFitSelection; -using DJDHeuristic = strategies::_DJDHeuristic; +using FillerSelection = selections::_FillerSelection; +using FirstFitSelection = selections::_FirstFitSelection; +using DJDHeuristic = selections::_DJDHeuristic; -using NfpPlacer = strategies::_NofitPolyPlacer; -using BottomLeftPlacer = strategies::_BottomLeftPlacer; +using NfpPlacer = placers::_NofitPolyPlacer; +using BottomLeftPlacer = placers::_BottomLeftPlacer; } diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp index b9dfd21854d..2982454cdc7 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp @@ -102,7 +102,7 @@ inline TPoint leftmostDownVertex(const RawShape& sh) auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh), __nfp::_vsort); - return *it; + return it == shapelike::cend(sh) ? TPoint() : *it;; } /** @@ -118,7 +118,7 @@ TPoint rightmostUpVertex(const RawShape& sh) auto it = std::max_element(shapelike::cbegin(sh), shapelike::cend(sh), __nfp::_vsort); - return *it; + return it == shapelike::cend(sh) ? TPoint() : *it; } /** diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 42255cbb48e..4d1e62f9919 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -65,8 +65,8 @@ class _Item { mutable VertexConstIterator lmb_; // leftmost bottom vertex mutable bool rmt_valid_ = false, lmb_valid_ = false; mutable struct BBCache { - Box bb; bool valid; Vertex tr; - BBCache(): valid(false), tr(0, 0) {} + Box bb; bool valid; + BBCache(): valid(false) {} } bb_cache_; public: @@ -310,7 +310,7 @@ class _Item { { if(translation_ != tr) { translation_ = tr; has_translation_ = true; tr_cache_valid_ = false; - bb_cache_.valid = false; + //bb_cache_.valid = false; } } @@ -345,13 +345,19 @@ class _Item { inline Box boundingBox() const { if(!bb_cache_.valid) { - bb_cache_.bb = sl::boundingBox(transformedShape()); - bb_cache_.tr = {0, 0}; + if(!has_rotation_) + bb_cache_.bb = sl::boundingBox(offsettedShape()); + else { + // TODO make sure this works + auto rotsh = offsettedShape(); + sl::rotate(rotsh, rotation_); + bb_cache_.bb = sl::boundingBox(rotsh); + } bb_cache_.valid = true; } - auto &bb = bb_cache_.bb; auto &tr = bb_cache_.tr; - return {bb.minCorner() + tr, bb.maxCorner() + tr}; + auto &bb = bb_cache_.bb; auto &tr = translation_; + return {bb.minCorner() + tr, bb.maxCorner() + tr }; } inline Vertex referenceVertex() const { diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp index 0ba9eb3c060..18c27c40cd3 100644 --- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp @@ -5,7 +5,7 @@ #include "placer_boilerplate.hpp" -namespace libnest2d { namespace strategies { +namespace libnest2d { namespace placers { template struct Epsilon {}; diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index b9e0ba8f160..c86fb507e3c 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -21,6 +21,12 @@ #include "tools/svgtools.hpp" +#ifdef USE_TBB +#include +#elif defined(_OPENMP) +#include +#endif + namespace libnest2d { namespace __parallel { @@ -33,20 +39,52 @@ using TIteratorValue = typename iterator_traits::value_type; template inline void enumerate( Iterator from, Iterator to, - function, unsigned)> fn, + function, size_t)> fn, std::launch policy = std::launch::deferred | std::launch::async) { - auto N = to-from; + using TN = size_t; + auto iN = to-from; + TN N = iN < 0? 0 : TN(iN); + +#ifdef USE_TBB + if((policy & std::launch::async) == std::launch::async) { + tbb::parallel_for(0, N, [from, fn] (TN n) { fn(*(from + n), n); } ); + } else { + for(TN n = 0; n < N; n++) fn(*(from + n), n); + } +#elif defined(_OPENMP) + if((policy & std::launch::async) == std::launch::async) { + #pragma omp parallel for + for(TN n = 0; n < N; n++) fn(*(from + n), n); + } + else { + for(TN n = 0; n < N; n++) fn(*(from + n), n); + } +#else std::vector> rets(N); auto it = from; - for(unsigned b = 0; b < N; b++) { - rets[b] = std::async(policy, fn, *it++, b); + for(TN b = 0; b < N; b++) { + rets[b] = std::async(policy, fn, *it++, unsigned(b)); } - for(unsigned fi = 0; fi < rets.size(); ++fi) rets[fi].wait(); + for(TN fi = 0; fi < N; ++fi) rets[fi].wait(); +#endif } +class SpinLock { + static std::atomic_flag locked; +public: + void lock() { + while (locked.test_and_set(std::memory_order_acquire)) { ; } + } + void unlock() { + locked.clear(std::memory_order_release); + } +}; + +std::atomic_flag SpinLock::locked = ATOMIC_FLAG_INIT ; + } namespace __itemhash { @@ -98,7 +136,7 @@ using Hash = std::unordered_map>; } -namespace strategies { +namespace placers { template struct NfpPConfig { @@ -134,30 +172,12 @@ struct NfpPConfig { * that will optimize for the best pack efficiency. With a custom fitting * function you can e.g. influence the shape of the arranged pile. * - * \param shapes The first parameter is a container with all the placed - * polygons excluding the current candidate. You can calculate a bounding - * box or convex hull on this pile of polygons without the candidate item - * or push back the candidate item into the container and then calculate - * some features. - * - * \param item The second parameter is the candidate item. - * - * \param remaining A container with the remaining items waiting to be - * placed. You can use some features about the remaining items to alter to - * score of the current placement. If you know that you have to leave place - * for other items as well, that might influence your decision about where - * the current candidate should be placed. E.g. imagine three big circles - * which you want to place into a box: you might place them in a triangle - * shape which has the maximum pack density. But if there is a 4th big - * circle than you won't be able to pack it. If you knew apriori that - * there four circles are to be placed, you would have placed the first 3 - * into an L shape. This parameter can be used to make these kind of - * decisions (for you or a more intelligent AI). + * \param item The only parameter is the candidate item which has info + * about its current position. Your job is to rate this position compared to + * the already packed items. * */ - std::function&, const _Item&, - const ItemGroup&)> - object_function; + std::function&)> object_function; /** * @brief The quality of search for an optimal placement. @@ -180,6 +200,34 @@ struct NfpPConfig { */ bool parallel = true; + /** + * @brief before_packing Callback that is called just before a search for + * a new item's position is started. You can use this to create various + * cache structures and update them between subsequent packings. + * + * \param merged pile A polygon that is the union of all items in the bin. + * + * \param pile The items parameter is a container with all the placed + * polygons excluding the current candidate. You can for instance check the + * alignment with the candidate item or do anything else. + * + * \param remaining A container with the remaining items waiting to be + * placed. You can use some features about the remaining items to alter to + * score of the current placement. If you know that you have to leave place + * for other items as well, that might influence your decision about where + * the current candidate should be placed. E.g. imagine three big circles + * which you want to place into a box: you might place them in a triangle + * shape which has the maximum pack density. But if there is a 4th big + * circle than you won't be able to pack it. If you knew apriori that + * there four circles are to be placed, you would have placed the first 3 + * into an L shape. This parameter can be used to make these kind of + * decisions (for you or a more intelligent AI). + */ + std::function&, // merged pile + const ItemGroup&, // packed items + const ItemGroup& // remaining items + )> before_packing; + NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}), alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {} }; @@ -428,7 +476,7 @@ Circle minimizeCircle(const RawShape& sh) { opt::StopCriteria stopcr; - stopcr.max_iterations = 100; + stopcr.max_iterations = 30; stopcr.relative_score_difference = 1e-3; opt::TOptimizer solver(stopcr); @@ -590,35 +638,25 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer subnfp_r; -// if(fnd == nfpcache_.end()) { - - auto subnfp_r = noFitPolygon( - sh.transformedShape(), trsh.transformedShape()); -// nfpcache_[ik] = subnfp_r; -// } else { -// subnfp_r = fnd->second; -// } + __parallel::enumerate(items_.begin(), items_.end(), + [&nfps, &trsh](const Item& sh, size_t n) + { + auto& fixedp = sh.transformedShape(); + auto& orbp = trsh.transformedShape(); + auto subnfp_r = noFitPolygon(fixedp, orbp); correctNfpPosition(subnfp_r, sh, trsh); + nfps[n] = subnfp_r.first; + }); - // nfps.emplace_back(subnfp_r.first); - nfps = nfp::merge(nfps, subnfp_r.first); - } +// for(auto& n : nfps) { +// auto valid = sl::isValid(n); +// if(!valid.first) std::cout << "Warning: " << valid.second << std::endl; +// } - // nfps = nfp::merge(nfps); - - return nfps; + return nfp::merge(nfps); } template @@ -777,6 +815,21 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer; template> @@ -810,6 +863,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer; using OptResults = std::vector; @@ -914,21 +964,27 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer(pos), @@ -959,24 +1015,27 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer(pos), diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp index 44e2bc1b004..0df1b8c913a 100644 --- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp @@ -3,7 +3,7 @@ #include "../libnest2d.hpp" -namespace libnest2d { namespace strategies { +namespace libnest2d { namespace placers { struct EmptyConfig {}; diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 846b00badfb..ee93d059281 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -8,7 +8,7 @@ #include "selection_boilerplate.hpp" -namespace libnest2d { namespace strategies { +namespace libnest2d { namespace selections { /** * Selection heuristic based on [López-Camacho]\ diff --git a/xs/src/libnest2d/libnest2d/selections/filler.hpp b/xs/src/libnest2d/libnest2d/selections/filler.hpp index b20455b0e4b..0da7220a199 100644 --- a/xs/src/libnest2d/libnest2d/selections/filler.hpp +++ b/xs/src/libnest2d/libnest2d/selections/filler.hpp @@ -3,7 +3,7 @@ #include "selection_boilerplate.hpp" -namespace libnest2d { namespace strategies { +namespace libnest2d { namespace selections { template class _FillerSelection: public SelectionBoilerplate { diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index eb820a51842..bca7497db83 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -4,7 +4,7 @@ #include "../libnest2d.hpp" #include "selection_boilerplate.hpp" -namespace libnest2d { namespace strategies { +namespace libnest2d { namespace selections { template class _FirstFitSelection: public SelectionBoilerplate { diff --git a/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp b/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp index 59ef5cb238c..05bbae658e2 100644 --- a/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp @@ -3,8 +3,7 @@ #include "../libnest2d.hpp" -namespace libnest2d { -namespace strategies { +namespace libnest2d { namespace selections { template class SelectionBoilerplate { diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index b85bbc11125..323fb8d31e3 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -102,17 +102,17 @@ TEST(BasicFunctionality, creationAndDestruction) TEST(GeometryAlgorithms, boundingCircle) { using namespace libnest2d; - using strategies::boundingCircle; + using placers::boundingCircle; PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}}; - _Circle c = boundingCircle(p); + Circle c = boundingCircle(p); ASSERT_EQ(c.center().X, 0); ASSERT_EQ(c.center().Y, 0); ASSERT_DOUBLE_EQ(c.radius(), 10); shapelike::translate(p, PointImpl{10, 10}); - c = boundingCircle(p); + c = boundingCircle(p); ASSERT_EQ(c.center().X, 10); ASSERT_EQ(c.center().Y, 10); @@ -712,7 +712,7 @@ void testNfp(const std::vector& testdata) { auto& exportfun = exportSVG; - auto onetest = [&](Item& orbiter, Item& stationary){ + auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){ testcase++; orbiter.translate({210*SCALE, 0}); @@ -720,15 +720,19 @@ void testNfp(const std::vector& testdata) { auto&& nfp = nfp::noFitPolygon(stationary.rawShape(), orbiter.transformedShape()); - strategies::correctNfpPosition(nfp, stationary, orbiter); + placers::correctNfpPosition(nfp, stationary, orbiter); - auto v = shapelike::isValid(nfp.first); + auto valid = shapelike::isValid(nfp.first); - if(!v.first) { - std::cout << v.second << std::endl; - } + /*Item infp(nfp.first); + if(!valid.first) { + std::cout << "test instance: " << testidx << " " + << valid.second << std::endl; + std::vector> inp = {std::ref(infp)}; + exportfun(inp, bin, testidx); + }*/ - ASSERT_TRUE(v.first); + ASSERT_TRUE(valid.first); Item infp(nfp.first); @@ -748,7 +752,7 @@ void testNfp(const std::vector& testdata) { bool touching = Item::touches(tmp, stationary); - if(!touching) { + if(!touching || !valid.first) { std::vector> inp = { std::ref(stationary), std::ref(tmp), std::ref(infp) }; @@ -760,16 +764,18 @@ void testNfp(const std::vector& testdata) { } }; + unsigned tidx = 0; for(auto& td : testdata) { auto orbiter = td.orbiter; auto stationary = td.stationary; - onetest(orbiter, stationary); + onetest(orbiter, stationary, tidx++); } + tidx = 0; for(auto& td : testdata) { auto orbiter = td.stationary; auto stationary = td.orbiter; - onetest(orbiter, stationary); + onetest(orbiter, stationary, tidx++); } } } @@ -796,7 +802,7 @@ TEST(GeometryAlgorithms, pointOnPolygonContour) { Rectangle input(10, 10); - strategies::EdgeCache ecache(input); + placers::EdgeCache ecache(input); auto first = *input.begin(); ASSERT_TRUE(getX(first) == getX(ecache.coords(0))); diff --git a/xs/src/libnest2d/tools/nfp_svgnest.hpp b/xs/src/libnest2d/tools/nfp_svgnest.hpp index 8ab571c003e..ac5700c1013 100644 --- a/xs/src/libnest2d/tools/nfp_svgnest.hpp +++ b/xs/src/libnest2d/tools/nfp_svgnest.hpp @@ -32,15 +32,18 @@ template struct _alg { #define dNAN std::nan("") struct Vector { - Coord x, y; + Coord x = 0.0, y = 0.0; bool marked = false; Vector() = default; Vector(Coord X, Coord Y): x(X), y(Y) {} - Vector(const Point& p): x(getX(p)), y(getY(p)) {} + Vector(const Point& p): x(Coord(getX(p))), y(Coord(getY(p))) {} operator Point() const { return {iCoord(x), iCoord(y)}; } Vector& operator=(const Point& p) { x = getX(p), y = getY(p); return *this; } + bool operator!=(const Vector& v) const { + return v.x != x || v.y != y; + } Vector(std::initializer_list il): x(*il.begin()), y(*std::next(il.begin())) {} }; @@ -58,8 +61,10 @@ template struct _alg { v_.reserve(c.size()); std::transform(c.begin(), c.end(), std::back_inserter(v_), [](const Point& p) { - return Vector(double(x(p))/1e6, double(y(p))/1e6); + return Vector(double(x(p)) / 1e6, double(y(p)) / 1e6); }); + std::reverse(v_.begin(), v_.end()); + v_.pop_back(); } Cntr() = default; @@ -67,8 +72,10 @@ template struct _alg { Coord offsety = 0; size_t size() const { return v_.size(); } bool empty() const { return v_.empty(); } - typename Contour::const_iterator begin() const { return v_.cbegin(); } - typename Contour::const_iterator end() const { return v_.cend(); } + typename std::vector::const_iterator cbegin() const { return v_.cbegin(); } + typename std::vector::const_iterator cend() const { return v_.cend(); } + typename std::vector::iterator begin() { return v_.begin(); } + typename std::vector::iterator end() { return v_.end(); } Vector& operator[](size_t idx) { return v_[idx]; } const Vector& operator[](size_t idx) const { return v_[idx]; } template @@ -83,12 +90,16 @@ template struct _alg { operator Contour() const { Contour cnt; - cnt.reserve(v_.size()); + cnt.reserve(v_.size() + 1); std::transform(v_.begin(), v_.end(), std::back_inserter(cnt), [](const Vector& vertex) { - return Point(iCoord(vertex.x*1e6), iCoord(vertex.y*1e6)); + return Point(iCoord(vertex.x) * 1000000, iCoord(vertex.y) * 1000000); }); - return cnt; + if(!cnt.empty()) cnt.emplace_back(cnt.front()); + S sh = shapelike::create(cnt); + +// std::reverse(cnt.begin(), cnt.end()); + return shapelike::getContour(sh); } }; @@ -235,11 +246,11 @@ template struct _alg { if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && (pdotnorm>s1dotnorm && pdotnorm>s2dotnorm)) { - return double(min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm)); + return min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm); } if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && (pdotnorm struct _alg { auto EFmin = min(dotE, dotF); // segments that will merely touch at one point - if(_almostEqual(ABmax, EFmin,TOL) || _almostEqual(ABmin, EFmax,TOL)){ + if(_almostEqual(ABmax, EFmin, TOL) || _almostEqual(ABmin, EFmax,TOL)) { return dNAN; } // segments miss eachother completely @@ -362,7 +373,7 @@ template struct _alg { d = dNAN; } } - if(isnan(d)){ + if(!isnan(d)){ distances.emplace_back(d); } } @@ -392,7 +403,7 @@ template struct _alg { auto d = pointDistance(E,A,B,direction); if(!isnan(d) && _almostEqual(d, 0)) { // crossF struct _alg { if(!isnan(d) && _almostEqual(d, 0)) { // && crossE struct _alg { return *std::min_element(distances.begin(), distances.end()); } - static double polygonSlideDistance( const Cntr& A, - const Cntr& B, + static double polygonSlideDistance( const Cntr& AA, + const Cntr& BB, Vector direction, bool ignoreNegative) { // Vector A1, A2, B1, B2; + Cntr A = AA; + Cntr B = BB; + Coord Aoffsetx = A.offsetx; Coord Boffsetx = B.offsetx; Coord Aoffsety = A.offsety; Coord Boffsety = B.offsety; + // close the loop for polygons + if(A[0] != A[A.size()-1]){ + A.emplace_back(AA[0]); + } + + if(B[0] != B[B.size()-1]){ + B.emplace_back(BB[0]); + } + auto& edgeA = A; auto& edgeB = B; @@ -457,7 +480,7 @@ template struct _alg { Vector A1 = {x(edgeA[j]) + Aoffsetx, y(edgeA[j]) + Aoffsety }; Vector A2 = {x(edgeA[j+1]) + Aoffsetx, y(edgeA[j+1]) + Aoffsety}; Vector B1 = {x(edgeB[i]) + Boffsetx, y(edgeB[i]) + Boffsety }; - Vector B2 = {x(edgeB[i+1]) + Boffsety, y(edgeB[i+1]) + Boffsety}; + Vector B2 = {x(edgeB[i+1]) + Boffsetx, y(edgeB[i+1]) + Boffsety}; if((_almostEqual(A1.x, A2.x) && _almostEqual(A1.y, A2.y)) || (_almostEqual(B1.x, B2.x) && _almostEqual(B1.y, B2.y))){ @@ -476,23 +499,26 @@ template struct _alg { return distance; } - static double polygonProjectionDistance(const Cntr& A, - const Cntr& B, + static double polygonProjectionDistance(const Cntr& AA, + const Cntr& BB, Vector direction) { + Cntr A = AA; + Cntr B = BB; + auto Boffsetx = B.offsetx; auto Boffsety = B.offsety; auto Aoffsetx = A.offsetx; auto Aoffsety = A.offsety; // close the loop for polygons - /*if(A[0] != A[A.length-1]){ + if(A[0] != A[A.size()-1]){ A.push(A[0]); } - if(B[0] != B[B.length-1]){ + if(B[0] != B[B.size()-1]){ B.push(B[0]); - }*/ + } auto& edgeA = A; auto& edgeB = B; @@ -665,7 +691,7 @@ template struct _alg { A.offsetx = 0; A.offsety = 0; - unsigned i = 0, j = 0; + long i = 0, j = 0; auto minA = y(A[0]); long minAindex = 0; @@ -709,7 +735,8 @@ template struct _alg { struct Touch { int type; - long A, B; + long A; + long B; Touch(int t, long a, long b): type(t), A(a), B(b) {} }; @@ -721,6 +748,15 @@ template struct _alg { // maintain a list of touching points/edges std::vector touching; + struct V { + Coord x, y; + Vector *start, *end; + operator bool() { + return start != nullptr && end != nullptr; + } + operator Vector() const { return {x, y}; } + } prevvector = {0, 0, nullptr, nullptr}; + Cntr NFP; NFP.emplace_back(x(B[0]) + B.offsetx, y(B[0]) + B.offsety); @@ -736,10 +772,10 @@ template struct _alg { // find touching vertices/edges for(i = 0; i < A.size(); i++){ - auto nexti = (i == A.size() - 1) ? 0 : i + 1; + long nexti = (i == A.size() - 1) ? 0 : i + 1; for(j = 0; j < B.size(); j++){ - auto nextj = (j == B.size() - 1) ? 0 : j + 1; + long nextj = (j == B.size() - 1) ? 0 : j + 1; if( _almostEqual(A[i].x, B[j].x+B.offsetx) && _almostEqual(A[i].y, B[j].y+B.offsety)) @@ -762,15 +798,6 @@ template struct _alg { } } - struct V { - Coord x, y; - Vector *start, *end; - operator bool() { - return start != nullptr && end != nullptr; - } - operator Vector() const { return {x, y}; } - }; - // generate translation vectors from touching vertices/edges std::vector vectors; for(i=0; i < touching.size(); i++){ @@ -871,7 +898,6 @@ template struct _alg { // will cause immediate intersection. For now just check them all V translate = {0, 0, nullptr, nullptr}; - V prevvector = {0, 0, nullptr, nullptr}; double maxd = 0; for(i = 0; i < vectors.size(); i++) { @@ -897,7 +923,8 @@ template struct _alg { } } - double d = polygonSlideDistance(A, B, vectors[i], true); + V vi = vectors[i]; + double d = polygonSlideDistance(A, B, vi, true); double vecd2 = vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y; if(isnan(d) || d*d > vecd2){ @@ -985,19 +1012,6 @@ template struct _alg { template const double _alg::TOL = std::pow(10, -9); -//template -//nfp::NfpResult nfpSimpleSimple(const S& stat, const S& orb) { -//// using Cntr = TContour; -// using Point = TPoint; -//// using Coord = TCoord; -//// using Shapes = nfp::Shapes; - -// namespace sl = shapelike; - -// noFitPolygon(sl::getContour(stat), sl::getContour(orb), true, true); -// return {S(), Point()}; -//} - } } diff --git a/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp index 7ceb2d24dc2..ea1fb4d0764 100644 --- a/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp +++ b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp @@ -25,13 +25,11 @@ template<> struct NfpImpl { namespace sl = shapelike; using alg = __svgnest::_alg; - std::cout << "Itt vagyok" << std::endl; auto nfp_p = alg::noFitPolygon(sl::getContour(sh), sl::getContour(cother), false, false); PolygonImpl nfp_cntr; - nfp_cntr.Contour = nfp_p.front(); - std::cout << "Contour size: " << nfp_cntr.Contour.size() << std::endl; + if(!nfp_p.empty()) nfp_cntr.Contour = nfp_p.front(); return {nfp_cntr, referenceVertex(nfp_cntr)}; } }; diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index dcb0da9e5dd..618230cb7ea 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -100,55 +100,54 @@ namespace bgi = boost::geometry::index; using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; using ItemGroup = std::vector>; +template +using TPacker = typename placers::_NofitPolyPlacer; + +const double BIG_ITEM_TRESHOLD = 0.02; + +Box boundingBox(const Box& pilebb, const Box& ibb ) { + auto& pminc = pilebb.minCorner(); + auto& pmaxc = pilebb.maxCorner(); + auto& iminc = ibb.minCorner(); + auto& imaxc = ibb.maxCorner(); + PointImpl minc, maxc; + + setX(minc, std::min(getX(pminc), getX(iminc))); + setY(minc, std::min(getY(pminc), getY(iminc))); + + setX(maxc, std::max(getX(pmaxc), getX(imaxc))); + setY(maxc, std::max(getY(pmaxc), getY(imaxc))); + return Box(minc, maxc); +} std::tuple objfunc(const PointImpl& bincenter, - double bin_area, - sl::Shapes& pile, // The currently arranged pile + const shapelike::Shapes& merged_pile, + const Box& pilebb, + const ItemGroup& items, const Item &item, + double bin_area, double norm, // A norming factor for physical dimensions - std::vector& areacache, // pile item areas will be cached // a spatial index to quickly get neighbors of the candidate item - SpatIndex& spatindex, + const SpatIndex& spatindex, const ItemGroup& remaining ) { using Coord = TCoord; - static const double BIG_ITEM_TRESHOLD = 0.02; static const double ROUNDNESS_RATIO = 0.5; static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; // We will treat big items (compared to the print bed) differently - auto isBig = [&areacache, bin_area](double a) { + auto isBig = [bin_area](double a) { return a/bin_area > BIG_ITEM_TRESHOLD ; }; - // If a new bin has been created: - if(pile.size() < areacache.size()) { - areacache.clear(); - spatindex.clear(); - } - - // We must fill the caches: - int idx = 0; - for(auto& p : pile) { - if(idx == areacache.size()) { - areacache.emplace_back(sl::area(p)); - if(isBig(areacache[idx])) - spatindex.insert({sl::boundingBox(p), idx}); - } - - idx++; - } - // Candidate item bounding box - auto ibb = item.boundingBox(); + auto ibb = sl::boundingBox(item.transformedShape()); // Calculate the full bounding box of the pile with the candidate item - pile.emplace_back(item.transformedShape()); - auto fullbb = sl::boundingBox(pile); - pile.pop_back(); + auto fullbb = boundingBox(pilebb, ibb); // The bounding box of the big items (they will accumulate in the center // of the pile @@ -189,10 +188,12 @@ objfunc(const PointImpl& bincenter, double density = 0; if(remaining.empty()) { - pile.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(pile); - pile.pop_back(); - strategies::EdgeCache ec(chull); + + auto mp = merged_pile; + mp.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + + placers::EdgeCache ec(chull); double circ = ec.circumference() / norm; double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; @@ -201,16 +202,15 @@ objfunc(const PointImpl& bincenter, } else { // Prepare a variable for the alignment score. // This will indicate: how well is the candidate item aligned with - // its neighbors. We will check the aligment with all neighbors and + // its neighbors. We will check the alignment with all neighbors and // return the score for the best alignment. So it is enough for the // candidate to be aligned with only one item. auto alignment_score = 1.0; density = (fullbb.width()*fullbb.height()) / (norm*norm); - auto& trsh = item.transformedShape(); auto querybb = item.boundingBox(); - // Query the spatial index for the neigbours + // Query the spatial index for the neighbors std::vector result; result.reserve(spatindex.size()); spatindex.query(bgi::intersects(querybb), @@ -218,10 +218,10 @@ objfunc(const PointImpl& bincenter, for(auto& e : result) { // now get the score for the best alignment auto idx = e.second; - auto& p = pile[idx]; - auto parea = areacache[idx]; + Item& p = items[idx]; + auto parea = p.area(); if(std::abs(1.0 - parea/item.area()) < 1e-6) { - auto bb = sl::boundingBox(sl::Shapes{p, trsh}); + auto bb = boundingBox(p.boundingBox(), ibb); auto bbarea = bb.area(); auto ascore = 1.0 - (item.area() + parea)/bbarea; @@ -231,7 +231,7 @@ objfunc(const PointImpl& bincenter, // The final mix of the score is the balance between the distance // from the full pile center, the pack density and the - // alignment with the neigbours + // alignment with the neighbors if(result.empty()) score = 0.5 * dist + 0.5 * density; else @@ -239,7 +239,6 @@ objfunc(const PointImpl& bincenter, } } else if( !isBig(item.area()) && spatindex.empty()) { auto bindist = pl::distance(ibb.center(), bincenter) / norm; - // Bindist is surprisingly enough... score = bindist; } else { @@ -271,7 +270,7 @@ void fillConfig(PConf& pcfg) { // Goes from 0.0 to 1.0 and scales performance as well pcfg.accuracy = 0.65f; - pcfg.parallel = false; + pcfg.parallel = true; } template @@ -280,7 +279,8 @@ class AutoArranger {}; template class _ArrBase { protected: - using Placer = strategies::_NofitPolyPlacer; + + using Placer = TPacker; using Selector = FirstFitSelection; using Packer = Nester; using PConfig = typename Packer::PlacementConfig; @@ -290,10 +290,12 @@ class _ArrBase { Packer pck_; PConfig pconf_; // Placement configuration double bin_area_; - std::vector areacache_; SpatIndex rtree_; double norm_; - Pile pile_cache_; + Pile merged_pile_; + Box pilebb_; + ItemGroup remaining_; + ItemGroup items_; public: _ArrBase(const TBin& bin, Distance dist, @@ -302,11 +304,35 @@ class _ArrBase { norm_(std::sqrt(sl::area(bin))) { fillConfig(pconf_); + + pconf_.before_packing = + [this](const Pile& merged_pile, // merged pile + const ItemGroup& items, // packed items + const ItemGroup& remaining) // future items to be packed + { + items_ = items; + merged_pile_ = merged_pile; + remaining_ = remaining; + + pilebb_ = sl::boundingBox(merged_pile); + + rtree_.clear(); + + // We will treat big items (compared to the print bed) differently + auto isBig = [this](double a) { + return a/bin_area_ > BIG_ITEM_TRESHOLD ; + }; + + for(unsigned idx = 0; idx < items.size(); ++idx) { + Item& itm = items[idx]; + if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx}); + } + }; + pck_.progressIndicator(progressind); } template inline IndexedPackGroup operator()(Args&&...args) { - areacache_.clear(); rtree_.clear(); return pck_.executeIndexed(std::forward(args)...); } @@ -320,26 +346,28 @@ class AutoArranger: public _ArrBase { std::function progressind): _ArrBase(bin, dist, progressind) { -// pconf_.object_function = [this, bin] ( -// const Pile& pile_c, -// const Item &item, -// const ItemGroup& rem) { -// auto& pile = pile_cache_; -// if(pile.size() != pile_c.size()) pile = pile_c; + pconf_.object_function = [this, bin] (const Item &item) { -// auto result = objfunc(bin.center(), bin_area_, pile, -// item, norm_, areacache_, rtree_, rem); -// double score = std::get<0>(result); -// auto& fullbb = std::get<1>(result); + auto result = objfunc(bin.center(), + merged_pile_, + pilebb_, + items_, + item, + bin_area_, + norm_, + rtree_, + remaining_); -// auto wdiff = fullbb.width() - bin.width(); -// auto hdiff = fullbb.height() - bin.height(); -// if(wdiff > 0) score += std::pow(wdiff, 2) / norm_; -// if(hdiff > 0) score += std::pow(hdiff, 2) / norm_; + double score = std::get<0>(result); + auto& fullbb = std::get<1>(result); -// return score; -// }; + double miss = Placer::overfit(fullbb, bin); + miss = miss > 0? miss : 0; + score += miss*miss; + + return score; + }; pck_.configure(pconf_); } @@ -355,36 +383,31 @@ class AutoArranger: public _ArrBase { std::function progressind): _ArrBase(bin, dist, progressind) { - pconf_.object_function = [this, &bin] ( - const Pile& pile_c, - const Item &item, - const ItemGroup& rem) { + pconf_.object_function = [this, &bin] (const Item &item) { - auto& pile = pile_cache_; - if(pile.size() != pile_c.size()) pile = pile_c; + auto result = objfunc(bin.center(), + merged_pile_, + pilebb_, + items_, + item, + bin_area_, + norm_, + rtree_, + remaining_); - auto result = objfunc(bin.center(), bin_area_, pile, item, norm_, - areacache_, rtree_, rem); double score = std::get<0>(result); - auto& fullbb = std::get<1>(result); - - auto d = pl::distance(fullbb.minCorner(), - fullbb.maxCorner()); - auto diff = d - 2*bin.radius(); - - if(diff > 0) { - if( item.area() > 0.01*bin_area_ && item.vertexCount() < 30) { - pile.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(pile); - pile.pop_back(); - - auto C = strategies::boundingCircle(chull); - auto rdiff = C.radius() - bin.radius(); - if(rdiff > 0) { - score += std::pow(rdiff, 3) / norm_; - } - } + auto isBig = [this](const Item& itm) { + return itm.area()/bin_area_ > BIG_ITEM_TRESHOLD ; + }; + + if(isBig(item)) { + auto mp = merged_pile_; + mp.push_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + double miss = Placer::overfit(chull, bin); + if(miss < 0) miss = 0; + score += miss*miss; } return score; @@ -401,17 +424,18 @@ class AutoArranger: public _ArrBase { std::function progressind): _ArrBase(bin, dist, progressind) { - pconf_.object_function = [this, &bin] ( - const Pile& pile_c, - const Item &item, - const ItemGroup& rem) { - - auto& pile = pile_cache_; - if(pile.size() != pile_c.size()) pile = pile_c; + pconf_.object_function = [this, &bin] (const Item &item) { auto binbb = sl::boundingBox(bin); - auto result = objfunc(binbb.center(), bin_area_, pile, item, norm_, - areacache_, rtree_, rem); + auto result = objfunc(binbb.center(), + merged_pile_, + pilebb_, + items_, + item, + bin_area_, + norm_, + rtree_, + remaining_); double score = std::get<0>(result); return score; @@ -428,16 +452,17 @@ class AutoArranger: public _ArrBase { AutoArranger(Distance dist, std::function progressind): _ArrBase(Box(0, 0), dist, progressind) { - this->pconf_.object_function = [this] ( - const Pile& pile_c, - const Item &item, - const ItemGroup& rem) { - - auto& pile = pile_cache_; - if(pile.size() != pile_c.size()) pile = pile_c; - - auto result = objfunc({0, 0}, 0, pile, item, norm_, - areacache_, rtree_, rem); + this->pconf_.object_function = [this] (const Item &item) { + + auto result = objfunc({0, 0}, + merged_pile_, + pilebb_, + items_, + item, + 0, + norm_, + rtree_, + remaining_); return std::get<0>(result); }; From b0216b190af13ff82adc12eca0f8e813a1543e18 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 3 Sep 2018 10:15:40 +0200 Subject: [PATCH 19/63] Bugfix - extruder temperature was sometimes not correctly set on the wipe tower --- xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 42c06252b92..caea1c5e72e 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -525,6 +525,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( ++ m_num_tool_changes; } + m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear + // in the output gcode - we should not remember emitting them (we will output them twice in the worst case) + // Reset the extruder current to a normal value. writer.set_extruder_trimpot(550) .feedrate(6000) From e3de278afcc611a379f60dc5a4bcdfbadd471dca Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 5 Sep 2018 09:18:42 +0200 Subject: [PATCH 20/63] Another attempt to fix the temperature change issue --- xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index caea1c5e72e..b6340e9914c 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -807,8 +807,9 @@ void WipeTowerPrusaMM::toolchange_Unload( .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ .resume_preview(); - - if (new_temperature != 0 && new_temperature != m_old_temperature ) { // Set the extruder temperature, but don't wait. + if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait. + // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset) + // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). writer.set_extruder_temp(new_temperature, false); m_old_temperature = new_temperature; } From c83a5474f6f10832c98a56fe8c9717803041809d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 5 Sep 2018 15:35:35 +0200 Subject: [PATCH 21/63] Wipe tower extrusions are now accounted for in the filament consumption statistics --- lib/Slic3r/GUI/Plater.pm | 17 ++++++++- xs/src/libslic3r/GCode.cpp | 22 ++++++----- xs/src/libslic3r/GCode.hpp | 1 + xs/src/libslic3r/GCode/WipeTower.hpp | 3 ++ xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 41 ++++++++++++++------- xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 7 ++++ xs/src/libslic3r/Print.cpp | 2 + xs/src/libslic3r/Print.hpp | 3 +- xs/xsp/Print.xsp | 20 ++++++++++ 9 files changed, 91 insertions(+), 25 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ee2689d1647..7c9e9899e0e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1648,13 +1648,26 @@ sub print_info_box_show { $print_info_sizer->Add($grid_sizer, 0, wxEXPAND); my @info = ( L("Used Filament (m)") - => sprintf("%.2f" , $self->{print}->total_used_filament / 1000), + => $self->{print}->total_wipe_tower_filament > 0 ? + sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_used_filament / 1000, + ($self->{print}->total_used_filament - $self->{print}->total_wipe_tower_filament) / 1000, + L("objects"), + $self->{print}->total_wipe_tower_filament / 1000, + L("wipe_tower")) : + sprintf("%.2f" , $self->{print}->total_used_filament / 1000), + L("Used Filament (mm³)") => sprintf("%.2f" , $self->{print}->total_extruded_volume), L("Used Filament (g)"), => sprintf("%.2f" , $self->{print}->total_weight), L("Cost"), - => sprintf("%.2f" , $self->{print}->total_cost), + => $self->{print}->total_wipe_tower_cost > 0 ? + sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_cost, + ($self->{print}->total_cost - $self->{print}->total_wipe_tower_cost), + L("objects"), + $self->{print}->total_wipe_tower_cost, + L("wipe_tower")) : + sprintf("%.2f" , $self->{print}->total_cost), L("Estimated printing time (normal mode)") => $self->{print}->estimated_normal_print_time, L("Estimated printing time (silent mode)") diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index b34ba544150..b6d17d56a88 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -276,7 +276,6 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco } - std::string WipeTowerIntegration::prime(GCode &gcodegen) { assert(m_layer_idx == 0); @@ -960,17 +959,20 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // Get filament stats. print.filament_stats.clear(); - print.total_used_filament = 0.; - print.total_extruded_volume = 0.; - print.total_weight = 0.; - print.total_cost = 0.; + print.total_used_filament = 0.; + print.total_extruded_volume = 0.; + print.total_weight = 0.; + print.total_cost = 0.; + print.total_wipe_tower_cost = 0.; + print.total_wipe_tower_filament = 0.; print.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); print.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; for (const Extruder &extruder : m_writer.extruders()) { - double used_filament = extruder.used_filament(); - double extruded_volume = extruder.extruded_volume(); + double used_filament = extruder.used_filament() + (has_wipe_tower ? print.m_wipe_tower_used_filament[extruder.id()] : 0.f); + double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.m_wipe_tower_used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter double filament_weight = extruded_volume * extruder.filament_density() * 0.001; double filament_cost = filament_weight * extruder.filament_cost() * 0.001; + print.filament_stats.insert(std::pair(extruder.id(), (float)used_filament)); _write_format(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001); if (filament_weight > 0.) { @@ -981,8 +983,10 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) _write_format(file, "; filament cost = %.1lf\n", filament_cost); } } - print.total_used_filament = print.total_used_filament + used_filament; - print.total_extruded_volume = print.total_extruded_volume + extruded_volume; + print.total_used_filament += used_filament; + print.total_extruded_volume += extruded_volume; + print.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; + print.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.; } _write_format(file, "; total filament cost = %.1lf\n", print.total_cost); _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str()); diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 4953c39fefb..d319bee0140 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -98,6 +98,7 @@ class WipeTowerIntegration { void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer); std::string finalize(GCode &gcodegen); + std::vector used_filament_length() const; private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp index 9bf35032829..e7cd8ea1a29 100644 --- a/xs/src/libslic3r/GCode/WipeTower.hpp +++ b/xs/src/libslic3r/GCode/WipeTower.hpp @@ -155,6 +155,9 @@ class WipeTower // the wipe tower has been completely covered by the tool change extrusions, // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. virtual bool layer_finished() const = 0; + + // Returns used filament length per extruder: + virtual std::vector get_used_filament() const = 0; }; }; // namespace Slic3r diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 42c06252b92..23a2bef9b2d 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -111,9 +111,10 @@ class Writer const WipeTower::xy start_pos_rotated() const { return m_start_pos; } const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); } float elapsed_time() const { return m_elapsed_time; } + float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; } // Extrude with an explicitely provided amount of extrusion. - Writer& extrude_explicit(float x, float y, float e, float f = 0.f) + Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false) { if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate)) // Neither extrusion nor a travel move. @@ -122,6 +123,8 @@ class Writer float dx = x - m_current_pos.x; float dy = y - m_current_pos.y; double len = sqrt(dx*dx+dy*dy); + if (record_length) + m_used_filament_length += e; // Now do the "internal rotation" with respect to the wipe tower center @@ -162,8 +165,8 @@ class Writer return *this; } - Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f) - { return extrude_explicit(dest.x, dest.y, e, f); } + Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false) + { return extrude_explicit(dest.x, dest.y, e, f, record_length); } // Travel to a new XY position. f=0 means use the current value. Writer& travel(float x, float y, float f = 0.f) @@ -177,7 +180,7 @@ class Writer { float dx = x - m_current_pos.x; float dy = y - m_current_pos.y; - return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f); + return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true); } Writer& extrude(const WipeTower::xy &dest, const float f = 0.f) @@ -259,8 +262,8 @@ class Writer // extrude quickly amount e to x2 with feed f. Writer& ram(float x1, float x2, float dy, float e0, float e, float f) { - extrude_explicit(x1, m_current_pos.y + dy, e0, f); - extrude_explicit(x2, m_current_pos.y, e); + extrude_explicit(x1, m_current_pos.y + dy, e0, f, true); + extrude_explicit(x2, m_current_pos.y, e, 0.f, true); return *this; } @@ -404,6 +407,7 @@ class Writer float m_last_fan_speed = 0.f; int current_temp = -1; const float m_default_analyzer_line_width; + float m_used_filament_length = 0.f; std::string set_format_X(float x) { @@ -537,6 +541,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // so that tool_change() will know to extrude the wipe tower brim: m_print_brim = true; + // Ask our writer about how much material was consumed: + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + ToolChangeResult result; result.priming = true; result.print_z = this->m_z_pos; @@ -632,6 +639,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo ";------------------\n" "\n\n"); + // Ask our writer about how much material was consumed: + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + ToolChangeResult result; result.priming = false; result.print_z = this->m_z_pos; @@ -683,6 +693,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo m_print_brim = false; // Mark the brim as extruded + // Ask our writer about how much material was consumed: + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + ToolChangeResult result; result.priming = false; result.print_z = this->m_z_pos; @@ -849,6 +862,9 @@ void WipeTowerPrusaMM::toolchange_Change( const unsigned int new_tool, material_type new_material) { + // Ask the writer about how much of the old filament we consumed: + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + // Speed override for the material. Go slow for flex and soluble materials. int speed_override; switch (new_material) { @@ -911,7 +927,6 @@ void WipeTowerPrusaMM::toolchange_Wipe( const float& xl = cleaning_box.ld.x; const float& xr = cleaning_box.rd.x; - // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least // the ordered volume, even if it means violating the box. This can later be removed and simply // wipe until the end of the assigned area. @@ -926,7 +941,6 @@ void WipeTowerPrusaMM::toolchange_Wipe( m_left_to_right = !m_left_to_right; } - // now the wiping itself: for (int i = 0; true; ++i) { if (i!=0) { @@ -935,7 +949,7 @@ void WipeTowerPrusaMM::toolchange_Wipe( else if (wipe_speed < 2210.f) wipe_speed = 4200.f; else wipe_speed = std::min(4800.f, wipe_speed + 50.f); } - + float traversed_x = writer.x(); if (m_left_to_right) writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); @@ -1050,6 +1064,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() m_depth_traversed = m_wipe_tower_depth-m_perimeter_width; + // Ask our writer about how much material was consumed: + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + ToolChangeResult result; result.priming = false; result.print_z = this->m_z_pos; @@ -1167,6 +1184,8 @@ void WipeTowerPrusaMM::generate(std::vector layer_result; for (auto layer : m_plan) @@ -1208,9 +1227,6 @@ void WipeTowerPrusaMM::generate(std::vector> speed) m_filpar[idx].ramming_speed.push_back(speed); + + m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later } @@ -172,6 +174,8 @@ class WipeTowerPrusaMM : public WipeTower return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); } + virtual std::vector get_used_filament() const { return m_used_filament_length; } + private: WipeTowerPrusaMM(); @@ -331,6 +335,9 @@ class WipeTowerPrusaMM : public WipeTower std::vector m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) std::vector::iterator m_layer_info = m_plan.end(); + // Stores information about used filament length per extruder: + std::vector m_used_filament_length; + // Returns gcode for wipe tower brim // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index bd14837d922..ba8abd0407e 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -1193,6 +1193,8 @@ void Print::_make_wipe_tower() } m_wipe_tower_final_purge = Slic3r::make_unique( wipe_tower.tool_change((unsigned int)-1, false)); + + m_wipe_tower_used_filament = wipe_tower.get_used_filament(); } std::string Print::output_filename() diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index e3430ad0e40..537070a34be 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -240,7 +240,7 @@ class Print // TODO: status_cb std::string estimated_normal_print_time; std::string estimated_silent_print_time; - double total_used_filament, total_extruded_volume, total_cost, total_weight; + double total_used_filament, total_extruded_volume, total_cost, total_weight, total_wipe_tower_cost, total_wipe_tower_filament; std::map filament_stats; PrintState state; @@ -309,6 +309,7 @@ class Print std::unique_ptr m_wipe_tower_priming; std::vector> m_wipe_tower_tool_changes; std::unique_ptr m_wipe_tower_final_purge; + std::vector m_wipe_tower_used_filament; std::string output_filename(); std::string output_filepath(const std::string &path); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 71706491668..50f899f2cdc 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -276,6 +276,26 @@ Print::total_cost(...) THIS->total_cost = (double)SvNV(ST(1)); } RETVAL = THIS->total_cost; + OUTPUT: + RETVAL + +double +Print::total_wipe_tower_cost(...) + CODE: + if (items > 1) { + THIS->total_wipe_tower_cost = (double)SvNV(ST(1)); + } + RETVAL = THIS->total_wipe_tower_cost; + OUTPUT: + RETVAL + +double +Print::total_wipe_tower_filament(...) + CODE: + if (items > 1) { + THIS->total_wipe_tower_filament = (double)SvNV(ST(1)); + } + RETVAL = THIS->total_wipe_tower_filament; OUTPUT: RETVAL %} From e1417f182701858cc369a233096eb845d7a081f7 Mon Sep 17 00:00:00 2001 From: Martin Loidl Date: Tue, 4 Sep 2018 23:55:37 +0200 Subject: [PATCH 22/63] allow whitespaces for duet upload * now url_encoding characters which could not used for URLs --- xs/src/slic3r/Utils/Duet.cpp | 7 ++++--- xs/src/slic3r/Utils/Http.cpp | 12 ++++++++++++ xs/src/slic3r/Utils/Http.hpp | 3 +++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp index 82a42eb7107..a3dc4a3bb90 100644 --- a/xs/src/slic3r/Utils/Duet.cpp +++ b/xs/src/slic3r/Utils/Duet.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -197,7 +198,7 @@ std::string Duet::get_upload_url(const std::string &filename) const { return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%") % get_base_url() - % filename + % Http::url_encode(filename) % timestamp_str()).str(); } @@ -248,9 +249,10 @@ wxString Duet::format_error(const std::string &body, const std::string &error, u bool Duet::start_print(wxString &msg, const std::string &filename) const { bool res = false; + auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"") % get_base_url() - % filename).str(); + % Http::url_encode(filename)).str(); auto http = Http::get(std::move(url)); http.on_error([&](std::string body, std::string error, unsigned status) { @@ -275,5 +277,4 @@ int Duet::get_err_code_from_body(const std::string &body) const return root.get("err", 0); } - } diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp index a92e399a08d..3024b08fe05 100644 --- a/xs/src/slic3r/Utils/Http.cpp +++ b/xs/src/slic3r/Utils/Http.cpp @@ -421,6 +421,18 @@ bool Http::ca_file_supported() return res; } +std::string Http::url_encode(const std::string &str) +{ + ::CURL *curl = ::curl_easy_init(); + char *ce = ::curl_easy_escape(curl, str.c_str(), str.length()); + std::string encoded = std::string(ce); + + ::curl_free(ce); + if (curl != nullptr) { ::curl_easy_cleanup(curl); } + + return encoded; +} + std::ostream& operator<<(std::ostream &os, const Http::Progress &progress) { os << "Http::Progress(" diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp index f1302b0ed97..44580b7eaf1 100644 --- a/xs/src/slic3r/Utils/Http.hpp +++ b/xs/src/slic3r/Utils/Http.hpp @@ -98,6 +98,9 @@ class Http : public std::enable_shared_from_this { // Tells whether current backend supports seting up a CA file using ca_file() static bool ca_file_supported(); + + // converts the given string to an url_encoded_string + static std::string url_encode(const std::string &str); private: Http(const std::string &url); From 2a81408e8bd64bd3f28319b418ab933895b301d9 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 6 Sep 2018 14:19:20 +0200 Subject: [PATCH 23/63] Implemented support enforcers / blockers. Reduced amount of full support interfaces similar to S3D. --- lib/Slic3r/GUI/Plater/ObjectCutDialog.pm | 2 +- lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 79 ++++-- .../GUI/Plater/OverrideSettingsPanel.pm | 50 +++- resources/icons/support_blocker.png | Bin 0 -> 656 bytes resources/icons/support_enforcer.png | Bin 0 -> 509 bytes xs/src/libslic3r/EdgeGrid.cpp | 2 + xs/src/libslic3r/Format/3mf.cpp | 12 +- xs/src/libslic3r/Format/AMF.cpp | 14 +- xs/src/libslic3r/Model.cpp | 51 +++- xs/src/libslic3r/Model.hpp | 44 +++- xs/src/libslic3r/Print.cpp | 10 +- xs/src/libslic3r/Print.hpp | 5 + xs/src/libslic3r/PrintConfig.cpp | 2 +- xs/src/libslic3r/PrintObject.cpp | 77 ++++-- xs/src/libslic3r/PrintRegion.cpp | 2 +- xs/src/libslic3r/Slicing.cpp | 6 +- xs/src/libslic3r/SupportMaterial.cpp | 234 ++++++++++++------ xs/src/libslic3r/SupportMaterial.hpp | 1 + xs/src/slic3r/GUI/3DScene.cpp | 19 +- xs/xsp/Model.xsp | 14 +- 20 files changed, 461 insertions(+), 163 deletions(-) create mode 100644 resources/icons/support_blocker.png create mode 100644 resources/icons/support_enforcer.png diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm index 26a6fdec328..77efbb29b47 100644 --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -238,7 +238,7 @@ sub _update { my @expolygons = (); foreach my $volume (@{$self->{model_object}->volumes}) { next if !$volume->mesh; - next if $volume->modifier; + next if !$volume->model_part; my $expp = $volume->mesh->slice([ $z_cut ])->[0]; push @expolygons, @$expp; } diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 783c1a9f5b3..7e9ee05dd77 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -16,6 +16,8 @@ use base 'Wx::Panel'; use constant ICON_OBJECT => 0; use constant ICON_SOLIDMESH => 1; use constant ICON_MODIFIERMESH => 2; +use constant ICON_SUPPORT_ENFORCER => 3; +use constant ICON_SUPPORT_BLOCKER => 4; sub new { my ($class, $parent, %params) = @_; @@ -35,7 +37,7 @@ sub new { y => 0, z => 0, }; - + # create TreeCtrl my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100], wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT @@ -46,6 +48,8 @@ sub new { $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH + $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_enforcer.png"), wxBITMAP_TYPE_PNG)); # ICON_SUPPORT_ENFORCER + $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("support_blocker.png"), wxBITMAP_TYPE_PNG)); # ICON_SUPPORT_BLOCKER my $rootId = $tree->AddRoot("Object", ICON_OBJECT); $tree->SetPlData($rootId, { type => 'object' }); @@ -89,7 +93,14 @@ sub new { $self->{btn_move_down}->SetFont($Slic3r::GUI::small_font); # part settings panel - $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; $self->_update_canvas; }); + $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { + my ($key, $value) = @_; + wxTheApp->CallAfter(sub { + $self->set_part_type($value) if ($key eq "part_type"); + $self->{part_settings_changed} = 1; + $self->_update_canvas; + }); + }); my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL); $settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0); @@ -225,8 +236,11 @@ sub reload_tree { my $selectedId = $rootId; foreach my $volume_id (0..$#{$object->volumes}) { my $volume = $object->volumes->[$volume_id]; - - my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH; + my $icon = + $volume->modifier ? ICON_MODIFIERMESH : + $volume->support_enforcer ? ICON_SUPPORT_ENFORCER : + $volume->support_blocker ? ICON_SUPPORT_BLOCKER : + ICON_SOLIDMESH; my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon); if ($volume_id == $selected_volume_idx) { $selectedId = $itemId; @@ -288,6 +302,8 @@ sub selection_changed { if (my $itemData = $self->get_selection) { my ($config, @opt_keys); + my $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_OBJECT; + my $support = 0; if ($itemData->{type} eq 'volume') { # select volume in 3D preview if ($self->{canvas}) { @@ -301,16 +317,24 @@ sub selection_changed { # attach volume config to settings panel my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ]; - if ($volume->modifier) { + if (! $volume->model_part) { $self->{optgroup_movers}->enable; + if ($volume->support_enforcer || $volume->support_blocker) { + $support = 1; + $type = $volume->support_enforcer ? + Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER : + Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER; + } else { + $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER; + } } else { + $type = Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART; $self->{optgroup_movers}->disable; } $config = $volume->config; $self->{staticbox}->SetLabel('Part Settings'); - # get default values - @opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys}; + @opt_keys = $support ? () : @{Slic3r::Config::PrintRegion->new->get_keys}; } elsif ($itemData->{type} eq 'object') { # select nothing in 3D preview @@ -323,33 +347,54 @@ sub selection_changed { # get default values my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys); - # decide which settings will be shown by default + # decide which settings will be shown by default if ($itemData->{type} eq 'object') { $config->set_ifndef('wipe_into_objects', 0); $config->set_ifndef('wipe_into_infill', 0); } # append default extruder - push @opt_keys, 'extruder'; - $default_config->set('extruder', 0); - $config->set_ifndef('extruder', 0); + if (! $support) { + push @opt_keys, 'extruder'; + $default_config->set('extruder', 0); + $config->set_ifndef('extruder', 0); + } + $self->{settings_panel}->set_type($type); $self->{settings_panel}->set_default_config($default_config); $self->{settings_panel}->set_config($config); $self->{settings_panel}->set_opt_keys(\@opt_keys); # disable minus icon to remove the settings - if ($itemData->{type} eq 'object') { - $self->{settings_panel}->set_fixed_options([qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)]); - } else { - $self->{settings_panel}->set_fixed_options([qw(extruder)]); - } - + my $fixed_options = + ($itemData->{type} eq 'object') ? [qw(extruder), qw(wipe_into_infill), qw(wipe_into_objects)] : + $support ? [] : [qw(extruder)]; + $self->{settings_panel}->set_fixed_options($fixed_options); $self->{settings_panel}->enable; } Slic3r::GUI::_3DScene::render($self->{canvas}) if $self->{canvas}; } +sub set_part_type +{ + my ($self, $part_type) = @_; + if (my $itemData = $self->get_selection) { + if ($itemData->{type} eq 'volume') { + my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ]; + if ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER || + $part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_PART) { + $volume->set_modifier($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_MODIFIER); + } elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_ENFORCER) { + $volume->set_support_enforcer; + } elsif ($part_type == Slic3r::GUI::Plater::OverrideSettingsPanel->TYPE_SUPPORT_BLOCKER) { + $volume->set_support_blocker; + } + # We want the icon of the selected item to be changed as well. + $self->reload_tree($itemData->{volume_id}); + } + } +} + sub on_btn_load { my ($self, $is_modifier) = @_; diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index 3b10ed99f77..a64bff39393 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -7,15 +7,20 @@ use warnings; use utf8; use List::Util qw(first); -use Wx qw(:misc :sizer :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG - wxTheApp); -use Wx::Event qw(EVT_BUTTON EVT_LEFT_DOWN EVT_MENU); +use Wx qw(:misc :sizer :button :combobox wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxTheApp); +use Wx::Event qw(EVT_BUTTON EVT_COMBOBOX EVT_LEFT_DOWN EVT_MENU); use base 'Wx::ScrolledWindow'; use constant ICON_MATERIAL => 0; use constant ICON_SOLIDMESH => 1; use constant ICON_MODIFIERMESH => 2; +use constant TYPE_OBJECT => -1; +use constant TYPE_PART => 0; +use constant TYPE_MODIFIER => 1; +use constant TYPE_SUPPORT_ENFORCER => 2; +use constant TYPE_SUPPORT_BLOCKER => 3; + my %icons = ( 'Advanced' => 'wand.png', 'Extruders' => 'funnel.png', @@ -36,13 +41,14 @@ sub new { $self->{config} = Slic3r::Config->new; # On change callback. $self->{on_change} = $params{on_change}; + $self->{type} = TYPE_OBJECT; $self->{fixed_options} = {}; $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); $self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL); $self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0); - + # option selector { # create the button @@ -110,6 +116,16 @@ sub set_opt_keys { $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ]; } +sub set_type { + my ($self, $type) = @_; + $self->{type} = $type; + if ($type == TYPE_SUPPORT_ENFORCER || $type == TYPE_SUPPORT_BLOCKER) { + $self->{btn_add}->Hide; + } else { + $self->{btn_add}->Show; + } +} + sub set_fixed_options { my ($self, $opt_keys) = @_; $self->{fixed_options} = { map {$_ => 1} @$opt_keys }; @@ -121,12 +137,28 @@ sub update_optgroup { $self->{options_sizer}->Clear(1); return if !defined $self->{config}; - + + if ($self->{type} != TYPE_OBJECT) { + my $label = Wx::StaticText->new($self, -1, "Type:"), + my $selection = [ "Part", "Modifier", "Support Enforcer", "Support Blocker" ]; + my $field = Wx::ComboBox->new($self, -1, $selection->[$self->{type}], wxDefaultPosition, Wx::Size->new(160, -1), $selection, wxCB_READONLY); + my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $sizer->Add($label, 1, wxEXPAND | wxALL, 5); + $sizer->Add($field, 0, wxALL, 5); + EVT_COMBOBOX($self, $field, sub { + my $idx = $field->GetSelection; # get index of selected value + $self->{on_change}->("part_type", $idx) if $self->{on_change}; + }); + $self->{options_sizer}->Add($sizer, 0, wxEXPAND | wxBOTTOM, 0); + } + my %categories = (); - foreach my $opt_key (@{$self->{config}->get_keys}) { - my $category = $Slic3r::Config::Options->{$opt_key}{category}; - $categories{$category} ||= []; - push @{$categories{$category}}, $opt_key; + if ($self->{type} != TYPE_SUPPORT_ENFORCER && $self->{type} != TYPE_SUPPORT_BLOCKER) { + foreach my $opt_key (@{$self->{config}->get_keys}) { + my $category = $Slic3r::Config::Options->{$opt_key}{category}; + $categories{$category} ||= []; + push @{$categories{$category}}, $opt_key; + } } foreach my $category (sort keys %categories) { my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( diff --git a/resources/icons/support_blocker.png b/resources/icons/support_blocker.png new file mode 100644 index 0000000000000000000000000000000000000000..8a66c73f4f6aff7721a84c2ce768d78cf0aac128 GIT binary patch literal 656 zcmV;B0&o3^P)3tMfkRIh^O5_aHwQOgY^tr#t)fIa>v|l+!Iu&j8?5 zD&5$3|LEPgI4eX@x30IEwdTq6#7Z>)zN`NQ%Vfy# z7XVTy!ZBoaS1;y8HLt0ybQA(_#j70v1iMWx;22}K!q*zRhPDmIWh#2wInrDgO~xe3 zE?yc?r*pE~B})ME0;1i`$y%dKS$aNfe-bh3jGk9Xbvh@zEzSUdQd&?d!ktaiyDDYY z*7N@0$wxaMN93of$+KL*F~&MVZIn{2vV4nD?>YLAFG!_Rj@k9T;}uy@LTG=>YXyLA z$DP{^_Yw<Ej`Df#>9`*nR_mu-4n!x_N*QLMSDKC>SrNDU|}B-U*!2uup$1 qFjxG()tt0=-#>3KFScK2jQs;LJU(a{EOJ%=00007xp;PyQ4hxbfI(F{TE$onlr!JkMLv%4_b<9)@c8Cc-L1UY_VnnU< z`##UxVG_-m(tCS(`Ml4|hc|L}{7-f7>ZdK_qE2ahPlbTD@p(WA~Wj%jHP<%{<(*p7aN%A&0KzR z$;{07vM?L{5qEp;dcHqj`m#((PIJv%c)Fh@Gty}!opjL&V6u4aMCuep2j=GA6mP!m z25_rbFk?EIrW8ETCD9}RgQ=6{wUz2-ZEJgr;9*|TwNwNkH=%pT3QjPGH|f>9dFj2l&{SD#&Z0pQ}J^8f;aksW~9 z`|^U3X4d5`{U8P4-O@rj+4naP?epGIw8!)}S~4KOas1W>00000NkvXXu0mjf^^oOe literal 0 HcmV?d00001 diff --git a/xs/src/libslic3r/EdgeGrid.cpp b/xs/src/libslic3r/EdgeGrid.cpp index 733ff2ad745..804b526c7f9 100644 --- a/xs/src/libslic3r/EdgeGrid.cpp +++ b/xs/src/libslic3r/EdgeGrid.cpp @@ -1227,6 +1227,8 @@ bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coo Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const { + assert(std::abs(2 * offset) < m_resolution); + typedef std::unordered_multimap EndPointMapType; // 0) Prepare a binary grid. size_t cell_rows = m_rows + 2; diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index 945bb1f8682..fd911bf4e63 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -71,6 +71,7 @@ const char* VOLUME_TYPE = "volume"; const char* NAME_KEY = "name"; const char* MODIFIER_KEY = "modifier"; +const char* VOLUME_TYPE_KEY = "volume_type"; const unsigned int VALID_OBJECT_TYPES_COUNT = 1; const char* VALID_OBJECT_TYPES[] = @@ -1501,7 +1502,9 @@ namespace Slic3r { if (metadata.key == NAME_KEY) volume->name = metadata.value; else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) - volume->modifier = true; + volume->set_type(ModelVolume::PARAMETER_MODIFIER); + else if (metadata.key == VOLUME_TYPE_KEY) + volume->set_type(ModelVolume::type_from_string(metadata.value)); else volume->config.set_deserialize(metadata.key, metadata.value); } @@ -2017,9 +2020,12 @@ namespace Slic3r { if (!volume->name.empty()) stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; - // stores volume's modifier field - if (volume->modifier) + // stores volume's modifier field (legacy, to support old slicers) + if (volume->is_modifier()) stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + // stores volume's type (overrides the modifier field above) + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " << + VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n"; // stores volume's config data for (const std::string& key : volume->config.keys()) diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index 886bbae9714..5aa922f62de 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -458,9 +458,14 @@ void AMFParserContext::endElement(const char * /* name */) p = end + 1; } m_object->layer_height_profile_valid = true; - } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume && strcmp(opt_key, "modifier") == 0) { - // Is this volume a modifier volume? - m_volume->modifier = atoi(m_value[1].c_str()) == 1; + } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) { + if (strcmp(opt_key, "modifier") == 0) { + // Is this volume a modifier volume? + // "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag. + m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); + } else if (strcmp(opt_key, "volume_type") == 0) { + m_volume->set_type(ModelVolume::type_from_string(m_value[1])); + } } } else if (m_path.size() == 3) { if (m_path[1] == NODE_TYPE_MATERIAL) { @@ -781,8 +786,9 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c stream << " " << volume->config.serialize(key) << "\n"; if (!volume->name.empty()) stream << " " << xml_escape(volume->name) << "\n"; - if (volume->modifier) + if (volume->is_modifier()) stream << " 1\n"; + stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 09b515c2fbc..e2e84dfd48e 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -609,7 +609,7 @@ const BoundingBoxf3& ModelObject::bounding_box() const if (! m_bounding_box_valid) { BoundingBoxf3 raw_bbox; for (const ModelVolume *v : this->volumes) - if (! v->modifier) + if (v->is_model_part()) raw_bbox.merge(v->mesh.bounding_box()); BoundingBoxf3 bb; for (const ModelInstance *i : this->instances) @@ -640,7 +640,7 @@ TriangleMesh ModelObject::raw_mesh() const { TriangleMesh mesh; for (const ModelVolume *v : this->volumes) - if (! v->modifier) + if (v->is_model_part()) mesh.merge(v->mesh); return mesh; } @@ -651,7 +651,7 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const { BoundingBoxf3 bb; for (const ModelVolume *v : this->volumes) - if (! v->modifier) { + if (v->is_model_part()) { if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances"); bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true)); } @@ -663,7 +663,7 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_ { BoundingBoxf3 bb; for (ModelVolume *v : this->volumes) - if (! v->modifier) + if (v->is_model_part()) bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&v->mesh, dont_translate)); return bb; } @@ -674,7 +674,7 @@ void ModelObject::center_around_origin() // center this object around the origin BoundingBoxf3 bb; for (ModelVolume *v : this->volumes) - if (! v->modifier) + if (v->is_model_part()) bb.merge(v->mesh.bounding_box()); // first align to origin on XYZ @@ -778,7 +778,7 @@ size_t ModelObject::facets_count() const { size_t num = 0; for (const ModelVolume *v : this->volumes) - if (! v->modifier) + if (v->is_model_part()) num += v->mesh.stl.stats.number_of_facets; return num; } @@ -786,7 +786,7 @@ size_t ModelObject::facets_count() const bool ModelObject::needed_repair() const { for (const ModelVolume *v : this->volumes) - if (! v->modifier && v->mesh.needed_repair()) + if (v->is_model_part() && v->mesh.needed_repair()) return true; return false; } @@ -802,7 +802,7 @@ void ModelObject::cut(coordf_t z, Model* model) const lower->input_file = ""; for (ModelVolume *volume : this->volumes) { - if (volume->modifier) { + if (! volume->is_model_part()) { // don't cut modifiers upper->add_volume(*volume); lower->add_volume(*volume); @@ -854,7 +854,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects) ModelVolume* new_volume = new_object->add_volume(*mesh); new_volume->name = volume->name; new_volume->config = volume->config; - new_volume->modifier = volume->modifier; + new_volume->set_type(volume->type()); new_volume->material_id(volume->material_id()); new_objects->push_back(new_object); @@ -868,7 +868,7 @@ void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_ { for (const ModelVolume* vol : this->volumes) { - if (!vol->modifier) + if (vol->is_model_part()) { for (ModelInstance* inst : this->instances) { @@ -972,6 +972,37 @@ const TriangleMesh& ModelVolume::get_convex_hull() const return m_convex_hull; } +ModelVolume::Type ModelVolume::type_from_string(const std::string &s) +{ + // Legacy support + if (s == "0") + return MODEL_PART; + if (s == "1") + return PARAMETER_MODIFIER; + // New type (supporting the support enforcers & blockers) + if (s == "ModelPart") + return MODEL_PART; + if (s == "ParameterModifier") + return PARAMETER_MODIFIER; + if (s == "SupportEnforcer") + return SUPPORT_ENFORCER; + if (s == "SupportBlocker") + return SUPPORT_BLOCKER; +} + +std::string ModelVolume::type_to_string(const Type t) +{ + switch (t) { + case MODEL_PART: return "ModelPart"; + case PARAMETER_MODIFIER: return "ParameterModifier"; + case SUPPORT_ENFORCER: return "SupportEnforcer"; + case SUPPORT_BLOCKER: return "SupportBlocker"; + default: + assert(false); + return "ModelPart"; + } +} + // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index dadd515dea7..34a7c7cc13b 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -165,15 +165,27 @@ class ModelVolume // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. DynamicPrintConfig config; - // Is it an object to be printed, or a modifier volume? - bool modifier; - + + enum Type { + MODEL_TYPE_INVALID = -1, + MODEL_PART = 0, + PARAMETER_MODIFIER, + SUPPORT_ENFORCER, + SUPPORT_BLOCKER, + }; + // A parent object owning this modifier volume. - ModelObject* get_object() const { return this->object; }; + ModelObject* get_object() const { return this->object; }; + Type type() const { return m_type; } + void set_type(const Type t) { m_type = t; } + bool is_model_part() const { return m_type == MODEL_PART; } + bool is_modifier() const { return m_type == PARAMETER_MODIFIER; } + bool is_support_enforcer() const { return m_type == SUPPORT_ENFORCER; } + bool is_support_blocker() const { return m_type == SUPPORT_BLOCKER; } t_model_material_id material_id() const { return this->_material_id; } - void material_id(t_model_material_id material_id); - ModelMaterial* material() const; - void set_material(t_model_material_id material_id, const ModelMaterial &material); + void material_id(t_model_material_id material_id); + ModelMaterial* material() const; + void set_material(t_model_material_id material_id, const ModelMaterial &material); // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. @@ -184,24 +196,30 @@ class ModelVolume void calculate_convex_hull(); const TriangleMesh& get_convex_hull() const; + // Helpers for loading / storing into AMF / 3MF files. + static Type type_from_string(const std::string &s); + static std::string type_to_string(const Type t); + private: // Parent object owning this ModelVolume. - ModelObject* object; - t_model_material_id _material_id; + ModelObject* object; + // Is it an object to be printed, or a modifier volume? + Type m_type; + t_model_material_id _material_id; - ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) + ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object) { if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } - ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), modifier(false), object(object) {} + ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {} ModelVolume(ModelObject *object, const ModelVolume &other) : - name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), object(object) + name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object) { this->material_id(other.material_id()); } ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : - name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object) + name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object) { this->material_id(other.material_id()); if (mesh.stl.stats.number_of_facets > 1) diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index bd14837d922..830c857c9ed 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -362,9 +362,12 @@ void Print::add_model_object(ModelObject* model_object, int idx) // Invalidate all print steps. this->invalidate_all_steps(); - for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) { + size_t volume_id = 0; + for (const ModelVolume *volume : model_object->volumes) { + if (! volume->is_model_part() && ! volume->is_modifier()) + continue; // Get the config applied to this volume. - PrintRegionConfig config = this->_region_config_from_model_volume(*model_object->volumes[volume_id]); + PrintRegionConfig config = this->_region_config_from_model_volume(*volume); // Find an existing print region with the same config. size_t region_id = size_t(-1); for (size_t i = 0; i < this->regions.size(); ++ i) @@ -379,6 +382,7 @@ void Print::add_model_object(ModelObject* model_object, int idx) } // Assign volume to a region. object->add_region_volume(region_id, volume_id); + ++ volume_id; } // Apply config to print object. @@ -853,7 +857,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) { ModelVolume *volume = model_object->volumes[volume_id]; //FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned. - if (! volume->material_id().empty() && ! volume->config.has("extruder")) + if ((volume->is_model_part() || volume->is_modifier()) && ! volume->material_id().empty() && ! volume->config.has("extruder")) volume->config.opt("extruder", true)->value = int(volume_id + 1); } } diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index b16341732fb..a0cb83e0976 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -214,6 +214,10 @@ class PrintObject bool is_printable() const { return !this->_shifted_copies.empty(); } + // Helpers to slice support enforcer / blocker meshes by the support generator. + std::vector slice_support_enforcers() const; + std::vector slice_support_blockers() const; + private: Print* _print; ModelObject* _model_object; @@ -225,6 +229,7 @@ class PrintObject ~PrintObject() {} std::vector _slice_region(size_t region_id, const std::vector &z, bool modifier); + std::vector _slice_volumes(const std::vector &z, const std::vector &volumes) const; }; typedef std::vector PrintObjectPtrs; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index f9f0b2056a8..1a8b19c3f16 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1734,7 +1734,7 @@ PrintConfigDef::PrintConfigDef() "for the first object layer."); def->sidetext = L("mm"); def->cli = "support-material-contact-distance=f"; - def->min = 0; +// def->min = 0; def->enum_values.push_back("0"); def->enum_values.push_back("0.2"); def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str()); diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 7150ead596f..6dd764ae1c6 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -1320,29 +1320,62 @@ void PrintObject::_slice() std::vector PrintObject::_slice_region(size_t region_id, const std::vector &z, bool modifier) { - std::vector layers; + std::vector volumes; if (region_id < this->region_volumes.size()) { - std::vector &volumes = this->region_volumes[region_id]; - if (! volumes.empty()) { - // Compose mesh. - //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. - TriangleMesh mesh; - for (int volume_id : volumes) { - ModelVolume *volume = this->model_object()->volumes[volume_id]; - if (volume->modifier == modifier) - mesh.merge(volume->mesh); - } - if (mesh.stl.stats.number_of_facets > 0) { - // transform mesh - // we ignore the per-instance transformations currently and only - // consider the first one - this->model_object()->instances.front()->transform_mesh(&mesh, true); - // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift - mesh.translate(- float(unscale(this->_copies_shift.x)), - float(unscale(this->_copies_shift.y)), -float(this->model_object()->bounding_box().min.z)); - // perform actual slicing - TriangleMeshSlicer mslicer(&mesh); - mslicer.slice(z, &layers); - } + for (int volume_id : this->region_volumes[region_id]) { + const ModelVolume *volume = this->model_object()->volumes[volume_id]; + if (modifier ? volume->is_modifier() : volume->is_model_part()) + volumes.emplace_back(volume); + } + } + return this->_slice_volumes(z, volumes); +} + +std::vector PrintObject::slice_support_enforcers() const +{ + std::vector volumes; + for (const ModelVolume *volume : this->model_object()->volumes) + if (volume->is_support_enforcer()) + volumes.emplace_back(volume); + std::vector zs; + zs.reserve(this->layers.size()); + for (const Layer *l : this->layers) + zs.emplace_back(l->slice_z); + return this->_slice_volumes(zs, volumes); +} + +std::vector PrintObject::slice_support_blockers() const +{ + std::vector volumes; + for (const ModelVolume *volume : this->model_object()->volumes) + if (volume->is_support_blocker()) + volumes.emplace_back(volume); + std::vector zs; + zs.reserve(this->layers.size()); + for (const Layer *l : this->layers) + zs.emplace_back(l->slice_z); + return this->_slice_volumes(zs, volumes); +} + +std::vector PrintObject::_slice_volumes(const std::vector &z, const std::vector &volumes) const +{ + std::vector layers; + if (! volumes.empty()) { + // Compose mesh. + //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. + TriangleMesh mesh; + for (const ModelVolume *v : volumes) + mesh.merge(v->mesh); + if (mesh.stl.stats.number_of_facets > 0) { + // transform mesh + // we ignore the per-instance transformations currently and only + // consider the first one + this->model_object()->instances.front()->transform_mesh(&mesh, true); + // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift + mesh.translate(- float(unscale(this->_copies_shift.x)), - float(unscale(this->_copies_shift.y)), -float(this->model_object()->bounding_box().min.z)); + // perform actual slicing + TriangleMeshSlicer mslicer(&mesh); + mslicer.slice(z, &layers); } } return layers; diff --git a/xs/src/libslic3r/PrintRegion.cpp b/xs/src/libslic3r/PrintRegion.cpp index 71fcc876ae9..5bb1fffb3ce 100644 --- a/xs/src/libslic3r/PrintRegion.cpp +++ b/xs/src/libslic3r/PrintRegion.cpp @@ -59,7 +59,7 @@ coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const { - return this->nozzle_dmr_avg(print_config) * this->config.bridge_flow_ratio.value; + return this->nozzle_dmr_avg(print_config) * sqrt(this->config.bridge_flow_ratio.value); } } diff --git a/xs/src/libslic3r/Slicing.cpp b/xs/src/libslic3r/Slicing.cpp index e9295d1e331..d3fbcc7cb53 100644 --- a/xs/src/libslic3r/Slicing.cpp +++ b/xs/src/libslic3r/Slicing.cpp @@ -224,9 +224,9 @@ std::vector layer_height_profile_adaptive( // 1) Initialize the SlicingAdaptive class with the object meshes. SlicingAdaptive as; as.set_slicing_parameters(slicing_params); - for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it) - if (! (*it)->modifier) - as.add_mesh(&(*it)->mesh); + for (const ModelVolume *volume : volumes) + if (volume->is_model_part()) + as.add_mesh(&volume->mesh); as.prepare(); // 2) Generate layers using the algorithm of @platsch diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 1bf9675df0a..e1a8995a648 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -283,7 +283,9 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) object, bottom_contacts, top_contacts, layer_storage); // this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); - this->trim_support_layers_by_object(object, top_contacts, 0., 0., m_gap_xy); + this->trim_support_layers_by_object(object, top_contacts, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); #ifdef SLIC3R_DEBUG for (const MyLayer *layer : top_contacts) @@ -428,29 +430,17 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t { // 1) Count the new polygons first. size_t n_polygons_new = 0; - for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) { - const LayerRegion ®ion = *(*it_region); - const SurfaceCollection &slices = region.slices; - for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) { - const Surface &surface = *it; + for (const LayerRegion *region : layer.regions) + for (const Surface &surface : region->slices.surfaces) if (surface.surface_type == surface_type) n_polygons_new += surface.expolygon.holes.size() + 1; - } - } - // 2) Collect the new polygons. Polygons out; out.reserve(n_polygons_new); - for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) { - const LayerRegion ®ion = *(*it_region); - const SurfaceCollection &slices = region.slices; - for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) { - const Surface &surface = *it; + for (const LayerRegion *region : layer.regions) + for (const Surface &surface : region->slices.surfaces) if (surface.surface_type == surface_type) polygons_append(out, surface.expolygon); - } - } - return out; } @@ -460,8 +450,8 @@ Polygons collect_slices_outer(const Layer &layer) { Polygons out; out.reserve(out.size() + layer.slices.expolygons.size()); - for (ExPolygons::const_iterator it = layer.slices.expolygons.begin(); it != layer.slices.expolygons.end(); ++ it) - out.push_back(it->contour); + for (const ExPolygon &expoly : layer.slices.expolygons) + out.emplace_back(expoly.contour); return out; } @@ -469,8 +459,11 @@ class SupportGridPattern { public: SupportGridPattern( + // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy) const Polygons &support_polygons, - const Polygons &trimming_polygons, + // Trimming polygons, to trim the stretched support islands. support_polygons were already trimmed with trimming_polygons. + const Polygons &trimming_polygons, + // Grid spacing, given by "support_material_spacing" + m_support_material_flow.spacing() coordf_t support_spacing, coordf_t support_angle) : m_support_polygons(&support_polygons), m_trimming_polygons(&trimming_polygons), @@ -493,7 +486,8 @@ class SupportGridPattern m_grid.set_bbox(bbox); m_grid.create(*m_support_polygons, grid_resolution); m_grid.calculate_sdf(); - // Extract a bounding contour from the grid, trim by the object. + // Sample a single point per input support polygon, keep it as a reference to maintain corresponding + // polygons if ever these polygons get split into parts by the trimming polygons. m_island_samples = island_samples(*m_support_polygons); } @@ -504,19 +498,19 @@ class SupportGridPattern Polygons extract_support(const coord_t offset_in_grid) { // Generate islands, so each island may be tested for overlap with m_island_samples. + assert(std::abs(2 * offset_in_grid) < m_grid.resolution()); ExPolygons islands = diff_ex( m_grid.contours_simplified(offset_in_grid), *m_trimming_polygons, false); // Extract polygons, which contain some of the m_island_samples. Polygons out; - std::vector> samples_inside; - for (ExPolygon &island : islands) { BoundingBox bbox = get_extents(island.contour); + // Samples are sorted lexicographically. auto it_lower = std::lower_bound(m_island_samples.begin(), m_island_samples.end(), bbox.min - Point(1, 1)); auto it_upper = std::upper_bound(m_island_samples.begin(), m_island_samples.end(), bbox.max + Point(1, 1)); - samples_inside.clear(); + std::vector> samples_inside; for (auto it = it_lower; it != it_upper; ++ it) if (bbox.contains(*it)) samples_inside.push_back(std::make_pair(*it, false)); @@ -577,8 +571,10 @@ class SupportGridPattern private: SupportGridPattern& operator=(const SupportGridPattern &rhs); +#if 0 // Get some internal point of an expolygon, to be used as a representative // sample to test, whether this island is inside another island. + //FIXME this was quick, but not sufficiently robust. static Point island_sample(const ExPolygon &expoly) { // Find the lowest point lexicographically. @@ -599,7 +595,10 @@ class SupportGridPattern double coef = 20. / sqrt(l2); return Point(p2.x + coef * v.x, p2.y + coef * v.y); } +#endif + // Sample one internal point per expolygon. + // FIXME this is quite an overkill to calculate a complete offset just to get a single point, but at least it is robust. static Points island_samples(const ExPolygons &expolygons) { Points pts; @@ -637,6 +636,8 @@ class SupportGridPattern coordf_t m_support_spacing; Slic3r::EdgeGrid::Grid m_grid; + // Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding + // to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons. Points m_island_samples; }; @@ -804,6 +805,10 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ ++ iRun; #endif /* SLIC3R_DEBUG */ + // Slice support enforcers / support blockers. + std::vector enforcers = object.slice_support_enforcers(); + std::vector blockers = object.slice_support_blockers(); + // Output layers, sorted by top Z. MyLayersPtr contact_out; @@ -841,10 +846,12 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers. // So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers. size_t num_layers = this->has_support() ? object.layer_count() : 1; + // For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow, + // and the other for the overhangs extruded with a normal flow. contact_out.assign(num_layers * 2, nullptr); tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(this->has_raft() ? 0 : 1, num_layers), - [this, &object, &buildplate_covered, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out](const tbb::blocked_range& range) { + [this, &object, &buildplate_covered, &enforcers, &blockers, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out](const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { const Layer &layer = *object.layers[layer_id]; @@ -855,6 +862,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ Polygons contact_polygons; Polygons slices_margin_cached; float slices_margin_cached_offset = -1.; + Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers[layer_id-1]->slices.expolygons); + // Offset of the lower layer, to trim the support polygons with to calculate dense supports. + float no_interface_offset = 0.f; if (layer_id == 0) { // This is the first object layer, so the object is being printed on a raft and // we're here just to get the object footprint for the raft. @@ -869,6 +879,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Extrusion width accounts for the roundings of the extrudates. // It is the maximum widh of the extrudate. float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); + no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); float lower_layer_offset = (layer_id < this->m_object_config->support_material_enforce_layers.value) ? // Enforce a full possible support, ignore the overhang angle. @@ -881,7 +892,6 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Overhang polygons for this layer and region. Polygons diff_polygons; Polygons layerm_polygons = to_polygons(layerm->slices); - Polygons lower_layer_polygons = to_polygons(lower_layer.slices.expolygons); if (lower_layer_offset == 0.f) { // Support everything. diff_polygons = diff(layerm_polygons, lower_layer_polygons); @@ -893,26 +903,58 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ } else { // Get the regions needing a suport, collapse very tiny spots. //FIXME cache the lower layer offset if this layer has multiple regions. +#if 1 diff_polygons = offset2( diff(layerm_polygons, - offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)), - -0.1f*fw, +0.1f*fw); + offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), + //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to + // no support at all for not so steep overhangs. + - 0.1f * fw, 0.1f * fw); +#else + diff_polygons = + diff(layerm_polygons, + offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); +#endif if (! buildplate_covered.empty()) { // Don't support overhangs above the top surfaces. // This step is done before the contact surface is calculated by growing the overhang region. diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); } - if (diff_polygons.empty()) - continue; - // Offset the support regions back to a full overhang, restrict them to the full overhang. - diff_polygons = diff( - intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), - lower_layer_polygons); + if (! diff_polygons.empty()) { + // Offset the support regions back to a full overhang, restrict them to the full overhang. + // This is done to increase size of the supporting columns below, as they are calculated by + // propagating these contact surfaces downwards. + diff_polygons = diff( + intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), + lower_layer_polygons); + } + + if (! enforcers.empty()) { + // Apply the "support enforcers". + //FIXME add the "enforcers" to the sparse support regions only. + const ExPolygons &enforcer = enforcers[layer_id - 1]; + if (! enforcer.empty()) { + // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. + Polygons new_contacts = diff(intersection(layerm_polygons, to_polygons(enforcer)), + offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (! new_contacts.empty()) { + if (diff_polygons.empty()) + diff_polygons = std::move(new_contacts); + else + diff_polygons = union_(diff_polygons, new_contacts); + } + } + } + } + // Apply the "support blockers". + if (! diff_polygons.empty() && ! blockers.empty() && ! blockers[layer_id].empty()) { + // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. + diff_polygons = diff(diff_polygons, to_polygons(blockers[layer_id])); } if (diff_polygons.empty()) continue; - #ifdef SLIC3R_DEBUG + #ifdef SLIC3R_DEBUG { ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg", iRun, layer_id, @@ -939,7 +981,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ union_ex(diff_polygons, false)); #endif /* SLIC3R_DEBUG */ - if (this->has_contact_loops()) + //FIXME the overhang_polygons are used to construct the support towers as well. + //if (this->has_contact_loops()) + // Store the exact contour of the overhang for the contact loops. polygons_append(overhang_polygons, diff_polygons); // Let's define the required contact area by using a max gap of half the upper @@ -948,12 +992,15 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // on the other side of the object (if it's very thin). { //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable. + //FIXME one should trim with the layer span colliding with the support layer, this layer + // may be lower than lower_layer, so the support area needed may need to be actually bigger! + // For the same reason, the non-bridging support area may be smaller than the bridging support area! float slices_margin_offset = std::min(lower_layer_offset, float(scale_(m_gap_xy))); if (slices_margin_cached_offset != slices_margin_offset) { slices_margin_cached_offset = slices_margin_offset; slices_margin_cached = (slices_margin_offset == 0.f) ? - to_polygons(lower_layer.slices.expolygons) : - offset(lower_layer.slices.expolygons, slices_margin_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS); + lower_layer_polygons : + offset2(to_polygons(lower_layer.slices.expolygons), - scale_(- no_interface_offset * 0.5f), slices_margin_offset + scale_(- no_interface_offset * 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! buildplate_covered.empty()) { // Trim the inflated contact surfaces by the top surfaces as well. polygons_append(slices_margin_cached, buildplate_covered[layer_id]); @@ -1028,7 +1075,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Align the layer with the 1st layer height. bridging_print_z = m_slicing_params.first_print_layer_height; } - if (bridging_print_z != new_layer.print_z) { + if (bridging_print_z < new_layer.print_z - EPSILON) { // Allocate the new layer. bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); bridging_layer->idx_object_layer_above = layer_id; @@ -1051,20 +1098,38 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ contact_polygons, // Trimming polygons, to trim the stretched support islands. slices_margin_cached, - // How much to offset the extracted contour outside of the grid. + // Grid resolution. m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); - // 1) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5); - // 2) Contact polygons will be projected down. To keep the interface and base layers to grow, return a contour a tiny bit smaller than the grid cells. + // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3)); + // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. + if (layer_id == 0) { + // if (no_interface_offset == 0.f) { + new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5); + } else { + //Polygons dense_interface_polygons = diff(overhang_polygons, offset(lower_layer_polygons, scale_(no_interface_offset * 0.7f))); + Polygons dense_interface_polygons = diff(overhang_polygons, + offset2(lower_layer_polygons, scale_(- no_interface_offset * 0.5f), scale_(no_interface_offset * (0.7f + 0.5f)), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (! dense_interface_polygons.empty()) { + SupportGridPattern support_grid_pattern( + // Support islands, to be stretched into a grid. + dense_interface_polygons, + // Trimming polygons, to trim the stretched support islands. + slices_margin_cached, + // Grid resolution. + m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), + Geometry::deg2rad(m_object_config->support_material_angle.value)); + new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5); + } + } // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. // Store the overhang polygons. // The overhang polygons are used in the path generator for planning of the contact loops. - // if (this->has_contact_loops()) + // if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug. new_layer.overhang_polygons = new Polygons(std::move(overhang_polygons)); contact_out[layer_id * 2] = &new_layer; if (bridging_layer != nullptr) { @@ -1085,11 +1150,36 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), // the top contact layer is merged into the bottom contact layer. { - int k = 0; - for (int i = 0; i < int(contact_out.size()); ++ k) { + int i = 0; + int k = 0; + { + // Find the span of layers, which are to be printed at the first layer height. + int j = 0; + for (; j < contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); + if (j > 0) { + // Merge the contact_out layers (0) to (j - 1) into the contact_out[0]. + MyLayer &dst = *contact_out.front(); + for (int u = 1; u < j; ++ u) { + MyLayer &src = *contact_out[u]; + // The union_() does not support move semantic yet, but maybe one day it will. + dst.polygons = union_(dst.polygons, std::move(src.polygons)); + *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); + *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); + // Source polygon is no more needed, it will not be refrenced. Release its data. + src.reset(); + } + // Snap the first layer to the 1st layer height. + dst.print_z = m_slicing_params.first_print_layer_height; + dst.height = m_slicing_params.first_print_layer_height; + dst.bottom_z = 0; + ++ k; + } + i = j; + } + for (; i < int(contact_out.size()); ++ k) { // Find the span of layers closer than m_support_layer_height_min. int j = i + 1; - coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min - EPSILON; + coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON; for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; if (i + 1 < j) { // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i]. @@ -1148,7 +1238,6 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta const Layer &layer = *object.get_layer(layer_id); // Collect projections of all contact areas above or at the same level as this top surface. for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { - auto *l = top_contacts[contact_idx]; Polygons polygons_new; // Contact surfaces are expanded away from the object, trimmed by the object. // Use a slight positive offset to overlap the touching regions. @@ -1156,7 +1245,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form. polygons_append(polygons_new, offset(*top_contacts[contact_idx]->contact_polygons, SCALED_EPSILON)); #else - // Consume the contact_polygons. The contact polygons are already expanded into a grid form. + // Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller + // than the grid cells. polygons_append(polygons_new, std::move(*top_contacts[contact_idx]->contact_polygons)); #endif // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. @@ -1168,9 +1258,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta continue; Polygons projection_raw = union_(projection); - // Top surfaces of this layer, to be used to stop the surface volume from growing down. tbb::task_group task_group; if (! m_object_config->support_material_buildplate_only) + // Find the bottom contact layers above the top surfaces of this layer. task_group.run([this, &object, &top_contacts, contact_idx, &layer, layer_id, &layer_storage, &layer_support_areas, &bottom_contacts, &projection_raw] { Polygons top = collect_region_slices_by_type(layer, stTop); #ifdef SLIC3R_DEBUG @@ -1206,24 +1296,26 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta // Place a bridge flow interface layer over the top surface. //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?) // According to Jindrich the bottom surfaces work well. + //FIXME test the bridging flow instead? m_support_material_interface_flow.nozzle_diameter; layer_new.print_z = m_slicing_params.soluble_interface ? object.layers[layer_id + 1]->print_z : layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value; layer_new.bottom_z = layer.print_z; layer_new.idx_object_layer_below = layer_id; layer_new.bridging = ! m_slicing_params.soluble_interface; - //FIXME how much to inflate the top surface? + //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. + //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! m_slicing_params.soluble_interface) { // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min. for (size_t top_idx = size_t(std::max(0, contact_idx)); - top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min; + top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min + EPSILON; ++ top_idx) { - if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min) { + if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min - EPSILON) { // A top layer has been found, which is close to the new bottom layer. coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z; - assert(std::abs(diff) <= this->m_support_layer_height_min); + assert(std::abs(diff) <= this->m_support_layer_height_min + EPSILON); if (diff > 0.) { // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer. assert(diff < layer_new.height + EPSILON); @@ -1247,10 +1339,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta union_ex(layer_new.polygons, false)); #endif /* SLIC3R_DEBUG */ // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. + //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? touching = offset(touching, float(SCALED_EPSILON)); for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { const Layer &layer_above = *object.layers[layer_id_above]; - if (layer_above.print_z > layer_new.print_z + EPSILON) + if (layer_above.print_z > layer_new.print_z - EPSILON) break; if (! layer_support_areas[layer_id_above].empty()) { #ifdef SLIC3R_DEBUG @@ -1303,7 +1396,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta projection, // Trimming polygons, to trim the stretched support islands. trimming, - // How much to offset the extracted contour outside of the grid. + // Grid spacing. m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); tbb::task_group task_group_inner; @@ -1341,7 +1434,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta task_group.wait(); } std::reverse(bottom_contacts.begin(), bottom_contacts.end()); - trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy); +// trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy); + trim_support_layers_by_object(object, bottom_contacts, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); + } // ! top_contacts.empty() return bottom_contacts; @@ -1658,9 +1755,6 @@ void PrintObjectSupportMaterial::generate_base_layers( assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z); // Find a top_contact layer touching the layer_intermediate from above, if any, and collect its polygons into polygons_new. - idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, - [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); - // New polygons for layer_intermediate. Polygons polygons_new; @@ -1679,12 +1773,10 @@ void PrintObjectSupportMaterial::generate_base_layers( // 3) base.print_z > top.print_z && base.bottom_z >= top.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the base layer height where it overlaps the top layer. No trimming needed here. // 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen. // 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top. - int idx_top_contact_overlapping = idx_top_contact_above; - while (idx_top_contact_overlapping >= 0 && - top_contacts[idx_top_contact_overlapping]->bottom_z > layer_intermediate.print_z - EPSILON) - -- idx_top_contact_overlapping; + idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, + [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); // Collect all the top_contact layer intersecting with this layer. - for (; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { + for ( int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON) break; @@ -1764,7 +1856,10 @@ void PrintObjectSupportMaterial::generate_base_layers( ++ iRun; #endif /* SLIC3R_DEBUG */ - trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy); +// trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy); + this->trim_support_layers_by_object(object, intermediate_layers, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); } void PrintObjectSupportMaterial::trim_support_layers_by_object( @@ -1809,7 +1904,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( const Layer &object_layer = *object.layers[i]; if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON) break; - polygons_append(polygons_trimming, (Polygons)object_layer.slices); + polygons_append(polygons_trimming, offset(object_layer.slices.expolygons, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); } if (! this->m_slicing_params.soluble_interface) { // Collect all bottom surfaces, which will be extruded with a bridging flow. @@ -1958,11 +2053,12 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_int coordf_t top_z = intermediate_layers[std::min(intermediate_layers.size()-1, idx_intermediate_layer + m_object_config->support_material_interface_layers - 1)]->print_z; coordf_t bottom_z = intermediate_layers[std::max(0, int(idx_intermediate_layer) - int(m_object_config->support_material_interface_layers) + 1)]->bottom_z; // Move idx_top_contact_first up until above the current print_z. - idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); + idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON // Collect the top contact areas above this intermediate layer, below top_z. Polygons polygons_top_contact_projected; for (size_t idx_top_contact = idx_top_contact_first; idx_top_contact < top_contacts.size(); ++ idx_top_contact) { const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; + //FIXME maybe this adds one interface layer in excess? if (top_contact_layer.bottom_z - EPSILON > top_z) break; polygons_append(polygons_top_contact_projected, top_contact_layer.polygons); @@ -2517,7 +2613,7 @@ void modulate_extrusion_by_overlapping_layers( (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); } private: - ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&); + ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {} const std::vector &m_path_fragments; }; const coord_t search_radius = 7; diff --git a/xs/src/libslic3r/SupportMaterial.hpp b/xs/src/libslic3r/SupportMaterial.hpp index da11cd82c35..dcb3bd5b3ab 100644 --- a/xs/src/libslic3r/SupportMaterial.hpp +++ b/xs/src/libslic3r/SupportMaterial.hpp @@ -12,6 +12,7 @@ class PrintConfig; class PrintObjectConfig; // how much we extend support around the actual contact area +//FIXME this should be dependent on the nozzle diameter! #define SUPPORT_MATERIAL_MARGIN 1.5 // This class manages raft and supports for a single PrintObject. diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 7053470943f..51e749a58c5 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -622,7 +622,7 @@ std::vector GLVolumeCollection::load_object( const ModelVolume *model_volume = model_object->volumes[volume_idx]; int extruder_id = -1; - if (!model_volume->modifier) + if (model_volume->is_model_part()) { extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0; if (extruder_id == 0) @@ -635,7 +635,16 @@ std::vector GLVolumeCollection::load_object( volumes_idx.push_back(int(this->volumes.size())); float color[4]; memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); - color[3] = model_volume->modifier ? 0.5f : 1.f; + if (model_volume->is_support_blocker()) { + color[0] = 1.0f; + color[1] = 0.2f; + color[2] = 0.2f; + } else if (model_volume->is_support_enforcer()) { + color[0] = 0.2f; + color[1] = 0.2f; + color[2] = 1.0f; + } + color[3] = model_volume->is_model_part() ? 1.f : 0.5f; this->volumes.emplace_back(new GLVolume(color)); GLVolume &v = *this->volumes.back(); if (use_VBOs) @@ -658,15 +667,15 @@ std::vector GLVolumeCollection::load_object( else if (drag_by == "instance") v.drag_group_id = obj_idx * 1000 + instance_idx; - if (!model_volume->modifier) + if (model_volume->is_model_part()) { v.set_convex_hull(model_volume->get_convex_hull()); v.layer_height_texture = layer_height_texture; if (extruder_id != -1) v.extruder_id = extruder_id; } - v.is_modifier = model_volume->modifier; - v.shader_outside_printer_detection_enabled = !model_volume->modifier; + v.is_modifier = ! model_volume->is_model_part(); + v.shader_outside_printer_detection_enabled = model_volume->is_model_part(); v.set_origin(Pointf3(instance->offset.x, instance->offset.y, 0.0)); v.set_angle_z(instance->rotation); v.set_scale_factor(instance->scaling_factor); diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 1829632572c..0f9b5cd1599 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -340,9 +340,19 @@ ModelMaterial::attributes() %code%{ RETVAL = &THIS->mesh; %}; bool modifier() - %code%{ RETVAL = THIS->modifier; %}; + %code%{ RETVAL = THIS->is_modifier(); %}; void set_modifier(bool modifier) - %code%{ THIS->modifier = modifier; %}; + %code%{ THIS->set_type(modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); %}; + bool model_part() + %code%{ RETVAL = THIS->is_model_part(); %}; + bool support_enforcer() + %code%{ RETVAL = THIS->is_support_enforcer(); %}; + void set_support_enforcer() + %code%{ THIS->set_type(ModelVolume::SUPPORT_ENFORCER); %}; + bool support_blocker() + %code%{ RETVAL = THIS->is_support_blocker(); %}; + void set_support_blocker() + %code%{ THIS->set_type(ModelVolume::SUPPORT_BLOCKER); %}; size_t split(unsigned int max_extruders); From 6960958276457fe5fbcee0ad10fb1d3c991a41fd Mon Sep 17 00:00:00 2001 From: Martin Loidl Date: Thu, 6 Sep 2018 19:07:54 +0200 Subject: [PATCH 24/63] minor changes for url_encode --- xs/src/slic3r/Utils/Duet.cpp | 1 - xs/src/slic3r/Utils/Http.cpp | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp index a3dc4a3bb90..f253271611a 100644 --- a/xs/src/slic3r/Utils/Duet.cpp +++ b/xs/src/slic3r/Utils/Duet.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp index 3024b08fe05..9b67ceea870 100644 --- a/xs/src/slic3r/Utils/Http.cpp +++ b/xs/src/slic3r/Utils/Http.cpp @@ -424,11 +424,14 @@ bool Http::ca_file_supported() std::string Http::url_encode(const std::string &str) { ::CURL *curl = ::curl_easy_init(); + if (curl == nullptr) { + return str; + } char *ce = ::curl_easy_escape(curl, str.c_str(), str.length()); std::string encoded = std::string(ce); ::curl_free(ce); - if (curl != nullptr) { ::curl_easy_cleanup(curl); } + ::curl_easy_cleanup(curl); return encoded; } From 961d8942187dcda70d87b358bceb6cf0a967a6b7 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 7 Sep 2018 08:43:21 +0200 Subject: [PATCH 25/63] Added a Layout call for 'Sliced Info' box to show sliders when needed --- lib/Slic3r/GUI/Plater.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 7c9e9899e0e..56c21bbbb00 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1687,6 +1687,7 @@ sub print_info_box_show { $scrolled_window_sizer->Show(2, $show); $scrolled_window_panel->Layout; + $self->Layout; } sub do_print { From 125ece0aadb51814473787a33852cf94a48a53fd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 7 Sep 2018 09:33:18 +0200 Subject: [PATCH 26/63] Fixed #1204 --- lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index 3b10ed99f77..ea4ce71329c 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -136,7 +136,7 @@ sub update_optgroup { full_labels => 1, label_font => $Slic3r::GUI::small_font, sidetext_font => $Slic3r::GUI::small_font, - label_width => 120, + label_width => 150, on_change => sub { $self->{on_change}->() if $self->{on_change} }, extra_column => sub { my ($line) = @_; From ec3e1403b645928f0c19ae225b9924848b8aa6bb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 7 Sep 2018 10:24:05 +0200 Subject: [PATCH 27/63] Cleaning up and fixing localization issues with AppController. --- xs/CMakeLists.txt | 3 +- xs/src/slic3r/AppController.cpp | 211 +----------------- xs/src/slic3r/AppController.hpp | 52 ++--- xs/src/slic3r/AppControllerWx.cpp | 73 +++--- ...essIndicator.hpp => ProgressIndicator.hpp} | 33 ++- xs/src/slic3r/Strings.hpp | 10 - xs/xsp/AppController.xsp | 3 - 7 files changed, 72 insertions(+), 313 deletions(-) rename xs/src/slic3r/{IProgressIndicator.hpp => ProgressIndicator.hpp} (62%) delete mode 100644 xs/src/slic3r/Strings.hpp diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index c4d3492a316..efd52ac1371 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -267,11 +267,10 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/Utils/Time.hpp ${LIBDIR}/slic3r/Utils/HexFile.cpp ${LIBDIR}/slic3r/Utils/HexFile.hpp - ${LIBDIR}/slic3r/IProgressIndicator.hpp + ${LIBDIR}/slic3r/ProgressIndicator.hpp ${LIBDIR}/slic3r/AppController.hpp ${LIBDIR}/slic3r/AppController.cpp ${LIBDIR}/slic3r/AppControllerWx.cpp - ${LIBDIR}/slic3r/Strings.hpp ) add_library(admesh STATIC diff --git a/xs/src/slic3r/AppController.cpp b/xs/src/slic3r/AppController.cpp index 9394df3637c..7bd044b25d2 100644 --- a/xs/src/slic3r/AppController.cpp +++ b/xs/src/slic3r/AppController.cpp @@ -43,15 +43,6 @@ namespace GUI { PresetBundle* get_preset_bundle(); } -static const PrintObjectStep STEP_SLICE = posSlice; -static const PrintObjectStep STEP_PERIMETERS = posPerimeters; -static const PrintObjectStep STEP_PREPARE_INFILL = posPrepareInfill; -static const PrintObjectStep STEP_INFILL = posInfill; -static const PrintObjectStep STEP_SUPPORTMATERIAL = posSupportMaterial; -static const PrintStep STEP_SKIRT = psSkirt; -static const PrintStep STEP_BRIM = psBrim; -static const PrintStep STEP_WIPE_TOWER = psWipeTower; - AppControllerBoilerplate::ProgresIndicatorPtr AppControllerBoilerplate::global_progress_indicator() { ProgresIndicatorPtr ret; @@ -71,193 +62,8 @@ void AppControllerBoilerplate::global_progress_indicator( pri_data_->m.unlock(); } -void PrintController::make_skirt() -{ - assert(print_ != nullptr); - - // prerequisites - for(auto obj : print_->objects) make_perimeters(obj); - for(auto obj : print_->objects) infill(obj); - for(auto obj : print_->objects) gen_support_material(obj); - - if(!print_->state.is_done(STEP_SKIRT)) { - print_->state.set_started(STEP_SKIRT); - print_->skirt.clear(); - if(print_->has_skirt()) print_->_make_skirt(); - - print_->state.set_done(STEP_SKIRT); - } -} - -void PrintController::make_brim() -{ - assert(print_ != nullptr); - - // prerequisites - for(auto obj : print_->objects) make_perimeters(obj); - for(auto obj : print_->objects) infill(obj); - for(auto obj : print_->objects) gen_support_material(obj); - make_skirt(); - - if(!print_->state.is_done(STEP_BRIM)) { - print_->state.set_started(STEP_BRIM); - - // since this method must be idempotent, we clear brim paths *before* - // checking whether we need to generate them - print_->brim.clear(); - - if(print_->config.brim_width > 0) print_->_make_brim(); - - print_->state.set_done(STEP_BRIM); - } -} - -void PrintController::make_wipe_tower() -{ - assert(print_ != nullptr); - - // prerequisites - for(auto obj : print_->objects) make_perimeters(obj); - for(auto obj : print_->objects) infill(obj); - for(auto obj : print_->objects) gen_support_material(obj); - make_skirt(); - make_brim(); - - if(!print_->state.is_done(STEP_WIPE_TOWER)) { - print_->state.set_started(STEP_WIPE_TOWER); - - // since this method must be idempotent, we clear brim paths *before* - // checking whether we need to generate them - print_->brim.clear(); - - if(print_->has_wipe_tower()) print_->_make_wipe_tower(); - - print_->state.set_done(STEP_WIPE_TOWER); - } -} - -void PrintController::slice(PrintObject *pobj) -{ - assert(pobj != nullptr && print_ != nullptr); - - if(pobj->state.is_done(STEP_SLICE)) return; - - pobj->state.set_started(STEP_SLICE); - - pobj->_slice(); - - auto msg = pobj->_fix_slicing_errors(); - if(!msg.empty()) report_issue(IssueType::WARN, msg); - - // simplify slices if required - if (print_->config.resolution) - pobj->_simplify_slices(scale_(print_->config.resolution)); - - - if(pobj->layers.empty()) - report_issue(IssueType::ERR, - _(L("No layers were detected. You might want to repair your " - "STL file(s) or check their size or thickness and retry")) - ); - - pobj->state.set_done(STEP_SLICE); -} - -void PrintController::make_perimeters(PrintObject *pobj) -{ - assert(pobj != nullptr); - - slice(pobj); - - if (!pobj->state.is_done(STEP_PERIMETERS)) { - pobj->_make_perimeters(); - } -} - -void PrintController::infill(PrintObject *pobj) -{ - assert(pobj != nullptr); - - make_perimeters(pobj); - - if (!pobj->state.is_done(STEP_PREPARE_INFILL)) { - pobj->state.set_started(STEP_PREPARE_INFILL); - - pobj->_prepare_infill(); - - pobj->state.set_done(STEP_PREPARE_INFILL); - } - - pobj->_infill(); -} - -void PrintController::gen_support_material(PrintObject *pobj) -{ - assert(pobj != nullptr); - - // prerequisites - slice(pobj); - - if(!pobj->state.is_done(STEP_SUPPORTMATERIAL)) { - pobj->state.set_started(STEP_SUPPORTMATERIAL); - - pobj->clear_support_layers(); - - if((pobj->config.support_material || pobj->config.raft_layers > 0) - && pobj->layers.size() > 1) { - pobj->_generate_support_material(); - } - - pobj->state.set_done(STEP_SUPPORTMATERIAL); - } -} - -void PrintController::slice(AppControllerBoilerplate::ProgresIndicatorPtr pri) -{ - auto st = pri->state(); - - Slic3r::trace(3, "Starting the slicing process."); - - pri->update(st+20, _(L("Generating perimeters"))); - for(auto obj : print_->objects) make_perimeters(obj); - - pri->update(st+60, _(L("Infilling layers"))); - for(auto obj : print_->objects) infill(obj); - - pri->update(st+70, _(L("Generating support material"))); - for(auto obj : print_->objects) gen_support_material(obj); - - pri->message_fmt(_(L("Weight: %.1fg, Cost: %.1f")), - print_->total_weight, print_->total_cost); - pri->state(st+85); - - - pri->update(st+88, _(L("Generating skirt"))); - make_skirt(); - - - pri->update(st+90, _(L("Generating brim"))); - make_brim(); - - pri->update(st+95, _(L("Generating wipe tower"))); - make_wipe_tower(); - - pri->update(st+100, _(L("Done"))); - - // time to make some statistics.. - - Slic3r::trace(3, _(L("Slicing process finished."))); -} - -void PrintController::slice() -{ - auto pri = global_progress_indicator(); - if(!pri) pri = create_progress_indicator(100, L("Slicing")); - slice(pri); -} - -void IProgressIndicator::message_fmt( - const string &fmtstr, ...) { +void ProgressIndicator::message_fmt( + const std::string &fmtstr, ...) { std::stringstream ss; va_list args; va_start(args, fmtstr); @@ -288,7 +94,6 @@ const PrintConfig &PrintController::config() const return print_->config; } - void AppController::arrange_model() { auto ftr = std::async( @@ -322,7 +127,7 @@ void AppController::arrange_model() for(auto& v : bedpoints) bed.append(Point::new_scale(v.x, v.y)); - if(pind) pind->update(0, _(L("Arranging objects..."))); + if(pind) pind->update(0, L("Arranging objects...")); try { arr::BedShapeHint hint; @@ -336,20 +141,20 @@ void AppController::arrange_model() false, // create many piles not just one pile [pind, count](unsigned rem) { if(pind) - pind->update(count - rem, _(L("Arranging objects..."))); + pind->update(count - rem, L("Arranging objects...")); }); } catch(std::exception& e) { std::cerr << e.what() << std::endl; report_issue(IssueType::ERR, - _(L("Could not arrange model objects! " - "Some geometries may be invalid.")), - _(L("Exception occurred"))); + L("Could not arrange model objects! " + "Some geometries may be invalid."), + L("Exception occurred")); } // Restore previous max value if(pind) { pind->max(pmax); - pind->update(0, _(L("Arranging done."))); + pind->update(0, L("Arranging done.")); } }); diff --git a/xs/src/slic3r/AppController.hpp b/xs/src/slic3r/AppController.hpp index e9252b50cbe..3ef47ffdc5d 100644 --- a/xs/src/slic3r/AppController.hpp +++ b/xs/src/slic3r/AppController.hpp @@ -7,7 +7,7 @@ #include #include -#include "IProgressIndicator.hpp" +#include "ProgressIndicator.hpp" namespace Slic3r { @@ -15,7 +15,8 @@ class Model; class Print; class PrintObject; class PrintConfig; - +class ProgressStatusBar; +class DynamicPrintConfig; /** * @brief A boilerplate class for creating application logic. It should provide @@ -33,7 +34,7 @@ class AppControllerBoilerplate { public: /// A Progress indicator object smart pointer - using ProgresIndicatorPtr = std::shared_ptr; + using ProgresIndicatorPtr = std::shared_ptr; private: class PriData; // Some structure to store progress indication data @@ -46,7 +47,7 @@ class AppControllerBoilerplate { AppControllerBoilerplate(); ~AppControllerBoilerplate(); - using Path = string; + using Path = std::string; using PathList = std::vector; /// Common runtime issue types @@ -67,20 +68,20 @@ class AppControllerBoilerplate { * @return Returns a list of paths choosed by the user. */ PathList query_destination_paths( - const string& title, + const std::string& title, const std::string& extensions) const; /** * @brief Same as query_destination_paths but works for directories only. */ PathList query_destination_dirs( - const string& title) const; + const std::string& title) const; /** * @brief Same as query_destination_paths but returns only one path. */ Path query_destination_path( - const string& title, + const std::string& title, const std::string& extensions, const std::string& hint = "") const; @@ -95,11 +96,11 @@ class AppControllerBoilerplate { * title. */ bool report_issue(IssueType issuetype, - const string& description, - const string& brief); + const std::string& description, + const std::string& brief); bool report_issue(IssueType issuetype, - const string& description); + const std::string& description); /** * @brief Return the global progress indicator for the current controller. @@ -150,12 +151,12 @@ class AppControllerBoilerplate { */ ProgresIndicatorPtr create_progress_indicator( unsigned statenum, - const string& title, - const string& firstmsg) const; + const std::string& title, + const std::string& firstmsg) const; ProgresIndicatorPtr create_progress_indicator( unsigned statenum, - const string& title) const; + const std::string& title) const; // This is a global progress indicator placeholder. In the Slic3r UI it can // contain the progress indicator on the statusbar. @@ -167,24 +168,6 @@ class AppControllerBoilerplate { */ class PrintController: public AppControllerBoilerplate { Print *print_ = nullptr; -protected: - - void make_skirt(); - void make_brim(); - void make_wipe_tower(); - - void make_perimeters(PrintObject *pobj); - void infill(PrintObject *pobj); - void gen_support_material(PrintObject *pobj); - - /** - * @brief Slice one pront object. - * @param pobj The print object. - */ - void slice(PrintObject *pobj); - - void slice(ProgresIndicatorPtr pri); - public: // Must be public for perl to use it @@ -199,11 +182,6 @@ class PrintController: public AppControllerBoilerplate { return PrintController::Ptr( new PrintController(print) ); } - /** - * @brief Slice the loaded print scene. - */ - void slice(); - const PrintConfig& config() const; }; @@ -248,7 +226,7 @@ class AppController: public AppControllerBoilerplate { * In perl we have a progress indicating status bar on the bottom of the * window which is defined and created in perl. We can pass the ID-s of the * gauge and the statusbar id and make a wrapper implementation of the - * IProgressIndicator interface so we can use this GUI widget from C++. + * ProgressIndicator interface so we can use this GUI widget from C++. * * This function should be called from perl. * diff --git a/xs/src/slic3r/AppControllerWx.cpp b/xs/src/slic3r/AppControllerWx.cpp index 4e116c7b911..36a465919f3 100644 --- a/xs/src/slic3r/AppControllerWx.cpp +++ b/xs/src/slic3r/AppControllerWx.cpp @@ -32,11 +32,11 @@ void AppControllerBoilerplate::process_events() AppControllerBoilerplate::PathList AppControllerBoilerplate::query_destination_paths( - const string &title, + const std::string &title, const std::string &extensions) const { - wxFileDialog dlg(wxTheApp->GetTopWindow(), title ); + wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); dlg.SetWildcard(extensions); dlg.ShowModal(); @@ -52,11 +52,11 @@ AppControllerBoilerplate::query_destination_paths( AppControllerBoilerplate::Path AppControllerBoilerplate::query_destination_path( - const string &title, + const std::string &title, const std::string &extensions, const std::string& hint) const { - wxFileDialog dlg(wxTheApp->GetTopWindow(), title ); + wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); dlg.SetWildcard(extensions); dlg.SetFilename(hint); @@ -71,8 +71,8 @@ AppControllerBoilerplate::query_destination_path( } bool AppControllerBoilerplate::report_issue(IssueType issuetype, - const string &description, - const string &brief) + const std::string &description, + const std::string &brief) { auto icon = wxICON_INFORMATION; auto style = wxOK|wxCENTRE; @@ -84,15 +84,15 @@ bool AppControllerBoilerplate::report_issue(IssueType issuetype, case IssueType::FATAL: icon = wxICON_ERROR; } - auto ret = wxMessageBox(description, brief, icon | style); + auto ret = wxMessageBox(_(description), _(brief), icon | style); return ret != wxCANCEL; } bool AppControllerBoilerplate::report_issue( AppControllerBoilerplate::IssueType issuetype, - const string &description) + const std::string &description) { - return report_issue(issuetype, description, string()); + return report_issue(issuetype, description, std::string()); } wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent); @@ -104,10 +104,10 @@ namespace { * the main thread as well. */ class GuiProgressIndicator: - public IProgressIndicator, public wxEvtHandler { + public ProgressIndicator, public wxEvtHandler { wxProgressDialog gauge_; - using Base = IProgressIndicator; + using Base = ProgressIndicator; wxString message_; int range_; wxString title_; bool is_asynch_ = false; @@ -136,8 +136,8 @@ class GuiProgressIndicator: /// Get the mode of parallel operation. inline bool asynch() const { return is_asynch_; } - inline GuiProgressIndicator(int range, const string& title, - const string& firstmsg) : + inline GuiProgressIndicator(int range, const wxString& title, + const wxString& firstmsg) : gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(), wxPD_APP_MODAL | wxPD_AUTO_HIDE), message_(firstmsg), @@ -151,11 +151,6 @@ class GuiProgressIndicator: this, id_); } - virtual void cancel() override { - update(max(), "Abort"); - IProgressIndicator::cancel(); - } - virtual void state(float val) override { state(static_cast(val)); } @@ -170,26 +165,28 @@ class GuiProgressIndicator: } else _state(st); } - virtual void message(const string & msg) override { - message_ = msg; + virtual void message(const std::string & msg) override { + message_ = _(msg); } - virtual void messageFmt(const string& fmt, ...) { + virtual void messageFmt(const std::string& fmt, ...) { va_list arglist; va_start(arglist, fmt); - message_ = wxString::Format(wxString(fmt), arglist); + message_ = wxString::Format(_(fmt), arglist); va_end(arglist); } - virtual void title(const string & title) override { - title_ = title; + virtual void title(const std::string & title) override { + title_ = _(title); } }; } AppControllerBoilerplate::ProgresIndicatorPtr AppControllerBoilerplate::create_progress_indicator( - unsigned statenum, const string& title, const string& firstmsg) const + unsigned statenum, + const std::string& title, + const std::string& firstmsg) const { auto pri = std::make_shared(statenum, title, firstmsg); @@ -202,20 +199,20 @@ AppControllerBoilerplate::create_progress_indicator( } AppControllerBoilerplate::ProgresIndicatorPtr -AppControllerBoilerplate::create_progress_indicator(unsigned statenum, - const string &title) const +AppControllerBoilerplate::create_progress_indicator( + unsigned statenum, const std::string &title) const { - return create_progress_indicator(statenum, title, string()); + return create_progress_indicator(statenum, title, std::string()); } namespace { // A wrapper progress indicator class around the statusbar created in perl. -class Wrapper: public IProgressIndicator, public wxEvtHandler { +class Wrapper: public ProgressIndicator, public wxEvtHandler { wxGauge *gauge_; wxStatusBar *stbar_; - using Base = IProgressIndicator; - std::string message_; + using Base = ProgressIndicator; + wxString message_; AppControllerBoilerplate& ctl_; void showProgress(bool show = true) { @@ -223,7 +220,7 @@ class Wrapper: public IProgressIndicator, public wxEvtHandler { } void _state(unsigned st) { - if( st <= IProgressIndicator::max() ) { + if( st <= ProgressIndicator::max() ) { Base::state(st); if(!gauge_->IsShown()) showProgress(true); @@ -266,7 +263,7 @@ class Wrapper: public IProgressIndicator, public wxEvtHandler { virtual void max(float val) override { if(val > 1.0) { gauge_->SetRange(static_cast(val)); - IProgressIndicator::max(val); + ProgressIndicator::max(val); } } @@ -280,18 +277,18 @@ class Wrapper: public IProgressIndicator, public wxEvtHandler { } } - virtual void message(const string & msg) override { - message_ = msg; + virtual void message(const std::string & msg) override { + message_ = _(msg); } - virtual void message_fmt(const string& fmt, ...) override { + virtual void message_fmt(const std::string& fmt, ...) override { va_list arglist; va_start(arglist, fmt); - message_ = wxString::Format(fmt, arglist); + message_ = wxString::Format(_(fmt), arglist); va_end(arglist); } - virtual void title(const string & /*title*/) override {} + virtual void title(const std::string & /*title*/) override {} }; } diff --git a/xs/src/slic3r/IProgressIndicator.hpp b/xs/src/slic3r/ProgressIndicator.hpp similarity index 62% rename from xs/src/slic3r/IProgressIndicator.hpp rename to xs/src/slic3r/ProgressIndicator.hpp index 70493157418..1b064077e86 100644 --- a/xs/src/slic3r/IProgressIndicator.hpp +++ b/xs/src/slic3r/ProgressIndicator.hpp @@ -3,16 +3,15 @@ #include #include -#include "Strings.hpp" namespace Slic3r { /** * @brief Generic progress indication interface. */ -class IProgressIndicator { +class ProgressIndicator { public: - using CancelFn = std::function; // Cancel functio signature. + using CancelFn = std::function; // Cancel function signature. private: float state_ = .0f, max_ = 1.f, step_; @@ -20,7 +19,7 @@ class IProgressIndicator { public: - inline virtual ~IProgressIndicator() {} + inline virtual ~ProgressIndicator() {} /// Get the maximum of the progress range. float max() const { return max_; } @@ -28,14 +27,14 @@ class IProgressIndicator { /// Get the current progress state float state() const { return state_; } - /// Set the maximum of hte progress range + /// Set the maximum of the progress range virtual void max(float maxval) { max_ = maxval; } /// Set the current state of the progress. virtual void state(float val) { state_ = val; } /** - * @brief Number of states int the progress. Can be used insted of giving a + * @brief Number of states int the progress. Can be used instead of giving a * maximum value. */ virtual void states(unsigned statenum) { @@ -43,25 +42,19 @@ class IProgressIndicator { } /// Message shown on the next status update. - virtual void message(const string&) = 0; + virtual void message(const std::string&) = 0; - /// Title of the operaton. - virtual void title(const string&) = 0; + /// Title of the operation. + virtual void title(const std::string&) = 0; - /// Formatted message for the next status update. Works just like sprinf. - virtual void message_fmt(const string& fmt, ...); + /// Formatted message for the next status update. Works just like sprintf. + virtual void message_fmt(const std::string& fmt, ...); /// Set up a cancel callback for the operation if feasible. - inline void on_cancel(CancelFn func) { cancelfunc_ = func; } + virtual void on_cancel(CancelFn func = CancelFn()) { cancelfunc_ = func; } - /** - * Explicitly shut down the progress indicator and call the associated - * callback. - */ - virtual void cancel() { cancelfunc_(); } - - /// Convinience function to call message and status update in one function. - void update(float st, const string& msg) { + /// Convenience function to call message and status update in one function. + void update(float st, const std::string& msg) { message(msg); state(st); } }; diff --git a/xs/src/slic3r/Strings.hpp b/xs/src/slic3r/Strings.hpp deleted file mode 100644 index b267fe0641f..00000000000 --- a/xs/src/slic3r/Strings.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef STRINGS_HPP -#define STRINGS_HPP - -#include "GUI/GUI.hpp" - -namespace Slic3r { -using string = wxString; -} - -#endif // STRINGS_HPP diff --git a/xs/xsp/AppController.xsp b/xs/xsp/AppController.xsp index 1b653081dfa..a578fe0b179 100644 --- a/xs/xsp/AppController.xsp +++ b/xs/xsp/AppController.xsp @@ -8,10 +8,7 @@ %} %name{Slic3r::PrintController} class PrintController { - PrintController(Print *print); - - void slice(); }; %name{Slic3r::AppController} class AppController { From e04805eaa2593a7c8a5c521c33a3c1adc05ff976 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 7 Sep 2018 10:30:13 +0200 Subject: [PATCH 28/63] Fixed rendering of endcaps on open toolpaths --- xs/src/slic3r/GUI/3DScene.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 7053470943f..9cf55513c87 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -1182,7 +1182,7 @@ static void thick_lines_to_indexed_vertex_array( b1_prev = b1; v_prev = v; - if (bottom_z_different) + if (bottom_z_different && (closed || (!is_first && !is_last))) { // Found a change of the layer thickness -> Add a cap at the beginning of this segment. volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]); @@ -1190,10 +1190,10 @@ static void thick_lines_to_indexed_vertex_array( if (! closed) { // Terminate open paths with caps. - if (is_first && !bottom_z_different) + if (is_first) volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]); // We don't use 'else' because both cases are true if we have only one line. - if (is_last && !bottom_z_different) + if (is_last) volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]); } From 1acee8900695b7060f2ee136b4da8e3778b407cb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 7 Sep 2018 12:03:49 +0200 Subject: [PATCH 29/63] Refinements for small item arrangement using the increased cpu power. --- xs/src/libslic3r/ModelArrange.hpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 618230cb7ea..b1ccf4d13f2 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -130,6 +130,7 @@ objfunc(const PointImpl& bincenter, double norm, // A norming factor for physical dimensions // a spatial index to quickly get neighbors of the candidate item const SpatIndex& spatindex, + const SpatIndex& smalls_spatindex, const ItemGroup& remaining ) { @@ -161,7 +162,7 @@ objfunc(const PointImpl& bincenter, // Will hold the resulting score double score = 0; - if(isBig(item.area())) { + if(isBig(item.area()) || spatindex.empty()) { // This branch is for the bigger items.. auto minc = ibb.minCorner(); // bottom left corner @@ -183,6 +184,8 @@ objfunc(const PointImpl& bincenter, // The smalles distance from the arranged pile center: auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; + auto bindist = pl::distance(ibb.center(), bincenter) / norm; + dist = 0.8*dist + 0.2*bindist; // Density is the pack density: how big is the arranged pile double density = 0; @@ -207,14 +210,20 @@ objfunc(const PointImpl& bincenter, // candidate to be aligned with only one item. auto alignment_score = 1.0; - density = (fullbb.width()*fullbb.height()) / (norm*norm); + density = std::sqrt((fullbb.width() / norm )* + (fullbb.height() / norm)); auto querybb = item.boundingBox(); // Query the spatial index for the neighbors std::vector result; result.reserve(spatindex.size()); - spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); + if(isBig(item.area())) { + spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + } else { + smalls_spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + } for(auto& e : result) { // now get the score for the best alignment auto idx = e.second; @@ -235,12 +244,8 @@ objfunc(const PointImpl& bincenter, if(result.empty()) score = 0.5 * dist + 0.5 * density; else - score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; + score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; } - } else if( !isBig(item.area()) && spatindex.empty()) { - auto bindist = pl::distance(ibb.center(), bincenter) / norm; - // Bindist is surprisingly enough... - score = bindist; } else { // Here there are the small items that should be placed around the // already processed bigger items. @@ -291,6 +296,7 @@ class _ArrBase { PConfig pconf_; // Placement configuration double bin_area_; SpatIndex rtree_; + SpatIndex smallsrtree_; double norm_; Pile merged_pile_; Box pilebb_; @@ -317,6 +323,7 @@ class _ArrBase { pilebb_ = sl::boundingBox(merged_pile); rtree_.clear(); + smallsrtree_.clear(); // We will treat big items (compared to the print bed) differently auto isBig = [this](double a) { @@ -326,6 +333,7 @@ class _ArrBase { for(unsigned idx = 0; idx < items.size(); ++idx) { Item& itm = items[idx]; if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx}); + smallsrtree_.insert({itm.boundingBox(), idx}); } }; @@ -357,6 +365,7 @@ class AutoArranger: public _ArrBase { bin_area_, norm_, rtree_, + smallsrtree_, remaining_); double score = std::get<0>(result); @@ -393,6 +402,7 @@ class AutoArranger: public _ArrBase { bin_area_, norm_, rtree_, + smallsrtree_, remaining_); double score = std::get<0>(result); @@ -435,6 +445,7 @@ class AutoArranger: public _ArrBase { bin_area_, norm_, rtree_, + smallsrtree_, remaining_); double score = std::get<0>(result); @@ -462,6 +473,7 @@ class AutoArranger: public _ArrBase { 0, norm_, rtree_, + smallsrtree_, remaining_); return std::get<0>(result); }; From 6de8e211317578e71398170fd0b4d215d9cc2781 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 7 Sep 2018 14:53:42 +0200 Subject: [PATCH 30/63] New support settings: "support_material_auto" controls, whether the supports shall be generated automatically. If not, only supports inside support enforcers are generated. --- xs/src/libslic3r/EdgeGrid.cpp | 4 +- xs/src/libslic3r/EdgeGrid.hpp | 2 +- xs/src/libslic3r/PrintConfig.cpp | 8 +++ xs/src/libslic3r/PrintConfig.hpp | 3 ++ xs/src/libslic3r/PrintObject.cpp | 1 + xs/src/libslic3r/SupportMaterial.cpp | 81 +++++++++++++++------------- xs/src/slic3r/GUI/Preset.cpp | 2 +- xs/src/slic3r/GUI/Tab.cpp | 5 +- 8 files changed, 63 insertions(+), 43 deletions(-) diff --git a/xs/src/libslic3r/EdgeGrid.cpp b/xs/src/libslic3r/EdgeGrid.cpp index 804b526c7f9..801a27e3a51 100644 --- a/xs/src/libslic3r/EdgeGrid.cpp +++ b/xs/src/libslic3r/EdgeGrid.cpp @@ -1225,7 +1225,7 @@ bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coo return true; } -Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const +Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) const { assert(std::abs(2 * offset) < m_resolution); @@ -1239,7 +1239,7 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1); // Fill in empty cells, which have a left / right neighbor filled. // Fill in empty cells, which have the top / bottom neighbor filled. - { + if (fill_holes) { std::vector cell_inside2(cell_inside); for (int r = 1; r + 1 < int(cell_rows); ++ r) { for (int c = 1; c + 1 < int(cell_cols); ++ c) { diff --git a/xs/src/libslic3r/EdgeGrid.hpp b/xs/src/libslic3r/EdgeGrid.hpp index 3eb741865ca..ab1aa4ed0a0 100644 --- a/xs/src/libslic3r/EdgeGrid.hpp +++ b/xs/src/libslic3r/EdgeGrid.hpp @@ -58,7 +58,7 @@ class Grid const size_t cols() const { return m_cols; } // For supports: Contours enclosing the rasterized edges. - Polygons contours_simplified(coord_t offset) const; + Polygons contours_simplified(coord_t offset, bool fill_holes) const; protected: struct Cell { diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 1a8b19c3f16..32c3f57009e 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1696,6 +1696,14 @@ PrintConfigDef::PrintConfigDef() def->cli = "support-material!"; def->default_value = new ConfigOptionBool(false); + def = this->add("support_material_auto", coBool); + def->label = L("Auto generated supports"); + def->category = L("Support material"); + def->tooltip = L("If checked, supports will be generated automatically based on the overhang threshold value."\ + " If unchecked, supports will be generated inside the \"Support Enforcer\" volumes only."); + def->cli = "support-material-auto!"; + def->default_value = new ConfigOptionBool(true); + def = this->add("support_material_xy_spacing", coFloatOrPercent); def->label = L("XY separation between an object and its support"); def->category = L("Support material"); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 99959cebb4d..89ba81124b0 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -331,6 +331,8 @@ class PrintObjectConfig : public StaticPrintConfig // ConfigOptionFloat seam_preferred_direction; // ConfigOptionFloat seam_preferred_direction_jitter; ConfigOptionBool support_material; + // Automatic supports (generated based on support_material_threshold). + ConfigOptionBool support_material_auto; // Direction of the support pattern (in XY plane). ConfigOptionFloat support_material_angle; ConfigOptionBool support_material_buildplate_only; @@ -372,6 +374,7 @@ class PrintObjectConfig : public StaticPrintConfig // OPT_PTR(seam_preferred_direction); // OPT_PTR(seam_preferred_direction_jitter); OPT_PTR(support_material); + OPT_PTR(support_material_auto); OPT_PTR(support_material_angle); OPT_PTR(support_material_buildplate_only); OPT_PTR(support_material_contact_distance); diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 6dd764ae1c6..ea6b3928025 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -172,6 +172,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vectorsupport_material_auto.value; // If user specified a custom angle threshold, convert it to radians. // Zero means automatic overhang detection. const double threshold_rad = (m_object_config->support_material_threshold.value > 0) ? @@ -851,7 +852,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ contact_out.assign(num_layers * 2, nullptr); tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(this->has_raft() ? 0 : 1, num_layers), - [this, &object, &buildplate_covered, &enforcers, &blockers, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out](const tbb::blocked_range& range) { + [this, &object, &buildplate_covered, &enforcers, &blockers, support_auto, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out] + (const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { const Layer &layer = *object.layers[layer_id]; @@ -901,34 +903,35 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); } } else { - // Get the regions needing a suport, collapse very tiny spots. - //FIXME cache the lower layer offset if this layer has multiple regions. -#if 1 - diff_polygons = offset2( - diff(layerm_polygons, - offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), - //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to - // no support at all for not so steep overhangs. - - 0.1f * fw, 0.1f * fw); -#else - diff_polygons = - diff(layerm_polygons, - offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); -#endif - if (! buildplate_covered.empty()) { - // Don't support overhangs above the top surfaces. - // This step is done before the contact surface is calculated by growing the overhang region. - diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); + if (support_auto) { + // Get the regions needing a suport, collapse very tiny spots. + //FIXME cache the lower layer offset if this layer has multiple regions. + #if 1 + diff_polygons = offset2( + diff(layerm_polygons, + offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), + //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to + // no support at all for not so steep overhangs. + - 0.1f * fw, 0.1f * fw); + #else + diff_polygons = + diff(layerm_polygons, + offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + #endif + if (! buildplate_covered.empty()) { + // Don't support overhangs above the top surfaces. + // This step is done before the contact surface is calculated by growing the overhang region. + diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); + } + if (! diff_polygons.empty()) { + // Offset the support regions back to a full overhang, restrict them to the full overhang. + // This is done to increase size of the supporting columns below, as they are calculated by + // propagating these contact surfaces downwards. + diff_polygons = diff( + intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), + lower_layer_polygons); + } } - if (! diff_polygons.empty()) { - // Offset the support regions back to a full overhang, restrict them to the full overhang. - // This is done to increase size of the supporting columns below, as they are calculated by - // propagating these contact surfaces downwards. - diff_polygons = diff( - intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), - lower_layer_polygons); - } - if (! enforcers.empty()) { // Apply the "support enforcers". //FIXME add the "enforcers" to the sparse support regions only. @@ -1000,7 +1003,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ slices_margin_cached_offset = slices_margin_offset; slices_margin_cached = (slices_margin_offset == 0.f) ? lower_layer_polygons : - offset2(to_polygons(lower_layer.slices.expolygons), - scale_(- no_interface_offset * 0.5f), slices_margin_offset + scale_(- no_interface_offset * 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); + offset2(to_polygons(lower_layer.slices.expolygons), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! buildplate_covered.empty()) { // Trim the inflated contact surfaces by the top surfaces as well. polygons_append(slices_margin_cached, buildplate_covered[layer_id]); @@ -1102,16 +1105,18 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. - new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3)); + new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3, true)); // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. if (layer_id == 0) { // if (no_interface_offset == 0.f) { - new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5); + new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, true); } else { - //Polygons dense_interface_polygons = diff(overhang_polygons, offset(lower_layer_polygons, scale_(no_interface_offset * 0.7f))); Polygons dense_interface_polygons = diff(overhang_polygons, - offset2(lower_layer_polygons, scale_(- no_interface_offset * 0.5f), scale_(no_interface_offset * (0.7f + 0.5f)), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS)); +// offset(lower_layer_polygons, no_interface_offset * 0.6f, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (! dense_interface_polygons.empty()) { + //FIXME do it for non-soluble support interfaces only. + //FIXME do it for the bridges only? SupportGridPattern support_grid_pattern( // Support islands, to be stretched into a grid. dense_interface_polygons, @@ -1120,7 +1125,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Grid resolution. m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); - new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5); + new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false); } } @@ -1407,7 +1412,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta , &layer #endif /* SLIC3R_DEBUG */ ] { - layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25); + layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25, true); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-layer_support_area-gridded-%d-%lf.svg", iRun, layer.print_z), @@ -1421,7 +1426,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta , &layer #endif /* SLIC3R_DEBUG */ ] { - projection_new = support_grid_pattern.extract_support(-5); + projection_new = support_grid_pattern.extract_support(-5, true); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index af2cd8ff68f..cede4c707f0 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -292,7 +292,7 @@ const std::vector& Preset::print_options() "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", - "min_skirt_length", "brim_width", "support_material", "support_material_threshold", "support_material_enforce_layers", + "min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 6505e109278..9d265cfc4df 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -847,6 +847,7 @@ void TabPrint::build() page = add_options_page(_(L("Support material")), "building.png"); optgroup = page->new_optgroup(_(L("Support material"))); optgroup->append_single_option_line("support_material"); + optgroup->append_single_option_line("support_material_auto"); optgroup->append_single_option_line("support_material_threshold"); optgroup->append_single_option_line("support_material_enforce_layers"); @@ -1183,13 +1184,15 @@ void TabPrint::update() bool have_raft = m_config->opt_int("raft_layers") > 0; bool have_support_material = m_config->opt_bool("support_material") || have_raft; + bool have_support_material_auto = have_support_material && m_config->opt_bool("support_material_auto"); bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0; bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0; - for (auto el : {"support_material_threshold", "support_material_pattern", "support_material_with_sheath", + for (auto el : {"support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_angle", "support_material_interface_layers", "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance", "support_material_xy_spacing" }) get_field(el)->toggle(have_support_material); + get_field("support_material_threshold")->toggle(have_support_material_auto); for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder", "support_material_interface_speed", "support_material_interface_contact_loops" }) From 62894d3f7b841436c1fb849336b4e0dd15726fad Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 7 Sep 2018 16:05:10 +0200 Subject: [PATCH 31/63] Export the first M73 line to gcode before custom gcode --- xs/src/libslic3r/GCode.cpp | 9 ++++++++- xs/src/libslic3r/GCodeTimeEstimator.cpp | 13 ++++++++++++- xs/src/libslic3r/GCodeTimeEstimator.hpp | 3 +++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index b34ba544150..67a682d1881 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -665,6 +665,14 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) _write_format(file, "\n"); } + // adds tags for time estimators + if (print.config.remaining_times.value) + { + _writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag); + if (m_silent_time_estimator_enabled) + _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag); + } + // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser; m_placeholder_parser.update_timestamp(); @@ -724,7 +732,6 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config.single_extruder_multi_material_priming); std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id); - // Set bed temperature if the start G-code does not contain any bed temp control G-codes. this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); // Set extruder(s) temperature before and after start G-code. diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp index c4ffb572a9b..f4f6472e56d 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.cpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -168,6 +168,9 @@ namespace Slic3r { } #endif // ENABLE_MOVE_STATS + const std::string GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag = "; NORMAL_FIRST_M73_OUTPUT_PLACEHOLDER"; + const std::string GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag = "; SILENT_FIRST_M73_OUTPUT_PLACEHOLDER"; + GCodeTimeEstimator::GCodeTimeEstimator(EMode mode) : _mode(mode) { @@ -294,7 +297,15 @@ namespace Slic3r { throw std::runtime_error(std::string("Remaining times export failed.\nError while reading from file.\n")); } - gcode_line += "\n"; + // replaces placeholders for initial line M73 with the real lines + if (((_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || + ((_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) + { + sprintf(time_line, time_mask.c_str(), std::to_string(0), _get_time_minutes(_time).c_str()); + gcode_line = time_line; + } + else + gcode_line += "\n"; // add remaining time lines where needed _parser.parse_line(gcode_line, diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp index 1fa74e304c2..e9da584c319 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.hpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp @@ -17,6 +17,9 @@ namespace Slic3r { class GCodeTimeEstimator { public: + static const std::string Normal_First_M73_Output_Placeholder_Tag; + static const std::string Silent_First_M73_Output_Placeholder_Tag; + enum EMode : unsigned char { Normal, From be508b003a5348aaf3c79a7eeb14b3b90f197b41 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 10 Sep 2018 13:15:12 +0200 Subject: [PATCH 32/63] Fixed #1211 --- lib/Slic3r/GUI/Plater.pm | 9 +++++++++ xs/src/libslic3r/Format/3mf.cpp | 2 -- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ee2689d1647..e2c95859fa2 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -767,6 +767,15 @@ sub load_files { $model->convert_multipart_object(scalar(@$nozzle_dmrs)) if $dialog->ShowModal() == wxID_YES; } + # objects imported from 3mf require a call to center_around_origin to have gizmos working properly and this call + # need to be done after looks_like_multipart_object detection + if ($input_file =~ /.3[mM][fF]$/) + { + foreach my $model_object (@{$model->objects}) { + $model_object->center_around_origin; # also aligns object to Z = 0 + } + } + if ($one_by_one) { push @obj_idx, $self->load_model_objects(@{$model->objects}); } else { diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index 945bb1f8682..5de1d26c5fc 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -603,8 +603,6 @@ namespace Slic3r { if (!_generate_volumes(*object.second, obj_geometry->second, *volumes_ptr)) return false; - - object.second->center_around_origin(); } // fixes the min z of the model if negative From 78bc688a7fa601900502cf1821bd396e2a946af8 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 10 Sep 2018 13:48:35 +0200 Subject: [PATCH 33/63] Fixed compile on Linux --- xs/src/libslic3r/GCodeTimeEstimator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp index f4f6472e56d..7471367fe59 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.cpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -301,7 +301,7 @@ namespace Slic3r { if (((_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || ((_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) { - sprintf(time_line, time_mask.c_str(), std::to_string(0), _get_time_minutes(_time).c_str()); + sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(_time).c_str()); gcode_line = time_line; } else From 712fef0669a491c4335286d61d34ebb0b24345e6 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 12 Sep 2018 09:28:26 +0200 Subject: [PATCH 34/63] Added number of toolchanges into 'Sliced info' statistics --- lib/Slic3r/GUI/Plater.pm | 12 ++++++++---- xs/src/libslic3r/GCode/WipeTower.hpp | 3 +++ xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 2 +- xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 5 +++-- xs/src/libslic3r/Print.cpp | 1 + xs/src/libslic3r/Print.hpp | 1 + xs/xsp/Print.xsp | 10 ++++++++++ 7 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 56c21bbbb00..469fa42ddfa 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1646,14 +1646,15 @@ sub print_info_box_show { $grid_sizer->AddGrowableCol(1, 1); $grid_sizer->AddGrowableCol(3, 1); $print_info_sizer->Add($grid_sizer, 0, wxEXPAND); + my $is_wipe_tower = $self->{print}->total_wipe_tower_filament > 0; my @info = ( L("Used Filament (m)") - => $self->{print}->total_wipe_tower_filament > 0 ? + => $is_wipe_tower ? sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_used_filament / 1000, ($self->{print}->total_used_filament - $self->{print}->total_wipe_tower_filament) / 1000, L("objects"), $self->{print}->total_wipe_tower_filament / 1000, - L("wipe_tower")) : + L("wipe tower")) : sprintf("%.2f" , $self->{print}->total_used_filament / 1000), L("Used Filament (mm³)") @@ -1661,18 +1662,21 @@ sub print_info_box_show { L("Used Filament (g)"), => sprintf("%.2f" , $self->{print}->total_weight), L("Cost"), - => $self->{print}->total_wipe_tower_cost > 0 ? + => $is_wipe_tower ? sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_cost, ($self->{print}->total_cost - $self->{print}->total_wipe_tower_cost), L("objects"), $self->{print}->total_wipe_tower_cost, - L("wipe_tower")) : + L("wipe tower")) : sprintf("%.2f" , $self->{print}->total_cost), L("Estimated printing time (normal mode)") => $self->{print}->estimated_normal_print_time, L("Estimated printing time (silent mode)") => $self->{print}->estimated_silent_print_time ); + # if there is a wipe tower, insert number of toolchanges info into the array: + splice (@info, 8, 0, L("Number of tool changes") => sprintf("%.d", $self->{print}->m_wipe_tower_number_of_toolchanges)) if ($is_wipe_tower); + while ( my $label = shift @info) { my $value = shift @info; next if $value eq "N/A"; diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp index e7cd8ea1a29..21c10969a06 100644 --- a/xs/src/libslic3r/GCode/WipeTower.hpp +++ b/xs/src/libslic3r/GCode/WipeTower.hpp @@ -158,6 +158,9 @@ class WipeTower // Returns used filament length per extruder: virtual std::vector get_used_filament() const = 0; + + // Returns total number of toolchanges: + virtual int get_number_of_toolchanges() const = 0; }; }; // namespace Slic3r diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 23a2bef9b2d..0427e32d69c 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -613,10 +613,10 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo toolchange_Load(writer, cleaning_box); writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. + ++ m_num_tool_changes; } else toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature); - ++ m_num_tool_changes; m_depth_traversed += wipe_area; if (last_change_in_layer) {// draw perimeter line diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index 964ee0039c1..06625d189fe 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -46,7 +46,7 @@ class WipeTowerPrusaMM : public WipeTower WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging, const std::vector>& wiping_matrix, unsigned int initial_tool) : - m_wipe_tower_pos(x, y), + m_wipe_tower_pos(x, y), m_wipe_tower_width(width), m_wipe_tower_rotation_angle(rotation_angle), m_y_shift(0.f), @@ -174,7 +174,8 @@ class WipeTowerPrusaMM : public WipeTower return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); } - virtual std::vector get_used_filament() const { return m_used_filament_length; } + virtual std::vector get_used_filament() const override { return m_used_filament_length; } + virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; } private: diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index ba8abd0407e..eb2112ef0e9 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -1195,6 +1195,7 @@ void Print::_make_wipe_tower() wipe_tower.tool_change((unsigned int)-1, false)); m_wipe_tower_used_filament = wipe_tower.get_used_filament(); + m_wipe_tower_number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); } std::string Print::output_filename() diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 537070a34be..95b8abc5b80 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -310,6 +310,7 @@ class Print std::vector> m_wipe_tower_tool_changes; std::unique_ptr m_wipe_tower_final_purge; std::vector m_wipe_tower_used_filament; + int m_wipe_tower_number_of_toolchanges = -1; std::string output_filename(); std::string output_filepath(const std::string &path); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 50f899f2cdc..1dee8a4c4c2 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -296,6 +296,16 @@ Print::total_wipe_tower_filament(...) THIS->total_wipe_tower_filament = (double)SvNV(ST(1)); } RETVAL = THIS->total_wipe_tower_filament; + OUTPUT: + RETVAL + +int +Print::m_wipe_tower_number_of_toolchanges(...) + CODE: + if (items > 1) { + THIS->m_wipe_tower_number_of_toolchanges = (double)SvNV(ST(1)); + } + RETVAL = THIS->m_wipe_tower_number_of_toolchanges; OUTPUT: RETVAL %} From 578792be31d57eb084b39a1a04540946765264aa Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 14 Sep 2018 11:37:26 +0200 Subject: [PATCH 35/63] Fixed #1229 --- lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 783c1a9f5b3..4032886f37c 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -362,13 +362,17 @@ sub on_btn_load { } foreach my $object (@{$model->objects}) { + $object->center_around_origin; foreach my $volume (@{$object->volumes}) { my $new_volume = $self->{model_object}->add_volume($volume); $new_volume->set_modifier($is_modifier); $new_volume->set_name(basename($input_file)); # apply the same translation we applied to the object - $new_volume->mesh->translate(@{$self->{model_object}->origin_translation}); + my $delta_x = $self->{model_object}->origin_translation->x - $object->origin_translation->x; + my $delta_y = $self->{model_object}->origin_translation->y - $object->origin_translation->y; + my $delta_z = $self->{model_object}->origin_translation->z - $object->origin_translation->z; + $new_volume->mesh->translate($delta_x, $delta_y, $delta_z); # set a default extruder value, since user can't add it manually $new_volume->config->set_ifndef('extruder', 0); From 646e991d4b6907a2bcba40073e7895f63522d86d Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 3 Sep 2018 16:19:16 +0200 Subject: [PATCH 36/63] ConfigWizard: Properly apply gcode_flavor Fixes #1138 --- xs/src/slic3r/GUI/ConfigWizard.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 9f736ded86d..bd9b9328ab5 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -409,11 +409,10 @@ PageFirmware::PageFirmware(ConfigWizard *parent) : void PageFirmware::apply_custom_config(DynamicPrintConfig &config) { - ConfigOptionEnum opt; - auto sel = gcode_picker->GetSelection(); - if (sel != wxNOT_FOUND && opt.deserialize(gcode_picker->GetString(sel).ToStdString())) { - config.set_key_value("gcode_flavor", &opt); + if (sel >= 0 && sel < gcode_opt.enum_labels.size()) { + auto *opt = new ConfigOptionEnum(static_cast(sel)); + config.set_key_value("gcode_flavor", opt); } } From a8c28e210ddd4ebfdd64c4015fdf688044513542 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 4 Sep 2018 10:40:28 +0200 Subject: [PATCH 37/63] Fix CMake string comparison issue Fixes #1187 --- xs/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index efd52ac1371..d19aad70a34 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -503,7 +503,7 @@ if (WIN32) endif () # SLIC3R_MSVC_PDB -if (MSVC AND SLIC3R_MSVC_PDB AND ${CMAKE_BUILD_TYPE} STREQUAL "Release") +if (MSVC AND SLIC3R_MSVC_PDB AND "${CMAKE_BUILD_TYPE}" STREQUAL "Release") set_target_properties(XS PROPERTIES COMPILE_FLAGS "/Zi" LINK_FLAGS "/DEBUG /OPT:REF /OPT:ICF" From 8988e8cf0ac831307871eae226c77aedd7d06191 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 4 Sep 2018 11:36:58 +0200 Subject: [PATCH 38/63] Firmware updater: Fix MMU2 lookup wrt. other Prusa devices being connected --- xs/src/slic3r/GUI/FirmwareDialog.cpp | 22 ++++++++++++++-------- xs/src/slic3r/Utils/Serial.cpp | 7 ++++++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index d0cd9f8cf65..0c06e517e1b 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -367,7 +367,7 @@ void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries) auto ports = Utils::scan_serial_ports_extended(); ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { - return port.id_vendor != USB_VID_PRUSA && port.id_product != USB_PID_MMU_BOOT; + return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT; }), ports.end()); if (ports.size() == 1) { @@ -390,23 +390,22 @@ void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port) void FirmwareDialog::priv::lookup_port_mmu() { + static const auto msg_not_found = + "The Multi Material Control device was not found.\n" + "If the device is connected, please press the Reset button next to the USB connector ..."; + BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; auto ports = Utils::scan_serial_ports_extended(); ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { - return port.id_vendor != USB_VID_PRUSA && + return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT && port.id_product != USB_PID_MMU_APP; }), ports.end()); if (ports.size() == 0) { BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ..."; - - queue_status(_(L( - "The Multi Material Control device was not found.\n" - "If the device is connected, please press the Reset button next to the USB connector ..." - ))); - + queue_status(_(L(msg_not_found))); wait_for_mmu_bootloader(30); } else if (ports.size() > 1) { BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found"; @@ -417,6 +416,13 @@ void FirmwareDialog::priv::lookup_port_mmu() BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port; mmu_reboot(ports[0]); wait_for_mmu_bootloader(10); + + if (! port) { + // The device in bootloader mode was not found, inform the user and wait some more... + BOOST_LOG_TRIVIAL(info) << "MMU 2.0 bootloader device not found after reboot, asking the user to press Reset and waiting for the device to show up ..."; + queue_status(_(L(msg_not_found))); + wait_for_mmu_bootloader(30); + } } else { port = ports[0]; } diff --git a/xs/src/slic3r/Utils/Serial.cpp b/xs/src/slic3r/Utils/Serial.cpp index 183119b442e..601719b50e9 100644 --- a/xs/src/slic3r/Utils/Serial.cpp +++ b/xs/src/slic3r/Utils/Serial.cpp @@ -231,7 +231,12 @@ std::vector scan_serial_ports_extended() spi.port = path; #ifdef __linux__ auto friendly_name = sysfs_tty_prop(name, "product"); - spi.friendly_name = friendly_name ? (boost::format("%1% (%2%)") % *friendly_name % path).str() : path; + if (friendly_name) { + spi.is_printer = looks_like_printer(*friendly_name); + spi.friendly_name = (boost::format("%1% (%2%)") % *friendly_name % path).str(); + } else { + spi.friendly_name = path; + } auto vid = sysfs_tty_prop_hex(name, "idVendor"); auto pid = sysfs_tty_prop_hex(name, "idProduct"); if (vid && pid) { From 7258c597b9140f09879db12ffc7334135b2d72d8 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 6 Aug 2018 18:26:29 +0200 Subject: [PATCH 39/63] Fix window size persistence Fixes #1116 Fixes #1175 --- lib/Slic3r/GUI.pm | 24 -------- lib/Slic3r/GUI/MainFrame.pm | 4 +- lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm | 4 +- xs/src/slic3r/GUI/AppConfig.cpp | 8 +++ xs/src/slic3r/GUI/AppConfig.hpp | 8 +++ xs/src/slic3r/GUI/ConfigWizard.cpp | 9 +-- xs/src/slic3r/GUI/GUI.cpp | 57 ++++++++++++++++++- xs/src/slic3r/GUI/GUI.hpp | 8 ++- xs/xsp/GUI.xsp | 6 ++ 9 files changed, 93 insertions(+), 35 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 483fd36f953..7510d22afb4 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -356,28 +356,4 @@ sub set_menu_item_icon { } } -sub save_window_pos { - my ($self, $window, $name) = @_; - - $self->{app_config}->set("${name}_pos", join ',', $window->GetScreenPositionXY); - $self->{app_config}->set("${name}_size", join ',', $window->GetSizeWH); - $self->{app_config}->set("${name}_maximized", $window->IsMaximized); - $self->{app_config}->save; -} - -sub restore_window_pos { - my ($self, $window, $name) = @_; - if ($self->{app_config}->has("${name}_pos")) { - my $size = [ split ',', $self->{app_config}->get("${name}_size"), 2 ]; - $window->SetSize($size); - - my $display = Wx::Display->new->GetClientArea(); - my $pos = [ split ',', $self->{app_config}->get("${name}_pos"), 2 ]; - if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) { - $window->Move($pos); - } - $window->Maximize(1) if $self->{app_config}->get("${name}_maximized"); - } -} - 1; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 6baefa545a4..9d2fd19cead 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -88,7 +88,7 @@ sub new { $self->Fit; $self->SetMinSize([760, 490]); $self->SetSize($self->GetMinSize); - wxTheApp->restore_window_pos($self, "main_frame"); + Slic3r::GUI::restore_window_size($self, "main_frame"); $self->Show; $self->Layout; } @@ -101,7 +101,7 @@ sub new { return; } # save window size - wxTheApp->save_window_pos($self, "main_frame"); + Slic3r::GUI::save_window_size($self, "main_frame"); # Save the slic3r.ini. Usually the ini file is saved from "on idle" callback, # but in rare cases it may not have been called yet. wxTheApp->{app_config}->save; diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm index 3befba708da..3ccf1d7f8a7 100644 --- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm @@ -33,7 +33,7 @@ sub new { $self->{layers}->Closing; # save window size - wxTheApp->save_window_pos($self, "object_settings"); + Slic3r::GUI::save_window_size($self, "object_settings"); $self->EndModal(wxID_OK); $self->{parts}->Destroy; @@ -49,7 +49,7 @@ sub new { $self->Layout; - wxTheApp->restore_window_pos($self, "object_settings"); + Slic3r::GUI::restore_window_size($self, "object_settings"); return $self; } diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 2a33cd73313..e66221351ae 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -60,6 +60,14 @@ void AppConfig::set_defaults() if (get("remember_output_path").empty()) set("remember_output_path", "1"); + + // Remove legacy window positions/sizes + erase("", "main_frame_maximized"); + erase("", "main_frame_pos"); + erase("", "main_frame_size"); + erase("", "object_settings_maximized"); + erase("", "object_settings_pos"); + erase("", "object_settings_size"); } void AppConfig::load() diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index b742176ed31..5af635a12c3 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -72,6 +72,14 @@ class AppConfig bool has(const std::string &key) const { return this->has("", key); } + void erase(const std::string §ion, const std::string &key) + { + auto it = m_storage.find(section); + if (it != m_storage.end()) { + it->second.erase(key); + } + } + void clear_section(const std::string §ion) { m_storage[section].clear(); } diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index bd9b9328ab5..e784d852561 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -870,10 +870,11 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) : // If the screen is smaller, resize wizrad to match, which will enable scrollbars. auto wizard_size = GetSize(); unsigned width, height; - GUI::get_current_screen_size(width, height); - wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN))); - wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN))); - SetMinSize(wizard_size); + if (GUI::get_current_screen_size(this, width, height)) { + wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN))); + wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN))); + SetMinSize(wizard_size); + } Fit(); p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); }); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 8555f0b921d..0272aae4e40 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #if __APPLE__ #import @@ -974,14 +975,66 @@ int get_export_option(wxFileDialog* dlg) } -void get_current_screen_size(unsigned &width, unsigned &height) +bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height) { - wxDisplay display(wxDisplay::GetFromWindow(g_wxMainFrame)); + const auto idx = wxDisplay::GetFromWindow(window); + if (idx == wxNOT_FOUND) { + return false; + } + + wxDisplay display(idx); const auto disp_size = display.GetClientArea(); width = disp_size.GetWidth(); height = disp_size.GetHeight(); + + return true; +} + +void save_window_size(wxTopLevelWindow *window, const std::string &name) +{ + const wxSize size = window->GetSize(); + const wxPoint pos = window->GetPosition(); + const auto maximized = window->IsMaximized() ? "1" : "0"; + + g_AppConfig->set((boost::format("window_%1%_size") % name).str(), (boost::format("%1%;%2%") % size.GetWidth() % size.GetHeight()).str()); + g_AppConfig->set((boost::format("window_%1%_maximized") % name).str(), maximized); +} + +void restore_window_size(wxTopLevelWindow *window, const std::string &name) +{ + // XXX: This still doesn't behave nicely in some situations (mostly on Linux). + // The problem is that it's hard to obtain window position with respect to screen geometry reliably + // from wxWidgets. Sometimes wxWidgets claim a window is located on a different screen than on which + // it's actually visible. I suspect this has something to do with window initialization (maybe we + // restore window geometry too early), but haven't yet found a workaround. + + const auto display_idx = wxDisplay::GetFromWindow(window); + if (display_idx == wxNOT_FOUND) { return; } + + const auto display = wxDisplay(display_idx).GetClientArea(); + std::vector pair; + + try { + const auto key_size = (boost::format("window_%1%_size") % name).str(); + if (g_AppConfig->has(key_size)) { + if (unescape_strings_cstyle(g_AppConfig->get(key_size), pair) && pair.size() == 2) { + auto width = boost::lexical_cast(pair[0]); + auto height = boost::lexical_cast(pair[1]); + + window->SetSize(width, height); + } + } + } catch(const boost::bad_lexical_cast &) {} + + // Maximizing should be the last thing to do. + // This ensure the size and position are sane when the user un-maximizes the window. + const auto key_maximized = (boost::format("window_%1%_maximized") % name).str(); + if (g_AppConfig->get(key_maximized) == "1") { + window->Maximize(true); + } } + void about() { AboutDialog dlg; diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 165288819bd..68dbdfe8480 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -24,6 +24,7 @@ class wxBoxSizer; class wxFlexGridSizer; class wxButton; class wxFileDialog; +class wxTopLevelWindow; namespace Slic3r { @@ -182,7 +183,12 @@ void add_export_option(wxFileDialog* dlg, const std::string& format); int get_export_option(wxFileDialog* dlg); // Returns the dimensions of the screen on which the main frame is displayed -void get_current_screen_size(unsigned &width, unsigned &height); +bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height); + +// Save window size and maximized status into AppConfig +void save_window_size(wxTopLevelWindow *window, const std::string &name); +// Restore the above +void restore_window_size(wxTopLevelWindow *window, const std::string &name); // Display an About dialog extern void about(); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index c6eead1ad58..8d2efb8588f 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -111,3 +111,9 @@ void register_on_request_update_callback(SV* callback) void deregister_on_request_update_callback() %code%{ Slic3r::GUI::g_on_request_update_callback.deregister_callback(); %}; +void save_window_size(SV *window, std::string name) + %code%{ Slic3r::GUI::save_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %}; + +void restore_window_size(SV *window, std::string name) + %code%{ Slic3r::GUI::restore_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %}; + From 61a6aa86922cf1a123dfef14e525fd2d9ee42fe1 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 6 Sep 2018 16:45:31 +0200 Subject: [PATCH 40/63] FirmwareDialog: Add appropriate set of wildcards to the file picker --- xs/src/slic3r/GUI/FirmwareDialog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index 0c06e517e1b..d5ac64d9004 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -708,7 +708,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : panel->SetSizer(vsizer); auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:"))); - p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY); + p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, wxFileSelectorPromptStr, + "Hex files (*.hex)|*.hex|All files|*.*"); auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:"))); p->port_picker = new wxComboBox(panel, wxID_ANY); From 78e7bb04272fa9a41e7d88c3ae0be60c3446343b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 17 Sep 2018 11:46:52 +0200 Subject: [PATCH 41/63] Supports - fix of the recent changes: Always generate dense supports below overhangs, even if the overhangs are steep. --- xs/src/libslic3r/PrintConfig.cpp | 21 ++++++++++----------- xs/src/libslic3r/SupportMaterial.cpp | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 32c3f57009e..cad23337a43 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -123,7 +123,7 @@ PrintConfigDef::PrintConfigDef() def->tooltip = L("Speed for printing bridges."); def->sidetext = L("mm/s"); def->cli = "bridge-speed=f"; - def->aliases.push_back("bridge_feed_rate"); + def->aliases = { "bridge_feed_rate" }; def->min = 0; def->default_value = new ConfigOptionFloat(60); @@ -236,7 +236,7 @@ PrintConfigDef::PrintConfigDef() def->tooltip = L("Distance used for the auto-arrange feature of the plater."); def->sidetext = L("mm"); def->cli = "duplicate-distance=f"; - def->aliases.push_back("multiply_distance"); + def->aliases = { "multiply_distance" }; def->min = 0; def->default_value = new ConfigOptionFloat(6); @@ -297,7 +297,7 @@ PrintConfigDef::PrintConfigDef() def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Octagram Spiral")); // solid_fill_pattern is an obsolete equivalent to external_fill_pattern. - def->aliases.push_back("solid_fill_pattern"); + def->aliases = { "solid_fill_pattern" }; def->default_value = new ConfigOptionEnum(ipRectilinear); def = this->add("external_perimeter_extrusion_width", coFloatOrPercent); @@ -885,8 +885,7 @@ PrintConfigDef::PrintConfigDef() def->tooltip = L("Speed for printing the internal fill. Set to zero for auto."); def->sidetext = L("mm/s"); def->cli = "infill-speed=f"; - def->aliases.push_back("print_feed_rate"); - def->aliases.push_back("infill_feed_rate"); + def->aliases = { "print_feed_rate", "infill_feed_rate" }; def->min = 0; def->default_value = new ConfigOptionFloat(80); @@ -1251,7 +1250,7 @@ PrintConfigDef::PrintConfigDef() def->category = L("Extruders"); def->tooltip = L("The extruder to use when printing perimeters and brim. First extruder is 1."); def->cli = "perimeter-extruder=i"; - def->aliases.push_back("perimeters_extruder"); + def->aliases = { "perimeters_extruder" }; def->min = 1; def->default_value = new ConfigOptionInt(1); @@ -1264,7 +1263,7 @@ PrintConfigDef::PrintConfigDef() "If expressed as percentage (for example 200%) it will be computed over layer height."); def->sidetext = L("mm or % (leave 0 for default)"); def->cli = "perimeter-extrusion-width=s"; - def->aliases.push_back("perimeters_extrusion_width"); + def->aliases = { "perimeters_extrusion_width" }; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("perimeter_speed", coFloat); @@ -1273,7 +1272,7 @@ PrintConfigDef::PrintConfigDef() def->tooltip = L("Speed for perimeters (contours, aka vertical shells). Set to zero for auto."); def->sidetext = L("mm/s"); def->cli = "perimeter-speed=f"; - def->aliases.push_back("perimeter_feed_rate"); + def->aliases = { "perimeter_feed_rate" }; def->min = 0; def->default_value = new ConfigOptionFloat(60); @@ -1286,7 +1285,7 @@ PrintConfigDef::PrintConfigDef() "if the Extra Perimeters option is enabled."); def->sidetext = L("(minimum)"); def->cli = "perimeters=i"; - def->aliases.push_back("perimeter_offsets"); + def->aliases = { "perimeter_offsets" }; def->min = 0; def->default_value = new ConfigOptionInt(3); @@ -1614,7 +1613,7 @@ PrintConfigDef::PrintConfigDef() def->sidetext = L("mm/s or %"); def->cli = "solid-infill-speed=s"; def->ratio_over = "infill_speed"; - def->aliases.push_back("solid_infill_feed_rate"); + def->aliases = { "solid_infill_feed_rate" }; def->min = 0; def->default_value = new ConfigOptionFloatOrPercent(20, false); @@ -1968,7 +1967,7 @@ PrintConfigDef::PrintConfigDef() def->tooltip = L("Speed for travel moves (jumps between distant extrusion points)."); def->sidetext = L("mm/s"); def->cli = "travel-speed=f"; - def->aliases.push_back("travel_feed_rate"); + def->aliases = { "travel_feed_rate" }; def->min = 1; def->default_value = new ConfigOptionFloat(130); diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 9019583b95b..cc689eaaab2 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -1107,15 +1107,15 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3, true)); // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - if (layer_id == 0) { + if (layer_id == 0 || m_slicing_params.soluble_interface) { // if (no_interface_offset == 0.f) { new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, true); } else { + // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. Polygons dense_interface_polygons = diff(overhang_polygons, offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS)); // offset(lower_layer_polygons, no_interface_offset * 0.6f, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (! dense_interface_polygons.empty()) { - //FIXME do it for non-soluble support interfaces only. //FIXME do it for the bridges only? SupportGridPattern support_grid_pattern( // Support islands, to be stretched into a grid. From cce5f3203b82f0e2d22ca8c81b91b9e47ea93feb Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 21 Sep 2018 12:39:15 +0200 Subject: [PATCH 42/63] Enhanced fix of #1229 - perl part --- lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index fd02a030f40..00373f138e2 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -407,17 +407,24 @@ sub on_btn_load { } foreach my $object (@{$model->objects}) { - $object->center_around_origin; + my $delta_x = 0.0; + my $delta_y = 0.0; + my $delta_z = 0.0; + if (($self->{model_object}->origin_translation->x != 0.0) || ($self->{model_object}->origin_translation->y != 0.0) || ($self->{model_object}->origin_translation->z != 0.0)) { + $object->center_around_origin; + $delta_x = $self->{model_object}->origin_translation->x - $object->origin_translation->x; + $delta_y = $self->{model_object}->origin_translation->y - $object->origin_translation->y; + $delta_z = $self->{model_object}->origin_translation->z - $object->origin_translation->z; + } foreach my $volume (@{$object->volumes}) { my $new_volume = $self->{model_object}->add_volume($volume); $new_volume->set_modifier($is_modifier); $new_volume->set_name(basename($input_file)); # apply the same translation we applied to the object - my $delta_x = $self->{model_object}->origin_translation->x - $object->origin_translation->x; - my $delta_y = $self->{model_object}->origin_translation->y - $object->origin_translation->y; - my $delta_z = $self->{model_object}->origin_translation->z - $object->origin_translation->z; - $new_volume->mesh->translate($delta_x, $delta_y, $delta_z); + if (($delta_x != 0.0) || ($delta_y != 0.0) || ($delta_z != 0.0)) { + $new_volume->mesh->translate($delta_x, $delta_y, $delta_z); + } # set a default extruder value, since user can't add it manually $new_volume->config->set_ifndef('extruder', 0); From 3835a1cacf6ea58a50739ff60e8ebb84c2f52a61 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 26 Sep 2018 11:22:01 +0200 Subject: [PATCH 43/63] Fixed #1244 - perl part --- lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 1 + xs/src/libslic3r/Model.cpp | 5 +++++ xs/src/libslic3r/Model.hpp | 1 + xs/xsp/Model.xsp | 2 ++ 4 files changed, 9 insertions(+) diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 00373f138e2..13c1b694503 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -424,6 +424,7 @@ sub on_btn_load { # apply the same translation we applied to the object if (($delta_x != 0.0) || ($delta_y != 0.0) || ($delta_z != 0.0)) { $new_volume->mesh->translate($delta_x, $delta_y, $delta_z); + $new_volume->convex_hull->translate($delta_x, $delta_y, $delta_z); } # set a default extruder value, since user can't add it manually diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index af764eb8951..e0097ce2e19 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -973,6 +973,11 @@ const TriangleMesh& ModelVolume::get_convex_hull() const return m_convex_hull; } +TriangleMesh& ModelVolume::get_convex_hull() +{ + return m_convex_hull; +} + ModelVolume::Type ModelVolume::type_from_string(const std::string &s) { // Legacy support diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index 34a7c7cc13b..353dc5cccca 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -195,6 +195,7 @@ class ModelVolume void calculate_convex_hull(); const TriangleMesh& get_convex_hull() const; + TriangleMesh& get_convex_hull(); // Helpers for loading / storing into AMF / 3MF files. static Type type_from_string(const std::string &s); diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 0f9b5cd1599..0b59b3126b2 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -338,6 +338,8 @@ ModelMaterial::attributes() %code%{ RETVAL = &THIS->config; %}; Ref mesh() %code%{ RETVAL = &THIS->mesh; %}; + Ref convex_hull() + %code%{ RETVAL = &THIS->get_convex_hull(); %}; bool modifier() %code%{ RETVAL = THIS->is_modifier(); %}; From b338eb0ce0e5b88ab57940fc0bca4046660237b1 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 27 Sep 2018 17:41:01 +0200 Subject: [PATCH 44/63] Fix of https://github.com/prusa3d/Slic3r/issues/1142 https://github.com/prusa3d/Slic3r/issues/1257 The preset keys not belonging into the config group are removed and the problem is reported into the boost::log --- xs/src/libslic3r/Config.hpp | 3 ++- xs/src/libslic3r/PrintConfig.cpp | 20 ++++++++++++++++++++ xs/src/libslic3r/PrintConfig.hpp | 4 ++++ xs/src/slic3r/GUI/Preset.cpp | 9 ++++++++- xs/src/slic3r/GUI/PresetBundle.cpp | 18 +++--------------- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 377bdbea473..2d995551ca0 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -1171,6 +1171,7 @@ class DynamicConfig : public virtual ConfigBase // Allow DynamicConfig to be instantiated on ints own without a definition. // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. const ConfigDef* def() const override { return nullptr; }; + bool has(const t_config_option_key &opt_key) const { return this->options.find(opt_key) != this->options.end(); } template T* opt(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast(this->option(opt_key, create)); } template const T* opt(const t_config_option_key &opt_key) const @@ -1214,7 +1215,7 @@ class DynamicConfig : public virtual ConfigBase bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } -private: +protected: typedef std::map t_options_map; t_options_map options; }; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index cad23337a43..a5418f0a991 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1,6 +1,7 @@ #include "PrintConfig.hpp" #include "I18N.hpp" +#include #include #include #include @@ -2241,6 +2242,25 @@ std::string DynamicPrintConfig::validate() return fpc.validate(); } +size_t DynamicPrintConfig::remove_keys_not_in(const DynamicPrintConfig &default_config, std::string &removed_keys_message) +{ + size_t n_removed_keys = 0; + for (const auto &kvp : this->options) { + const std::string &key = kvp.first; + if (! default_config.has(key)) { + if (removed_keys_message.empty()) + removed_keys_message = key; + else { + removed_keys_message += ", "; + removed_keys_message += key; + } + this->erase(key); + ++ n_removed_keys; + } + } + return n_removed_keys; +} + double PrintConfig::min_object_distance() const { return PrintConfig::min_object_distance(static_cast(this)); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 89ba81124b0..28d5aa8f422 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -168,6 +168,10 @@ class DynamicPrintConfig : public DynamicConfig // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. std::string validate(); + // Remove all keys not in "valid_keys", return number of removed keys and add the list of keys to "removed_keys_message. + // valid_keys has to be sorted lexicographically. + size_t remove_keys_not_in(const DynamicPrintConfig &default_config, std::string &removed_keys_message); + // Verify whether the opt_key has not been obsoleted or renamed. // Both opt_key and value may be modified by handle_legacy(). // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index cede4c707f0..8af10d293a0 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -417,7 +417,14 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri try { Preset preset(m_type, name, false); preset.file = dir_entry.path().string(); - preset.load(keys); + DynamicPrintConfig &config = preset.load(keys); + // Report configuration fields, which are misplaced into a wrong group. + std::string incorrect_keys; + if (config.remove_keys_not_in(this->default_preset().config, incorrect_keys) > 0) + BOOST_LOG_TRIVIAL(error) << "Error in \"" << dir_entry.path().string() << "\": The preset contains the following incorrect keys: " << + incorrect_keys << ", which were ignored"; + // Normalize once again to set the length of the filament specific vectors to 1. + Preset::normalize(config); m_presets.emplace_back(preset); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 94baa0e0eff..e7ae4ebd9e2 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -919,24 +919,12 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla DynamicPrintConfig config(default_config); for (auto &kvp : section.second) config.set_deserialize(kvp.first, kvp.second.data()); - Preset::normalize(config); // Report configuration fields, which are misplaced into a wrong group. std::string incorrect_keys; - size_t n_incorrect_keys = 0; - for (const std::string &key : config.keys()) - if (! default_config.has(key)) { - if (incorrect_keys.empty()) - incorrect_keys = key; - else { - incorrect_keys += ", "; - incorrect_keys += key; - } - config.erase(key); - ++ n_incorrect_keys; - } - if (! incorrect_keys.empty()) + if (config.remove_keys_not_in(default_config, incorrect_keys) > 0) BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << - section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; + section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; + Preset::normalize(config); if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) { // Filter out printer presets, which are not mentioned in the vendor profile. // These presets are considered not installed. From 986103be1dbb1cd4a91d2940d6f198e9a7c338ad Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 1 Oct 2018 11:07:31 +0200 Subject: [PATCH 45/63] Fixed crashes caused by the preceding commit. --- xs/src/libslic3r/PrintConfig.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index a5418f0a991..decbc783382 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -2245,8 +2245,7 @@ std::string DynamicPrintConfig::validate() size_t DynamicPrintConfig::remove_keys_not_in(const DynamicPrintConfig &default_config, std::string &removed_keys_message) { size_t n_removed_keys = 0; - for (const auto &kvp : this->options) { - const std::string &key = kvp.first; + for (const std::string &key : this->keys()) { if (! default_config.has(key)) { if (removed_keys_message.empty()) removed_keys_message = key; From 69449781cebb470dcd3a9e0f264d707654e4d7ad Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 3 Oct 2018 13:24:14 +0200 Subject: [PATCH 46/63] Improvement in mesh slicing: Hole fill is disabled in 3D, and maximum 2mm gaps are closed at 2D by a greedy algorithm. --- xs/src/libslic3r/TriangleMesh.cpp | 236 +++++++++++++++--------------- 1 file changed, 117 insertions(+), 119 deletions(-) diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 97e60306f81..9576ec10d57 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -207,10 +207,14 @@ TriangleMesh::repair() { } // fill_holes +#if 0 + // Don't fill holes, the current algorithm does more harm than good on complex holes. + // Rather let the slicing algorithm close gaps in 2D slices. if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { stl_fill_holes(&stl); stl_clear_error(&stl); } +#endif // normal_directions stl_fix_normal_directions(&stl); @@ -982,29 +986,7 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vectorslice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) { boost::lock_guard l(*lines_mutex); if (il.edge_type == feHorizontal) { - // Insert all marked edges of the face. The marked edges do not share an edge with another horizontal face - // (they may not have a nighbor, or their neighbor is vertical) - const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; - const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0; - for (int j = 0; j < 3; ++ j) - if (il.flags & ((IntersectionLine::EDGE0_NO_NEIGHBOR | IntersectionLine::EDGE0_FOLD) << j)) { - int a_id = vertices[j % 3]; - int b_id = vertices[(j+1) % 3]; - if (reverse) - std::swap(a_id, b_id); - const stl_vertex *a = &this->v_scaled_shared[a_id]; - const stl_vertex *b = &this->v_scaled_shared[b_id]; - il.a.x = a->x; - il.a.y = a->y; - il.b.x = b->x; - il.b.y = b->y; - il.a_id = a_id; - il.b_id = b_id; - assert(il.a != il.b); - // This edge will not be used as a seed for loop extraction if it was added due to a fold of two overlapping horizontal faces. - il.set_no_seed((IntersectionLine::EDGE0_FOLD << j) != 0); - (*lines)[layer_idx].emplace_back(il); - } + // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume. } else (*lines)[layer_idx].emplace_back(il); } @@ -1066,50 +1048,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( if (min_z == max_z) { // All three vertices are aligned with slice_z. line_out->edge_type = feHorizontal; - // Mark neighbor edges, which do not have a neighbor. - uint32_t edges = 0; - for (int nbr_idx = 0; nbr_idx != 3; ++ nbr_idx) { - // If the neighbor with an edge starting with a vertex idx (nbr_idx - 2) shares no - // opposite face, add it to the edges to process when slicing. - if (nbr.neighbor[nbr_idx] == -1) { - // Mark this edge to be added to the slice. - edges |= (IntersectionLine::EDGE0_NO_NEIGHBOR << nbr_idx); - } -#if 1 - else if (normal.z > 0) { - // Produce edges for opposite faced overlapping horizontal faces aka folds. - // This method often produces connecting lines (noise) at the cutting plane. - // Produce the edges for the top facing face of the pair of top / bottom facing faces. - - // Index of a neighbor face. - const int nbr_face = nbr.neighbor[nbr_idx]; - const int *nbr_vertices = this->mesh->stl.v_indices[nbr_face].vertex; - int idx_vertex_opposite = nbr_vertices[nbr.which_vertex_not[nbr_idx]]; - const stl_vertex *c2 = &this->v_scaled_shared[idx_vertex_opposite]; - if (c2->z == slice_z) { - // Edge shared by facet_idx and nbr_face. - int a_id = vertices[nbr_idx]; - int b_id = vertices[(nbr_idx + 1) % 3]; - int c1_id = vertices[(nbr_idx + 2) % 3]; - const stl_vertex *a = &this->v_scaled_shared[a_id]; - const stl_vertex *b = &this->v_scaled_shared[b_id]; - const stl_vertex *c1 = &this->v_scaled_shared[c1_id]; - // Verify that the two neighbor faces share a common edge. - assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id); - assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id); - double n1 = (double(c1->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c1->y) - double(a->y)) * (double(b->x) - double(a->x)); - double n2 = (double(c2->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c2->y) - double(a->y)) * (double(b->x) - double(a->x)); - if (n1 * n2 > 0) - // The two faces overlap. This indicates an invalid mesh geometry (non-manifold), - // but these are the real world objects, and leaving out these edges leads to missing contours. - edges |= (IntersectionLine::EDGE0_FOLD << nbr_idx); - } - } -#endif - } - // Use some edges of this triangle for slicing only if at least one of its edge does not have an opposite face. - result = (edges == 0) ? Cutting : Slicing; - line_out->flags |= edges; + result = Cutting; if (normal.z < 0) { // If normal points downwards this is a bottom horizontal facet so we reverse its point order. std::swap(a, b); @@ -1121,42 +1060,11 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( int nbr_face = nbr.neighbor[nbr_idx]; // Is the third vertex below the cutting plane? bool third_below = v0.z < slice_z || v1.z < slice_z || v2.z < slice_z; - // Is this a concave corner? - if (nbr_face == -1) { -#ifdef _DEBUG - printf("Face has no neighbor!\n"); -#endif - } else { - assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id); - assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id); - int idx_vertex_opposite = this->mesh->stl.v_indices[nbr_face].vertex[nbr.which_vertex_not[nbr_idx]]; - const stl_vertex *c = &this->v_scaled_shared[idx_vertex_opposite]; - if (c->z == slice_z) { - double normal_nbr = (double(c->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c->y) - double(a->y)) * (double(b->x) - double(a->x)); -#if 0 - if ((normal_nbr < 0) == third_below) { - printf("Flipped normal?\n"); - } -#endif - result = - // A vertical face shares edge with a horizontal face. Verify, whether the shared edge makes a convex or concave corner. - // Unfortunately too often there are flipped normals, which brake our assumption. Let's rather return every edge, - // and leth the code downstream hopefully handle it. - #if 1 - // Ignore concave corners for slicing. - // This method has the unfortunate property, that folds in a horizontal plane create concave corners, - // leading to broken contours, if these concave corners are not replaced by edges of the folds, see above. - ((normal_nbr < 0) == third_below) ? Cutting : Slicing; - #else - // Use concave corners for slicing. This leads to the test 01_trianglemesh.t "slicing a top tangent plane includes its area" failing, - // and rightly so. - Slicing; - #endif - } else { - // For a pair of faces touching exactly at the cutting plane, ignore one of them. An arbitrary rule is to ignore the face with a higher index. - result = (facet_idx < nbr_face) ? Slicing : Cutting; - } - } + // Two vertices on the cutting plane, the third vertex is below the plane. Consider the edge to be part of the slice + // only if it is the upper edge. + // (the bottom most edge resp. vertex of a triangle is not owned by the triangle, but the top most edge resp. vertex is part of the triangle + // in respect to the cutting plane). + result = third_below ? Slicing : Cutting; if (third_below) { line_out->edge_type = feTop; std::swap(a, b); @@ -1333,7 +1241,9 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo assert(l.a != l.b); #endif /* _DEBUG */ - remove_tangent_edges(lines); + // There should be no tangent edges, as the horizontal triangles are ignored and if two triangles touch at a cutting plane, + // only the bottom triangle is considered to be cutting the plane. +// remove_tangent_edges(lines); struct OpenPolyline { OpenPolyline() {}; @@ -1450,7 +1360,10 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo } // Now process the open polylines. - if (! open_polylines.empty()) { + // Do it in two rounds, first try to connect in the same direction only, + // then try to connect the open polylines in reversed order as well. + for (size_t round = 0; round < 2 && ! open_polylines.empty(); ++ round) { + bool try_connect_reversed = round == 1; // Store the end points of open_polylines into vectors sorted struct OpenPolylineEnd { OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} @@ -1470,12 +1383,16 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo for (OpenPolyline &opl : open_polylines) { if (opl.start.edge_id != -1) by_edge_id .emplace_back(OpenPolylineEnd(&opl, true)); - if (opl.end.edge_id != -1) - by_edge_id .emplace_back(OpenPolylineEnd(&opl, false)); + if (try_connect_reversed) { + if (opl.end.edge_id != -1) + by_edge_id .emplace_back(OpenPolylineEnd(&opl, false)); + } if (opl.start.point_id != -1) by_point_id.emplace_back(OpenPolylineEnd(&opl, true)); - if (opl.end.point_id != -1) - by_point_id.emplace_back(OpenPolylineEnd(&opl, false)); + if (try_connect_reversed) { + if (opl.end.point_id != -1) + by_point_id.emplace_back(OpenPolylineEnd(&opl, false)); + } } std::sort(by_edge_id .begin(), by_edge_id .end(), by_edge_lower); std::sort(by_point_id.begin(), by_point_id.end(), by_point_lower); @@ -1520,11 +1437,9 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo if (next_start->start) { auto it = next_start->polyline->points.begin(); std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points)); - //opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.end()); } else { auto it = next_start->polyline->points.rbegin(); std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points)); - //opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.rend()); } end = *next_start; end.start = !end.start; @@ -1541,14 +1456,16 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // Remove the duplicate last point. opl.points.pop_back(); if (opl.points.size() >= 3) { - // The closed polygon is patched from pieces with messed up orientation, therefore - // the orientation of the patched up polygon is not known. - // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. - double area = 0.; - for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++) - area += double(opl.points[j].x + opl.points[i].x) * double(opl.points[i].y - opl.points[j].y); - if (area < 0) - std::reverse(opl.points.begin(), opl.points.end()); + if (try_connect_reversed) { + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + double area = 0.; + for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++) + area += double(opl.points[j].x + opl.points[i].x) * double(opl.points[i].y - opl.points[j].y); + if (area < 0) + std::reverse(opl.points.begin(), opl.points.end()); + } loops->emplace_back(std::move(opl.points)); } opl.points.clear(); @@ -1558,6 +1475,87 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo } } } + + // Try to close gaps. + // Do it in two rounds, first try to connect in the same direction only, + // then try to connect the open polylines in reversed order as well. + const coord_t max_gap_scaled = (coord_t)scale_(2.); // 2mm + for (size_t round = 0; round < 2 && ! open_polylines.empty(); ++ round) { + bool try_connect_reversed = round == 1; + // Store the end points of open_polylines into ClosestPointInRadiusLookup. + struct OpenPolylineEnd { + OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} + OpenPolyline *polyline; + // Is it the start or end point? + bool start; + const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); } + }; + struct OpenPolylineEndAccessor { + const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + ClosestPointLookupType closest_end_point_lookup(max_gap_scaled); + for (OpenPolyline &opl : open_polylines) { + closest_end_point_lookup.insert(OpenPolylineEnd(&opl, true)); + if (try_connect_reversed) + closest_end_point_lookup.insert(OpenPolylineEnd(&opl, false)); + } + // Try to connect the loops. + for (OpenPolyline &opl : open_polylines) { + if (opl.consumed) + continue; + opl.consumed = true; + OpenPolylineEnd end(&opl, false); + for (;;) { + // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). + std::pair next_start_and_dist = closest_end_point_lookup.find(end.point()); + const OpenPolylineEnd *next_start = next_start_and_dist.first; + if (next_start == nullptr) { + // Check whether we closed this loop. + double dist = opl.points.front().distance_to(opl.points.back()); + if (dist < max_gap_scaled) { + if (dist == 0.) { + // Remove the duplicate last point. + opl.points.pop_back(); + } else { + // The end points are different, keep both of them. + } + if (opl.points.size() >= 3) { + if (try_connect_reversed) { + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + double area = 0.; + for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++) + area += double(opl.points[j].x + opl.points[i].x) * double(opl.points[i].y - opl.points[j].y); + if (area < 0) + std::reverse(opl.points.begin(), opl.points.end()); + } + loops->emplace_back(std::move(opl.points)); + } + opl.points.clear(); + break; + } + // The current loop could not be closed. Unmark the segment. + opl.consumed = false; + break; + } + // Attach this polyline to the end of the initial polyline. + if (next_start->start) { + auto it = next_start->polyline->points.begin(); + std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points)); + } else { + auto it = next_start->polyline->points.rbegin(); + std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points)); + } + end = *next_start; + end.start = !end.start; + next_start->polyline->points.clear(); + next_start->polyline->consumed = true; + // Continue with the current loop. + } + } + } } // Only used to cut the mesh into two halves. From f8eb215937e0b338a75e54968f6232e474e63a88 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 3 Oct 2018 13:54:04 +0200 Subject: [PATCH 47/63] Updated unit tests for triangle mesh slicing, as now the slicing considers the vertical structures to be open intervals at the bottom end, closed at the top end, so slicing at the bottom of a mesh produces empty slice. --- xs/t/01_trianglemesh.t | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/xs/t/01_trianglemesh.t b/xs/t/01_trianglemesh.t index fd57cf80511..4013a1f830b 100644 --- a/xs/t/01_trianglemesh.t +++ b/xs/t/01_trianglemesh.t @@ -79,7 +79,9 @@ my $cube = { my $m = Slic3r::TriangleMesh->new; $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); $m->repair; - my @z = (0,2,4,8,6,8,10,12,14,16,18,20); + # The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be + # open intervals at the bottom end, closed at the top end. + my @z = (0.0001,2,4,8,6,8,10,12,14,16,18,20); my $result = $m->slice(\@z); my $SCALING_FACTOR = 0.000001; for my $i (0..$#z) { @@ -105,7 +107,9 @@ my $cube = { # this second test also checks that performing a second slice on a mesh after # a transformation works properly (shared_vertices is correctly invalidated); # at Z = -10 we have a bottom horizontal surface - my $slices = $m->slice([ -5, -10 ]); + # (The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be + # open intervals at the bottom end, closed at the top end, so the Z = -10 is shifted a bit up to get a valid slice). + my $slices = $m->slice([ -5, -10+0.00001 ]); is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a bottom tangent plane includes its area'; } } From 38f8ed62a015c49ed9002fde7fd796c77afb3315 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 3 Oct 2018 16:12:24 +0200 Subject: [PATCH 48/63] Fix of SPE-513: unblockable supports There was a bug in the "don't support bridges" feature, where there were supports added to the end of non-supported bridges without taking into account the overhang angle or the support blockers. --- xs/src/libslic3r/SupportMaterial.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index cc689eaaab2..2f0fbbfd6cc 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -783,14 +783,13 @@ namespace SupportMaterialInternal { if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) polygons_append(bridges, surface.expolygon); //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? - contact_polygons = diff(contact_polygons, bridges, true); - // Add the bridge anchors into the region. + // Remove the unsupported ends of the bridges from the bridged areas. //FIXME add supports at regular intervals to support long bridges! - polygons_append(contact_polygons, - intersection( + bridges = diff(bridges, // Offset unsupported edges into polygons. - offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), - bridges)); + offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + // Remove bridged areas from the supported areas. + contact_polygons = diff(contact_polygons, bridges, true); } } From 79bbe7bec9474e341890ced94423db04029e1edf Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Oct 2018 11:16:55 +0200 Subject: [PATCH 49/63] =?UTF-8?q?Fix=20of=20SPE-525=20V=20p=C5=99ilo=C5=BE?= =?UTF-8?q?en=C3=A9m=20AMF=20se=20nap=C5=99.=20u=20spodn=C3=AD=20hrany=20o?= =?UTF-8?q?ken=20generuj=C3=AD=20pouze=20top=20solid=20layers,=20ale=20nik?= =?UTF-8?q?oliv=20solid=20layers.=20Objevuje=20se=20u=C5=BE=20v=201.41.0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xs/src/libslic3r/PrintObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index ea6b3928025..1ddcac13d88 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -915,7 +915,7 @@ void PrintObject::discover_vertical_shells() #if 1 // Intentionally inflate a bit more than how much the region has been shrunk, // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). - shell = offset2(shell, - 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); + shell = offset(offset_ex(union_ex(shell), - 0.5f * min_perimeter_infill_spacing), 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); if (shell.empty()) continue; #else From abd22b31c9896a5cb51cfcf01652ae1214fc6813 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Oct 2018 15:29:55 +0200 Subject: [PATCH 50/63] Improved slicing of objects with cracks and gaps. --- xs/src/libslic3r/Point.hpp | 2 +- xs/src/libslic3r/TriangleMesh.cpp | 67 ++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 347966c03a6..03bc2c2d2c1 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -177,7 +177,7 @@ template class ClosestPointInRadiusL } } } - return (value_min != nullptr && dist_min < coordf_t(m_search_radius * m_search_radius)) ? + return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ? std::make_pair(value_min, dist_min) : std::make_pair(nullptr, std::numeric_limits::max()); } diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 9576ec10d57..a1aa9d274ce 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -1245,6 +1245,21 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // only the bottom triangle is considered to be cutting the plane. // remove_tangent_edges(lines); +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + BoundingBox bbox_svg; + { + static int iRun = 0; + for (const Line &line : lines) { + bbox_svg.merge(line.a); + bbox_svg.merge(line.b); + } + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-raw_lines-%d.svg", iRun ++).c_str(), bbox_svg); + for (const Line &line : lines) + svg.draw(line); + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + struct OpenPolyline { OpenPolyline() {}; OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : @@ -1359,6 +1374,17 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo } } +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-%d.svg", iRun ++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) + svg.draw(Polyline(pl.points), "red"); + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + // Now process the open polylines. // Do it in two rounds, first try to connect in the same direction only, // then try to connect the open polylines in reversed order as well. @@ -1476,6 +1502,22 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo } } +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) { + if (pl.points.empty()) + continue; + svg.draw(Polyline(pl.points), "red"); + svg.draw(pl.points.front(), "blue"); + svg.draw(pl.points.back(), "blue"); + } + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + // Try to close gaps. // Do it in two rounds, first try to connect in the same direction only, // then try to connect the open polylines in reversed order as well. @@ -1506,22 +1548,23 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo continue; opl.consumed = true; OpenPolylineEnd end(&opl, false); + size_t n_segments_joined = 1; for (;;) { // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). std::pair next_start_and_dist = closest_end_point_lookup.find(end.point()); const OpenPolylineEnd *next_start = next_start_and_dist.first; - if (next_start == nullptr) { - // Check whether we closed this loop. - double dist = opl.points.front().distance_to(opl.points.back()); - if (dist < max_gap_scaled) { - if (dist == 0.) { + // Check whether we closed this loop. + double current_loop_closing_distance2 = opl.points.front().distance_to_sq(opl.points.back()); + if (next_start == nullptr || current_loop_closing_distance2 < next_start_and_dist.second) { + if (current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled)) { + if (current_loop_closing_distance2 == 0.) { // Remove the duplicate last point. opl.points.pop_back(); } else { // The end points are different, keep both of them. } if (opl.points.size() >= 3) { - if (try_connect_reversed) { + if (try_connect_reversed && n_segments_joined > 1) { // The closed polygon is patched from pieces with messed up orientation, therefore // the orientation of the patched up polygon is not known. // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. @@ -1548,6 +1591,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo auto it = next_start->polyline->points.rbegin(); std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points)); } + ++ n_segments_joined; end = *next_start; end.start = !end.start; next_start->polyline->points.clear(); @@ -1556,6 +1600,17 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo } } } + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) + svg.draw(Polyline(pl.points), "red"); + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // Only used to cut the mesh into two halves. From 517b96eef89b4127878a73d6f375841fabb555ca Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 5 Oct 2018 10:25:50 +0200 Subject: [PATCH 51/63] Yet another improvement in closing gaps in slices. Fixes #1256 and it finally fixes #1119 as well. --- xs/src/libslic3r/MultiPoint.hpp | 17 +++ xs/src/libslic3r/Point.hpp | 18 +++ xs/src/libslic3r/Polyline.hpp | 4 +- xs/src/libslic3r/TriangleMesh.cpp | 216 +++++++++++++++--------------- 4 files changed, 147 insertions(+), 108 deletions(-) diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index f4f82a353e0..02086ded8a8 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -105,6 +105,23 @@ extern BoundingBox get_extents(const MultiPoint &mp); extern BoundingBox get_extents_rotated(const std::vector &points, double angle); extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle); +inline double length(const Points &pts) { + double total = 0; + if (! pts.empty()) { + auto it = pts.begin(); + for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev) + total += it->distance_to(*it_prev); + } + return total; +} + +inline double area(const Points &polygon) { + double area = 0.; + for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++) + area += double(polygon[j].x + polygon[i].x) * double(polygon[i].y - polygon[j].y); + return area; +} + } // namespace Slic3r #endif diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 03bc2c2d2c1..a8c732fd7e3 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -150,6 +150,24 @@ template class ClosestPointInRadiusL m_map.emplace(std::make_pair(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2), std::move(value))); } + // Erase a data point equal to value. (ValueType has to declare the operator==). + // Returns true if the data point equal to value was found and removed. + bool erase(const ValueType &value) { + const Point *pt = m_point_accessor(value); + if (pt != nullptr) { + // Range of fragment starts around grid_corner, close to pt. + auto range = m_map.equal_range(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2)); + // Remove the first item. + for (auto it = range.first; it != range.second; ++ it) { + if (it->second == value) { + m_map.erase(it); + return true; + } + } + } + return false; + } + // Return a pair of std::pair find(const Point &pt) { // Iterate over 4 closest grid cells around pt, diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index 123ca5d2c41..774af3fabb8 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -81,8 +81,8 @@ extern BoundingBox get_extents(const Polylines &polylines); inline double total_length(const Polylines &polylines) { double total = 0; - for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) - total += it->length(); + for (const Polyline &pl : polylines) + total += pl.length(); return total; } diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index a1aa9d274ce..83cf0fa8856 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -1397,101 +1397,80 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // Is it the start or end point? bool start; const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; } - int point_id() const { return ipref().point_id; } - int edge_id () const { return ipref().edge_id; } + // Return a unique ID for the intersection point. + // Return a positive id for a point, or a negative id for an edge. + int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; } + bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } }; - auto by_edge_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.edge_id() < ope2.edge_id(); }; - auto by_point_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.point_id() < ope2.point_id(); }; - std::vector by_edge_id; - std::vector by_point_id; - by_edge_id.reserve(2 * open_polylines.size()); - by_point_id.reserve(2 * open_polylines.size()); + auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); }; + std::vector by_id; + by_id.reserve(2 * open_polylines.size()); for (OpenPolyline &opl : open_polylines) { - if (opl.start.edge_id != -1) - by_edge_id .emplace_back(OpenPolylineEnd(&opl, true)); - if (try_connect_reversed) { - if (opl.end.edge_id != -1) - by_edge_id .emplace_back(OpenPolylineEnd(&opl, false)); - } - if (opl.start.point_id != -1) - by_point_id.emplace_back(OpenPolylineEnd(&opl, true)); - if (try_connect_reversed) { - if (opl.end.point_id != -1) - by_point_id.emplace_back(OpenPolylineEnd(&opl, false)); - } + if (opl.start.point_id != -1 || opl.start.edge_id != -1) + by_id.emplace_back(OpenPolylineEnd(&opl, true)); + if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1)) + by_id.emplace_back(OpenPolylineEnd(&opl, false)); } - std::sort(by_edge_id .begin(), by_edge_id .end(), by_edge_lower); - std::sort(by_point_id.begin(), by_point_id.end(), by_point_lower); - + std::sort(by_id.begin(), by_id.end(), by_id_lower); + // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute). + auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector::iterator { + for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + it != by_id.end() && it->id() == end.id(); ++ it) + if (*it == end) + return it; + return by_id.end(); + }; // Try to connect the loops. for (OpenPolyline &opl : open_polylines) { if (opl.consumed) continue; opl.consumed = true; - OpenPolylineEnd end(&opl, false); + OpenPolylineEnd end(&opl, false); for (;;) { // find a line starting where last one finishes - OpenPolylineEnd* next_start = nullptr; - if (end.edge_id() != -1) { - auto it_begin = std::lower_bound(by_edge_id.begin(), by_edge_id.end(), end, by_edge_lower); - if (it_begin != by_edge_id.end()) { - auto it_end = std::upper_bound(it_begin, by_edge_id.end(), end, by_edge_lower); - for (auto it_edge = it_begin; it_edge != it_end; ++ it_edge) - if (! it_edge->polyline->consumed) { - next_start = &(*it_edge); - break; - } - } - } - if (next_start == nullptr && end.point_id() != -1) { - auto it_begin = std::lower_bound(by_point_id.begin(), by_point_id.end(), end, by_point_lower); - if (it_begin != by_point_id.end()) { - auto it_end = std::upper_bound(it_begin, by_point_id.end(), end, by_point_lower); - for (auto it_point = it_begin; it_point != it_end; ++ it_point) - if (! it_point->polyline->consumed) { - next_start = &(*it_point); - break; - } - } - } - if (next_start == nullptr) { - // The current loop could not be closed. Unmark the segment. - opl.consumed = false; - break; - } + auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start) + if (! it_next_start->polyline->consumed) + goto found; + // The current loop could not be closed. Unmark the segment. + opl.consumed = false; + break; + found: // Attach this polyline to the end of the initial polyline. - if (next_start->start) { - auto it = next_start->polyline->points.begin(); - std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points)); + if (it_next_start->start) { + auto it = it_next_start->polyline->points.begin(); + std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl.points)); } else { - auto it = next_start->polyline->points.rbegin(); - std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points)); + auto it = it_next_start->polyline->points.rbegin(); + std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl.points)); } - end = *next_start; - end.start = !end.start; - next_start->polyline->points.clear(); - next_start->polyline->consumed = true; + // Mark the next polyline as consumed. + it_next_start->polyline->points.clear(); + it_next_start->polyline->consumed = true; + if (try_connect_reversed) { + // Running in a mode, where the polylines may be connected by mixing their orientations. + // Update the end point lookup structure after the end point of the current polyline was extended. + auto it_end = find_polyline_end(end); + auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start)); + // Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag. + std::swap(opl.end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end); + // Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions. + std::swap(*it_end, *it_next_end); + } // Check whether we closed this loop. - const IntersectionReference &ip1 = opl.start; - const IntersectionReference &ip2 = end.ipref(); - if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) || - (ip1.point_id != -1 && ip1.point_id == ip2.point_id)) { + if ((opl.start.edge_id != -1 && opl.start.edge_id == opl.end.edge_id) || + (opl.start.point_id != -1 && opl.start.point_id == opl.end.point_id)) { // The current loop is complete. Add it to the output. //assert(opl.points.front().point_id == opl.points.back().point_id); //assert(opl.points.front().edge_id == opl.points.back().edge_id); // Remove the duplicate last point. opl.points.pop_back(); if (opl.points.size() >= 3) { - if (try_connect_reversed) { + if (try_connect_reversed && area(opl.points) < 0) // The closed polygon is patched from pieces with messed up orientation, therefore // the orientation of the patched up polygon is not known. // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. - double area = 0.; - for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++) - area += double(opl.points[j].x + opl.points[i].x) * double(opl.points[i].y - opl.points[j].y); - if (area < 0) - std::reverse(opl.points.begin(), opl.points.end()); - } + std::reverse(opl.points.begin(), opl.points.end()); loops->emplace_back(std::move(opl.points)); } opl.points.clear(); @@ -1531,6 +1510,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // Is it the start or end point? bool start; const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); } + bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } }; struct OpenPolylineEndAccessor { const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); } @@ -1546,57 +1526,76 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo for (OpenPolyline &opl : open_polylines) { if (opl.consumed) continue; - opl.consumed = true; OpenPolylineEnd end(&opl, false); + if (try_connect_reversed) + // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it. + closest_end_point_lookup.erase(end); + opl.consumed = true; size_t n_segments_joined = 1; for (;;) { // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). std::pair next_start_and_dist = closest_end_point_lookup.find(end.point()); const OpenPolylineEnd *next_start = next_start_and_dist.first; // Check whether we closed this loop. - double current_loop_closing_distance2 = opl.points.front().distance_to_sq(opl.points.back()); - if (next_start == nullptr || current_loop_closing_distance2 < next_start_and_dist.second) { - if (current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled)) { - if (current_loop_closing_distance2 == 0.) { - // Remove the duplicate last point. - opl.points.pop_back(); - } else { - // The end points are different, keep both of them. - } - if (opl.points.size() >= 3) { - if (try_connect_reversed && n_segments_joined > 1) { - // The closed polygon is patched from pieces with messed up orientation, therefore - // the orientation of the patched up polygon is not known. - // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. - double area = 0.; - for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++) - area += double(opl.points[j].x + opl.points[i].x) * double(opl.points[i].y - opl.points[j].y); - if (area < 0) - std::reverse(opl.points.begin(), opl.points.end()); - } - loops->emplace_back(std::move(opl.points)); - } - opl.points.clear(); - break; + double current_loop_closing_distance2 = opl.points.front().distance_to_sq(opl.points.back()); + bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled); + if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) { + // Heuristics to decide, whether to close the loop, or connect another polyline. + // One should avoid closing loops shorter than max_gap_scaled. + loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl.points); + } + if (loop_closed) { + // Remove the start point of the current polyline from the lookup. + // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail. + opl.consumed = false; + closest_end_point_lookup.erase(OpenPolylineEnd(&opl, true)); + if (current_loop_closing_distance2 == 0.) { + // Remove the duplicate last point. + opl.points.pop_back(); + } else { + // The end points are different, keep both of them. + } + if (opl.points.size() >= 3) { + if (try_connect_reversed && n_segments_joined > 1 && area(opl.points) < 0) + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + std::reverse(opl.points.begin(), opl.points.end()); + loops->emplace_back(std::move(opl.points)); } + opl.points.clear(); + opl.consumed = true; + break; + } + if (next_start == nullptr) { // The current loop could not be closed. Unmark the segment. opl.consumed = false; + if (try_connect_reversed) + // Re-insert the end point. + closest_end_point_lookup.insert(OpenPolylineEnd(&opl, false)); break; } // Attach this polyline to the end of the initial polyline. if (next_start->start) { auto it = next_start->polyline->points.begin(); - std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points)); + if (*it == opl.points.back()) + ++ it; + std::copy(it, next_start->polyline->points.end(), back_inserter(opl.points)); } else { auto it = next_start->polyline->points.rbegin(); - std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points)); + if (*it == opl.points.back()) + ++ it; + std::copy(it, next_start->polyline->points.rend(), back_inserter(opl.points)); } ++ n_segments_joined; - end = *next_start; - end.start = !end.start; - next_start->polyline->points.clear(); - next_start->polyline->consumed = true; - // Continue with the current loop. + // Remove the end points of the consumed polyline segment from the lookup. + OpenPolyline *opl2 = next_start->polyline; + closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true)); + if (try_connect_reversed) + closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false)); + opl2->points.clear(); + opl2->consumed = true; + // Continue with the current loop. } } } @@ -1606,8 +1605,13 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo static int iRun = 0; SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg); svg.draw(union_ex(*loops)); - for (const OpenPolyline &pl : open_polylines) - svg.draw(Polyline(pl.points), "red"); + for (const OpenPolyline &pl : open_polylines) { + if (pl.points.empty()) + continue; + svg.draw(Polyline(pl.points), "red"); + svg.draw(pl.points.front(), "blue"); + svg.draw(pl.points.back(), "blue"); + } svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ From b8c2566c447b3c261a092ce9409c791048114ffb Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 5 Oct 2018 11:23:24 +0200 Subject: [PATCH 52/63] Slicing of meshes, chaining of open polylines: The open polylines are sorted by decreasing length, so the new chains are seeded with the longer chains. --- xs/src/libslic3r/TriangleMesh.cpp | 648 ++++++++++++++++-------------- 1 file changed, 345 insertions(+), 303 deletions(-) diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 83cf0fa8856..544a5d00bcc 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -1,6 +1,7 @@ #include "TriangleMesh.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" +#include "MultiPoint.hpp" #include "qhull/src/libqhullcpp/Qhull.h" #include "qhull/src/libqhullcpp/QhullFacetList.h" #include "qhull/src/libqhullcpp/QhullVertexSet.h" @@ -1232,6 +1233,344 @@ static inline void remove_tangent_edges(std::vector &lines) } } +struct OpenPolyline { + OpenPolyline() {}; + OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : + start(start), end(end), points(std::move(points)), consumed(false) { this->length = Slic3r::length(this->points); } + void reverse() { + std::swap(start, end); + std::reverse(points.begin(), points.end()); + } + IntersectionReference start; + IntersectionReference end; + Points points; + double length; + bool consumed; +}; + +// called by TriangleMeshSlicer::make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity. +// Only connects segments crossing triangles of the same orientation. +static void chain_lines_by_triangle_connectivity(std::vector &lines, Polygons &loops, std::vector &open_polylines) +{ + // Build a map of lines by edge_a_id and a_id. + std::vector by_edge_a_id; + std::vector by_a_id; + by_edge_a_id.reserve(lines.size()); + by_a_id.reserve(lines.size()); + for (IntersectionLine &line : lines) { + if (! line.skip()) { + if (line.edge_a_id != -1) + by_edge_a_id.emplace_back(&line); + if (line.a_id != -1) + by_a_id.emplace_back(&line); + } + } + auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; + auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; + std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); + std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); + // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. + IntersectionLines::iterator it_line_seed = lines.begin(); + for (;;) { + // take first spare line and start a new loop + IntersectionLine *first_line = nullptr; + for (; it_line_seed != lines.end(); ++ it_line_seed) + if (it_line_seed->is_seed_candidate()) { + //if (! it_line_seed->skip()) { + first_line = &(*it_line_seed ++); + break; + } + if (first_line == nullptr) + break; + first_line->set_skip(); + Points loop_pts; + loop_pts.emplace_back(first_line->a); + IntersectionLine *last_line = first_line; + + /* + printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, + first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); + */ + + IntersectionLine key; + for (;;) { + // find a line starting where last one finishes + IntersectionLine* next_line = nullptr; + if (last_line->edge_b_id != -1) { + key.edge_a_id = last_line->edge_b_id; + auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); + if (it_begin != by_edge_a_id.end()) { + auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); + for (auto it_line = it_begin; it_line != it_end; ++ it_line) + if (! (*it_line)->skip()) { + next_line = *it_line; + break; + } + } + } + if (next_line == nullptr && last_line->b_id != -1) { + key.a_id = last_line->b_id; + auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); + if (it_begin != by_a_id.end()) { + auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); + for (auto it_line = it_begin; it_line != it_end; ++ it_line) + if (! (*it_line)->skip()) { + next_line = *it_line; + break; + } + } + } + if (next_line == nullptr) { + // Check whether we closed this loop. + if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || + (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { + // The current loop is complete. Add it to the output. + loops.emplace_back(std::move(loop_pts)); + #ifdef SLIC3R_TRIANGLEMESH_DEBUG + printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); + #endif + } else { + // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. + loop_pts.emplace_back(last_line->b); + open_polylines.emplace_back(OpenPolyline( + IntersectionReference(first_line->a_id, first_line->edge_a_id), + IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); + } + break; + } + /* + printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, + next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); + */ + loop_pts.emplace_back(next_line->a); + last_line = next_line; + next_line->set_skip(); + } + } +} + +std::vector open_polylines_sorted(std::vector &open_polylines, bool update_lengths) +{ + std::vector out; + out.reserve(open_polylines.size()); + for (OpenPolyline &opl : open_polylines) + if (! opl.consumed) { + if (update_lengths) + opl.length = Slic3r::length(opl.points); + out.emplace_back(&opl); + } + std::sort(out.begin(), out.end(), [](const OpenPolyline *lhs, const OpenPolyline *rhs){ return lhs->length > rhs->length; }); + return out; +} + +// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices. +// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. +static void chain_open_polylines_exact(std::vector &open_polylines, Polygons &loops, bool try_connect_reversed) +{ + // Store the end points of open_polylines into vectors sorted + struct OpenPolylineEnd { + OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} + OpenPolyline *polyline; + // Is it the start or end point? + bool start; + const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; } + // Return a unique ID for the intersection point. + // Return a positive id for a point, or a negative id for an edge. + int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; } + bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } + }; + auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); }; + std::vector by_id; + by_id.reserve(2 * open_polylines.size()); + for (OpenPolyline &opl : open_polylines) { + if (opl.start.point_id != -1 || opl.start.edge_id != -1) + by_id.emplace_back(OpenPolylineEnd(&opl, true)); + if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1)) + by_id.emplace_back(OpenPolylineEnd(&opl, false)); + } + std::sort(by_id.begin(), by_id.end(), by_id_lower); + // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute). + auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector::iterator { + for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + it != by_id.end() && it->id() == end.id(); ++ it) + if (*it == end) + return it; + return by_id.end(); + }; + // Try to connect the loops. + std::vector sorted_by_length = open_polylines_sorted(open_polylines, false); + for (OpenPolyline *opl : sorted_by_length) { + if (opl->consumed) + continue; + opl->consumed = true; + OpenPolylineEnd end(opl, false); + for (;;) { + // find a line starting where last one finishes + auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start) + if (! it_next_start->polyline->consumed) + goto found; + // The current loop could not be closed. Unmark the segment. + opl->consumed = false; + break; + found: + // Attach this polyline to the end of the initial polyline. + if (it_next_start->start) { + auto it = it_next_start->polyline->points.begin(); + std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl->points)); + } else { + auto it = it_next_start->polyline->points.rbegin(); + std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl->points)); + } + opl->length += it_next_start->polyline->length; + // Mark the next polyline as consumed. + it_next_start->polyline->points.clear(); + it_next_start->polyline->length = 0.; + it_next_start->polyline->consumed = true; + if (try_connect_reversed) { + // Running in a mode, where the polylines may be connected by mixing their orientations. + // Update the end point lookup structure after the end point of the current polyline was extended. + auto it_end = find_polyline_end(end); + auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start)); + // Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag. + std::swap(opl->end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end); + // Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions. + std::swap(*it_end, *it_next_end); + } + // Check whether we closed this loop. + if ((opl->start.edge_id != -1 && opl->start.edge_id == opl->end.edge_id) || + (opl->start.point_id != -1 && opl->start.point_id == opl->end.point_id)) { + // The current loop is complete. Add it to the output. + //assert(opl->points.front().point_id == opl->points.back().point_id); + //assert(opl->points.front().edge_id == opl->points.back().edge_id); + // Remove the duplicate last point. + opl->points.pop_back(); + if (opl->points.size() >= 3) { + if (try_connect_reversed && area(opl->points) < 0) + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + std::reverse(opl->points.begin(), opl->points.end()); + loops.emplace_back(std::move(opl->points)); + } + opl->points.clear(); + break; + } + // Continue with the current loop. + } + } +} + +// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices, +// possibly closing small gaps. +// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. +static void chain_open_polylines_close_gaps(std::vector &open_polylines, Polygons &loops, double max_gap, bool try_connect_reversed) +{ + const coord_t max_gap_scaled = (coord_t)scale_(max_gap); + + // Sort the open polylines by their length, so the new loops will be seeded from longer chains. + // Update the polyline lengths, return only not yet consumed polylines. + std::vector sorted_by_length = open_polylines_sorted(open_polylines, true); + + // Store the end points of open_polylines into ClosestPointInRadiusLookup. + struct OpenPolylineEnd { + OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} + OpenPolyline *polyline; + // Is it the start or end point? + bool start; + const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); } + bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } + }; + struct OpenPolylineEndAccessor { + const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + ClosestPointLookupType closest_end_point_lookup(max_gap_scaled); + for (OpenPolyline *opl : sorted_by_length) { + closest_end_point_lookup.insert(OpenPolylineEnd(opl, true)); + if (try_connect_reversed) + closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); + } + // Try to connect the loops. + for (OpenPolyline *opl : sorted_by_length) { + if (opl->consumed) + continue; + OpenPolylineEnd end(opl, false); + if (try_connect_reversed) + // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it. + closest_end_point_lookup.erase(end); + opl->consumed = true; + size_t n_segments_joined = 1; + for (;;) { + // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). + std::pair next_start_and_dist = closest_end_point_lookup.find(end.point()); + const OpenPolylineEnd *next_start = next_start_and_dist.first; + // Check whether we closed this loop. + double current_loop_closing_distance2 = opl->points.front().distance_to_sq(opl->points.back()); + bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled); + if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) { + // Heuristics to decide, whether to close the loop, or connect another polyline. + // One should avoid closing loops shorter than max_gap_scaled. + loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl->points); + } + if (loop_closed) { + // Remove the start point of the current polyline from the lookup. + // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail. + opl->consumed = false; + closest_end_point_lookup.erase(OpenPolylineEnd(opl, true)); + if (current_loop_closing_distance2 == 0.) { + // Remove the duplicate last point. + opl->points.pop_back(); + } else { + // The end points are different, keep both of them. + } + if (opl->points.size() >= 3) { + if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0) + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + std::reverse(opl->points.begin(), opl->points.end()); + loops.emplace_back(std::move(opl->points)); + } + opl->points.clear(); + opl->consumed = true; + break; + } + if (next_start == nullptr) { + // The current loop could not be closed. Unmark the segment. + opl->consumed = false; + if (try_connect_reversed) + // Re-insert the end point. + closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); + break; + } + // Attach this polyline to the end of the initial polyline. + if (next_start->start) { + auto it = next_start->polyline->points.begin(); + if (*it == opl->points.back()) + ++ it; + std::copy(it, next_start->polyline->points.end(), back_inserter(opl->points)); + } else { + auto it = next_start->polyline->points.rbegin(); + if (*it == opl->points.back()) + ++ it; + std::copy(it, next_start->polyline->points.rend(), back_inserter(opl->points)); + } + ++ n_segments_joined; + // Remove the end points of the consumed polyline segment from the lookup. + OpenPolyline *opl2 = next_start->polyline; + closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true)); + if (try_connect_reversed) + closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false)); + opl2->points.clear(); + opl2->consumed = true; + // Continue with the current loop. + } + } +} + void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { #if 0 @@ -1260,119 +1599,8 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - struct OpenPolyline { - OpenPolyline() {}; - OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : - start(start), end(end), points(std::move(points)), consumed(false) {} - void reverse() { - std::swap(start, end); - std::reverse(points.begin(), points.end()); - } - IntersectionReference start; - IntersectionReference end; - Points points; - bool consumed; - }; std::vector open_polylines; - { - // Build a map of lines by edge_a_id and a_id. - std::vector by_edge_a_id; - std::vector by_a_id; - by_edge_a_id.reserve(lines.size()); - by_a_id.reserve(lines.size()); - for (IntersectionLine &line : lines) { - if (! line.skip()) { - if (line.edge_a_id != -1) - by_edge_a_id.emplace_back(&line); - if (line.a_id != -1) - by_a_id.emplace_back(&line); - } - } - auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; - auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; - std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); - std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); - // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. - IntersectionLines::iterator it_line_seed = lines.begin(); - for (;;) { - // take first spare line and start a new loop - IntersectionLine *first_line = nullptr; - for (; it_line_seed != lines.end(); ++ it_line_seed) - if (it_line_seed->is_seed_candidate()) { - //if (! it_line_seed->skip()) { - first_line = &(*it_line_seed ++); - break; - } - if (first_line == nullptr) - break; - first_line->set_skip(); - Points loop_pts; - loop_pts.emplace_back(first_line->a); - IntersectionLine *last_line = first_line; - - /* - printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, - first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); - */ - - IntersectionLine key; - for (;;) { - // find a line starting where last one finishes - IntersectionLine* next_line = nullptr; - if (last_line->edge_b_id != -1) { - key.edge_a_id = last_line->edge_b_id; - auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); - if (it_begin != by_edge_a_id.end()) { - auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); - for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip()) { - next_line = *it_line; - break; - } - } - } - if (next_line == nullptr && last_line->b_id != -1) { - key.a_id = last_line->b_id; - auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); - if (it_begin != by_a_id.end()) { - auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); - for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip()) { - next_line = *it_line; - break; - } - } - } - if (next_line == nullptr) { - // Check whether we closed this loop. - if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || - (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { - // The current loop is complete. Add it to the output. - loops->emplace_back(std::move(loop_pts)); - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); - #endif - } else { - // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. - loop_pts.emplace_back(last_line->b); - open_polylines.emplace_back(OpenPolyline( - IntersectionReference(first_line->a_id, first_line->edge_a_id), - IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); - } - break; - } - /* - printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, - next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); - */ - loop_pts.emplace_back(next_line->a); - last_line = next_line; - next_line->set_skip(); - } - } - } + chain_lines_by_triangle_connectivity(lines, *loops, open_polylines); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -1388,98 +1616,8 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // Now process the open polylines. // Do it in two rounds, first try to connect in the same direction only, // then try to connect the open polylines in reversed order as well. - for (size_t round = 0; round < 2 && ! open_polylines.empty(); ++ round) { - bool try_connect_reversed = round == 1; - // Store the end points of open_polylines into vectors sorted - struct OpenPolylineEnd { - OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} - OpenPolyline *polyline; - // Is it the start or end point? - bool start; - const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; } - // Return a unique ID for the intersection point. - // Return a positive id for a point, or a negative id for an edge. - int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; } - bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } - }; - auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); }; - std::vector by_id; - by_id.reserve(2 * open_polylines.size()); - for (OpenPolyline &opl : open_polylines) { - if (opl.start.point_id != -1 || opl.start.edge_id != -1) - by_id.emplace_back(OpenPolylineEnd(&opl, true)); - if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1)) - by_id.emplace_back(OpenPolylineEnd(&opl, false)); - } - std::sort(by_id.begin(), by_id.end(), by_id_lower); - // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute). - auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector::iterator { - for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); - it != by_id.end() && it->id() == end.id(); ++ it) - if (*it == end) - return it; - return by_id.end(); - }; - // Try to connect the loops. - for (OpenPolyline &opl : open_polylines) { - if (opl.consumed) - continue; - opl.consumed = true; - OpenPolylineEnd end(&opl, false); - for (;;) { - // find a line starting where last one finishes - auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); - for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start) - if (! it_next_start->polyline->consumed) - goto found; - // The current loop could not be closed. Unmark the segment. - opl.consumed = false; - break; - found: - // Attach this polyline to the end of the initial polyline. - if (it_next_start->start) { - auto it = it_next_start->polyline->points.begin(); - std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl.points)); - } else { - auto it = it_next_start->polyline->points.rbegin(); - std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl.points)); - } - // Mark the next polyline as consumed. - it_next_start->polyline->points.clear(); - it_next_start->polyline->consumed = true; - if (try_connect_reversed) { - // Running in a mode, where the polylines may be connected by mixing their orientations. - // Update the end point lookup structure after the end point of the current polyline was extended. - auto it_end = find_polyline_end(end); - auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start)); - // Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag. - std::swap(opl.end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end); - // Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions. - std::swap(*it_end, *it_next_end); - } - // Check whether we closed this loop. - if ((opl.start.edge_id != -1 && opl.start.edge_id == opl.end.edge_id) || - (opl.start.point_id != -1 && opl.start.point_id == opl.end.point_id)) { - // The current loop is complete. Add it to the output. - //assert(opl.points.front().point_id == opl.points.back().point_id); - //assert(opl.points.front().edge_id == opl.points.back().edge_id); - // Remove the duplicate last point. - opl.points.pop_back(); - if (opl.points.size() >= 3) { - if (try_connect_reversed && area(opl.points) < 0) - // The closed polygon is patched from pieces with messed up orientation, therefore - // the orientation of the patched up polygon is not known. - // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. - std::reverse(opl.points.begin(), opl.points.end()); - loops->emplace_back(std::move(opl.points)); - } - opl.points.clear(); - break; - } - // Continue with the current loop. - } - } - } + chain_open_polylines_exact(open_polylines, *loops, false); + chain_open_polylines_exact(open_polylines, *loops, true); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -1500,105 +1638,9 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // Try to close gaps. // Do it in two rounds, first try to connect in the same direction only, // then try to connect the open polylines in reversed order as well. - const coord_t max_gap_scaled = (coord_t)scale_(2.); // 2mm - for (size_t round = 0; round < 2 && ! open_polylines.empty(); ++ round) { - bool try_connect_reversed = round == 1; - // Store the end points of open_polylines into ClosestPointInRadiusLookup. - struct OpenPolylineEnd { - OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} - OpenPolyline *polyline; - // Is it the start or end point? - bool start; - const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); } - bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } - }; - struct OpenPolylineEndAccessor { - const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); } - }; - typedef ClosestPointInRadiusLookup ClosestPointLookupType; - ClosestPointLookupType closest_end_point_lookup(max_gap_scaled); - for (OpenPolyline &opl : open_polylines) { - closest_end_point_lookup.insert(OpenPolylineEnd(&opl, true)); - if (try_connect_reversed) - closest_end_point_lookup.insert(OpenPolylineEnd(&opl, false)); - } - // Try to connect the loops. - for (OpenPolyline &opl : open_polylines) { - if (opl.consumed) - continue; - OpenPolylineEnd end(&opl, false); - if (try_connect_reversed) - // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it. - closest_end_point_lookup.erase(end); - opl.consumed = true; - size_t n_segments_joined = 1; - for (;;) { - // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). - std::pair next_start_and_dist = closest_end_point_lookup.find(end.point()); - const OpenPolylineEnd *next_start = next_start_and_dist.first; - // Check whether we closed this loop. - double current_loop_closing_distance2 = opl.points.front().distance_to_sq(opl.points.back()); - bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled); - if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) { - // Heuristics to decide, whether to close the loop, or connect another polyline. - // One should avoid closing loops shorter than max_gap_scaled. - loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl.points); - } - if (loop_closed) { - // Remove the start point of the current polyline from the lookup. - // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail. - opl.consumed = false; - closest_end_point_lookup.erase(OpenPolylineEnd(&opl, true)); - if (current_loop_closing_distance2 == 0.) { - // Remove the duplicate last point. - opl.points.pop_back(); - } else { - // The end points are different, keep both of them. - } - if (opl.points.size() >= 3) { - if (try_connect_reversed && n_segments_joined > 1 && area(opl.points) < 0) - // The closed polygon is patched from pieces with messed up orientation, therefore - // the orientation of the patched up polygon is not known. - // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. - std::reverse(opl.points.begin(), opl.points.end()); - loops->emplace_back(std::move(opl.points)); - } - opl.points.clear(); - opl.consumed = true; - break; - } - if (next_start == nullptr) { - // The current loop could not be closed. Unmark the segment. - opl.consumed = false; - if (try_connect_reversed) - // Re-insert the end point. - closest_end_point_lookup.insert(OpenPolylineEnd(&opl, false)); - break; - } - // Attach this polyline to the end of the initial polyline. - if (next_start->start) { - auto it = next_start->polyline->points.begin(); - if (*it == opl.points.back()) - ++ it; - std::copy(it, next_start->polyline->points.end(), back_inserter(opl.points)); - } else { - auto it = next_start->polyline->points.rbegin(); - if (*it == opl.points.back()) - ++ it; - std::copy(it, next_start->polyline->points.rend(), back_inserter(opl.points)); - } - ++ n_segments_joined; - // Remove the end points of the consumed polyline segment from the lookup. - OpenPolyline *opl2 = next_start->polyline; - closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true)); - if (try_connect_reversed) - closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false)); - opl2->points.clear(); - opl2->consumed = true; - // Continue with the current loop. - } - } - } + const double max_gap = 2.; //mm + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { From eb595683680e78b60ae00038f877927033509413 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 11 Oct 2018 14:13:52 +0200 Subject: [PATCH 53/63] Fix of "Supports not generating interface layers" #1306 Fix of "Spurious support interfaces" #1309 --- xs/src/libslic3r/EdgeGrid.cpp | 155 +++++++++++++++++++- xs/src/libslic3r/EdgeGrid.hpp | 12 ++ xs/src/libslic3r/SupportMaterial.cpp | 207 ++++++++++++++++++++++++++- 3 files changed, 367 insertions(+), 7 deletions(-) diff --git a/xs/src/libslic3r/EdgeGrid.cpp b/xs/src/libslic3r/EdgeGrid.cpp index 801a27e3a51..9249d0ff1ba 100644 --- a/xs/src/libslic3r/EdgeGrid.cpp +++ b/xs/src/libslic3r/EdgeGrid.cpp @@ -9,7 +9,9 @@ #endif /* SLIC3R_GUI */ #include "libslic3r.h" +#include "ClipperUtils.hpp" #include "EdgeGrid.hpp" +#include "SVG.hpp" #if 0 // Enable debugging and assert in this file. @@ -756,8 +758,8 @@ void EdgeGrid::Grid::calculate_sdf() float search_radius = float(m_resolution<<1); m_signed_distance_field.assign(nrows * ncols, search_radius); // For each cell: - for (size_t r = 0; r < m_rows; ++ r) { - for (size_t c = 0; c < m_cols; ++ c) { + for (int r = 0; r < (int)m_rows; ++ r) { + for (int c = 0; c < (int)m_cols; ++ c) { const Cell &cell = m_cells[r * m_cols + c]; // For each segment in the cell: for (size_t i = cell.begin; i != cell.end; ++ i) { @@ -842,6 +844,8 @@ void EdgeGrid::Grid::calculate_sdf() #if 0 static int iRun = 0; ++ iRun; + if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) + wxImage::AddHandler(new wxPNGHandler); //#ifdef SLIC3R_GUI { wxImage img(ncols, nrows); @@ -1356,9 +1360,101 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) co return out; } +inline int segments_could_intersect( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + Slic3r::Point iv = ip1.vector_to(ip2); + Slic3r::Point vij1 = ip1.vector_to(jp1); + Slic3r::Point vij2 = ip1.vector_to(jp2); + int64_t tij1 = int64_t(iv.x) * int64_t(vij1.y) - int64_t(iv.y) * int64_t(vij1.x); // cross(iv, vij1) + int64_t tij2 = int64_t(iv.x) * int64_t(vij2.y) - int64_t(iv.y) * int64_t(vij2.x); // cross(iv, vij2) + int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum + int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0); + return sij1 * sij2; +} + +inline bool segments_intersect( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 && + segments_could_intersect(jp1, jp2, ip1, ip2) <= 0; +} + +std::vector> EdgeGrid::Grid::intersecting_edges() const +{ + std::vector> out; + // For each cell: + for (int r = 0; r < (int)m_rows; ++ r) { + for (int c = 0; c < (int)m_cols; ++ c) { + const Cell &cell = m_cells[r * m_cols + c]; + // For each pair of segments in the cell: + for (size_t i = cell.begin; i != cell.end; ++ i) { + const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first]; + size_t ipt = m_cell_data[i].second; + // End points of the line segment and their vector. + const Slic3r::Point &ip1 = ipts[ipt]; + const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]; + for (size_t j = i + 1; j != cell.end; ++ j) { + const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first]; + size_t jpt = m_cell_data[j].second; + // End points of the line segment and their vector. + const Slic3r::Point &jp1 = jpts[jpt]; + const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1]; + if (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) + // Segments of the same contour share a common vertex. + continue; + if (segments_intersect(ip1, ip2, jp1, jp2)) { + // The two segments intersect. Add them to the output. + int jfirst = (&jpts < &ipts) || (&jpts == &ipts && jpt < ipt); + out.emplace_back(jfirst ? + std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt)) : + std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt))); + } + } + } + } + } + Slic3r::sort_remove_duplicates(out); + return out; +} + +bool EdgeGrid::Grid::has_intersecting_edges() const +{ + // For each cell: + for (int r = 0; r < (int)m_rows; ++ r) { + for (int c = 0; c < (int)m_cols; ++ c) { + const Cell &cell = m_cells[r * m_cols + c]; + // For each pair of segments in the cell: + for (size_t i = cell.begin; i != cell.end; ++ i) { + const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first]; + size_t ipt = m_cell_data[i].second; + // End points of the line segment and their vector. + const Slic3r::Point &ip1 = ipts[ipt]; + const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]; + for (size_t j = i + 1; j != cell.end; ++ j) { + const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first]; + size_t jpt = m_cell_data[j].second; + // End points of the line segment and their vector. + const Slic3r::Point &jp1 = jpts[jpt]; + const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1]; + if (! (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) && + segments_intersect(ip1, ip2, jp1, jp2)) + return true; + } + } + } + } + return false; +} + #if 0 void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path) { + if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) + wxImage::AddHandler(new wxPNGHandler); + unsigned int w = (bbox.max.x - bbox.min.x + resolution - 1) / resolution; unsigned int h = (bbox.max.y - bbox.min.y + resolution - 1) / resolution; wxImage img(w, h); @@ -1450,4 +1546,59 @@ void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coo } #endif /* SLIC3R_GUI */ +// Find all pairs of intersectiong edges from the set of polygons. +std::vector> intersecting_edges(const Polygons &polygons) +{ + double len = 0; + size_t cnt = 0; + BoundingBox bbox; + for (const Polygon &poly : polygons) { + if (poly.points.size() < 2) + continue; + for (size_t i = 0; i < poly.points.size(); ++ i) { + bbox.merge(poly.points[i]); + size_t j = (i == 0) ? (poly.points.size() - 1) : i - 1; + len += poly.points[i].distance_to(poly.points[j]); + ++ cnt; + } + } + len /= double(cnt); + bbox.offset(20); + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(polygons, len); + return grid.intersecting_edges(); +} + +// Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG. +void export_intersections_to_svg(const std::string &filename, const Polygons &polygons) +{ + std::vector> intersections = intersecting_edges(polygons); + BoundingBox bbox = get_extents(polygons); + SVG svg(filename.c_str(), bbox); + svg.draw(union_ex(polygons), "gray", 0.25f); + svg.draw_outline(polygons, "black"); + std::set intersecting_contours; + for (const std::pair &ie : intersections) { + intersecting_contours.insert(ie.first.first); + intersecting_contours.insert(ie.second.first); + } + // Highlight the contours with intersections. + coord_t line_width = coord_t(scale_(0.01)); + for (const Points *ic : intersecting_contours) { + svg.draw_outline(Polygon(*ic), "green"); + svg.draw_outline(Polygon(*ic), "black", line_width); + } + // Paint the intersections. + for (const std::pair &intersecting_edges : intersections) { + auto edge = [](const EdgeGrid::Grid::ContourEdge &e) { + return Line(e.first->at(e.second), + e.first->at((e.second + 1 == e.first->size()) ? 0 : e.second + 1)); + }; + svg.draw(edge(intersecting_edges.first), "red", line_width); + svg.draw(edge(intersecting_edges.second), "red", line_width); + } + svg.Close(); +} + } // namespace Slic3r diff --git a/xs/src/libslic3r/EdgeGrid.hpp b/xs/src/libslic3r/EdgeGrid.hpp index ab1aa4ed0a0..f99ab30c463 100644 --- a/xs/src/libslic3r/EdgeGrid.hpp +++ b/xs/src/libslic3r/EdgeGrid.hpp @@ -60,6 +60,11 @@ class Grid // For supports: Contours enclosing the rasterized edges. Polygons contours_simplified(coord_t offset, bool fill_holes) const; + typedef std::pair ContourPoint; + typedef std::pair ContourEdge; + std::vector> intersecting_edges() const; + bool has_intersecting_edges() const; + protected: struct Cell { Cell() : begin(0), end(0) {} @@ -113,6 +118,13 @@ extern void save_png(const Grid &grid, const BoundingBox &bbox, coord_t resoluti #endif /* SLIC3R_GUI */ } // namespace EdgeGrid + +// Find all pairs of intersectiong edges from the set of polygons. +extern std::vector> intersecting_edges(const Polygons &polygons); + +// Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG. +extern void export_intersections_to_svg(const std::string &filename, const Polygons &polygons); + } // namespace Slic3r #endif /* slic3r_EdgeGrid_hpp_ */ diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 2f0fbbfd6cc..e31b7a201a4 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -458,6 +458,8 @@ Polygons collect_slices_outer(const Layer &layer) class SupportGridPattern { public: + // Achtung! The support_polygons need to be trimmed by trimming_polygons, otherwise + // the selection by island_samples (see the island_samples() method) will not work! SupportGridPattern( // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy) const Polygons &support_polygons, @@ -485,6 +487,18 @@ class SupportGridPattern bbox.align_to_grid(grid_resolution); m_grid.set_bbox(bbox); m_grid.create(*m_support_polygons, grid_resolution); +#if 0 + if (m_grid.has_intersecting_edges()) { + // EdgeGrid fails to produce valid signed distance function for self-intersecting polygons. + m_support_polygons_rotated = simplify_polygons(*m_support_polygons); + m_support_polygons = &m_support_polygons_rotated; + m_grid.set_bbox(bbox); + m_grid.create(*m_support_polygons, grid_resolution); +// assert(! m_grid.has_intersecting_edges()); + printf("SupportGridPattern: fixing polygons with intersection %s\n", + m_grid.has_intersecting_edges() ? "FAILED" : "SUCCEEDED"); + } +#endif m_grid.calculate_sdf(); // Sample a single point per input support polygon, keep it as a reference to maintain corresponding // polygons if ever these polygons get split into parts by the trimming polygons. @@ -499,9 +513,12 @@ class SupportGridPattern { // Generate islands, so each island may be tested for overlap with m_island_samples. assert(std::abs(2 * offset_in_grid) < m_grid.resolution()); - ExPolygons islands = diff_ex( - m_grid.contours_simplified(offset_in_grid, fill_holes), - *m_trimming_polygons, false); +#ifdef SLIC3R_DEBUG + Polygons support_polygons_simplified = m_grid.contours_simplified(offset_in_grid, fill_holes); + ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons, false); +#else + ExPolygons islands = diff_ex(m_grid.contours_simplified(offset_in_grid, fill_holes), *m_trimming_polygons, false); +#endif // Extract polygons, which contain some of the m_island_samples. Polygons out; @@ -551,7 +568,10 @@ class SupportGridPattern bbox.merge(get_extents(islands)); if (!out.empty()) bbox.merge(get_extents(out)); + if (!support_polygons_simplified.empty()) + bbox.merge(get_extents(support_polygons_simplified)); SVG svg(debug_out_path("extract_support_from_grid_trimmed-%d.svg", iRun).c_str(), bbox); + svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f); svg.draw(islands, "red", 0.5f); svg.draw(union_ex(out), "green", 0.5f); svg.draw(union_ex(*m_support_polygons), "blue", 0.5f); @@ -568,7 +588,121 @@ class SupportGridPattern return out; } +#ifdef SLIC3R_DEBUG + void serialize(const std::string &path) + { + FILE *file = ::fopen(path.c_str(), "wb"); + ::fwrite(&m_support_spacing, 8, 1, file); + ::fwrite(&m_support_angle, 8, 1, file); + uint32_t n_polygons = m_support_polygons->size(); + ::fwrite(&n_polygons, 4, 1, file); + for (uint32_t i = 0; i < n_polygons; ++ i) { + const Polygon &poly = (*m_support_polygons)[i]; + uint32_t n_points = poly.size(); + ::fwrite(&n_points, 4, 1, file); + for (uint32_t j = 0; j < n_points; ++ j) { + const Point &pt = poly.points[j]; + ::fwrite(&pt.x, sizeof(coord_t), 1, file); + ::fwrite(&pt.y, sizeof(coord_t), 1, file); + } + } + n_polygons = m_trimming_polygons->size(); + ::fwrite(&n_polygons, 4, 1, file); + for (uint32_t i = 0; i < n_polygons; ++ i) { + const Polygon &poly = (*m_trimming_polygons)[i]; + uint32_t n_points = poly.size(); + ::fwrite(&n_points, 4, 1, file); + for (uint32_t j = 0; j < n_points; ++ j) { + const Point &pt = poly.points[j]; + ::fwrite(&pt.x, sizeof(coord_t), 1, file); + ::fwrite(&pt.y, sizeof(coord_t), 1, file); + } + } + ::fclose(file); + } + + static SupportGridPattern deserialize(const std::string &path, int which = -1) + { + SupportGridPattern out; + out.deserialize_(path, which); + return out; + } + + // Deserialization constructor + bool deserialize_(const std::string &path, int which = -1) + { + FILE *file = ::fopen(path.c_str(), "rb"); + if (file == nullptr) + return false; + + m_support_polygons = &m_support_polygons_deserialized; + m_trimming_polygons = &m_trimming_polygons_deserialized; + + ::fread(&m_support_spacing, 8, 1, file); + ::fread(&m_support_angle, 8, 1, file); + //FIXME + //m_support_spacing *= 0.01 / 2; + uint32_t n_polygons; + ::fread(&n_polygons, 4, 1, file); + m_support_polygons_deserialized.reserve(n_polygons); + int32_t scale = 1; + for (uint32_t i = 0; i < n_polygons; ++ i) { + Polygon poly; + uint32_t n_points; + ::fread(&n_points, 4, 1, file); + poly.points.reserve(n_points); + for (uint32_t j = 0; j < n_points; ++ j) { + coord_t x, y; + ::fread(&x, sizeof(coord_t), 1, file); + ::fread(&y, sizeof(coord_t), 1, file); + poly.points.emplace_back(Point(x * scale, y * scale)); + } + if (which == -1 || which == i) + m_support_polygons_deserialized.emplace_back(std::move(poly)); + printf("Polygon %d, area: %lf\n", i, area(poly.points)); + } + ::fread(&n_polygons, 4, 1, file); + m_trimming_polygons_deserialized.reserve(n_polygons); + for (uint32_t i = 0; i < n_polygons; ++ i) { + Polygon poly; + uint32_t n_points; + ::fread(&n_points, 4, 1, file); + poly.points.reserve(n_points); + for (uint32_t j = 0; j < n_points; ++ j) { + coord_t x, y; + ::fread(&x, sizeof(coord_t), 1, file); + ::fread(&y, sizeof(coord_t), 1, file); + poly.points.emplace_back(Point(x * scale, y * scale)); + } + m_trimming_polygons_deserialized.emplace_back(std::move(poly)); + } + ::fclose(file); + + m_support_polygons_deserialized = simplify_polygons(m_support_polygons_deserialized, false); + //m_support_polygons_deserialized = to_polygons(union_ex(m_support_polygons_deserialized, false)); + + // Create an EdgeGrid, initialize it with projection, initialize signed distance field. + coord_t grid_resolution = coord_t(scale_(m_support_spacing)); + BoundingBox bbox = get_extents(*m_support_polygons); + bbox.offset(20); + bbox.align_to_grid(grid_resolution); + m_grid.set_bbox(bbox); + m_grid.create(*m_support_polygons, grid_resolution); + m_grid.calculate_sdf(); + // Sample a single point per input support polygon, keep it as a reference to maintain corresponding + // polygons if ever these polygons get split into parts by the trimming polygons. + m_island_samples = island_samples(*m_support_polygons); + return true; + } + + const Polygons& support_polygons() const { return *m_support_polygons; } + const Polygons& trimming_polygons() const { return *m_trimming_polygons; } + const EdgeGrid::Grid& grid() const { return m_grid; } + +#endif /* SLIC3R_DEBUG */ + private: + SupportGridPattern() {} SupportGridPattern& operator=(const SupportGridPattern &rhs); #if 0 @@ -639,6 +773,12 @@ class SupportGridPattern // Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding // to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons. Points m_island_samples; + +#ifdef SLIC3R_DEBUG + // support for deserialization of m_support_polygons, m_trimming_polygons + Polygons m_support_polygons_deserialized; + Polygons m_trimming_polygons_deserialized; +#endif /* SLIC3R_DEBUG */ }; namespace SupportMaterialInternal { @@ -793,6 +933,30 @@ namespace SupportMaterialInternal { } } +#ifdef SLIC3R_DEBUG +static int Test() +{ +// for (int i = 0; i < 30; ++ i) + { + int i = -1; +// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000-prev.bin", i); +// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000.bin", i); + auto grid = SupportGridPattern::deserialize("d:\\temp\\support-top-contacts-final-run1-layer27-z5.650000.bin", i); + std::vector> intersections = grid.grid().intersecting_edges(); + if (! intersections.empty()) + printf("Intersections between contours!\n"); + Slic3r::export_intersections_to_svg("d:\\temp\\support_polygon_intersections.svg", grid.support_polygons()); + Slic3r::SVG::export_expolygons("d:\\temp\\support_polygons.svg", union_ex(grid.support_polygons(), false)); + Slic3r::SVG::export_expolygons("d:\\temp\\trimming_polygons.svg", union_ex(grid.trimming_polygons(), false)); + Polygons extracted = grid.extract_support(scale_(0.21 / 2), true); + Slic3r::SVG::export_expolygons("d:\\temp\\extracted.svg", union_ex(extracted, false)); + printf("hu!"); + } + return 0; +} +static int run_support_test = Test(); +#endif /* SLIC3R_DEBUG */ + // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. @@ -1095,6 +1259,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ } } + // Achtung! The contact_polygons need to be trimmed by slices_margin_cached, otherwise + // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work! SupportGridPattern support_grid_pattern( // Support islands, to be stretched into a grid. contact_polygons, @@ -1113,9 +1279,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. Polygons dense_interface_polygons = diff(overhang_polygons, offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS)); -// offset(lower_layer_polygons, no_interface_offset * 0.6f, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (! dense_interface_polygons.empty()) { - //FIXME do it for the bridges only? + dense_interface_polygons = + // Achtung! The dense_interface_polygons need to be trimmed by slices_margin_cached, otherwise + // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work! + diff( + // Regularize the contour. + offset(dense_interface_polygons, no_interface_offset * 0.1f), + slices_margin_cached); SupportGridPattern support_grid_pattern( // Support islands, to be stretched into a grid. dense_interface_polygons, @@ -1125,8 +1296,34 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false); + #ifdef SLIC3R_DEBUG + { + support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z)); + + BoundingBox bbox = get_extents(contact_polygons); + bbox.merge(get_extents(new_layer.polygons)); + ::Slic3r::SVG svg(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z)); + svg.draw(union_ex(*new_layer.contact_polygons, false), "gray", 0.5f); + svg.draw(union_ex(contact_polygons, false), "blue", 0.5f); + svg.draw(union_ex(dense_interface_polygons, false), "green", 0.5f); + svg.draw(union_ex(new_layer.polygons, true), "red", 0.5f); + svg.draw_outline(union_ex(new_layer.polygons, true), "black", "black", scale_(0.1f)); + } + #endif /* SLIC3R_DEBUG */ } } + #ifdef SLIC3R_DEBUG + { + BoundingBox bbox = get_extents(contact_polygons); + bbox.merge(get_extents(new_layer.polygons)); + ::Slic3r::SVG svg(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z)); + svg.draw(union_ex(*new_layer.contact_polygons, false), "gray", 0.5f); + svg.draw(union_ex(contact_polygons, false), "blue", 0.5f); + svg.draw(union_ex(overhang_polygons, false), "green", 0.5f); + svg.draw(union_ex(new_layer.polygons, true), "red", 0.5f); + svg.draw_outline(union_ex(new_layer.polygons, true), "black", "black", scale_(0.1f)); + } + #endif /* SLIC3R_DEBUG */ // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. From 55b43077d754defd52b8d0c56f021a3e9ed136a0 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 10 Oct 2018 15:47:33 +0200 Subject: [PATCH 54/63] libslic3r utils: Add a Windows-proof file move function --- xs/src/libslic3r/Utils.hpp | 5 +++ xs/src/libslic3r/utils.cpp | 63 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index fd63d412a6a..9334573e86b 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -44,6 +44,11 @@ extern local_encoded_string encode_path(const char *src); extern std::string decode_path(const char *src); extern std::string normalize_utf8_nfc(const char *src); +// Safely rename a file even if the target exists. +// On Windows, the file explorer (or anti-virus or whatever else) often locks the file +// for a short while, so the file may not be movable. Retry while we see recoverable errors. +extern int rename_file(const std::string &from, const std::string &to); + // File path / name / extension splitting utilities, working with UTF-8, // to be published to Perl. namespace PerlUtils { diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 4ca4f69fd8f..45a39bbad7e 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -149,6 +150,68 @@ const std::string& data_dir() return g_data_dir; } +// borrowed from LLVM lib/Support/Windows/Path.inc +int rename_file(const std::string &from, const std::string &to) +{ + int ec = 0; + +#ifdef _WIN32 + + // Convert to utf-16. + std::wstring wide_from = boost::nowide::widen(from); + std::wstring wide_to = boost::nowide::widen(to); + + // Retry while we see recoverable errors. + // System scanners (eg. indexer) might open the source file when it is written + // and closed. + bool TryReplace = true; + + // This loop may take more than 2000 x 1ms to finish. + for (int i = 0; i < 2000; ++ i) { + if (i > 0) + // Sleep 1ms + ::Sleep(1); + if (TryReplace) { + // Try ReplaceFile first, as it is able to associate a new data stream + // with the destination even if the destination file is currently open. + if (::ReplaceFileW(wide_to.data(), wide_from.data(), NULL, 0, NULL, NULL)) + return 0; + DWORD ReplaceError = ::GetLastError(); + ec = -1; // ReplaceError + // If ReplaceFileW returned ERROR_UNABLE_TO_MOVE_REPLACEMENT or + // ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, retry but only use MoveFileExW(). + if (ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT || + ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) { + TryReplace = false; + continue; + } + // If ReplaceFileW returned ERROR_UNABLE_TO_REMOVE_REPLACED, retry + // using ReplaceFileW(). + if (ReplaceError == ERROR_UNABLE_TO_REMOVE_REPLACED) + continue; + // We get ERROR_FILE_NOT_FOUND if the destination file is missing. + // MoveFileEx can handle this case. + if (ReplaceError != ERROR_ACCESS_DENIED && ReplaceError != ERROR_FILE_NOT_FOUND && ReplaceError != ERROR_SHARING_VIOLATION) + break; + } + if (::MoveFileExW(wide_from.c_str(), wide_to.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) + return 0; + DWORD MoveError = ::GetLastError(); + ec = -1; // MoveError + if (MoveError != ERROR_ACCESS_DENIED && MoveError != ERROR_SHARING_VIOLATION) + break; + } + +#else + + boost::nowide::remove(to.c_str()); + ec = boost::nowide::rename(from.c_str(), to.c_str()); + +#endif + + return ec; +} + } // namespace Slic3r #include From c06cf009eb2a22cb7a7a84b3a54117795313088e Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 5 Oct 2018 16:05:53 +0200 Subject: [PATCH 55/63] AppConfig: Prevent race conditions when writing slic3r.ini --- xs/src/slic3r/GUI/AppConfig.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index e66221351ae..c2ae0bf0b87 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -16,6 +16,8 @@ #include #include #include +#include + namespace Slic3r { @@ -125,8 +127,14 @@ void AppConfig::load() void AppConfig::save() { + // The config is first written to a file with a PID suffix and then moved + // to avoid race conditions with multiple instances of Slic3r + + const auto path = config_path(); + std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str(); + boost::nowide::ofstream c; - c.open(AppConfig::config_path(), std::ios::out | std::ios::trunc); + c.open(path_pid, std::ios::out | std::ios::trunc); c << "# " << Slic3r::header_slic3r_generated() << std::endl; // Make sure the "no" category is written first. for (const std::pair &kvp : m_storage[""]) @@ -155,6 +163,9 @@ void AppConfig::save() } } c.close(); + + rename_file(path_pid, path); + m_dirty = false; } From b429b39abedf13ad77839aa9cc478e28225c1a2c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 15 Oct 2018 10:52:39 +0200 Subject: [PATCH 56/63] Gyroid infill - turned off bridge flow and adjusted the density multiplier to compensate --- xs/src/libslic3r/Fill/FillGyroid.cpp | 2 +- xs/src/libslic3r/Fill/FillGyroid.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/src/libslic3r/Fill/FillGyroid.cpp b/xs/src/libslic3r/Fill/FillGyroid.cpp index 89d5d231e98..46d6382f7d2 100644 --- a/xs/src/libslic3r/Fill/FillGyroid.cpp +++ b/xs/src/libslic3r/Fill/FillGyroid.cpp @@ -131,7 +131,7 @@ void FillGyroid::_fill_surface_single( // no rotation is supported for this infill pattern (yet) BoundingBox bb = expolygon.contour.bounding_box(); // Density adjusted to have a good %of weight. - double density_adjusted = std::max(0., params.density * 2.); + double density_adjusted = std::max(0., params.density * 2.44); // Distance between the gyroid waves in scaled coordinates. coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); diff --git a/xs/src/libslic3r/Fill/FillGyroid.hpp b/xs/src/libslic3r/Fill/FillGyroid.hpp index 17924b5ab21..9c3cef940b7 100644 --- a/xs/src/libslic3r/Fill/FillGyroid.hpp +++ b/xs/src/libslic3r/Fill/FillGyroid.hpp @@ -14,7 +14,7 @@ class FillGyroid : public Fill virtual Fill* clone() const { return new FillGyroid(*this); } // require bridge flow since most of this pattern hangs in air - virtual bool use_bridge_flow() const { return true; } + virtual bool use_bridge_flow() const { return false; } protected: virtual void _fill_surface_single( From 49cfa5c759ce0bc1f587950224db7be3f0b18a85 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 23 Oct 2018 12:42:05 +0200 Subject: [PATCH 57/63] Updated Prusa3D profiles, added an MK2.5 MMU2 wizard image (as of now it is just a copy of the MK3 MMU2), changed default brim width on the platter main page (the shortcut checkbox) to 5mm. --- .../printers/PrusaResearch_MK2.5MMU2.png | Bin 0 -> 71328 bytes resources/profiles/PrusaResearch.idx | 5 + resources/profiles/PrusaResearch.ini | 217 +++++++++++++++--- xs/src/slic3r/GUI/GUI.cpp | 2 +- 4 files changed, 195 insertions(+), 29 deletions(-) create mode 100644 resources/icons/printers/PrusaResearch_MK2.5MMU2.png diff --git a/resources/icons/printers/PrusaResearch_MK2.5MMU2.png b/resources/icons/printers/PrusaResearch_MK2.5MMU2.png new file mode 100644 index 0000000000000000000000000000000000000000..eb5dccf08c6492825f2706d64805e2ba526d354c GIT binary patch literal 71328 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

X=%+NZBI531A>dAY2^iq9WcOZK zc&LcdZUhTqYLq9w1dr|vN)a(sTQG|y5LY)e$&!P#Qh6hddiF!~(DR{KVkBNB1g9S{ zE(hi4gPXdv90<5F;EFE?OoDc$a!SfH@~>Hwj0ihA6ijBj-A$r@kWHFOMIo%|TL!g6*yuWV)#9swL2OHoYh=6 z(g~WMp)zK9mcMOgF>B$irLp?X7H~gGH&ZlwHqP@%lRvjMfb`;ENufs6Cb%-D{w*B7}g!z@aUaWg|pzRL7eyaoX$Qpn)Xh9DxZsq#>w;)lmlT?|Y!9561A z#}m=5?61vD6{!@cm4fX|kK?OtgYA99zGyKf5WZ{Df1)UtAh(KFA^-$L z$&2K2@ZxqE?Y~U+SXu}6Do0^5V!`mdw&F^PxG}*d4WM4V70W($RC5ROMf#*<1D%pv zxuBM*UTyr-->*^)+fC=>w4Rjc`h+BtX3lSfkOym2fiQA?f%NJ8bZ-Je8ViL;wANQz2d7E|5;|Nl$6< zAj*F}Kl`HXj!g*-pB$>BD@nIUSZu@`ok!2=)b-*_xEp7=H-+vIvpF+rMyh*+4Z{(++ng4 zgJ<=yiIrW}PepH)kKIgoI!%KdAa43lG?w20CidWDGW06@=|V6w0*wn0Utv-j8LmoI zVzAMrhpl1x%!j|g&Z`txMz=@*EZbn77&b{2suEQ+ZfM$($-{{E(s<3kR9iIyG@eLO znaFN&#tbrpq>3Bve(YY=Q(p9%L-3(%n4Qddj_lX+ z?h4|=>faI(2_ZrN-J&%IYbulTN*?H*ucYIGX_~%(he;r%bm_NtpI!Y2# z`wgEDi(X!)iQe;@e{x~1U;$X0zL_+h=Gmnwtrwf`bCC9WBtsE>A8zGT+=A68m71~c z63?N2EyCn29f+w`Gmfn5y+;4!vth;XDE1K*X1;OsOC#M=FAH{DsLe~d&wO1RiaQ(% z%40Z(B>HCRY`QXFOrW&=w%{&)KXqj-fccZpoRz%MCK9H9DYxl0w9>`33EEI#Eh&)d zio$~|8B_Yu4ybY*_0Z&GeJ~0Yo+V9^OlK`*N?m-{FnJR4gcz zgFEu@oWwbmQmZ~q0Ol?)plp>Pl>Yif{2{EsP?4HSQEL^Q1#C}Nj8wGMtLDjVeckHYPWk6C2gqadxO`pug@iX~I$MI`0Sz6m)9c|{%eRrWgVBn&hkiqRO7 z%RO*>6MA6F2cRu>4SgOwz1(*7IvX5MqFEcBNah6MmCGUx#;xt*in8<)lYT+z7bkCY z)EQtcy>d;w0eNQyej%>R!mKf{>71lJ;gnSGc*S0v1kh7Q&H#(I{eoUMXoGau=33lk zfpeayyVF40bg?c86(Ai1Kd_-1DW&yrH?;TAy-gBKzasN}=vv#d3Q7v!&Nxr!^eU6G zX!*3MF-;^Yl5^|ZIDIDoW5lI`l`G;@XHwI`*?X7`%hV6atDt0na%!t54Fs{l zAg!gW%r7#xc#KmwZ9?augP=DU;Do1^B=q|QfeL%^`D?D_nky}Nk%5s&6OaN{I(-6x zG)?LCdZekNC<>A|!Mi?6Yn;m&lo=sQtK4bPX0bhocb+(o85xLm+n^Arz4`NGw(a9Mw4tvLJ~B+EtZFr47n!QmyH@9A_N$SfXqVP)Or& zKA?<&IKc-^tR+e~gcK;DP_aiU0Zw6*#F&&Kmq@LVQs9HdL<(&bL%)S!t8VllEd2l@ zams6V9iw42gUJSw1l7+72=XOMo;%72WbVNmh)ZMwqE{j^5I%s&2%#H-?^RsIC#%rO zIj^k!i%lkD|OpD^Hv#)||4L)>M;bvx)Q(LIn=*Kg6L! zN4aC?J&a9`lO#!16e&xLF@z|g#8TRVelH`0z#G2n4Q$=Ah4;Sqz1(~6{nYCX(ln(g z3Y-h5p+zZ*5-D^AfTAFhF;XaO;fSM{v{t9r?^3!zqzt1YqiACYD$s0>QcJ5GtyY2} zcmkegt3{cY;43yLG4x5j_Y}6Qf;B5}g#%?PC}-ZM&{ay!4##_BLvE4aEJ1mMQb@0= zr;ja$E|s9I1hJ7Q1R;2otbE&EC~*N|5tGJ-;62_43h!|EVZbVg zRQmW6Z#A7a$TDP*C~YWhfz}$~tM7&@@x=g7pj$ZRnkB!r*|Bg_!Hp`Rc>ZO)@W?&P zZg0|ey~?H3$0KQ=z3M(s7Ulf7#jYQR_JVMhENWzv=js-ksHptxAAL#^V64g zj;Wqsx^U&WZ~8&inPJzSJ#gw~?!V?XZoKC6bP~_^ubtsLH!kokyWd7WUMEj$(95ZY zL3*SMTzTIuM3-O855Msz_}{uYJ2TYERkq=7BK}k_MzV~~-i&M9r%BMc{Y0fy~43?Ib$+8@+6TA_p>F$sh~qdAIH0*k`L3Y1_(H!C+odaSN4 zLQTaez=l9!EiO1xX=tPkyz@9;rNYug;DW~sP}U)(KuB4YoCg`D&#N4}lhu?q6IDti z1Xe&HN@59h72};FSFY;a6B6&iYmjBtQ>cta4J)HQc*@d)tfmaOGN5!)xoxt#iOaGg zs9^}Z9TK~tl*D;UY$Bxf6~sHKRepFXx!MfaAC=4u2He-z%uhdu#T)kV<7+g@h$5RC za$1KW?#EGCd_N$B3?U{#)WH`O$m3)c+D^|9tRs{KP+5s+eBzioZR?*cV6aho=D6Vp zYaYaX`}op(KgMUS`UII<=Ki9`H3tTqb$N@k)h(n1-tg_#s$xhyy=Iy5@9 zij_rtfGwwN=8WxU^N;`U@7b|qCog)@^Vzs@BX`|(H%Ss7uhL0X5yL1XQi&>93$BW3 zt*yFUti?KqQ<5l&P#(OpNa>MMVRHweL>pgGZc%Y>1R)p*kS3l%@p`yaYYW9hpe+zNqk5-;Jm{R3PMl>Yl&6W|&D1d-8{rA6zZdnBtTf%gvYOSCpflMq5hlB2Y)&QG3`rYYVBBw^T{ zQsGOaP>cpaA;#J7yPP()$hM`3iBl%{!uS+3pWMN>J$IU1&sBH*kRS&QxB_8&l|^5^d)%#g*PO%+QxKh9Qp$$S~bpo*`=I);c3jE`T1Y{ zU;NAdEBWJl6aIQ?1|1!u2pWVTU_lC2jN%pblsmt02mf^2y==YYV)h?9%!sPS|0Z!n zt==N4)sQ+O@0Cn%-o(59;@#YO_dT@p4pSo|yy~)7vS-&}_8i<#BTeb`Gfv%lD)-%U zH#>Ih1gb}*(HgB)ReBCp#=}~RNm7&$IO`||d38Y@XekMzDw&%oq6`676f{gq&n-YR zBCWNMRwK(QXWP%gNP~*N=Ow{g`nIGWB%**|9YP7>D5CGd2UjU@#lc#o(sVToCP+!? zT*Y=20>V4AQYdZkg~K4Rag`2KA)*M1TzHmqOD6<82~q~U^c1qP)R8uX!l6WrRuOrb zgA_w-3xuqixa)_RnSiU@4p}lh6r_S+OZ>1j??Yw53tQr?MQcfBbDR&vGGV!l>6IQP z8cYbqyq{y{)KRYcLXVfXR)}V)DmOuZcyJPNa30YZ!QXTpj63v8uYje;;B$L23Jp|a zB&0$_qGH|`kO@siA@4)*9w{aLb|04dobiH7sV8;L`p0v4!Tdh{b^jq=b5DcUJf}rj z4hR^eY*30mxpKs!&*gc^4&iy=*j^fU-_QK=94oy6Dmt4Unw9KkG!Tn~xLHRDg>;tl zww*zm*7&*q@8>u?cZ78_GdSyTE>ILXJ9plHJPjX5QH76ip2^7xmY0_q3^Jytr|EP$ z6wWa*F~w+W1QQ$9tyy!Uu%RmgCT%vPGD2(TRZ$4zy;sPN^mpE=>qic28Eb89p+g+2 zL2XoNVGMa;x)ZayJKj=GdV!Y+XHg;$N=IfLd1=wAB3Ac8kd-;YJ7T3Myu~6YiYgsi zjq3LJtQrXG8Sj zB5Yixb@icQ$%vweBF_L2QVpR38mT1C7r4B@Xj8RU1&`61G%_fou(qVImX69PU5R!* z7*B{q69$6mMvWc7?!`sUJQZ>!9>kYY6>C636XeJw`{zx%~3y zarp3I4jnv16d7Le&EL#Lmz>}Ew%5M)5^p_9DxxH=r~rzBJhSxj0WwzPAO09>*8%)Q zDl=aylBUuvqwI3msJ4%KTelj!{R~w%O8L?m=WOI%X|lmUB9(I9>)?Ya^S)l`^<kU2S*Zg~ zIpKuzNMDqyEKB9Q)TJwxl}_j&qzXZnd8u6R^2p&MBgYOMWF#Iz5r~z>3&FDTI05K_ zG}Q#1RIOwP%1EN*YMPJAhxVcRMF_AdQpSWNgy3=rPoc0vRnV^XR?#R7p)D{(Wd|uF z!3Atykj_q#PLBidp%4Ej|Ne!~0x{>$dQb&E)8KLfeWVZ=VQ{i!SvoEonc%XQy_mE) zPP$NX=gxiDqc`$num4Vd^nd&uzw~Rr&VBFreb$Xnv3bKrS|cr_R3ute>8m05r`c@M z7#XGAZnM0yNSqja@YK?jm6c_ZB*qxYncKHBcXWYc$L2_rlx^F#ar4bL;XKlLf(!F>pS80!UR zzG5Am)`ADxxW;A)^Avg))BovG1{ZKUZ4y?%zn(OGG; zckdq7Y*@#ZO`BO>oW~X=^9##jVR1oCubCAaH*S(;k;|;#SA#5*MN!WF`8)sk|9$F1 zAAZidHM3R4)lw`fFp|pm8H$fnk zKu0PFA-u;0<$aJ+xg?6M7Rm*!y-19eT9p!IBc+{Isx-=2VWg8%S}9E_v~*etrrBlg#7Rmmt3w>!N6&R7ud1!+2Kk4_f8G zYR4T2g>YsFl>~4@#eNEDyWvlZpc?|Q2nZ=<>7=NbgR(48t09TmyJtTp4QxC66!z}9 zpYc=2xbWhO_~yfZ4>u2BZHZ4?&>8wi##cRSN+E%oZqPn(1mU1HI?CFuTeUk&VrtE;M-Cl2*Eu^^mR7moBM{m- zr?Fm#;0?md(pp`Xxuz_&b54~-5f|3$G6YrXpj`-p5~)CmAcR5&C#`Zm2=7g>IBA4a zcu9`OdvB!oqDqJbWt>vhJ8LO?q>aE!C!Dihi4dd?O5j{sAdvwfLI}N}TnNjZj*s*h z^|29eZQr@zLvv)@38jj>pOtZ%*raAM?|okO%d$B-Vrywy^aq2|1@G(iX4z=eifqs= z%fgq+h&+jtEYEEzR49d%d77q$K)NW3Nz!VRg~c;IImyiQ8q!FyY5k}G46~pfsjf}Q#AO8MOoT7N}j2|y%J!Ep=W5RT+7MRCh+Bv6fS?a=;+Lku*)|tG> zb!p4UJ8xX+lr2kTy)~r^%GpwsMWJzC6=fmIvS$?1Oa#o1yY7l^{KQr9)a-gQyK$}A zyJvsB*U!Y7b!$adTy$bcVrSFf=P^Y!fRWWqRes;d@!}Np+xCqZVN4p zFxnWbErUi06DJ|kv6Vqs7o4}`UPr0d2q%Lo_uTV^=bnAK!Vc!ia!bn#nl1)4jaDOs z++eY+AuxVCII93|VhA}7VK^_xOGL={<>xFuE$eYHjFrI^6rfeFWgr^DbfC`t_8~(rUHXv}qIT*01ABH-3rhZ@8Yz zF1?H>iV&ir=M5pCP@H%9^RJg*y7!9nfVce9-+uQ)zF$Fg>Z0NIf8<*2)LixW9?E6) z$Q$6%Bs}8xsy#p{rq3yOYzmp33^Q>)E?&FYSJh4I9>CaV)RQaqnF_5J7SAB^R@I=RR}y zJ@+KDYsRBfx1DAV>^l%2J9H>HN?xu8>Q`T*zk&bfz zefKdK^qCl+f&j}4OJx0mjcYbjtJlf<1xb{YXP$XhIX=;1YHFIY^o+K~*u3$SVM$&w zF)_~c>?||Wvm}vW^VU;2usa?d?7jATC&s5pnhrlH$o}sggd6Y0k47j{l6g;xAki8k zTNSh5c!!P93L&$~8;R?9od)kBgf}O0Djp_J=&V@lh+}g{S|5Ghnl&@{0A*3&gD09< zgIm7pJp;jaoM}G>N9uHI3bBfnRsj?!SK*2?k>g#C7?N-&QXxVGqLHHFL>mkSEG#ad zg=Ed_EM-{^J1nUB;=HHV?J_bt!iK4J%rDN9Wm$zgkOrj$V=c+s-|~yUzGd5~Y<%w7 z{O+&)#;59OOi~xr)0nh2Nz|&5)-)q=g4Qu;Un*6zk%_$)!io@zQaD#=<)R?1@mK;D zsk{*03Y=9^IN`mQN?I=?>x6bvdMELv5>Y8-WFc6oD2y_BfWiu2YHe~8RE8*X5M@AT zPJ|4|RgjqwA`?PnGLSnN3ZZ1KM3gD13#~#ag(#Nhm-2-p2g_Pix0CCp3e&9F{k!)O zr!ng{j$y6fz<~oO4QprDB9ZjkJwoAFyI~XKlT$o!|1OM5*|O~n(ndrGfwb0TdTJv| z#xz?sHg9rtIxCcgW7Ebhq_wJLFVC$%dT1YF(55vu#?I>(Nw1%0vNb~_B81H_USNQr zG(lDEYC%>RIw2UM_Z*ac#G17*HUotxgs0Dk{xT86a&syeQ~(dyGzvGPz-fm8B&P?cPHz zPLPa}rY*Eii6c$J)R}CKvvy{hxS3+&#Ap+nNNR-E1Xm!u$4XVfbG)nCmJn6D7QTW# zQV}=Wuv*o1;4OGX@RAT5Hk5ehD|8$lm-i@(fDZ$_3*@nIXnWsZwAo8k^Vut?4@z$8C{V5EV z5Zf+>$x}io7C;rxoIS!=O{3)ftcNQ>{r#vu!+VdaygLluJ^&R`Z@Q%yRjwtt) z$Q^WpjZRWbY*11hXAF&#I<=%myEji+med+e%E6!_EtFM9 zbqJwq+zeqz{8(j0ilSiPJ*UWo7hLdSMlU`4LqaJCB2Cj;lB8+e9BH&0jk?o8#8T=+ zMX@p3Yo#Kkj4{dw9Rf<~NChX1ab9`h1u7BfAU$4rq>#Z#=YtSINa3Z>0tsFsy)cmw zS_B++*ySy;u6nw7=c_Rq(Quw(@T4^u@nzNCCOk?=jL>)`aLQD83E|Mv6Oa(35wd1t zqY%2H5w^DSLsjW<-cn*K=BA*@f&&DJGN8fv>ZVhaIAyew)0p+g8tD&ebQh6qY4CuInQuTdq^x~eQnZ3`EBp9!hTpoAj?C!iEs z6NFG|1Q+m1ODnaq%1E2&dM<^|^0Ej;QR1xgW#Qc*%f-UNv5CAt zX!??}$V*v-pbBf1%LjtetKe7@}885yGtiIz$k zC$)@YPa-9SQK(1}1%Z(YAtJmC7*T5QaSDL?R}!W5`IMbf0D|VREX0qDiAs zC&$pQwMdRWK*+B`G#fbW>1KTh3hg!Cl)iM* z2PZ;5lfIuRraiZ*IdX?3LD8odYD zK@}+GR9P9ILhbn>aR0!8-CX&(&oJm&d{)p*YjS$adbR)P5z}2580~^wvuTYiip&fa zmdufb!>TzxBU?I+miygkrQg@S@Oo-$GA^>^w7uLmh0XNz+HQy*;`+^cvuobi=@$Iqq_^8Dx^wLNrVe^N(53nf~iQ* zg&YQkSfmsLFTh)b@QC1~_YU#MIh4V!^hgqxJ00d%7L&LB@~^)2;)^dh zbm4^;{5`OSDoxioaP(+#=RJ2YIx&Ti5~0K}J!;WLkmm#Pvc%ey*FArk{?RTU{X)X{ zM3dk>QbaWB6Jk(!<%6J6AA?Zgkkd3p%LwNRq^R5rDFr49E*Y0VmjU+E(&jm$^g7$lQQ)&N+|LdG5RC zZX}v>&O4V*Z;`_XkI`G{aQd0s=?^3;$F6z#%=m#Y6KAH#N!B4Q;#1TmnlcseVwHnP@izKcQM=?s6p%2~0 z!U-bL*sF+*_gA6QmR@&tK9{qlw++{2;E>Sv9zt=-z zSXx}7KUksP9nkOP%pE;IRs>`y*SbtIIiBLOl)|<-a{4y<)6?wlDWZ_EJm{gfkCC50 z!Mxv(2%0z@M=MKjB}2&?(MX5o-e_NM`@!EeBd7e6a#_WR_lzj?u-DMURZ6FHc39QxPNYrSXDiF&Q(yvjq5ftR&O#tKhMfahoW#an{}qw&oB{>l6y}t8*tG@7qWl< zUhcW;Zd$D-&N^Z(X*FuA1FH-bRz-}CjrXc~CbAmKjKG#9K?=}jNU9P@q6&mRHmVsL zZBnnRYWCj{tD8hIS}K%Mv_@LQCP5&mHJT(Q#XDbNjf(<;tO8%}K~;DWLWOzJ%AnJj zPP@bDr)}RoGSb>XB=k zBw?f_iK41)s&W;oVMh#`FXA|Y5U{?iy1<2`C<;W7^jyCpbqStetLWZYhZYgWL>1;7 zfGTrz`Wa{3N>xQ{P`McoRc4M~*GI^?7hgzPuW|Q%cULJ@@mTLXIq^P_rix_500JK( zf(NZLYONBTcJQ%b@3m8`oeiwnRB&h^A@`DzamUE`9Nk7jFB)fl&p7ee7NT{6;>Q0& zM}Wr~U3l$MYfJqQl^lL^7gpi=Pn`YGs~H3D10e_`p(1%1@?9L?_M_bStFJt;bLaNl z_SiHt&6+ivcG%LAXbPt^V-JIF{J74vv<5`2Rx6|wLnci}=1Q!$RXWWLp+lh}W3j%X zo%PNQIqCvZR^u)Jl#<8iTpMi&L68kHlB6oZYc;H-2aoj*9VxUl$7i0e7RG(>Oi#~n z`t~#K0Y<1?ibpE*sywQyZrgqa7RTqWyAB;r{*nFVI}-%wsK=smHGP%k5&@K!_#mm( z$2q*a&*Gsvz9<-*?9pflrbZ$X(`ENPi>!1@3V)C!U7;2R=qRO(n|ON2ic(TY#BgfX zgHkDT7@mwLzDspcR868~X)9Kw)$KirJ6s4szn`&l*RBl!r*7HQU$=htp2fv3Qp!>` zYQ&Q>+`o5^YPXk&l=m{Op`(Ig7I%V>HdWV_|+kQ9_c+<0sSye_SE0 zwP&^rZ6DAAmcLA|p2QW| zzWpo~mzMd`wKp(5G4aF~@Np!BYGkyxIkAEu5Z7VF!E#QkKglP~>H(}7n{oOm?)4lzZ z9i7MB^QRQkXKm)P7r)@zR&DGKDpzBb*KakT(v4;#i=qhcD!_T5>bDcN8uShzl|hG+ z#0Yx1u5zc%aZ$e`b1Ss`9-~P$CmimZjOfbI&ON8&5m+OT-D*X5fXBk;IHf ztaRq&nl)nxNm=AMQYNe$pJ1ErfYy-pp)VkAP!>I$ED2f=!U7^!xJDoi3eokrCKPd? zSXiPpQYSikds7XvW;r=VzErSqbWycV8G~V?{6h{`0J=3g(vOD}2Nel=RmKjYf;9$P zU9vpSDXc^(iPl5n4FDwCRKcSUju5Kzpp>G>OQL$E)Xw>en%3G1(5@=1%Bp*7Of@RQ zI?MRPI9pFUbq7^uBj?KjPLGD*drW!tw|)!n`Pctod3l9;qxN`jeN=H)FgZKUXe+@6 zix-a=PA?tSl|gAwcP?OyJ~O9}lkG?t8}Hx-f)Eywu1?y>*l^|?a|bj>4@kU?P{O0U ztVrDjxU!;rsODFK3js%Xn9KWQMStSspQc_*X*L^G;Bx|^l%g!{=*n`t39#|B({8{- zEG^9A^RAV(2pL5vfkaDk;cS0k5kfLEuKCRUeYWo0Kz*={GB2r9Cl)1%5Xi`5M2-wg zD3-~T!>5A0RYPq*{a||9`a?F)v!hW|rkB3>^FMUiD{risW@Fvj4W9#=k8#Md-e_cL znzGXCqRoS2dj+DhuF6tcOOOVFp^;?Rvc$&v8VQP&OQ6WcC%UEOBKNPS?%$B5S{m=Ei_cA)YrcLbzF7jmGn+< zQk8W9C340_q6*UK!X9~-}=_K@Q3gC6OvkVJivLf62%ei zmCl)+m983ZG}t(^=C-WcCyG*fh+GCu6gR0i>sknbkit1p5?H1&k?@~A$8EQC`TsNb z-*J{*)tNv1-QlDgDp%)jsarWCBq1a~WD{(I4Hz)d0W&r@;4m*^k8M2m*m#@(4^HG{ zV`ISBCPyIz5|U8Poz%Izy5h~}oE_dj_PMvJTdfxM&oiDa-A`5Bx_#^1bJkgVt!F*! zS@qf!bIS*Dojpuvb!H|SOgG1wh$m4@Qc8=cDQxO7X^qRD{G#7}?seCEp!c<_mh{I48+PiIRp0kjh(x%}eXUh-k5z{tdmh z|2~6g{qmRn2nUZH=e9fVW@dWo`#xnJ&-lay-@W;}&sypn<;n}sUD~$sjJrB39cqmx z%i8C7N0v^I)f!QIX&Kck?bvvVF)hj>XS%kIPBzY>FIg0tV|`DO=S+z@O)E$;PeC1} zBD4s@b{Z;Xb|r}(IAWOW6r6eawTuU29uo5-fglOzF1hSdZn^%O_|rJ)YD_ol*EdO1hjmfF zBp>5X36&vG-nfYRltIclglnQwN!&Qbu~r{jl=vdY^pBA?0jHUqZjpBZB)%*W$GS8( z!LdP>TF0db*%{7s5<*z-y*0+VB#BBP#L6nn`PfTa>v+-?PvHKCc2*Lr-`~j+LXgD? zdB68$DA~Ae{X^5U6K-LtM;bR#=u&!#iafnMPjr~UXNA*5BB5DpV3a~Bh`gjOHIs3K zl8UiNGMQ?UC=Rk+F353d>qw2obm!9B@4b_W@ymGKbAEhyNNX?l+Ka!|s0BjwqDc2U ztyV)xiB}1wb=V>Y;jm@NATOC3OW3krbLZWrk`8mB-!Zt05O)(4fRvOtQ(~+jP9uC3 z$P8ziI3E~vQU>VHI*Ss5JkPn{f=gPBW@8WGW*l-i3RaE!v0~}(Yn^xA`5fB4huiPC zolR$Kebn#0hr$?U*EN`!O6cdk$43-w2WJa}g7MipNfHw!mbs%VsLU|CQ8K^3S&0UX z2N+*T14r2y`zH7CXj8y7x zrl)4!>K=#Zp%<5yID7kmUQs?{+GIAcKw@L`i}xivWw2 zhI7t4>tWzHCz6p$Rz3AvFFpKx|EBfir#+3k?!Jpbe}KqVLo@HeSx=)b$+Cp947wZ=Gy*Fk7ec!I86uV89oQeAk##Vjo?VT%G8$0r`#sUJgw7+ieO zB^@14vF^+r%-!{MOtHkwH7~_=cF}L|=4j_S~t=1BHdLs@S^p&Lz$BU-h?b2wBarSxVJX{^y zsl4;r82aDkp#f^mCQo|mQ+eot2iP<<6Ph-hqs$Fi0$PTV6)I@GJnoVzp_CEUB^Vpi zsHGgfr%kt|DGv)4k6BdaaZQby9}qp_C}wM@`vj4l#At~#n#@46gt^GFY0E~k+JJU@ znW8MfIh0mNDHxlYq8Q|dT}5C!-O@q`tTi+mP4c`5rRDGAB#n=av1j+br_ayNbKx1A zIsg2NzOyvHz*1|OZokA9d8yJdv`Dc76T8xqM5#$^3??Ve1UibtxG-z6whUxqEK+(@ zq)Yp`{SwLt&_cUnbp!>q#XF1ni8nVZKU~q40gSp z8MRDSzl@{Sb7V1M(@e<^oO3(5I?6zwO~*fxw7!`rQ|RTJnLc|xvoHQpqI?BOFlg17 z6S) zy5=cQ#aYYp+&or>nJQbitYdtvL0OzsRC}ByRTEb?mH&Q}z=| z_XMWu6YM|IpnD^19(Z=^IqWl1BI8j>M4ZG34><(~kIgeZS%5P{T9IXAlx5CXbBxkD z`h(m>%_P(hPO~(GttZwId-osU`20Lsdb&A7A$~q^_z2(p&bOZhaKX73?_9rb^MT`Y z^Tbg?tv<%F_HtotL83K-{vcHX;yAI9E%5mY&L$)(r7Q))1mbxt!u&z!3A6l!472yj z60aPo)JPP?AdmXJ9-VdzV@wEHR@cygPi4#wFljC8T5GY^F~|*BYB_M{2RM4TNj&)j zOwE6Z$*hCkww{HRCU@M`WYg3krjk#wZR=h1^=nz~o=NN^#3@C;jp?qCCAA>EYX(GR zhw-sG&9OS@0v|tB_@KtP1w~Muw`o#?Mw2aAW0KywTusb=)HXE6Mx6se&-MUb=N(c z<>kfG9+`$Pgr=5dY}>kp!WPuB+G#&rnk5vam4}ZUcrw7IbGF{#G%U84sb>>3#>QDZ zu)h?xBu!$XM5eA=;Ds-{c}{0pp;av)4CR2vSc85iOehs7LR+3g$0`&rtV4>3QaH3! zl!cX@PM3DKhczb@giiL*r|P`6aT50fh8&VjYfJL}3eC;uGj`#tIKJ~dddJ?+RC)P(_CIq4tj{j82KQ_=LPip!4s+k zbNm?Ic^Xrb53(9=oE$&@hlCqzSC(a*f62w{-~Rv;6N=tor4l#)x4n;YDW&a^rH+Mt z5pgOx=L*Mzw>X-!HB8SVglB2K4ikFa&?X1@C6FLL1E9(L_~kY_yonOuDFCA8Zu9`9gB5mTNQ%uY{})Dp5R zJuU9HY2yaIe*L$uIq=BNaRHon;RWB0(}XyQL-)29IHXlxkVFcUjEhbWllJp?attj6 zjiw{bB+eMpByb@V8Y_Y@xiuam3?7F-qg90SVFGQdvn=-=*v-=760O$NjE#@2J-k!J z&Ym^Kp|s57NTQ_!*Q3@vkE!@+$i;tPs@r4JS!Z+9WVDYt@?M8?&bXf03zxX_@Hs5A zfi%`Mw8lh=TCG8x))7S>M9F;^YUyo(OcFu?Mq@flI3W>PgTc|mY*@E$DT&k&-579U zsPP{fZmeB9&O3{4_aLTU01`Ev)Aw&!5)j^_6UF|A6e=C#;X4*MbiblL=V^B%yvT@5 z6EX83@~q=b`bo@1D-^fnL|)S?3|^BF;G{WNQ2(AHVT$HsZ!!3TMG*Dl7Ir!Ka6@7cINuF@Y6TgiWG-^#83YA89WvwYK=!D2ekxVjTp-SfsQ9Z`R4y6Jqs3?Q>ixjG& z5y{bgsI{(UYP&G?)RjcBRI}^Wg~o%&z8a4eLp$rt;I^B;g-Q}!zfCgVrZp(g-LsfI z@E|ke_cJ>iGrnn@uYEV>#(O)w=(<~Y(%9FT7$0Y0{yGi~4x{@6#!Sh0(LxG?AZS2q zQDCj1)|{YGORy0X%ZG>?W9ac2%Hv0wTEA`=0p0LC+C!gQR2hDr|Ie$_>4Ta;r~iJ1 zPpM~#vkC8wWp;}p?{_$|)6$rlpcccCy=}6nLurpP7#yR2G{Mnja9pFrINmFg)M1tl zHiz|FxAX4zy@yYH{EvC>yWh)s=bgjmt(#Cv@hiXj^L*?2FY*`fen0j4g)FapA0fwZ z)oK}oLCIIYb|dR%XHWSLhGWyd{q38cgZG?!!Nmv9KJWa8TdfvKMbwizjYfmmr{(fs znK((vM4a?JNF|N+oD69FtHv(vfD+Z!dT6TF9;{uGBln=9lPpI@Bhn=^cFp>kVFQuECw3(WzQ$`WD-jLHwb9}U!=J*_19%j&ra4rda_g=wyvyL;*hR=UzKQDUz>-o+9 z`Ua*ZXZh5p|AGJY_TS~F-tb1Qd*;)4<4^qrn>Ow6%PY&M$I-nz;kV06D_nKOui9`Mo# zp)mPzbhAO4HPEULUXChog$Zbf)^Q~g7@8^ZVRwk)h`}I_=I0k!SX^RrfACnJ?c~I8 z=<($A%+&IxEgQ|=J^OT|!uU1k9nP2dnqb+~SZJ-FWx_;Lk;ax4JHgHSXYk%q%U~+g z_*#>tenGFdf{q4AxkRKi&Ra|z;Z}}f_wEfGY8?~BF-P`&izih(IyOC63a%`-ljpqsa?l#)m!2<_v6De2~BFf##lDe+bz zy+jIK0r`Q^75F3qT$tJEHl)bzV~bo}TXbBE{XoO!|rJY7*`R4tTJwkM~? zJG*z82_Yo0j_Kz;a??k6N2)!Dgt#6N#T}e8)Dn%94kZHtDT*SLH6&S$vDyUQ1 zL~+c_

+^aW4~N%@YVq&N-&0XL;cM`(EDbEON=Y8@cTAE5Fh0v_}w|kSegygtFER z@WQcS-A3I$wm@gl%?n>gr(>ki7-d<6aZQ-{TF%9d;XG-eC*S_=e>VTx6@{6amjbPT^_Gec*1W5(CKv9 zwq-Nt?bty*tuZ+^K6-54xDjtmZTABYz0LwxUj6htH*Y;_XQ$mlAgDK+OwUd+Jv&2| z)vUESQhEl3i8|dLLioZWX?Gn-98;?$IPb8|t)5^H-aD*yK}a!*KuW9$gVbZ4V671= zD@&}bEYa(ocIxP38TMdeacq|Bs^`R4cYFc2;3ia5cWI!*%h43h?(NW+$ zI~&HFNg0%zN>hfD?jgu+27C0*p;gRtUhv$T2-x%5Wbm;{-T$HChSr+tndzzlLH)ZQ zk&Y1_=Svqxce+4?MJ==REg$eDDMB<+;y!3cGjjWNLc$ad{?>3Z%N-F3qtfZ+zoxS!s8O zs-{Kn9ox5W=R+U;_)i`>v^NI0`r0Rd6bN!sg=5#QgA{o|z1d`bZq7(8L1*;xJZ`Nl zvvFeFL{4K&%)nV37Ds`@VtomCl+q)k_SN4t)`IoavpPaLk}Oi&&pL}VO{v!(MPUDa z!j58QW@gEGO<^TQDEcCzpu`J{6NU%(ER*$)vVVSzfzF6^5{Nur;jJRlHFT2Vyu>IQ;wa0KqRgq=21%NtvL2D>+KeuFEox|+qs`Nuu9;n1K}U1A|9i-L*q3C1VJ zN2gP3&G>kY?|tvKU(8L9i??s&2cGx*zi+o&j7^O3&;z@8|9d}#loIP;!)#VQ{TWXU z3Mu_=eDQ@Bv1xL;oI7$DVu4YiAYpBR(qLVQaU~FDCJHG=++?X#;2JAQtGz&$rTUz6 z&!^cOV`6ghRG(|DA)lNSI>64%&Mem>OIFS^CQ4G`9UzxC|jVdJ_DeC^9$9t4S;obVdZQk6HDX~(E2+gXt@%0q({KC*rkm^YKm6^t;K@lzs4eex_6LIUZ%$P3`Qp$sE&hM8g(D zwomDEa&IWGxWr;qOe9J&mWc2~Duh5bPEcwY;tATiLD#ov$uZ`mj74f-J*o6ar5F@_ zoUvSR-bGyTq^k}R`UVY;_Rf#xq5qGpjhoivoMCxsg(NvW)#2o2$`bFL4?1-ctpvsp z44EKj>5?%DKSiGeuUrVra6$IVc<>Gz`rSD1{PXzZKlq>g=5PNN@B7eu`NOxr1LG}w zcJAa?-~3wcyyI@(^*4V{6ekP@ryohPwpng*Wx<+$L6k&TCzWuL+E`Y4@2M4*#19ZsqGUuQ6C$0U zWQ=hk-f}KnTcu*0HS}$dOs6!|7)7tp?bb5gcAF$VrP}-HDuotcVs`!VIb+i_%>Wlk zauK8pwDU-3@fy4qXf&C3C?Jg_i4tTr#n@QP*tkR`4lCLeZiNL4hiuH$xe)+J)vbt!rFW>PGb7Kk~?m_{lx$~HqE-rpLoS5 zj~+dYH6?{D=`Aeb7FVzitdOW~&uL#`okXidm&E`>URqt^%m8C&kTL_m7*IM_p$*<+ zok2ztsX{2?Y>>}YGVnYKV`Zn?q1WpYC&@@)@uc-wD2D@*sg0ZVPo8~2yLHRgnqs_G z;dufbS_dgB62I^j#35Xze58=VW2GdQF*;4~5)$v&IvB7+sdkc>!a?U&dS}xeW z1Ch6#QsNL{6MU?=`Tq#^e7d!1^F|JNN4M9jL~|bh-v}uX0_#OUg^UsCs%@$$9il8y z#X-DKI2#-VfcFX~GK5UA&f%*z8WG1tS&f&!>=iua+9&aZCtb~Rp7(5i=!agy1H130 z82kfidk2l?4`58^w8wUW6-Xt5I;8MH4YJDqr>Dnx_~D(G-+#|NGXSrA?W^B8yK&vh z(ZdHQ$^l1@9OmxZzQ^%HM>xFi0Q+|CX7TuZ>12dJQ)@)=@rA=2U6`|Rtxhf!IRb1T zMpm@!wl6Wsr$|EVjS<1pwSjUoXaLm;a&IQf8IAPFk zF*Y@IfPiiYPF+O#Kb8zW-rBr*GmTmulbbM-T1Za&pBHGMsU;~<6vna{8|H<_iAGva z&qAK)kT?!o+z9h7@gcuZ6}Ne6avgvBu@7K<$xr{6H}j$&{!v=(1wQ_X5Awx-elL^R zA+~KEqu*V|mya7mi;){&Zmm$761f(m6wqjl@xc#$+GgqWmP?-aM|bc;j)# zV_oQ!P(Iw;2#*thkmxj}mL$XtjTj$8^(}d42`@bZl`#+rN`ztY&9MoJqDN9oce6Sy z=ww4anTYg%&6oiYuxZOC4(vNbr`xUQxQ~4Zd1>(AmDH3b$9aozmV%Iuxj4nUX=;q2 zs>xTL3f}Z3I9uTx9wA`oLl5$S4}1{y>j?)AAL6ZV{U!eDfB!k3_{^vI$=AP!8^7@` zS}QHiyKsg)e;f~OUCcQV>BGHO{H@Ae0t(m7mKM~zP)o8yS) z1gbHCNXBsKI3}IMMiUq{LAPwsYsCzf6-7VA7j^t%A2BE(iph0Cj=;Hstd_DocaU9= z?DdnIw(cV!N~cN!Pd?87y9QmY&09B;)iZKa1||K+cu1|V-uhnNM+$)^?5aXZa%;m0 zc5T|rOAPuh#Lbl?8p2|Qs+fsERqBj0xABE9evz4VGyLtxKg7@e?3+1oWS-~$z>o6l zzwuvr^Dq7d?ZIYWYtDcgJMCxCK-g-vpkB`&bt>0ynC7ONZhpb~=47<4QRm7hUHv(o z)#)ES9$I;3)lVWn3M928pAo?-sib2*5r3=$l z^V0CeZ{5JtpK{&tdc8i+st)GKvGaePcV4rklx#a=D~AsrW{~Gq-{_+cC(^2tR1VRb zk`jwRS`W@qnjEj{=-MPw$03|E1{o=g1|copt%e-=AZKxLj`zLquXyDvUc()?-^zzS z@L|rq;9M@a@LXQ@^1tV-9cM9r{79%%oMwgJRtc9BC&}SeKkVMQ>$!_Z7HDLXJoQ=6 z{u~~J4u$C`O*wM(2y^qtnXL}cT8nU{ftY;IWoF$vefIh1bJMNgv)(#ts*lobqzni} z5Q>xtCy_#pR6vzdp%UjErZi|76J-g7%yp;VqsR+Zy6w}cKAj4-8$Dz#^7X9~)ao&& zFc=@YSX^N+rNtSCAj}7G1P7I9sISXN&)(80qLML`?~b=4D=2PvczzCxosq2gP`(b8)& zsqwO`j6U#m%F+aGyHwc9I|Ce>JI3$7>JRzm4PWE^AAC3cc22+7<+;y&0dcH(|NH-j zg}Ep2jAuNXR;zW|+R|o9;+hwpBieM zyrh(D+Psk?hmMA%JdRGV3nk^KS+y)JiB>2Xk<=2}3-c%ef+M)T0ISd{!#RhN0%Zlp z2YT?(`)}H?g@69nf8tNy@jv;cH@}6S|HWTr&%OuQe{c_f{qA>gbnYlW^rDxM7X^kcXalM$;@|&%T58g@u(X^RmMQ=biCQtz>)Oo?U^{pd_8vGRF=bB2sZB*b)TG zg>!`v9uaAZqOX_c<{0F8SxE4<1||v3uu6}*kcV2z!{Ux7JST*}SFJ+MdWu1=Y>|U= zq*2N@zI;7tT4TrAJJx=-QyKJH!!Ar~zP>QO#O^{-I7pmfJ+8<{43Djd z^nV@QINef8v2oLS4j(!inx~^^bYuY)@>m->-CN5op8lNcc*@hR;I2FG<9m19!BWq# zLyn{M9M(9{mN#Pg;=ttkn@BH4+^VYZiKL7I% z{v7Kqy>8bJdc8m;KF!ji5>6RmW^2}1bByoZe)lDJ-hNloYAtZ~dC&Otu7@As;K74c zNT6}varoe2irmm?1)@+{mS{z3(Zm{TucOi=X4Cr3rFM!u>XMTahDLb`Qw9Z8;VT1{ zmHJSK)2#7GhMW>lq_n!^l1tgY?;y)d2XWr>$&Y`67ry8Rx%je6;L!$hs*nQ_*E%=t zFIZmg65+8*k|v4^GshAAABb-|Iwf2c`x{p@2^P~At>`8elk)SskZ%x z?|bN}58rn$V-uP)&pP8%2M-*k)d|o^P~0q-nVe#BY#gap8FT$k%NBV~nvJuv(v22Y zmY5hDFCCWL3!H0UZ9p!R(xhp^N;}MLDXkebu$D!D(NrudeKtWH*Q4d-E{6}z5l0b? zdV?rdeBoa{j}VfJF1-kXvGiD#)Lm;p7Kobbw;a0c-0fYv^OpKpR%dRm!>+DopO2YH zJauU}vOM5{#ga{98D}O6Rg^@T#_59Mz#)3lA(~?dRia|TF48OTy#d9bgNQY~B4g$L z2PbDPCnChsr|KE}f6ADLD~cnwY}v%oqelZ{Fw!U`Q7Q<&3u~#@Gro7rUF5x-Z7vjbKS5?olE|QRjCVm9A4wG>1!Q3; zEbcuNMb$wbMa<33pMUdBH-A|OVWkwlTq>=#t{CL~@)Lje_h$Qdx4Ug;ZhPp-*FNQM zYxV3Af>EzUI4t0lpxC^AqPTBQR&R+K^Zi98iyrGHr}4Fn-b#nf>n3>PC0m()=rD&5 z&Xc7vXPvR04VP}E-!k+Tmyu!;;uukMaK5Uf)iS)4j3pV%x3}pXTLI|u*p0Bm_5Yk< zM{&&h4I4Oe=m@5?LrrTJ1mZ&_HLKOQ{r0;M2+rKLjdHMzmmA2Ugh+(JNJ=Y|i%?~R zvt@wsfWnk)+_aT@?zx+}qsN)qob$~a|B;`3^?&EN&v_v?T>m+C?z)#tpKuL>{%PsL z!Jq};tf4H6(Hx5;i8*xeFh`HiU7pkvHmqNN_tf+x3kwU#3OY%%j9$0Rp}FHBaj3kz zvkp__`SvI#r^EDQBfjYT^Ozgtg(wWMH8_zYq(dUG-eO#Vul{`?^@kX1s39kX!Z}Fd zh~A)ETUwldu5*sE41I`7OX5URlm*|s{u?YVF7ko*z3;dF*Ps6B6`QwgzMoY!|6%B1 z0XNl%{liCE+_>BWYZ=FL?LvpEn<@286KsCM4&1=-*{|Nq?JbXQ_c`PK6)vn@f?qz) zRXe7LvIv92Gt@T*=Ond>8iRcgG4rI0+5Y2i`ZR%?GdS;rrxx?J`z|qtDEDDS@XcYlL+ZCFTeA~pZU2TV^zgpta+4iEiIpQ-W1)3`#dn$ zC66N7P7n*vMEThFXPk|6RfBGRGCS$dF; z_Rf2WH=fJIulv1sC+Xx3gmDv#oZdq}w(RGB^2)N5bsN^xZuJ=S3$%`q-h`Xj*r2y9 z1lAe6?_qj<`b7?URjASlPvHWcK&qglSCl#P#}D(7kGz*x{P-I5eWn~HZcv4^x4B8qO#JOcSmr)dP@X(R-A35|0SzU4Y6R+C2Y14MR zUwy-kTQ+gajki-S_Yv7RqGF-S0;I5|DvZ-Ql|^r$AGr5kmR6RF$%%10h2ea5FYXl7b7lk5qy|UQ$?xLZC}GA`&gk z9pfi{^3VC&*RJPn|KqoL@eg0b%U|{qu6y>IXm=jrpa1FYTzTb7x%#OupwnJD?eUGu zF2p(MUN^&_$SL~4kcE_ZA6mB>V@+zcI>!zl;%i_1!drgmMKAj~Lg_hTwXvbp&6!rK z)tQ~zx{fnbMR&PN-pffFF~kv3y+*OTL~UGg`ITqT-n)R-0=L{@@rb0hV+&qroRzpi zfzNXaYgqTJpSx}2RWG;^I7Sd)A1aKlHDXD)!PJ`PPL&{jKR=)9Cis4g{@?tX=6I6} zF20Cvr$DN30acK}#W;&cBAEo4;t7)ql#dWXASX}xlw|w_{mO9^K-5J|B`@GBP_u7vu@}#Adc;`5H;1Em8Es`Wg04uE? zyLaxo7(3vM^&97AXV&jrSz4wX4Hyy>gPethV|4mUWc_FrCv%QL-nCYgHqknoZ!aVZ zttHMn{{qwP7YwYz_$*ZalnkU5<%hIHLQ?3k&Z8uwxqa2QbqnG=+?KXx2C<6iCl!6YG{n7lg)iBjQUj1e?Q-!>OD?=?&KTf1Q>L3YBKDgag6dFYX$+z ztp>DN8Bv-Z&_fH2CPruvUSLWvrog-4I1e2>#EX9L#q8aG093;5cYU3=|Jl#+k{^B( zZ~oce=dOEhfc;wMOUw4N58L^m`a-S z((U%PO-zjMBFr&K35Gp2xn;-hiF2=9SiIx<>BjoaU_3(tuZ$#ly9d%CYqjvS0GVoJ ztdPQmE{&qVQ9BG8tcm#P-1`d+%Q}~=DHfSNpt*H{o1mQx{MwsRcT1jR%tm73g ze>GE+O@9ARev^;B?~9C$ZRL}n{3E`3S4sMYFN94gXe^%}-mOj%-tBg;~B z6r-dLrFp=4LA_ZAs1cH~TJ^}PugmM7^t}5Xx&HIlWiuXI<|K(CRtoPGs+Lk5I)d*u zfgpwx#hSPlA(8}{)^QeWULbsds*O|IeBnV>z3*CYoDdKWx8`@J>m@vuVShhgPiNdG z|NK~nf2zmOJr+Wsq?8C1s0>;HA`DS7rU0R+W$VE039WR>2YCx=@w!B)1ScfU!_4|w zKK+T0lg5(Y|HJ>q`gODX*4uuSMtzd!JnM4a{Il<7dg^RemKRoQ9gnf}@DoHjj2RPB z&}=j~a(Hgr{KEW1E!F5KyX(LsdpUCC@ajL*8YLy2PLDXr@N0xiMCf$3#tRe05xKSc zp}qTvHAyPMdC$c77;%~e za=r-3q*9tFiV;##Z=`@9Cv5*sg6C`!V(I$UE1vSuy<5+}rgdn46i=@sQxe%9;M*ON z^S1Tn3kJO)<)(!~NrkQo z(`6a@484cFJ9qKx@A&{f|7%Td{N{}uoxcecukf7b{3Pd{dlsMj#9y)foCThE?epk& zRyf_#p&`{V$orJWAf==*H5QiUH+H*i(t3@eF#8|bxrfE2Rd+9?!~^T5XJ{m8(9am1 zB2T~9brR*AlGuTbj_o}}lua2+gmMGwazLb`pkwF5l+r)~cdNrKl^Ut}4=N~5S0PcF zB&2CfO(#J)UrLnL)Y6PNPLUOP$~&mnn}C=kROu!O#++3bE74NUZF|adcRlc-Ke}LS z%VzqWg6^?4>(iK(d-h`;sCq`C6~3$}lUah03P0$E3Z{~{azHY({RjbXo*;x8S@jr} zyG1YoVpfG^)_UfXyH-wRppRvIr+W>j`Fg7NT5HI|V;p`4kU|R?U^g#4c!@V5)7oapiP}6PvET{#)$Ze}HDALA_q*z`;YY^M-noaK_d%RyJrl|yMN~%`n^6`ZS@a%@3Gc8UpnW7#1#FwTv()*rKQ#i?G$!k5JIC=2q(h)Y>5zo z_$?}hA}<2t?t=5rr{5ot7df?B7DlVZibzG&8x2&PAe1CaYbX(hTxMy8k}3#BHpXg| z5h+&HiiX>MdG>Q(a`XPLf9gEhYfIH^u$;ymeqfG?sTPfG<3#l_qNxTlPO!PDE|LIm z!Yl_bu@;;;VSJP)c;@ly=VAQ3RzCMseCU&n@?@|5{X9=uhA3E=GrD5xajjXiW9GO2(R;wYDWUM(xt=1$?Yh>Cn)l92G zi$v*YG_6%i#nHX@UsrE5&lOT>sgzMl87Y;OQdy;SnT?Gv&s=o%u}41r(T$Tk&Zbd= z^Kgw%f_vd`-Q=jA)f9VbU^qXG7@4f9$xa92T^A~^pEzJ~&91ZdodUDLw$2P7Y>ZoWHGSNwjaed|&mN_z>Nk)MROmBU6%EtubnZl|q6G!zqWu-l>{Z<0{0s zl4rViAkIgp)lhO9#f5BgNJVa_GRAt%Y;-Y zrNeMcsZlCIX-zsgW6QK*#aNTh?j^KsQyUj>q(l429_mvKTHdh|TQ*iR zg))%*0k&-0#(UrMm;A*`jpZ^JGZ+RyF_>Ujw{yRRz!oo3r zs2Pv7G(sSxVsffRQgg(aCyG3UL1ZZ)%hsNK548@@?I&L&@)5@oMNu$6zre~;o3dE# z8Zp*liUPc_TGi3QtJd5xa$h$}C3FWRsF*ZK!b}zC!bDb~5z?TfKnNES)hHrOQ=&K~ z$!cU-My*yuX~p_YTj;eHajm1&*Pq28$kUy0tpbVMBcqsrngAjQK;k{tnov?-UQ}}Z z8EX8z^HF;bA&**UFCz-U_*fm)OlX`vO?_O0b6BguIh-|^RtMo6daQ{xISaS{)1|$c zt1rzRXlYPVda1OPN?ReMS4vqSm6cK$A%vAm8!1((lq#i)a-~$MqNtEU8Y!ibN|!=b z-%DkrQiYV#C>;%ikfl3YHVHl1pocfnkRmW#Ln9wi2UMh` zhy*eR6=|HXV0_j21Za;F4uM4p8FD2BMpl!o3;~ChaP-JQe&mOLg#8ERNNY{r^EdD0 z{qO&4-tmsV;Z?8tNj~*Yf6Fbm-srBm<~q9F(>KX#=Nye@MiNOLxc_eQ{+W0{E8VZl z38Zx~8l~eXXv_`If)uDY;<6`RN~hH(PF4k9y!YgJpCoBIQx;g;Rr~hrVm6vGM1pQv zu)N%++bhDjGeV_MDiM;tC=B3?qKG(6iIa@1mQkE?4N=MG?In-q%$HumV$Q#*H( z?A&#T!eI)7GX`H46w56Hie#c${XI!MIF2@t(AL}NS&b+MLW+U*9$krpRhkr4rawAw zuKEi23bCmg9DvoItLH+}tWZihDWw%c8X0aDql7eojni6h%l30`$_KrPxx@R;jiR{F zQQViqkW1-OfR##>QmRr&SxTw;Qp$l;s;iXh2%$PssTCnaO9;6F98z(N%2E{ItVG%} zNQ5D9GQ`DsVGvOa0&;86QbHvi=0rKVV{{xbyKy6L{-vK~Vj|_gzx99evLAajS(fqN zfAg2wwfkZI$M5`crRA}913HC4iz+amnwnv1e3E+B2$EdJb_Z54W|P_lXJ0b4Zqw#o zdu2^Omk1~P%xz~;6!{5Tfl`XH$l3kSUTgC%g-P_j`FVC+m6b|YVlfUJo=9tiR5(|J z7M-fISm}UhgzobiwOU5KUZdWqGrN8xaguTWZC_y1M4b(rw})}XLax@1edqyn&V{f5 zKk`vl_>uOUs5;KQ17UKaZ5P1d2XSK^T8y#M?cy`TnF*l@G)~d6#VofdI|IB-h#ED# zl6X^wNd(;O$gWIhu$a~#tgOUiT7|wDNQdCfBKrKYo5FE~Dm?|%%%B7M< zDV^8rN%xTh^Szm|na+xtYvk4tfDVZl;GM(i1hNJag((e5Evar~g|ne#F15rv*f6`9 z&;9FXu-@|PZ~Z-lklgUiuTz?mm%i-PY*@Du?>ybk>Af>lIJC9UXw-4e(d+lZ&`Ve=a;JeCoIq9lqG z%|;rwA{ScVrIM&f6GstA5)-E>X_ir|C1hDbv)N>7dWzZgoB7_2pN7G4cAR?+QfWeS z_>g`|#SsY|5z=`qm2|H0Rfo9`+z}MkcvPg3rXbyN2Ko6~NOE*3bizZP8&JhYp?j+0hVRsW1 z9W!jnqVm?26B#yPctC|Q%mECJ@FJ9wy&?=MtxiT!2C>PkK8AC{_aELkz9N;0Q8$Wn z!)mS%$LPIq&ZSaGq!5i>+fPG!XBs7b$%f-pQsG2!D#|-@yM$*w(g&xZrNsJhp79aR zsZrfw?<0HpwO{*hy!lPP!tJ-+#O^&0Gdr`MXFlr%+;{JtyzSS2l^=iYPxG{AK8xk0 zrPCWU9=vtb>ovRz+*JoS?*_G+qTO5Jo_p_Ys3>7!{`l&-uacqlTQ*ax*D@vKqBo zjZIt5V&6lzuzdJluDbFW)a&DDr3tadYSJ#858l85(ji<>1$8*^AiMzKDC__wG+8Yn zNg{g3mw31{PLXbAa^*0xZRs0N?|6r#F~{7|7WqP#=C;}3O$Ys8RB44vSEDsB7TPO# z>5;v=&|@jBo@OPhF+E+QR?P&@iy(D|tZqji5-e*>H7+iUBOPXf-U8l@jD9Gy=_~J! z_iKzl{3DGTI%A}gj6~koH~|nyMAd;`ol7B3AaqCs?bagaU3%s9abv@W{{G|t@}Z&T zhSC~m6{RVOYAK0u$UzJ1g4}LlEJ9Vst~7FuXxG%_40ql39p3fM-{*xt@^W7Fs-I!k z-iNs1rmypzZ+wmGp79K>eA1Iyc^qR71}&h|?Xr3EX0CkFlPJrQMx#Ny)9pO6`)=mv z=4sE(Z=Gz8-PB!LKA}Sk?~z_q!r!ZJDk`JazGkE

JEdk^NC_(}C2HjTtCe_E1U^ zB~cjct|M}3(3)V#aU8s3no_UTX*L^d+PaVgU6CNCsT1k4msQ9XhBc#80F>v!+vo_ii*xu5go=f9ruW8a~?^BeT7UzDs_+P|h&o8F|OT{83*1MT9ZXYA@-luG&HrB@UklQE+calLX-cPtGseX ztzIL|5;ko)6Di=;qjcIOY-5D3MYtMcj-tzzp%g4!TP0}Rhj5D@!<&_r_ML`lET;C912D=I- zD}5D44IN5lbfHg%Q5ZD}Vnq1K+v>g6{F{(BjC}s+e2o$=@2i^TDnclgq`TDSoC~kI z0l2l(UU?-#jAr5(Tg}9g0&6_N$7E?89SKTblE`4V7=g7BUJj)x;P~+)yyTUy$NPwX z`1_A>_St9iu0MM(=bd{V|NR$#mal&O8~o+R{*E|KAK#$8_Y`HoYoB)Q6{Km35Q4NG z^}g|yFWqt9cfWPbmp}K}SG?fIUiS0jQm6g$l^xmSQ6roR4*pX6#6nGmM%z*Cl z3TJM;HooGj3-GQeosUx)MFG)(x}y0w{8)4(~7%MB~#Uv9B!0ec!UDwe3gtzl$SUkwsa{a%|^z)M-FlQjd!Aw8q>{5 zyf-+R(;MGJ*(uRF9nE+tMc8_~OFfciiqsNY7*v)J>u{mGDM3ag6BBfMEyUEt)U~F+ zG)J>OhVz16zr*r!j+GHR&e_hUb#)9u=tc~uepR2yyAw=x=v+sJ>%?kTz|hr51aA0V zLr;8y6sMu}#m;>rOLc8uz8%&cF}*S`8SoE1FlSwDb|B_Dp@UvYh-$upk&JpRjDevR71 z1PhCcquRw|tstB$1X-H0Yu7G5_qi|fm9KoAJHB@>S-t+gXkv_q_CBb;`_1crVcQwo zNRuj2%2H~z8gZmZvY4!%obYHgGmI8xDNB-Aamgi@#{gcM!kL^#Bc;}?W4%KvNtUIg zNkW{YWVIT#%Ba`Pu4j679pC-dXF0O}L1tz)hnawBttzp*An96`;bK&ttfJ+v#*-B_ zRZ4-gVJd*q0k(T+=YIM|Fg`v(lBF0Gv*+%w)4t=&GbXen{N6!mJ38P@RvkAPnlmNYKFkV=I2?B!6D5yDIGg3^XDB(W0M(v1eucRC#!6BAr;*+ooFHTj3X z|0tjR!e@B?3tr6mmp_p|{?p&%uG?;PGc(hVtIHvW$(@vf(pZijpX2z#Joi8F5LZ3v zi61@d!V9|dx#tU?|Ce7F^m~j=PLN~?NtO_&DM^-+XhkG7ky4{atRi$IO6MhUTqDh5 z4G<-�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 As{jB1 literal 0 HcmV?d00001 diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 63a81db49a2..06f2572f0fa 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,9 @@ +min_slic3r_version = 1.41.0 +0.2.6 Added MMU2 MK2.5 settings min_slic3r_version = 1.41.0-alpha +0.2.5 Prusament is out - added prusament settings +0.2.4 Added soluble support profiles for MMU2 +0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit 0.2.2 Edited MMU2 Single mode purge line 0.2.1 Added PET and BVOH settings for MMU2 0.2.0-beta5 Fixed MMU1 ramming parameters diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 3b44a4bc7e3..7b50a1a8d0e 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.2.2 +config_version = 0.2.6 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -35,6 +35,10 @@ variants = 0.4 name = Original Prusa i3 MK2/S MMU 1.0 variants = 0.4; 0.6 +[printer_model:MK2.5MMU2] +name = Original Prusa i3 MK2.5 MMU 2.0 +variants = 0.4 + # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -181,7 +185,6 @@ support_material_interface_spacing = 0.1 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 1 -wipe_tower = 1 # XXXXXXXXXXXXXXXXXXXX # XXX--- 0.05mm ---XXX @@ -329,7 +332,7 @@ top_solid_layers = 7 [print:0.15mm 100mms Linear Advance] inherits = *0.15mm* bridge_flow_ratio = 0.95 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 50 infill_speed = 100 max_print_speed = 150 @@ -341,7 +344,7 @@ top_solid_infill_speed = 70 [print:0.15mm OPTIMAL] inherits = *0.15mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 top_infill_extrusion_width = 0.45 [print:0.15mm OPTIMAL 0.25 nozzle] @@ -374,16 +377,33 @@ perimeter_speed = 45 solid_infill_speed = 200 top_solid_infill_speed = 50 +[print:0.15mm OPTIMAL MK3 SOLUBLE FULL] +inherits = 0.15mm OPTIMAL MK3; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +support_material_extruder = 5 +support_material_interface_extruder = 5 +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 + +[print:0.15mm OPTIMAL MK3 SOLUBLE INTERFACE] +inherits = 0.15mm OPTIMAL MK3 SOLUBLE FULL +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 + [print:0.15mm OPTIMAL SOLUBLE FULL] inherits = *0.15mm*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 external_perimeter_speed = 25 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder perimeter_speed = 40 solid_infill_speed = 40 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 30 -wipe_tower = 1 [print:0.15mm OPTIMAL SOLUBLE INTERFACE] inherits = 0.15mm OPTIMAL SOLUBLE FULL @@ -404,6 +424,7 @@ max_print_speed = 200 perimeter_speed = 45 solid_infill_speed = 200 top_solid_infill_speed = 50 + [print:*0.20mm*] inherits = *common* bottom_solid_layers = 4 @@ -436,7 +457,7 @@ top_solid_infill_speed = 50 [print:0.20mm 100mms Linear Advance] inherits = *0.20mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 50 infill_speed = 100 max_print_speed = 150 @@ -458,9 +479,27 @@ perimeter_speed = 45 solid_infill_speed = 200 top_solid_infill_speed = 50 +[print:0.20mm FAST MK3 SOLUBLE FULL] +inherits = 0.20mm FAST MK3; *soluble_support* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +support_material_extruder = 5 +support_material_interface_extruder = 5 +perimeter_speed = 40 +solid_infill_speed = 40 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 + +[print:0.20mm FAST MK3 SOLUBLE INTERFACE] +inherits = 0.20mm FAST MK3 SOLUBLE FULL +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +support_material_extruder = 0 +support_material_interface_layers = 3 +support_material_with_sheath = 0 + [print:0.20mm NORMAL] inherits = *0.20mm* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 [print:0.20mm NORMAL 0.6 nozzle] inherits = *0.20mm*; *0.6nozzle* @@ -468,7 +507,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and [print:0.20mm NORMAL SOLUBLE FULL] inherits = *0.20mm*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 and num_extruders>1 external_perimeter_speed = 30 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder perimeter_speed = 40 @@ -519,7 +558,7 @@ top_solid_layers = 4 [print:0.35mm FAST] inherits = *0.35mm* bridge_flow_ratio = 0.95 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2[^\.].*/ and nozzle_diameter[0]==0.4 first_layer_extrusion_width = 0.42 perimeter_extrusion_width = 0.43 solid_infill_extrusion_width = 0.7 @@ -547,6 +586,53 @@ support_material_interface_layers = 2 support_material_with_sheath = 0 support_material_xy_spacing = 150% +# XXXXXXXXXXXXXXXXXXXXXX +# XXX----- MK2.5 ----XXX +# XXXXXXXXXXXXXXXXXXXXXX + +[print:0.15mm 100mms Linear Advance MK2.5] +inherits = 0.15mm 100mms Linear Advance +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.15mm OPTIMAL MK2.5] +inherits = 0.15mm OPTIMAL +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.15mm OPTIMAL SOLUBLE FULL MK2.5] +inherits = 0.15mm OPTIMAL SOLUBLE FULL +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 + +[print:0.15mm OPTIMAL SOLUBLE INTERFACE MK2.5] +inherits = 0.15mm OPTIMAL SOLUBLE INTERFACE +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 + +[print:0.20mm 100mms Linear Advance MK2.5] +inherits = 0.20mm 100mms Linear Advance +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.20mm NORMAL MK2.5] +inherits = 0.20mm NORMAL +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + +[print:0.20mm NORMAL SOLUBLE FULL MK2.5] +inherits = 0.20mm NORMAL SOLUBLE FULL +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +single_extruder_multi_material_priming = 0 + +[print:0.20mm NORMAL SOLUBLE INTERFACE MK2.5] +inherits = 0.20mm NORMAL SOLUBLE INTERFACE +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 +single_extruder_multi_material_priming = 0 + +[print:0.35mm FAST MK2.5] +inherits = 0.35mm FAST +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.5.*/ and nozzle_diameter[0]==0.4 +single_extruder_multi_material_priming = 0 + # XXXXXXxxXXXXXXXXXXXXXX # XXX--- filament ---XXX # XXXXXXXXxxXXXXXXXXXXXX @@ -555,7 +641,7 @@ support_material_xy_spacing = 150% cooling = 1 compatible_printers = # For now, all but selected filaments are disabled for the MMU 2.0 -compatible_printers_condition = ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) +compatible_printers_condition = ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 filament_loading_speed = 28 @@ -655,7 +741,7 @@ temperature = 240 [filament:ColorFabb Brass Bronze] inherits = *PLA* # For now, all but selected filaments are disabled for the MMU 2.0 -compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) extrusion_multiplier = 1.2 filament_cost = 80.65 filament_density = 4 @@ -687,7 +773,7 @@ filament_density = 1.24 [filament:ColorFabb Woodfil] inherits = *PLA* # For now, all but selected filaments are disabled for the MMU 2.0 -compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) extrusion_multiplier = 1.2 filament_cost = 62.9 filament_density = 1.15 @@ -789,7 +875,7 @@ temperature = 275 [filament:Fillamentum Timberfil] inherits = *PLA* # For now, all but selected filaments are disabled for the MMU 2.0 -compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) extrusion_multiplier = 1.2 filament_cost = 68 filament_density = 1.15 @@ -851,7 +937,7 @@ filament_notes = "List of materials tested with standart ABS print settings for [filament:*ABS MMU2*] inherits = Prusa ABS -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material filament_cooling_final_speed = 50 filament_cooling_initial_speed = 10 filament_cooling_moves = 5 @@ -889,7 +975,7 @@ filament_notes = "List of manufacturers tested with standart PET print settings [filament:*PET MMU2*] inherits = Prusa PET -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material temperature = 230 first_layer_temperature = 230 filament_cooling_final_speed = 1 @@ -915,9 +1001,16 @@ filament_cost = 25.4 filament_density = 1.24 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +[filament:Prusament PLA] +inherits = *PLA* +temperature = 215 +filament_cost = 24.99 +filament_density = 1.24 +filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" + [filament:*PLA MMU2*] inherits = Prusa PLA -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material temperature = 205 filament_cooling_final_speed = 1 filament_cooling_initial_speed = 2 @@ -934,6 +1027,9 @@ inherits = *PLA MMU2* [filament:Prusa PLA MMU2] inherits = *PLA MMU2* +[filament:Prusament PLA MMU2] +inherits = *PLA MMU2* + [filament:SemiFlex or Flexfill 98A] inherits = *FLEX* filament_cost = 82 @@ -998,7 +1094,7 @@ temperature = 210 [filament:Verbatim BVOH MMU2] inherits = Verbatim BVOH -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material temperature = 195 filament_notes = BVOH fan_always_on = 1 @@ -1098,7 +1194,7 @@ z_offset = 0 printer_model = MK2S printer_variant = 0.4 default_print_profile = 0.15mm OPTIMAL -default_filament_profile = Prusa PLA +default_filament_profile = Prusament PLA [printer:*multimaterial*] inherits = *common* @@ -1193,19 +1289,83 @@ default_print_profile = 0.20mm NORMAL 0.6 nozzle inherits = Original Prusa i3 MK2 printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 + +[printer:Original Prusa i3 MK2.5 MMU2 Single] +inherits = Original Prusa i3 MK2.5; *mm2* +printer_model = MK2.5 +single_extruder_multi_material = 0 +max_print_height = 200 +remaining_times = 1 +silent_mode = 0 +retract_lift_below = 199 +machine_max_acceleration_e = 10000 +machine_max_acceleration_extruding = 2000 +machine_max_acceleration_retracting = 1500 +machine_max_acceleration_x = 9000 +machine_max_acceleration_y = 9000 +machine_max_acceleration_z = 500 +machine_max_feedrate_e = 120 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 +machine_max_feedrate_z = 12 +machine_max_jerk_e = 2.5 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 +machine_max_jerk_z = 0.2 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +default_print_profile = 0.15mm OPTIMAL MK2.5 +default_filament_profile = Prusament PLA +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n +start_gcode = M107\nM115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors + +[printer:Original Prusa i3 MK2.5 MMU2] +inherits = Original Prusa i3 MK2.5; *mm2* +printer_model = MK2.5 +max_print_height = 200 +remaining_times = 1 +silent_mode = 0 +retract_lift_below = 199 +machine_max_acceleration_e = 10000 +machine_max_acceleration_extruding = 2000 +machine_max_acceleration_retracting = 1500 +machine_max_acceleration_x = 9000 +machine_max_acceleration_y = 9000 +machine_max_acceleration_z = 500 +machine_max_feedrate_e = 120 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 +machine_max_feedrate_z = 12 +machine_max_jerk_e = 2.5 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 +machine_max_jerk_z = 0.2 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +default_print_profile = 0.15mm OPTIMAL MK2.5 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n +single_extruder_multi_material = 1 +# The 5x nozzle diameter defines the number of extruders. Other extruder parameters +# (for example the retract values) are duplicaed from the first value, so they do not need +# to be defined explicitely. +nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 +extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F +start_gcode = M107\nM115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n [printer:Original Prusa i3 MK2.5 0.25 nozzle] inherits = Original Prusa i3 MK2 0.25 nozzle printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.6 nozzle] inherits = Original Prusa i3 MK2 0.6 nozzle printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 # XXXXXXXXXXXXXXXXX # XXX--- MK3 ---XXX @@ -1221,8 +1381,8 @@ machine_max_acceleration_x = 1000,960 machine_max_acceleration_y = 1000,960 machine_max_acceleration_z = 1000,1000 machine_max_feedrate_e = 120,120 -machine_max_feedrate_x = 200,172 -machine_max_feedrate_y = 200,172 +machine_max_feedrate_x = 200,100 +machine_max_feedrate_y = 200,100 machine_max_feedrate_z = 12,12 machine_max_jerk_e = 1.5,1.5 machine_max_jerk_x = 8,8 @@ -1235,7 +1395,7 @@ remaining_times = 1 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 max_print_height = 210 -start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +start_gcode = M115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm OPTIMAL MK3 @@ -1265,11 +1425,12 @@ retract_length_toolchange = 3 extra_loading_move = -13 printer_model = MK3MMU2 default_print_profile = 0.15mm OPTIMAL MK3 -default_filament_profile = Prusa PLA MMU2 +default_filament_profile = Prusament PLA MMU2 [printer:Original Prusa i3 MK3 MMU2 Single] inherits = *mm2* -start_gcode = M107\nM115 U3.4.0-RC2 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +single_extruder_multi_material = 0 +start_gcode = M107\nM115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3 MMU2] @@ -1280,7 +1441,7 @@ inherits = *mm2* machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\nM115 U3.4.0-RC2 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 0272aae4e40..9d49ff49607 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -827,7 +827,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl double brim_width = config->opt_float("brim_width"); if (boost::any_cast(value) == true) { - new_val = m_brim_width == 0.0 ? 10 : + new_val = m_brim_width == 0.0 ? 5 : m_brim_width < 0.0 ? m_brim_width * (-1) : m_brim_width; } From 4fe0efba119d06f0016e98c3505f38e432283648 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 23 Oct 2018 12:45:03 +0200 Subject: [PATCH 58/63] Bumped up the version number to 1.41.1 --- xs/src/libslic3r/libslic3r.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index ddd056bc745..7ed001146d6 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.41.0" +#define SLIC3R_VERSION "1.41.1" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; From 44c0032b82f214e08af9ddcfbb1f4e3db08d11e2 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 23 Oct 2018 12:47:49 +0200 Subject: [PATCH 59/63] Updated Prusa3D config index to only allow 0.2.6 settings for Slic3r PE 1.41.1 --- resources/profiles/PrusaResearch.idx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 06f2572f0fa..05fb7c7a68b 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,4 @@ -min_slic3r_version = 1.41.0 +min_slic3r_version = 1.41.1 0.2.6 Added MMU2 MK2.5 settings min_slic3r_version = 1.41.0-alpha 0.2.5 Prusament is out - added prusament settings From 720fb29768fb702cfb185bfb05a70adb84f3b109 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 26 Oct 2018 13:20:03 +0200 Subject: [PATCH 60/63] Updated bundled profiles for 1.41.1 final. --- resources/profiles/PrusaResearch.idx | 8 ++++++++ resources/profiles/PrusaResearch.ini | 24 ++++++++++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 05fb7c7a68b..f6befe005a3 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,12 @@ min_slic3r_version = 1.41.1 +0.3.2 New MK2.5 and MK3 FW versions +0.3.1 New MK2.5 and MK3 FW versions +0.3.0 New MK2.5 and MK3 FW version +min_slic3r_version = 1.41.0-alpha +0.2.9 New MK2.5 and MK3 FW versions +0.2.8 New MK2.5 and MK3 FW version +min_slic3r_version = 1.41.1 +0.2.7 New MK2.5 and MK3 FW version 0.2.6 Added MMU2 MK2.5 settings min_slic3r_version = 1.41.0-alpha 0.2.5 Prusament is out - added prusament settings diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 7b50a1a8d0e..95859a67fcf 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.2.6 +config_version = 0.3.2 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -1289,11 +1289,11 @@ default_print_profile = 0.20mm NORMAL 0.6 nozzle inherits = Original Prusa i3 MK2 printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 MMU2 Single] inherits = Original Prusa i3 MK2.5; *mm2* -printer_model = MK2.5 +printer_model = MK2.5MMU2 single_extruder_multi_material = 0 max_print_height = 200 remaining_times = 1 @@ -1318,12 +1318,12 @@ machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL MK2.5 default_filament_profile = Prusament PLA printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n -start_gcode = M107\nM115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.4.2 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK2.5 MMU2] inherits = Original Prusa i3 MK2.5; *mm2* -printer_model = MK2.5 +printer_model = MK2.5MMU2 max_print_height = 200 remaining_times = 1 silent_mode = 0 @@ -1352,20 +1352,20 @@ single_extruder_multi_material = 1 # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\nM115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.4.2 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n [printer:Original Prusa i3 MK2.5 0.25 nozzle] inherits = Original Prusa i3 MK2 0.25 nozzle printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.6 nozzle] inherits = Original Prusa i3 MK2 0.6 nozzle printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 # XXXXXXXXXXXXXXXXX # XXX--- MK3 ---XXX @@ -1395,7 +1395,7 @@ remaining_times = 1 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 max_print_height = 210 -start_gcode = M115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +start_gcode = M115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm OPTIMAL MK3 @@ -1430,7 +1430,7 @@ default_filament_profile = Prusament PLA MMU2 [printer:Original Prusa i3 MK3 MMU2 Single] inherits = *mm2* single_extruder_multi_material = 0 -start_gcode = M107\nM115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3 MMU2] @@ -1441,10 +1441,10 @@ inherits = *mm2* machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\nM115 U3.4.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. [obsolete_presets] print="0.05mm DETAIL 0.25 nozzle";"0.05mm DETAIL MK3";"0.05mm DETAIL";"0.20mm NORMAL MK3";"0.35mm FAST MK3";"print:0.15mm OPTIMAL MK3 MMU2";"print:0.20mm FAST MK3 MMU2" -filament="ColorFabb Brass Bronze 1.75mm";"ColorFabb HT 1.75mm";"ColorFabb nGen 1.75mm";"ColorFabb Woodfil 1.75mm";"ColorFabb XT 1.75mm";"ColorFabb XT-CF20 1.75mm";"E3D PC-ABS 1.75mm";"Fillamentum ABS 1.75mm";"Fillamentum ASA 1.75mm";"Generic ABS 1.75mm";"Generic PET 1.75mm";"Generic PLA 1.75mm";"Prusa ABS 1.75mm";"Prusa HIPS 1.75mm";"Prusa PET 1.75mm";"Prusa PLA 1.75mm";"Taulman Bridge 1.75mm";"Taulman T-Glase 1.75mm" +filament="ColorFabb Brass Bronze 1.75mm";"ColorFabb HT 1.75mm";"ColorFabb nGen 1.75mm";"ColorFabb Woodfil 1.75mm";"ColorFabb XT 1.75mm";"ColorFabb XT-CF20 1.75mm";"E3D PC-ABS 1.75mm";"Fillamentum ABS 1.75mm";"Fillamentum ASA 1.75mm";"Generic ABS 1.75mm";"Generic PET 1.75mm";"Generic PLA 1.75mm";"Prusa ABS 1.75mm";"Prusa HIPS 1.75mm";"Prusa PET 1.75mm";"Prusa PLA 1.75mm";"Taulman Bridge 1.75mm";"Taulman T-Glase 1.75mm" \ No newline at end of file From e2d21d21d0fbe09038300e1128ace70a319e4dca Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 29 Oct 2018 16:27:07 +0100 Subject: [PATCH 61/63] looping perimeters: cleaning & now extrude long "move" between outline & holes if infill is at 0% --- xs/src/libslic3r/PerimeterGenerator.cpp | 72 ++++++++++--------------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 19040c6fb37..63ddb725a8a 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -722,12 +722,10 @@ get_nearest_point(const PerimeterGeneratorLoops &children, ExtrusionLoop &myPoly } -int id = 0; ExtrusionLoop PerimeterGenerator::_extrude_and_cut_loop(const PerimeterGeneratorLoop &loop, const Point entry_point, const Line &direction) const { - const int my_id = ++id; bool need_to_reverse = false; Polyline initial_polyline; const coord_t dist_cut = (coord_t)scale_(this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1)); @@ -772,7 +770,6 @@ PerimeterGenerator::_extrude_and_cut_loop(const PerimeterGeneratorLoop &loop, co //std::vector myPolylines; ExtrusionLoop my_loop; - ExtrusionLoop svg_out(elrDefault); //overhang / notoverhang { bool is_external = loop.is_external(); @@ -886,8 +883,18 @@ ExtrusionLoop PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, const PerimeterGeneratorLoops &children, const Point entry_point) const { //std::cout << " === ==== _traverse_and_join_loops ==== ===\n"; + // other perimeters + //this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); + //coord_t perimeter_width = this->perimeter_flow.scaled_width(); + const coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); - const coord_t dist_cut = (coord_t)scale_(this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1)); + //// external perimeters + //this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); + //coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); + const coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); + //coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow); + + //const coord_t dist_cut = (coord_t)scale_(this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1)); //TODO change this->external_perimeter_flow.scaled_width() if it's the first one! const coord_t max_width_extrusion = this->perimeter_flow.scaled_width(); ExtrusionLoop my_loop = _extrude_and_cut_loop(loop, entry_point); @@ -900,20 +907,13 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, //Polylines myPolylines = { myPolyline }; //iterate on each point ot find the best place to go into the child vector childs = children; - int child_idx = 0; while (!childs.empty()) { - child_idx++; PerimeterIntersectionPoint nearest = get_nearest_point(childs, my_loop, this->perimeter_flow.scaled_width(), this->perimeter_flow.scaled_width()* 0.8); if (nearest.idx_children == (size_t)-1) { //return ExtrusionEntityCollection(); break; } else { - stringstream log_bef; const PerimeterGeneratorLoop &child = childs[nearest.idx_children]; - log_bef << "dist travel @search swap is : " << unscale(nearest.outter_best.distance_to(nearest.child_best)) - << " from " << unscale(nearest.outter_best.x) << ":" << unscale(nearest.outter_best.y) - << " to " << unscale(nearest.child_best.x) << ":" << unscale(nearest.child_best.y) - << "\n"; //std::cout << "c." << child_idx << " === i have " << my_loop.paths.size() << " paths" << " == cut_path_is_ccw size " << path_is_ccw.size() << "\n"; //std::cout << "change to child " << nearest.idx_children << " @ " << unscale(nearest.outter_best.x) << ":" << unscale(nearest.outter_best.y) // << ", idxpolyline = " << nearest.idx_polyline_outter << "\n"; @@ -960,10 +960,6 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, outer_end->polyline.points.erase(outer_end->polyline.points.begin()+1, outer_end->polyline.points.end()); outer_end->polyline.points.insert(outer_end->polyline.points.begin(), nearest.outter_best); } - log_bef << "dist travel before child loop get is : " << unscale(outer_start->polyline.points.back().distance_to(nearest.child_best)) - << " from " << unscale(outer_start->polyline.points.back().x) << ":" << unscale(outer_start->polyline.points.back().y) - << " to " << unscale(nearest.child_best.x) << ":" << unscale(nearest.child_best.y) - << "\n"; Polyline to_reduce = outer_start->polyline; if (to_reduce.points.size()>1) to_reduce.clip_end(SCALED_RESOLUTION); deletedSection.a = to_reduce.points.back(); @@ -974,7 +970,11 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, //get the inner loop to connect to us. ExtrusionLoop child_loop = _extrude_and_cut_loop(child, nearest.child_best, deletedSection); - //FIXME: if child_loophas no point or 1 point or not enough space !!!!!!! + const coord_t inner_child_spacing = child.is_external() ? ext_perimeter_spacing : perimeter_spacing; + const coord_t outer_start_spacing = scale_(outer_start->width - outer_start->height * (1. - 0.25 * PI)); + const coord_t outer_end_spacing = scale_(outer_end->width - outer_end->height * (1. - 0.25 * PI)); + + //FIXME: if child_loop has no point or 1 point or not enough space !!!!!!! const size_t child_paths_size = child_loop.paths.size(); my_loop.paths.insert(my_loop.paths.begin() + nearest.idx_polyline_outter + 1, child_loop.paths.begin(), child_loop.paths.end()); for (size_t i = 0; i < child_paths_size; i++) path_is_ccw.insert(path_is_ccw.begin() + nearest.idx_polyline_outter + 1, !cut_path_is_ccw); @@ -984,23 +984,19 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, outer_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size + 1]; ExtrusionPath *inner_start = &my_loop.paths[nearest.idx_polyline_outter+1]; ExtrusionPath *inner_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size]; - log_bef << "dist travel before trim is : " << unscale(outer_start->polyline.points.back().distance_to(inner_start->polyline.points.front())) - << " from " << unscale(outer_start->polyline.points.back().x) << ":" << unscale(outer_start->polyline.points.back().y) - << " to " << unscale(inner_start->polyline.points.front().x) << ":" << unscale(inner_start->polyline.points.front().y) - << "\n"; //TRIM //choose trim direction if (outer_start->polyline.points.size() == 1) { - outer_end->polyline.clip_start(dist_cut); - my_loop.paths[nearest.idx_polyline_outter + child_paths_size ].polyline.clip_end(dist_cut); + outer_end->polyline.clip_start(outer_end_spacing); + my_loop.paths[nearest.idx_polyline_outter + child_paths_size].polyline.clip_end(inner_child_spacing); } else if (outer_end->polyline.points.size() == 1) { - outer_start->polyline.clip_end(dist_cut); - inner_start->polyline.clip_start(dist_cut); + outer_start->polyline.clip_end(outer_start_spacing); + inner_start->polyline.clip_start(inner_child_spacing); } else { coord_t length_poly_1 = outer_start->polyline.length(); coord_t length_poly_2 = outer_end->polyline.length(); - coord_t length_trim_1 = dist_cut / 2; - coord_t length_trim_2 = dist_cut / 2; + coord_t length_trim_1 = outer_start_spacing / 2; + coord_t length_trim_2 = outer_end_spacing / 2; if (length_poly_1 < length_trim_1) { length_trim_2 = length_trim_1 + length_trim_2 - length_poly_1; } @@ -1020,8 +1016,8 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, length_poly_1 = inner_start->polyline.length(); length_poly_2 = inner_end->polyline.length(); - length_trim_1 = dist_cut / 2; - length_trim_2 = dist_cut / 2; + length_trim_1 = inner_child_spacing / 2; + length_trim_2 = inner_child_spacing / 2; if (length_poly_1 < length_trim_1) { length_trim_2 = length_trim_1 + length_trim_2 - length_poly_1; } @@ -1046,10 +1042,6 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, //last check to see if we need a reverse { - log_bef << "dist travel before swap is : " << unscale(outer_start->polyline.points.back().distance_to(inner_start->polyline.points.front())) - << " from " << unscale(outer_start->polyline.points.back().x) << ":" << unscale(outer_start->polyline.points.back().y) - << " to " << unscale(inner_start->polyline.points.front().x) << ":" << unscale(inner_start->polyline.points.front().y) - << "\n"; Line l1(outer_start->polyline.points.back(), inner_start->polyline.points.front()); Line l2(inner_end->polyline.points.back(), outer_end->polyline.points.front()); Point p_inter(0, 0); @@ -1074,15 +1066,7 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, ExtrusionPaths travel_path_end;// (ExtrusionRole::erNone, 0, outer_end->width, outer_end->height); //travel_path_end.extruder_id = -1; double dist_travel = outer_start->polyline.points.back().distance_to(inner_start->polyline.points.front()); - if (dist_travel > max_width_extrusion * 10) { - std::cout << "ERROR: dist travel is to high : " << unscale(dist_travel) - << " from " << unscale(outer_start->polyline.points.back().x) << ":" << unscale(outer_start->polyline.points.back().y) - << " to " << unscale(inner_start->polyline.points.front().x) << ":" << unscale(inner_start->polyline.points.front().y) - << "\n"; - std::cout << log_bef.str(); - //std::cout << log.str(); - } - if (dist_travel > max_width_extrusion*1.5) { + if (dist_travel > max_width_extrusion*1.5 && this->config->fill_density.value > 0) { travel_path_begin.emplace_back(ExtrusionRole::erPerimeter, outer_start->mm3_per_mm, outer_start->width, outer_start->height); travel_path_begin.emplace_back(ExtrusionRole::erNone, 0, outer_start->width, outer_start->height); travel_path_begin.emplace_back(ExtrusionRole::erPerimeter, outer_start->mm3_per_mm, outer_start->width, outer_start->height); @@ -1109,7 +1093,7 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, } else { // the path is small enough to extrude all along. double flow_mult = 1; - if (dist_travel > max_width_extrusion) { + if (dist_travel > max_width_extrusion && this->config->fill_density.value > 0) { // the path is a bit too long, reduce the extrusion flow. flow_mult = max_width_extrusion / dist_travel; } @@ -1119,7 +1103,7 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, travel_path_begin[0].polyline.append(inner_start->polyline.points.front()); } dist_travel = inner_end->polyline.points.back().distance_to(outer_end->polyline.points.front()); - if (dist_travel > max_width_extrusion*1.5) { + if (dist_travel > max_width_extrusion*1.5 && this->config->fill_density.value > 0) { travel_path_end.emplace_back(ExtrusionRole::erPerimeter, outer_end->mm3_per_mm, outer_end->width, outer_end->height); travel_path_end.emplace_back(ExtrusionRole::erNone, 0, outer_end->width, outer_end->height); travel_path_end.emplace_back(ExtrusionRole::erPerimeter, outer_end->mm3_per_mm, outer_end->width, outer_end->height); @@ -1146,7 +1130,7 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, } else { // the path is small enough to extrude all along. double flow_mult = 1; - if (dist_travel > max_width_extrusion) { + if (dist_travel > max_width_extrusion && this->config->fill_density.value > 0) { // the path is a bit too long, reduce the extrusion flow. flow_mult = max_width_extrusion / dist_travel; } From f7b77b29b65058eb58d7d6706594a1e9d77647ce Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 31 Oct 2018 14:49:17 +0100 Subject: [PATCH 62/63] debug support generation from merge. --- xs/src/libslic3r/SupportMaterial.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 75b14f0d84e..404f3e2c9cb 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -3263,13 +3263,13 @@ void PrintObjectSupportMaterial::generate_toolpaths( } layer_cache.overlaps.reserve(4); - if (! bottom_contact_layer.empty()) + if (!bottom_contact_layer.empty() && !bottom_contact_layer.extrusions.empty()) layer_cache.overlaps.push_back(&bottom_contact_layer); - if (! top_contact_layer.empty()) + if (!top_contact_layer.empty() && !top_contact_layer.extrusions.empty()) layer_cache.overlaps.push_back(&top_contact_layer); - if (! interface_layer.empty()) + if (!interface_layer.empty() && !interface_layer.extrusions.empty()) layer_cache.overlaps.push_back(&interface_layer); - if (! base_layer.empty()) + if (!base_layer.empty() && !base_layer.extrusions.empty()) layer_cache.overlaps.push_back(&base_layer); // Sort the layers with the same print_z coordinate by their heights, thickest first. std::sort(layer_cache.overlaps.begin(), layer_cache.overlaps.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); From b8dca4cb9d8f154846ed433108540916484ad613 Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 31 Oct 2018 14:47:42 +0100 Subject: [PATCH 63/63] looping perimeters : bugfix (when very small perimeter) --- xs/src/libslic3r/PerimeterGenerator.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 63ddb725a8a..7763fa1594f 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -948,7 +948,10 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, break; } } - if (idx_before == (size_t)-1) std::cout << "ERROR: idx_before can't be finded\n"; + if (idx_before == (size_t)-1) { + std::cout << "ERROR: idx_before can't be finded\n"; + continue; + } Points &my_polyline_points = outer_start->polyline.points; my_polyline_points.erase(my_polyline_points.begin() + idx_before + 1, my_polyline_points.end()); @@ -976,6 +979,7 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, //FIXME: if child_loop has no point or 1 point or not enough space !!!!!!! const size_t child_paths_size = child_loop.paths.size(); + if (child_paths_size == 0) continue; my_loop.paths.insert(my_loop.paths.begin() + nearest.idx_polyline_outter + 1, child_loop.paths.begin(), child_loop.paths.end()); for (size_t i = 0; i < child_paths_size; i++) path_is_ccw.insert(path_is_ccw.begin() + nearest.idx_polyline_outter + 1, !cut_path_is_ccw); @@ -986,12 +990,20 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, ExtrusionPath *inner_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size]; //TRIM //choose trim direction - if (outer_start->polyline.points.size() == 1) { + if (outer_start->polyline.points.size() == 1 && outer_end->polyline.points.size() == 1) { + //do nothing + } else if (outer_start->polyline.points.size() == 1) { outer_end->polyline.clip_start(outer_end_spacing); - my_loop.paths[nearest.idx_polyline_outter + child_paths_size].polyline.clip_end(inner_child_spacing); + if (inner_end->polyline.length() > inner_child_spacing) + inner_end->polyline.clip_end(inner_child_spacing); + else + inner_end->polyline.clip_end(inner_end->polyline.length() / 2); } else if (outer_end->polyline.points.size() == 1) { outer_start->polyline.clip_end(outer_start_spacing); - inner_start->polyline.clip_start(inner_child_spacing); + if (inner_start->polyline.length() > inner_child_spacing) + inner_start->polyline.clip_start(inner_child_spacing); + else + inner_start->polyline.clip_start(inner_start->polyline.length()/2); } else { coord_t length_poly_1 = outer_start->polyline.length(); coord_t length_poly_2 = outer_end->polyline.length();