Working arrange_objects with DJD selection heuristic and a bottom-left placement strategy.

This commit is contained in:
tamasmeszaros 2018-05-17 10:37:26 +02:00
parent b6b7945830
commit fd829580e9
36 changed files with 6087 additions and 45 deletions

View file

@ -7,6 +7,12 @@
#include "Format/STL.hpp"
#include "Format/3mf.hpp"
#include <numeric>
#include <libnest2d.h>
#include <libnest2d/geometries_io.hpp>
#include <ClipperUtils.hpp>
#include "slic3r/GUI/GUI.hpp"
#include <float.h>
#include <boost/algorithm/string/predicate.hpp>
@ -296,35 +302,224 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb
return result;
}
namespace arr {
using namespace libnest2d;
// A container which stores a pointer to the 3D object and its projected
// 2D shape from top view.
using ShapeData2D =
std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
ShapeData2D ret;
auto s = std::accumulate(model.objects.begin(), model.objects.end(), 0,
[](size_t s, ModelObject* o){
return s + o->instances.size();
});
ret.reserve(s);
for(auto objptr : model.objects) {
if(objptr) {
auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) {
if(objinst) {
Slic3r::TriangleMesh tmpmesh = rmesh;
objinst->transform_mesh(&tmpmesh);
ClipperLib::PolyNode pn;
auto p = tmpmesh.convex_hull();
p.make_clockwise();
p.append(p.first_point());
pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
ret.emplace_back(objinst, Item(std::move(pn)));
}
}
}
}
return ret;
}
/**
* \brief Arranges the model objects on the screen.
*
* The arrangement considers multiple bins (aka. print beds) for placing all
* the items provided in the model argument. If the items don't fit on one
* print bed, the remaining will be placed onto newly created print beds.
* The first_bin_only parameter, if set to true, disables this behaviour and
* makes sure that only one print bed is filled and the remaining items will be
* untouched. When set to false, the items which could not fit onto the
* print bed will be placed next to the print bed so the user should see a
* pile of items on the print bed and some other piles outside the print
* area that can be dragged later onto the print bed as a group.
*
* \param model The model object with the 3D content.
* \param dist The minimum distance which is allowed for any pair of items
* on the print bed in any direction.
* \param bb The bounding box of the print bed. It corresponds to the 'bin'
* for bin packing.
* \param first_bin_only This parameter controls whether to place the
* remaining items which do not fit onto the print area next to the print
* bed or leave them untouched (let the user arrange them by hand or remove
* them).
*/
bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
bool first_bin_only)
{
using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
bool ret = true;
// Create the arranger config
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
// Get the 2D projected shapes with their 3D model instance pointers
auto shapemap = arr::projectModelFromTop(model);
double area = 0;
double area_max = 0;
Item *biggest = nullptr;
// Copy the references for the shapes only as the arranger expects a
// sequence of objects convertible to Item or ClipperPolygon
std::vector<std::reference_wrapper<Item>> shapes;
shapes.reserve(shapemap.size());
std::for_each(shapemap.begin(), shapemap.end(),
[&shapes, &area, min_obj_distance, &area_max, &biggest]
(ShapeData2D::value_type& it)
{
Item& item = it.second;
item.addOffset(min_obj_distance);
auto b = ShapeLike::boundingBox(item.transformedShape());
auto a = b.width()*b.height();
if(area_max < a) {
area_max = static_cast<double>(a);
biggest = &item;
}
area += b.width()*b.height();
shapes.push_back(std::ref(it.second));
});
Box bin;
if(bb != nullptr && bb->defined) {
// Scale up the bounding box to clipper scale.
BoundingBoxf bbb = *bb;
bbb.scale(1.0/SCALING_FACTOR);
bin = Box({
static_cast<libnest2d::Coord>(bbb.min.x),
static_cast<libnest2d::Coord>(bbb.min.y)
},
{
static_cast<libnest2d::Coord>(bbb.max.x),
static_cast<libnest2d::Coord>(bbb.max.y)
});
} else {
// Just take the biggest item as bin... ?
bin = ShapeLike::boundingBox(biggest->transformedShape());
}
// Will use the DJD selection heuristic with the BottomLeft placement
// strategy
using Arranger = Arranger<BottomLeftPlacer, DJDHeuristic>;
Arranger arranger(bin, min_obj_distance);
// Arrange and return the items with their respective indices within the
// input sequence.
ArrangeResult result =
arranger.arrangeIndexed(shapes.begin(), shapes.end());
auto applyResult = [&shapemap](ArrangeResult::value_type& group,
Coord batch_offset)
{
for(auto& r : group) {
auto idx = r.first; // get the original item index
Item& item = r.second; // get the item itself
// Get the model instance from the shapemap using the index
ModelInstance *inst_ptr = shapemap[idx].first;
// Get the tranformation data from the item object and scale it
// appropriately
Radians rot = item.rotation();
auto off = item.translation();
Pointf foff(off.X*SCALING_FACTOR + batch_offset,
off.Y*SCALING_FACTOR);
// write the tranformation data into the model instance
inst_ptr->rotation += rot;
inst_ptr->offset += foff;
// Debug
/*std::cout << "item " << idx << ": \n" << "\toffset_x: "
* << foff.x << "\n\toffset_y: " << foff.y << std::endl;*/
}
};
if(first_bin_only) {
applyResult(result.front(), 0);
} else {
Coord batch_offset = 0;
for(auto& group : result) {
applyResult(group, batch_offset);
// Only the first pack group can be placed onto the print bed. The
// other objects which could not fit will be placed next to the
// print bed
batch_offset += static_cast<Coord>(2*bin.width()*SCALING_FACTOR);
}
}
for(auto objptr : model.objects) objptr->invalidate_bounding_box();
return ret && result.size() == 1;
}
}
/* arrange objects preserving their instance count
but altering their instance positions */
bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
{
// get the (transformed) size of each instance so that we take
// into account their different transformations when packing
Pointfs instance_sizes;
Pointfs instance_centers;
for (const ModelObject *o : this->objects)
for (size_t i = 0; i < o->instances.size(); ++ i) {
// an accurate snug bounding box around the transformed mesh.
BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
instance_sizes.push_back(bbox.size());
instance_centers.push_back(bbox.center());
}
bool ret = false;
if(bb != nullptr && bb->defined) {
const bool FIRST_BIN_ONLY = true;
ret = arr::arrange(*this, dist, bb, FIRST_BIN_ONLY);
} else {
// get the (transformed) size of each instance so that we take
// into account their different transformations when packing
Pointfs instance_sizes;
Pointfs instance_centers;
for (const ModelObject *o : this->objects)
for (size_t i = 0; i < o->instances.size(); ++ i) {
// an accurate snug bounding box around the transformed mesh.
BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
instance_sizes.push_back(bbox.size());
instance_centers.push_back(bbox.center());
}
Pointfs positions;
if (! _arrange(instance_sizes, dist, bb, positions))
return false;
size_t idx = 0;
for (ModelObject *o : this->objects) {
for (ModelInstance *i : o->instances) {
i->offset = positions[idx] - instance_centers[idx];
++ idx;
Pointfs positions;
if (! _arrange(instance_sizes, dist, bb, positions))
return false;
size_t idx = 0;
for (ModelObject *o : this->objects) {
for (ModelInstance *i : o->instances) {
i->offset = positions[idx] - instance_centers[idx];
++ idx;
}
o->invalidate_bounding_box();
}
o->invalidate_bounding_box();
}
return true;
return ret;
}
// Duplicate the entire model preserving instance relative positions.