diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index f7c1168e6e..4a75390a99 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -918,9 +918,7 @@ sub arrange { $self->pause_background_process; my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape); - eval { - $self->{model}->arrange_objects($self->GetFrame->config->min_object_distance, $bb); - }; + my $success = $self->{model}->arrange_objects($self->GetFrame->config->min_object_distance, $bb); # ignore arrange failures on purpose: user has visual feedback and we don't need to warn him # when parts don't fit in print bed diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 1e1ae3d5b1..7f411f1f47 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -87,22 +87,22 @@ public: return this->role == erPerimeter || this->role == erExternalPerimeter || this->role == erOverhangPerimeter; - } + }; bool is_infill() const { return this->role == erBridgeInfill || this->role == erInternalInfill || this->role == erSolidInfill || this->role == erTopSolidInfill; - } + }; bool is_solid_infill() const { return this->role == erBridgeInfill || this->role == erSolidInfill || this->role == erTopSolidInfill; - } + }; bool is_bridge() const { return this->role == erBridgeInfill || this->role == erOverhangPerimeter; - } + }; // Produce a list of 2D polygons covered by the extruded path. Polygons grow() const; // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. @@ -148,13 +148,13 @@ class ExtrusionLoop : public ExtrusionEntity return this->paths.front().role == erPerimeter || this->paths.front().role == erExternalPerimeter || this->paths.front().role == erOverhangPerimeter; - } + }; bool is_infill() const { return this->paths.front().role == erBridgeInfill || this->paths.front().role == erInternalInfill || this->paths.front().role == erSolidInfill || this->paths.front().role == erTopSolidInfill; - } + }; bool is_solid_infill() const { return this->paths.front().role == erBridgeInfill || this->paths.front().role == erSolidInfill diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp index d988a97260..fec7dea89e 100644 --- a/xs/src/libslic3r/Geometry.cpp +++ b/xs/src/libslic3r/Geometry.cpp @@ -329,9 +329,26 @@ linint(double value, double oldmin, double oldmax, double newmin, double newmax) return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin; } -Pointfs -arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb) +class ArrangeItem { + public: + Pointf pos; + size_t index_x, index_y; + coordf_t dist; +}; +class ArrangeItemIndex { + public: + coordf_t index; + ArrangeItem item; + ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {}; +}; + +bool +arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const BoundingBoxf* bb, Pointfs &positions) { + positions.clear(); + + Pointf part = part_size; + // use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm part.x += dist; part.y += dist; @@ -349,7 +366,7 @@ arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb) size_t cellw = floor((area.x + dist) / part.x); size_t cellh = floor((area.y + dist) / part.y); if (total_parts > (cellw * cellh)) - CONFESS("%zu parts won't fit in your print area!\n", total_parts); + return false; // total space used by cells Pointf cells(cellw * part.x, cellh * part.y); @@ -404,7 +421,7 @@ arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb) } cellsorder.insert(cellsorder.begin() + low, ArrangeItemIndex(index, c)); } - ENDSORT: true; + ENDSORT: ; } } @@ -430,7 +447,6 @@ arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb) } } // now we actually place objects into cells, positioned such that the left and bottom borders are at 0 - Pointfs positions; for (size_t i = 1; i <= total_parts; ++i) { ArrangeItemIndex c = cellsorder.front(); cellsorder.erase(cellsorder.begin()); @@ -447,297 +463,9 @@ arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb) } } - return positions; + return true; } -#ifdef SLIC3R_DEBUG -// The following code for the visualization of the boost Voronoi diagram is based on: -// -// Boost.Polygon library voronoi_visualizer.cpp file -// Copyright Andrii Sydorchuk 2010-2012. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -namespace Voronoi { namespace Internal { - - typedef double coordinate_type; - typedef boost::polygon::point_data point_type; - typedef boost::polygon::segment_data segment_type; - typedef boost::polygon::rectangle_data rect_type; -// typedef voronoi_builder VB; - typedef boost::polygon::voronoi_diagram VD; - typedef VD::cell_type cell_type; - typedef VD::cell_type::source_index_type source_index_type; - typedef VD::cell_type::source_category_type source_category_type; - typedef VD::edge_type edge_type; - typedef VD::cell_container_type cell_container_type; - typedef VD::cell_container_type vertex_container_type; - typedef VD::edge_container_type edge_container_type; - typedef VD::const_cell_iterator const_cell_iterator; - typedef VD::const_vertex_iterator const_vertex_iterator; - typedef VD::const_edge_iterator const_edge_iterator; - - static const std::size_t EXTERNAL_COLOR = 1; - - inline void color_exterior(const VD::edge_type* edge) - { - if (edge->color() == EXTERNAL_COLOR) - return; - edge->color(EXTERNAL_COLOR); - edge->twin()->color(EXTERNAL_COLOR); - const VD::vertex_type* v = edge->vertex1(); - if (v == NULL || !edge->is_primary()) - return; - v->color(EXTERNAL_COLOR); - const VD::edge_type* e = v->incident_edge(); - do { - color_exterior(e); - e = e->rot_next(); - } while (e != v->incident_edge()); - } - - inline point_type retrieve_point(const std::vector &segments, const cell_type& cell) - { - assert(cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == SOURCE_CATEGORY_SEGMENT_END_POINT); - return (cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) ? low(segments[cell.source_index()]) : high(segments[cell.source_index()]); - } - - inline void clip_infinite_edge(const std::vector &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector* clipped_edge) - { - const cell_type& cell1 = *edge.cell(); - const cell_type& cell2 = *edge.twin()->cell(); - point_type origin, direction; - // Infinite edges could not be created by two segment sites. - if (cell1.contains_point() && cell2.contains_point()) { - point_type p1 = retrieve_point(segments, cell1); - point_type p2 = retrieve_point(segments, cell2); - origin.x((p1.x() + p2.x()) * 0.5); - origin.y((p1.y() + p2.y()) * 0.5); - direction.x(p1.y() - p2.y()); - direction.y(p2.x() - p1.x()); - } else { - origin = cell1.contains_segment() ? retrieve_point(segments, cell2) : retrieve_point(segments, cell1); - segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()]; - coordinate_type dx = high(segment).x() - low(segment).x(); - coordinate_type dy = high(segment).y() - low(segment).y(); - if ((low(segment) == origin) ^ cell1.contains_point()) { - direction.x(dy); - direction.y(-dx); - } else { - direction.x(-dy); - direction.y(dx); - } - } - coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y())); - if (edge.vertex0() == NULL) { - clipped_edge->push_back(point_type( - origin.x() - direction.x() * koef, - origin.y() - direction.y() * koef)); - } else { - clipped_edge->push_back( - point_type(edge.vertex0()->x(), edge.vertex0()->y())); - } - if (edge.vertex1() == NULL) { - clipped_edge->push_back(point_type( - origin.x() + direction.x() * koef, - origin.y() + direction.y() * koef)); - } else { - clipped_edge->push_back( - point_type(edge.vertex1()->x(), edge.vertex1()->y())); - } - } - - inline void sample_curved_edge(const std::vector &segments, const edge_type& edge, std::vector &sampled_edge, coordinate_type max_dist) - { - point_type point = edge.cell()->contains_point() ? - retrieve_point(segments, *edge.cell()) : - retrieve_point(segments, *edge.twin()->cell()); - segment_type segment = edge.cell()->contains_point() ? - segments[edge.twin()->cell()->source_index()] : - segments[edge.cell()->source_index()]; - ::boost::polygon::voronoi_visual_utils::discretize(point, segment, max_dist, &sampled_edge); - } - -} /* namespace Internal */ } // namespace Voronoi - -static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_diagram &vd, const ThickPolylines *polylines, const char *path) -{ - const double scale = 0.2; - const std::string inputSegmentPointColor = "lightseagreen"; - const coord_t inputSegmentPointRadius = coord_t(0.09 * scale / SCALING_FACTOR); - const std::string inputSegmentColor = "lightseagreen"; - const coord_t inputSegmentLineWidth = coord_t(0.03 * scale / SCALING_FACTOR); - - const std::string voronoiPointColor = "black"; - const coord_t voronoiPointRadius = coord_t(0.06 * scale / SCALING_FACTOR); - const std::string voronoiLineColorPrimary = "black"; - const std::string voronoiLineColorSecondary = "green"; - const std::string voronoiArcColor = "red"; - const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR); - - const bool internalEdgesOnly = false; - const bool primaryEdgesOnly = false; - - BoundingBox bbox = BoundingBox(lines); - bbox.min.x -= coord_t(1. / SCALING_FACTOR); - bbox.min.y -= coord_t(1. / SCALING_FACTOR); - bbox.max.x += coord_t(1. / SCALING_FACTOR); - bbox.max.y += coord_t(1. / SCALING_FACTOR); - - ::Slic3r::SVG svg(path, bbox); - - if (polylines != NULL) - svg.draw(*polylines, "lime", "lime", voronoiLineWidth); - -// bbox.scale(1.2); - // For clipping of half-lines to some reasonable value. - // The line will then be clipped by the SVG viewer anyway. - const double bbox_dim_max = double(bbox.max.x - bbox.min.x) + double(bbox.max.y - bbox.min.y); - // For the discretization of the Voronoi parabolic segments. - const double discretization_step = 0.0005 * bbox_dim_max; - - // Make a copy of the input segments with the double type. - std::vector segments; - for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it) - segments.push_back(Voronoi::Internal::segment_type( - Voronoi::Internal::point_type(double(it->a.x), double(it->a.y)), - Voronoi::Internal::point_type(double(it->b.x), double(it->b.y)))); - - // Color exterior edges. - for (voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) - if (!it->is_finite()) - Voronoi::Internal::color_exterior(&(*it)); - - // Draw the end points of the input polygon. - for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { - svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius); - svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius); - } - // Draw the input polygon. - for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) - svg.draw(Line(Point(coord_t(it->a.x), coord_t(it->a.y)), Point(coord_t(it->b.x), coord_t(it->b.y))), inputSegmentColor, inputSegmentLineWidth); - -#if 1 - // Draw voronoi vertices. - for (voronoi_diagram::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it) - if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR) - svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius); - - for (voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) { - if (primaryEdgesOnly && !it->is_primary()) - continue; - if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR)) - continue; - std::vector samples; - std::string color = voronoiLineColorPrimary; - if (!it->is_finite()) { - Voronoi::Internal::clip_infinite_edge(segments, *it, bbox_dim_max, &samples); - if (! it->is_primary()) - color = voronoiLineColorSecondary; - } else { - // Store both points of the segment into samples. sample_curved_edge will split the initial line - // until the discretization_step is reached. - samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y())); - samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y())); - if (it->is_curved()) { - Voronoi::Internal::sample_curved_edge(segments, *it, samples, discretization_step); - color = voronoiArcColor; - } else if (! it->is_primary()) - color = voronoiLineColorSecondary; - } - for (std::size_t i = 0; i + 1 < samples.size(); ++i) - svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth); - } -#endif - - if (polylines != NULL) - svg.draw(*polylines, "blue", voronoiLineWidth); - - svg.Close(); -} -#endif /* SLIC3R_DEBUG */ - -// Euclidian distance of two boost::polygon points. -template -T dist(const boost::polygon::point_data &p1,const boost::polygon::point_data &p2) -{ - T dx = p2.x() - p1.x(); - T dy = p2.y() - p1.y(); - return sqrt(dx*dx+dy*dy); -} - -// Find a foot point of "px" on a segment "seg". -template -inline point_type project_point_to_segment(segment_type &seg, point_type &px) -{ - typedef typename point_type::coordinate_type T; - const point_type &p0 = low(seg); - const point_type &p1 = high(seg); - const point_type dir(p1.x()-p0.x(), p1.y()-p0.y()); - const point_type dproj(px.x()-p0.x(), px.y()-p0.y()); - const T t = (dir.x()*dproj.x() + dir.y()*dproj.y()) / (dir.x()*dir.x() + dir.y()*dir.y()); - assert(t >= T(-1e-6) && t <= T(1. + 1e-6)); - return point_type(p0.x() + t*dir.x(), p0.y() + t*dir.y()); -} - -template -inline const typename VD::point_type retrieve_cell_point(const typename VD::cell_type& cell, const SEGMENTS &segments) -{ - assert(cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == SOURCE_CATEGORY_SEGMENT_END_POINT); - return (cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) ? low(segments[cell.source_index()]) : high(segments[cell.source_index()]); -} - -template -inline std::pair -measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments) -{ - typedef typename VD::coord_type T; - const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y()); - const typename VD::point_type pb(edge.vertex1()->x(), edge.vertex1()->y()); - const typename VD::cell_type &cell1 = *edge.cell(); - const typename VD::cell_type &cell2 = *edge.twin()->cell(); - if (cell1.contains_segment()) { - if (cell2.contains_segment()) { - // Both cells contain a linear segment, the left / right cells are symmetric. - // Project pa, pb to the left segment. - const typename VD::segment_type segment1 = segments[cell1.source_index()]; - const typename VD::point_type p1a = project_point_to_segment(segment1, pa); - const typename VD::point_type p1b = project_point_to_segment(segment1, pb); - return std::pair(T(2.)*dist(pa, p1a), T(2.)*dist(pb, p1b)); - } else { - // 1st cell contains a linear segment, 2nd cell contains a point. - // The medial axis between the cells is a parabolic arc. - // Project pa, pb to the left segment. - const typename VD::point_type p2 = retrieve_cell_point(cell2, segments); - return std::pair(T(2.)*dist(pa, p2), T(2.)*dist(pb, p2)); - } - } else if (cell2.contains_segment()) { - // 1st cell contains a point, 2nd cell contains a linear segment. - // The medial axis between the cells is a parabolic arc. - const typename VD::point_type p1 = retrieve_cell_point(cell1, segments); - return std::pair(T(2.)*dist(pa, p1), T(2.)*dist(pb, p1)); - } else { - // Both cells contain a point. The left / right regions are triangular and symmetric. - const typename VD::point_type p1 = retrieve_cell_point(cell1, segments); - return std::pair(T(2.)*dist(pa, p1), T(2.)*dist(pb, p1)); - } -} - -// Converts the Line instances of Lines vector to VD::segment_type. -template -class Lines2VDSegments -{ -public: - Lines2VDSegments(const Lines &alines) : lines(alines) {} - typename VD::segment_type operator[](size_t idx) const { - return typename VD::segment_type( - typename VD::point_type(typename VD::coord_type(lines[idx].a.x), typename VD::coord_type(lines[idx].a.y)), - typename VD::point_type(typename VD::coord_type(lines[idx].b.x), typename VD::coord_type(lines[idx].b.y))); - } -private: - const Lines &lines; -}; - void MedialAxis::build(ThickPolylines* polylines) { diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp index ccdfb5b2e1..4cb2be1a35 100644 --- a/xs/src/libslic3r/Geometry.hpp +++ b/xs/src/libslic3r/Geometry.hpp @@ -24,20 +24,12 @@ double rad2deg(double angle); double rad2deg_dir(double angle); double deg2rad(double angle); -class ArrangeItem { - public: - Pointf pos; - size_t index_x, index_y; - coordf_t dist; -}; -class ArrangeItemIndex { - public: - coordf_t index; - ArrangeItem item; - ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {}; -}; double linint(double value, double oldmin, double oldmax, double newmin, double newmax); -Pointfs arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb); +bool arrange( + // input + size_t num_parts, const Pointf &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box, + // output + Pointfs &positions); class MedialAxis { public: diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 9175995bf0..585e657ddf 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -224,21 +224,22 @@ Model::raw_mesh() const return mesh; } -Pointfs -Model::_arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb) const +bool +Model::_arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out) const { // we supply unscaled data to arrange() return Slic3r::Geometry::arrange( sizes.size(), // number of parts BoundingBoxf(sizes).max, // width and height of a single cell dist, // distance between cells - bb // bounding box of the area to fill + bb, // bounding box of the area to fill + out // output positions ); } /* arrange objects preserving their instance count but altering their instance positions */ -void +bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) { // get the (transformed) size of each instance so that we take @@ -250,7 +251,9 @@ Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) } } - Pointfs positions = this->_arrange(instance_sizes, dist, bb); + Pointfs positions; + if (! this->_arrange(instance_sizes, dist, bb, positions)) + return false; for (ModelObjectPtrs::const_iterator o = this->objects.begin(); o != this->objects.end(); ++o) { for (ModelInstancePtrs::const_iterator i = (*o)->instances.begin(); i != (*o)->instances.end(); ++i) { @@ -259,6 +262,7 @@ Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) } (*o)->invalidate_bounding_box(); } + return true; } /* duplicate the entire model preserving instance relative positions */ @@ -266,7 +270,9 @@ void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) { Pointfs model_sizes(copies_num-1, this->bounding_box().size()); - Pointfs positions = this->_arrange(model_sizes, dist, bb); + Pointfs positions; + if (! this->_arrange(model_sizes, dist, bb, positions)) + CONFESS("Cannot duplicate part as the resulting objects would not fit on the print bed.\n"); // note that this will leave the object count unaltered diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index c0a7d78ea3..e714907bda 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -66,8 +66,9 @@ class Model void translate(coordf_t x, coordf_t y, coordf_t z); TriangleMesh mesh() const; TriangleMesh raw_mesh() const; - Pointfs _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb = NULL) const; - void arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); + bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out) const; + bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); + // Croaks if the duplicated objects do not fit the print bed. void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp index de911ccb09..37dad4aeed 100644 --- a/xs/xsp/Geometry.xsp +++ b/xs/xsp/Geometry.xsp @@ -9,7 +9,12 @@ %package{Slic3r::Geometry}; Pointfs arrange(size_t total_parts, Pointf* part, coordf_t dist, BoundingBoxf* bb = NULL) - %code{% RETVAL = Slic3r::Geometry::arrange(total_parts, *part, dist, bb); %}; + %code{% + Pointfs points; + if (! Slic3r::Geometry::arrange(total_parts, *part, dist, bb, points)) + CONFESS("%zu parts won't fit in your print area!\n", total_parts); + RETVAL = points; + %}; %{ diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 8bfd10123b..187b57e683 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -66,8 +66,7 @@ ModelObjectPtrs* objects() %code%{ RETVAL = &THIS->objects; %}; - Pointfs _arrange(Pointfs sizes, double dist, BoundingBoxf* bb = NULL); - void arrange_objects(double dist, BoundingBoxf* bb = NULL); + bool arrange_objects(double dist, BoundingBoxf* bb = NULL); void duplicate(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); void duplicate_objects_grid(unsigned int x, unsigned int y, double dist);