work in progress on new ModelArrange interface

This commit is contained in:
tamasmeszaros 2019-06-26 17:09:26 +02:00
parent f4ed0d8137
commit e1d612d05f
3 changed files with 567 additions and 487 deletions

View file

@ -1,5 +1,5 @@
#include "ModelArrange.hpp" #include "ModelArrange.hpp"
#include "Model.hpp" //#include "Model.hpp"
#include "Geometry.hpp" #include "Geometry.hpp"
#include "SVG.hpp" #include "SVG.hpp"
#include "MTUtils.hpp" #include "MTUtils.hpp"
@ -43,87 +43,87 @@ namespace arr {
using namespace libnest2d; using namespace libnest2d;
// Only for debugging. Prints the model object vertices on stdout. // Only for debugging. Prints the model object vertices on stdout.
std::string toString(const Model& model, bool holes = true) { //std::string toString(const Model& model, bool holes = true) {
std::stringstream ss; // std::stringstream ss;
ss << "{\n"; // ss << "{\n";
for(auto objptr : model.objects) { // for(auto objptr : model.objects) {
if(!objptr) continue; // if(!objptr) continue;
auto rmesh = objptr->raw_mesh(); // auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) { // for(auto objinst : objptr->instances) {
if(!objinst) continue; // if(!objinst) continue;
Slic3r::TriangleMesh tmpmesh = rmesh; // Slic3r::TriangleMesh tmpmesh = rmesh;
// CHECK_ME -> Is the following correct ? // // CHECK_ME -> Is the following correct ?
tmpmesh.scale(objinst->get_scaling_factor()); // tmpmesh.scale(objinst->get_scaling_factor());
objinst->transform_mesh(&tmpmesh); // objinst->transform_mesh(&tmpmesh);
ExPolygons expolys = tmpmesh.horizontal_projection(); // ExPolygons expolys = tmpmesh.horizontal_projection();
for(auto& expoly_complex : expolys) { // for(auto& expoly_complex : expolys) {
ExPolygons tmp = expoly_complex.simplify(scaled<double>(1.)); // ExPolygons tmp = expoly_complex.simplify(scaled<double>(1.));
if(tmp.empty()) continue; // if(tmp.empty()) continue;
ExPolygon expoly = tmp.front(); // ExPolygon expoly = tmp.front();
expoly.contour.make_clockwise(); // expoly.contour.make_clockwise();
for(auto& h : expoly.holes) h.make_counter_clockwise(); // for(auto& h : expoly.holes) h.make_counter_clockwise();
ss << "\t{\n"; // ss << "\t{\n";
ss << "\t\t{\n"; // ss << "\t\t{\n";
for(auto v : expoly.contour.points) ss << "\t\t\t{" // for(auto v : expoly.contour.points) ss << "\t\t\t{"
<< v(0) << ", " // << v(0) << ", "
<< v(1) << "},\n"; // << v(1) << "},\n";
{ // {
auto v = expoly.contour.points.front(); // auto v = expoly.contour.points.front();
ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; // ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n";
} // }
ss << "\t\t},\n"; // ss << "\t\t},\n";
// Holes: // // Holes:
ss << "\t\t{\n"; // ss << "\t\t{\n";
if(holes) for(auto h : expoly.holes) { // if(holes) for(auto h : expoly.holes) {
ss << "\t\t\t{\n"; // ss << "\t\t\t{\n";
for(auto v : h.points) ss << "\t\t\t\t{" // for(auto v : h.points) ss << "\t\t\t\t{"
<< v(0) << ", " // << v(0) << ", "
<< v(1) << "},\n"; // << v(1) << "},\n";
{ // {
auto v = h.points.front(); // auto v = h.points.front();
ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; // ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n";
} // }
ss << "\t\t\t},\n"; // ss << "\t\t\t},\n";
} // }
ss << "\t\t},\n"; // ss << "\t\t},\n";
ss << "\t},\n"; // ss << "\t},\n";
} // }
} // }
} // }
ss << "}\n"; // ss << "}\n";
return ss.str(); // return ss.str();
} //}
// Debugging: Save model to svg file. // Debugging: Save model to svg file.
void toSVG(SVG& svg, const Model& model) { //void toSVG(SVG& svg, const Model& model) {
for(auto objptr : model.objects) { // for(auto objptr : model.objects) {
if(!objptr) continue; // if(!objptr) continue;
auto rmesh = objptr->raw_mesh(); // auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) { // for(auto objinst : objptr->instances) {
if(!objinst) continue; // if(!objinst) continue;
Slic3r::TriangleMesh tmpmesh = rmesh; // Slic3r::TriangleMesh tmpmesh = rmesh;
tmpmesh.scale(objinst->get_scaling_factor()); // tmpmesh.scale(objinst->get_scaling_factor());
objinst->transform_mesh(&tmpmesh); // objinst->transform_mesh(&tmpmesh);
ExPolygons expolys = tmpmesh.horizontal_projection(); // ExPolygons expolys = tmpmesh.horizontal_projection();
svg.draw(expolys); // svg.draw(expolys);
} // }
} // }
} //}
namespace bgi = boost::geometry::index; namespace bgi = boost::geometry::index;
@ -565,143 +565,143 @@ public:
// A container which stores a pointer to the 3D object and its projected // A container which stores a pointer to the 3D object and its projected
// 2D shape from top view. // 2D shape from top view.
using ShapeData2D = std::vector<std::pair<Slic3r::ModelInstance*, Item>>; //using ShapeData2D = std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
ShapeData2D projectModelFromTop(const Slic3r::Model &model, //ShapeData2D projectModelFromTop(const Slic3r::Model &model,
const WipeTowerInfo &wti, // const WipeTowerInfo &wti,
double tolerance) // double tolerance)
{ //{
ShapeData2D ret; // ShapeData2D ret;
// Count all the items on the bin (all the object's instances) // // Count all the items on the bin (all the object's instances)
auto s = std::accumulate(model.objects.begin(), model.objects.end(), // auto s = std::accumulate(model.objects.begin(), model.objects.end(),
size_t(0), [](size_t s, ModelObject* o) // size_t(0), [](size_t s, ModelObject* o)
{ // {
return s + o->instances.size(); // return s + o->instances.size();
}); // });
ret.reserve(s); // ret.reserve(s);
// for(ModelObject* objptr : model.objects) {
// if (! objptr->instances.empty()) {
for(ModelObject* objptr : model.objects) { // // TODO export the exact 2D projection. Cannot do it as libnest2d
if (! objptr->instances.empty()) { // // does not support concave shapes (yet).
// ClipperLib::Path clpath;
// TODO export the exact 2D projection. Cannot do it as libnest2d // // Object instances should carry the same scaling and
// does not support concave shapes (yet). // // x, y rotation that is why we use the first instance.
ClipperLib::Path clpath; // {
// ModelInstance *finst = objptr->instances.front();
// Object instances should carry the same scaling and // Vec3d rotation = finst->get_rotation();
// x, y rotation that is why we use the first instance. // rotation.z() = 0.;
{ // Transform3d trafo_instance = Geometry::assemble_transform(
ModelInstance *finst = objptr->instances.front(); // Vec3d::Zero(),
Vec3d rotation = finst->get_rotation(); // rotation,
rotation.z() = 0.; // finst->get_scaling_factor(),
Transform3d trafo_instance = Geometry::assemble_transform( // finst->get_mirror());
Vec3d::Zero(), // Polygon p = objptr->convex_hull_2d(trafo_instance);
rotation,
finst->get_scaling_factor(),
finst->get_mirror());
Polygon p = objptr->convex_hull_2d(trafo_instance);
assert(!p.points.empty()); // assert(!p.points.empty());
// this may happen for malformed models, see: // // this may happen for malformed models, see:
// https://github.com/prusa3d/PrusaSlicer/issues/2209 // // https://github.com/prusa3d/PrusaSlicer/issues/2209
if (p.points.empty()) continue; // if (p.points.empty()) continue;
if(tolerance > EPSILON) { // if(tolerance > EPSILON) {
Polygons pp { p }; // Polygons pp { p };
pp = p.simplify(scaled<double>(tolerance)); // pp = p.simplify(scaled<double>(tolerance));
if (!pp.empty()) p = pp.front(); // if (!pp.empty()) p = pp.front();
} // }
p.reverse(); // p.reverse();
assert(!p.is_counter_clockwise()); // assert(!p.is_counter_clockwise());
clpath = Slic3rMultiPoint_to_ClipperPath(p); // clpath = Slic3rMultiPoint_to_ClipperPath(p);
auto firstp = clpath.front(); clpath.emplace_back(firstp); // auto firstp = clpath.front(); clpath.emplace_back(firstp);
} // }
Vec3d rotation0 = objptr->instances.front()->get_rotation(); // Vec3d rotation0 = objptr->instances.front()->get_rotation();
rotation0(2) = 0.; // rotation0(2) = 0.;
for(ModelInstance* objinst : objptr->instances) { // for(ModelInstance* objinst : objptr->instances) {
ClipperLib::Polygon pn; // ClipperLib::Polygon pn;
pn.Contour = clpath; // pn.Contour = clpath;
// Efficient conversion to item. // // Efficient conversion to item.
Item item(std::move(pn)); // Item item(std::move(pn));
// Invalid geometries would throw exceptions when arranging // // Invalid geometries would throw exceptions when arranging
if(item.vertexCount() > 3) { // if(item.vertexCount() > 3) {
item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation())); // item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()));
item.translation({ // item.translation({
scaled<ClipperLib::cInt>(objinst->get_offset(X)), // scaled<ClipperLib::cInt>(objinst->get_offset(X)),
scaled<ClipperLib::cInt>(objinst->get_offset(Y)) // scaled<ClipperLib::cInt>(objinst->get_offset(Y))
}); // });
ret.emplace_back(objinst, item); // ret.emplace_back(objinst, item);
} // }
} // }
} // }
} // }
// The wipe tower is a separate case (in case there is one), let's duplicate the code // // The wipe tower is a separate case (in case there is one), let's duplicate the code
if (wti.is_wipe_tower) { // if (wti.is_wipe_tower) {
Points pts; // Points pts;
pts.emplace_back(coord_t(scale_(0.)), coord_t(scale_(0.))); // pts.emplace_back(coord_t(scale_(0.)), coord_t(scale_(0.)));
pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(0.))); // pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(0.)));
pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(wti.bb_size(1)))); // pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(wti.bb_size(1))));
pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(wti.bb_size(1)))); // pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(wti.bb_size(1))));
pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(0.))); // pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(0.)));
Polygon p(std::move(pts)); // Polygon p(std::move(pts));
ClipperLib::Path clpath = Slic3rMultiPoint_to_ClipperPath(p); // ClipperLib::Path clpath = Slic3rMultiPoint_to_ClipperPath(p);
ClipperLib::Polygon pn; // ClipperLib::Polygon pn;
pn.Contour = clpath; // pn.Contour = clpath;
// Efficient conversion to item. // // Efficient conversion to item.
Item item(std::move(pn)); // Item item(std::move(pn));
item.rotation(wti.rotation), // item.rotation(wti.rotation),
item.translation({ // item.translation({
scaled<ClipperLib::cInt>(wti.pos(0)), // scaled<ClipperLib::cInt>(wti.pos(0)),
scaled<ClipperLib::cInt>(wti.pos(1)) // scaled<ClipperLib::cInt>(wti.pos(1))
}); // });
ret.emplace_back(nullptr, item); // ret.emplace_back(nullptr, item);
} // }
return ret; // return ret;
} //}
// Apply the calculated translations and rotations (currently disabled) to // Apply the calculated translations and rotations (currently disabled) to
// the Model object instances. // the Model object instances.
void applyResult(IndexedPackGroup::value_type &group, //void applyResult(IndexedPackGroup::value_type &group,
ClipperLib::cInt batch_offset, // ClipperLib::cInt batch_offset,
ShapeData2D & shapemap, // ShapeData2D & shapemap,
WipeTowerInfo & wti) // WipeTowerInfo & wti)
{ //{
for(auto& r : group) { // for(auto& r : group) {
auto idx = r.first; // get the original item index // auto idx = r.first; // get the original item index
Item& item = r.second; // get the item itself // Item& item = r.second; // get the item itself
// Get the model instance from the shapemap using the index // // Get the model instance from the shapemap using the index
ModelInstance *inst_ptr = shapemap[idx].first; // ModelInstance *inst_ptr = shapemap[idx].first;
// Get the transformation data from the item object and scale it // // Get the transformation data from the item object and scale it
// appropriately // // appropriately
auto off = item.translation(); // auto off = item.translation();
Radians rot = item.rotation(); // Radians rot = item.rotation();
Vec3d foff(unscaled(off.X + batch_offset) , // Vec3d foff(unscaled(off.X + batch_offset),
unscaled(off.Y), // unscaled(off.Y),
inst_ptr ? inst_ptr->get_offset()(Z) : 0.); // inst_ptr ? inst_ptr->get_offset()(Z) : 0.);
if (inst_ptr) { // if (inst_ptr) {
// write the transformation data into the model instance // // write the transformation data into the model instance
inst_ptr->set_rotation(Z, rot); // inst_ptr->set_rotation(Z, rot);
inst_ptr->set_offset(foff); // inst_ptr->set_offset(foff);
} // }
else { // this is the wipe tower - we will modify the struct with the info // else { // this is the wipe tower - we will modify the struct with the info
// and leave it up to the called to actually move the wipe tower // // and leave it up to the called to actually move the wipe tower
wti.pos = Vec2d(foff(0), foff(1)); // wti.pos = Vec2d(foff(0), foff(1));
wti.rotation = rot; // wti.rotation = rot;
} // }
} // }
} //}
// Get the type of bed geometry from a simple vector of points. // Get the type of bed geometry from a simple vector of points.
BedShapeHint bedShape(const Polyline &bed) { BedShapeHint bedShape(const Polyline &bed) {
@ -784,254 +784,254 @@ BedShapeHint bedShape(const Polyline &bed) {
static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1;
template<class BinT> //template<class BinT>
IndexedPackGroup _arrange(std::vector<std::reference_wrapper<Item>> &shapes, //IndexedPackGroup _arrange(std::vector<std::reference_wrapper<Item>> &shapes,
const BinT & bin, // const BinT & bin,
coord_t minobjd, // coord_t minobjd,
std::function<void(unsigned)> prind, // std::function<void(unsigned)> prind,
std::function<bool()> stopfn) // std::function<bool()> stopfn)
{ //{
AutoArranger<BinT> arranger{bin, minobjd, prind, stopfn}; // AutoArranger<BinT> arranger{bin, minobjd, prind, stopfn};
return arranger(shapes.begin(), shapes.end()); // return arranger(shapes.begin(), shapes.end());
} //}
template<class BinT> //template<class BinT>
IndexedPackGroup _arrange(std::vector<std::reference_wrapper<Item>> &shapes, //IndexedPackGroup _arrange(std::vector<std::reference_wrapper<Item>> &shapes,
const PackGroup & preshapes, // const PackGroup & preshapes,
std::vector<ModelInstance *> &minstances, // std::vector<ModelInstance *> &minstances,
const BinT & bin, // const BinT & bin,
coord_t minobjd) // coord_t minobjd)
{ //{
auto binbb = sl::boundingBox(bin); // auto binbb = sl::boundingBox(bin);
AutoArranger<BinT> arranger{bin, minobjd}; // AutoArranger<BinT> arranger{bin, minobjd};
if(!preshapes.front().empty()) { // If there is something on the plate // if(!preshapes.front().empty()) { // If there is something on the plate
arranger.preload(preshapes); // arranger.preload(preshapes);
// Try to put the first item to the center, as the arranger will not // // Try to put the first item to the center, as the arranger will not
// do this for us. // // do this for us.
auto shptrit = minstances.begin(); // auto shptrit = minstances.begin();
for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit) // for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit)
{ // {
// Try to place items to the center // // Try to place items to the center
Item& itm = *shit; // Item& itm = *shit;
auto ibb = itm.boundingBox(); // auto ibb = itm.boundingBox();
auto d = binbb.center() - ibb.center(); // auto d = binbb.center() - ibb.center();
itm.translate(d); // itm.translate(d);
if(!arranger.is_colliding(itm)) { // if(!arranger.is_colliding(itm)) {
arranger.preload({{itm}}); // arranger.preload({{itm}});
auto offset = itm.translation(); // auto offset = itm.translation();
Radians rot = itm.rotation(); // Radians rot = itm.rotation();
ModelInstance *minst = *shptrit; // ModelInstance *minst = *shptrit;
Vec3d foffset(unscaled(offset.X), // Vec3d foffset(unscaled(offset.X),
unscaled(offset.Y), // unscaled(offset.Y),
minst->get_offset()(Z)); // minst->get_offset()(Z));
// write the transformation data into the model instance // // write the transformation data into the model instance
minst->set_rotation(Z, rot); // minst->set_rotation(Z, rot);
minst->set_offset(foffset); // minst->set_offset(foffset);
shit = shapes.erase(shit); // shit = shapes.erase(shit);
shptrit = minstances.erase(shptrit); // shptrit = minstances.erase(shptrit);
break; // break;
} // }
} // }
} // }
return arranger(shapes.begin(), shapes.end()); // return arranger(shapes.begin(), shapes.end());
} //}
inline SLIC3R_CONSTEXPR libnest2d::Coord stride_padding(Coord w) inline SLIC3R_CONSTEXPR libnest2d::Coord stride_padding(Coord w)
{ {
return w + w / 5; return w + w / 5;
} }
// The final client function to arrange the Model. A progress indicator and //// The final client function to arrange the Model. A progress indicator and
// a stop predicate can be also be passed to control the process. //// a stop predicate can be also be passed to control the process.
bool arrange(Model &model, // The model with the geometries //bool arrange(Model &model, // The model with the geometries
WipeTowerInfo& wti, // Wipe tower info // WipeTowerInfo& wti, // Wipe tower info
coord_t min_obj_distance, // Has to be in scaled (clipper) measure // coord_t min_obj_distance, // Has to be in scaled (clipper) measure
const Polyline &bed, // The bed geometry. // const Polyline &bed, // The bed geometry.
BedShapeHint bedhint, // Hint about the bed geometry type. // BedShapeHint bedhint, // Hint about the bed geometry type.
bool first_bin_only, // What to do is not all items fit. // bool first_bin_only, // What to do is not all items fit.
// Controlling callbacks. // // Controlling callbacks.
std::function<void (unsigned)> progressind, // std::function<void (unsigned)> progressind,
std::function<bool ()> stopcondition) // std::function<bool ()> stopcondition)
{ //{
bool ret = true; // bool ret = true;
// Get the 2D projected shapes with their 3D model instance pointers // // Get the 2D projected shapes with their 3D model instance pointers
auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); // auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM);
// Copy the references for the shapes only as the arranger expects a // // Copy the references for the shapes only as the arranger expects a
// sequence of objects convertible to Item or ClipperPolygon // // sequence of objects convertible to Item or ClipperPolygon
std::vector<std::reference_wrapper<Item>> shapes; // std::vector<std::reference_wrapper<Item>> shapes;
shapes.reserve(shapemap.size()); // shapes.reserve(shapemap.size());
std::for_each(shapemap.begin(), shapemap.end(), // std::for_each(shapemap.begin(), shapemap.end(),
[&shapes] (ShapeData2D::value_type& it) // [&shapes] (ShapeData2D::value_type& it)
{ // {
shapes.push_back(std::ref(it.second)); // shapes.push_back(std::ref(it.second));
}); // });
IndexedPackGroup result; // IndexedPackGroup result;
// If there is no hint about the shape, we will try to guess // // If there is no hint about the shape, we will try to guess
if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); // if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed);
BoundingBox bbb(bed); // BoundingBox bbb(bed);
auto& cfn = stopcondition; // auto& cfn = stopcondition;
// Integer ceiling the min distance from the bed perimeters // // Integer ceiling the min distance from the bed perimeters
coord_t md = min_obj_distance - SCALED_EPSILON; // coord_t md = min_obj_distance - SCALED_EPSILON;
md = (md % 2) ? md / 2 + 1 : md / 2; // md = (md % 2) ? md / 2 + 1 : md / 2;
auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, // auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md,
ClipperLib::cInt{bbb.min(1)} - md}, // ClipperLib::cInt{bbb.min(1)} - md},
{ClipperLib::cInt{bbb.max(0)} + md, // {ClipperLib::cInt{bbb.max(0)} + md,
ClipperLib::cInt{bbb.max(1)} + md}); // ClipperLib::cInt{bbb.max(1)} + md});
switch(bedhint.type) { // switch(bedhint.type) {
case BedShapeType::BOX: { // case BedShapeType::BOX: {
// Create the arranger for the box shaped bed // // Create the arranger for the box shaped bed
result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn); // result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn);
break; // break;
} // }
case BedShapeType::CIRCLE: { // case BedShapeType::CIRCLE: {
auto c = bedhint.shape.circ; // auto c = bedhint.shape.circ;
auto cc = to_lnCircle(c); // auto cc = to_lnCircle(c);
result = _arrange(shapes, cc, min_obj_distance, progressind, cfn); // result = _arrange(shapes, cc, min_obj_distance, progressind, cfn);
break; // break;
} // }
case BedShapeType::IRREGULAR: // case BedShapeType::IRREGULAR:
case BedShapeType::WHO_KNOWS: { // case BedShapeType::WHO_KNOWS: {
auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); // auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
ClipperLib::Polygon irrbed = sl::create<PolygonImpl>(std::move(ctour)); // ClipperLib::Polygon irrbed = sl::create<PolygonImpl>(std::move(ctour));
result = _arrange(shapes, irrbed, min_obj_distance, progressind, cfn); // result = _arrange(shapes, irrbed, min_obj_distance, progressind, cfn);
break; // break;
} // }
}; // };
if(result.empty() || stopcondition()) return false; // if(result.empty() || stopcondition()) return false;
if(first_bin_only) { // if(first_bin_only) {
applyResult(result.front(), 0, shapemap, wti); // applyResult(result.front(), 0, shapemap, wti);
} else { // } else {
ClipperLib::cInt stride = stride_padding(binbb.width()); // ClipperLib::cInt stride = stride_padding(binbb.width());
ClipperLib::cInt batch_offset = 0; // ClipperLib::cInt batch_offset = 0;
for(auto& group : result) { // for(auto& group : result) {
applyResult(group, batch_offset, shapemap, wti); // applyResult(group, batch_offset, shapemap, wti);
// Only the first pack group can be placed onto the print bed. The // // 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 // // other objects which could not fit will be placed next to the
// print bed // // print bed
batch_offset += stride; // batch_offset += stride;
} // }
} // }
for(auto objptr : model.objects) objptr->invalidate_bounding_box(); // for(auto objptr : model.objects) objptr->invalidate_bounding_box();
return ret && result.size() == 1; // return ret && result.size() == 1;
} //}
void find_new_position(const Model &model, //void find_new_position(const Model &model,
ModelInstancePtrs toadd, // ModelInstancePtrs toadd,
coord_t min_obj_distance, // coord_t min_obj_distance,
const Polyline &bed, // const Polyline &bed,
WipeTowerInfo& wti) // WipeTowerInfo& wti)
{ //{
// Get the 2D projected shapes with their 3D model instance pointers // // Get the 2D projected shapes with their 3D model instance pointers
auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); // auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM);
// Copy the references for the shapes only, as the arranger expects a // // Copy the references for the shapes only, as the arranger expects a
// sequence of objects convertible to Item or ClipperPolygon // // sequence of objects convertible to Item or ClipperPolygon
PackGroup preshapes; preshapes.emplace_back(); // PackGroup preshapes; preshapes.emplace_back();
ItemGroup shapes; // ItemGroup shapes;
preshapes.front().reserve(shapemap.size()); // preshapes.front().reserve(shapemap.size());
std::vector<ModelInstance*> shapes_ptr; shapes_ptr.reserve(toadd.size()); // std::vector<ModelInstance*> shapes_ptr; shapes_ptr.reserve(toadd.size());
IndexedPackGroup result; // IndexedPackGroup result;
// If there is no hint about the shape, we will try to guess // // If there is no hint about the shape, we will try to guess
BedShapeHint bedhint = bedShape(bed); // BedShapeHint bedhint = bedShape(bed);
BoundingBox bbb(bed); // BoundingBox bbb(bed);
// Integer ceiling the min distance from the bed perimeters // // Integer ceiling the min distance from the bed perimeters
coord_t md = min_obj_distance - SCALED_EPSILON; // coord_t md = min_obj_distance - SCALED_EPSILON;
md = (md % 2) ? md / 2 + 1 : md / 2; // md = (md % 2) ? md / 2 + 1 : md / 2;
auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, // auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md,
ClipperLib::cInt{bbb.min(1)} - md}, // ClipperLib::cInt{bbb.min(1)} - md},
{ClipperLib::cInt{bbb.max(0)} + md, // {ClipperLib::cInt{bbb.max(0)} + md,
ClipperLib::cInt{bbb.max(1)} + md}); // ClipperLib::cInt{bbb.max(1)} + md});
for(auto it = shapemap.begin(); it != shapemap.end(); ++it) { // for(auto it = shapemap.begin(); it != shapemap.end(); ++it) {
// `toadd` vector contains the instance pointers which have to be // // `toadd` vector contains the instance pointers which have to be
// considered by arrange. If `it` points to an ModelInstance, which // // considered by arrange. If `it` points to an ModelInstance, which
// is NOT in `toadd`, add it to preshapes. // // is NOT in `toadd`, add it to preshapes.
if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) { // if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) {
if(it->second.isInside(binbb)) // just ignore items which are outside // if(it->second.isInside(binbb)) // just ignore items which are outside
preshapes.front().emplace_back(std::ref(it->second)); // preshapes.front().emplace_back(std::ref(it->second));
} // }
else { // else {
shapes_ptr.emplace_back(it->first); // shapes_ptr.emplace_back(it->first);
shapes.emplace_back(std::ref(it->second)); // shapes.emplace_back(std::ref(it->second));
} // }
} // }
switch(bedhint.type) { // switch(bedhint.type) {
case BedShapeType::BOX: { // case BedShapeType::BOX: {
// Create the arranger for the box shaped bed // // Create the arranger for the box shaped bed
result = _arrange(shapes, preshapes, shapes_ptr, binbb, min_obj_distance); // result = _arrange(shapes, preshapes, shapes_ptr, binbb, min_obj_distance);
break; // break;
} // }
case BedShapeType::CIRCLE: { // case BedShapeType::CIRCLE: {
auto c = bedhint.shape.circ; // auto c = bedhint.shape.circ;
auto cc = to_lnCircle(c); // auto cc = to_lnCircle(c);
result = _arrange(shapes, preshapes, shapes_ptr, cc, min_obj_distance); // result = _arrange(shapes, preshapes, shapes_ptr, cc, min_obj_distance);
break; // break;
} // }
case BedShapeType::IRREGULAR: // case BedShapeType::IRREGULAR:
case BedShapeType::WHO_KNOWS: { // case BedShapeType::WHO_KNOWS: {
auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); // auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
ClipperLib::Polygon irrbed = sl::create<PolygonImpl>(std::move(ctour)); // ClipperLib::Polygon irrbed = sl::create<PolygonImpl>(std::move(ctour));
result = _arrange(shapes, preshapes, shapes_ptr, irrbed, min_obj_distance); // result = _arrange(shapes, preshapes, shapes_ptr, irrbed, min_obj_distance);
break; // break;
} // }
}; // };
// Now we go through the result which will contain the fixed and the moving // // Now we go through the result which will contain the fixed and the moving
// polygons as well. We will have to search for our item. // // polygons as well. We will have to search for our item.
ClipperLib::cInt stride = stride_padding(binbb.width()); // ClipperLib::cInt stride = stride_padding(binbb.width());
ClipperLib::cInt batch_offset = 0; // ClipperLib::cInt batch_offset = 0;
for(auto& group : result) { // for(auto& group : result) {
for(auto& r : group) if(r.first < shapes.size()) { // for(auto& r : group) if(r.first < shapes.size()) {
Item& resultitem = r.second; // Item& resultitem = r.second;
unsigned idx = r.first; // unsigned idx = r.first;
auto offset = resultitem.translation(); // auto offset = resultitem.translation();
Radians rot = resultitem.rotation(); // Radians rot = resultitem.rotation();
ModelInstance *minst = shapes_ptr[idx]; // ModelInstance *minst = shapes_ptr[idx];
Vec3d foffset(offset.X*SCALING_FACTOR + batch_offset, // Vec3d foffset(unscaled(offset.X + batch_offset),
offset.Y*SCALING_FACTOR, // unscaled(offset.Y),
minst->get_offset()(Z)); // minst->get_offset()(Z));
// write the transformation data into the model instance // // write the transformation data into the model instance
minst->set_rotation(Z, rot); // minst->set_rotation(Z, rot);
minst->set_offset(foffset); // minst->set_offset(foffset);
} // }
batch_offset += stride; // batch_offset += stride;
} // }
} //}
} }

View file

@ -1,7 +1,9 @@
#ifndef MODELARRANGE_HPP #ifndef MODELARRANGE_HPP
#define MODELARRANGE_HPP #define MODELARRANGE_HPP
#include "Model.hpp" //#include "Model.hpp"
#include "Polygon.hpp"
#include "BoundingBox.hpp"
namespace Slic3r { namespace Slic3r {
@ -40,13 +42,25 @@ struct BedShapeHint {
BedShapeHint bedShape(const Polyline& bed); BedShapeHint bedShape(const Polyline& bed);
struct WipeTowerInfo { class ArrangeItem {
bool is_wipe_tower = false; public:
Vec2d pos;
Vec2d bb_size; virtual ~ArrangeItem() = default;
double rotation;
virtual void transform(Vec2d offset, double rotation_rads) = 0;
virtual Polygon silhouette() const = 0;
}; };
using ArrangeItems = std::vector<std::reference_wrapper<ArrangeItem>>;
//struct WipeTowerInfo {
// bool is_wipe_tower = false;
// Vec2d pos;
// Vec2d bb_size;
// double rotation;
//};
/** /**
* \brief Arranges the model objects on the screen. * \brief Arranges the model objects on the screen.
* *
@ -73,22 +87,33 @@ struct WipeTowerInfo {
* packed. The unsigned argument is the number of items remaining to pack. * packed. The unsigned argument is the number of items remaining to pack.
* \param stopcondition A predicate returning true if abort is needed. * \param stopcondition A predicate returning true if abort is needed.
*/ */
bool arrange(Model &model, //bool arrange(Model &model,
WipeTowerInfo& wipe_tower_info, // WipeTowerInfo& wipe_tower_info,
// coord_t min_obj_distance,
// const Slic3r::Polyline& bed,
// BedShapeHint bedhint,
// bool first_bin_only,
// std::function<void(unsigned)> progressind,
// std::function<bool(void)> stopcondition);
bool arrange(ArrangeItems &items,
coord_t min_obj_distance, coord_t min_obj_distance,
const Slic3r::Polyline& bed,
BedShapeHint bedhint, BedShapeHint bedhint,
bool first_bin_only,
std::function<void(unsigned)> progressind, std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcondition); std::function<bool(void)> stopcondition);
/// This will find a suitable position for a new object instance and leave the /// This will find a suitable position for a new object instance and leave the
/// old items untouched. /// old items untouched.
void find_new_position(const Model& model, //void find_new_position(const Model& model,
ModelInstancePtrs instances_to_add, // ModelInstancePtrs instances_to_add,
// coord_t min_obj_distance,
// const Slic3r::Polyline& bed,
// WipeTowerInfo& wti);
void find_new_position(ArrangeItems &items,
const ArrangeItems &instances_to_add,
coord_t min_obj_distance, coord_t min_obj_distance,
const Slic3r::Polyline& bed, BedShapeHint bedhint);
WipeTowerInfo& wti);
} // arr } // arr
} // Slic3r } // Slic3r

View file

@ -2,6 +2,7 @@
#include <cstddef> #include <cstddef>
#include <algorithm> #include <algorithm>
#include <numeric>
#include <vector> #include <vector>
#include <string> #include <string>
#include <regex> #include <regex>
@ -2400,131 +2401,185 @@ void Plater::priv::sla_optimize_rotation() {
} }
void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() {
static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1;
class ArrItemModelInstance: public arr::ArrangeItem {
ModelInstance *m_inst = nullptr;
public:
ArrItemModelInstance() = default;
ArrItemModelInstance(ModelInstance *inst) : m_inst(inst) {}
virtual void transform(Vec2d offs, double rot_rads) override {
assert(m_inst);
// write the transformation data into the model instance
m_inst->set_rotation(Z, rot_rads);
m_inst->set_offset(offs);
}
virtual Polygon silhouette() const override {
assert(m_inst);
Vec3d rotation = m_inst->get_rotation();
rotation.z() = 0.;
Transform3d trafo_instance = Geometry::assemble_transform(
Vec3d::Zero(),
rotation,
m_inst->get_scaling_factor(),
m_inst->get_mirror());
Polygon p = m_inst->get_object()->convex_hull_2d(trafo_instance);
assert(!p.points.empty());
// this may happen for malformed models, see:
// https://github.com/prusa3d/PrusaSlicer/issues/2209
if (p.points.empty()) return {};
Polygons pp { p };
pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM));
if (!pp.empty()) p = pp.front();
return p;
}
};
// Count all the items on the bin (all the object's instances)
auto count = std::accumulate(plater().model.objects.begin(),
plater().model.objects.end(),
size_t(0), [](size_t s, ModelObject* o)
{
return s + o->instances.size();
});
// std::vector<ArrItemInstance> items(size_t);
// TODO: we should decide whether to allow arrange when the search is // TODO: we should decide whether to allow arrange when the search is
// running we should probably disable explicit slicing and background // running we should probably disable explicit slicing and background
// processing // processing
static const auto arrangestr = _(L("Arranging")); // static const auto arrangestr = _(L("Arranging"));
auto &config = plater().config; // auto &config = plater().config;
auto &view3D = plater().view3D; // auto &view3D = plater().view3D;
auto &model = plater().model; // auto &model = plater().model;
// FIXME: I don't know how to obtain the minimum distance, it depends // // FIXME: I don't know how to obtain the minimum distance, it depends
// on printer technology. I guess the following should work but it crashes. // // on printer technology. I guess the following should work but it crashes.
double dist = 6; // PrintConfig::min_object_distance(config); // double dist = 6; // PrintConfig::min_object_distance(config);
if (plater().printer_technology == ptFFF) { // if (plater().printer_technology == ptFFF) {
dist = PrintConfig::min_object_distance(config); // dist = PrintConfig::min_object_distance(config);
} // }
auto min_obj_distance = coord_t(dist / SCALING_FACTOR); // auto min_obj_distance = coord_t(dist / SCALING_FACTOR);
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>( // const auto *bed_shape_opt = config->opt<ConfigOptionPoints>(
"bed_shape"); // "bed_shape");
assert(bed_shape_opt); // assert(bed_shape_opt);
auto & bedpoints = bed_shape_opt->values; // auto & bedpoints = bed_shape_opt->values;
Polyline bed; // Polyline bed;
bed.points.reserve(bedpoints.size()); // bed.points.reserve(bedpoints.size());
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); // for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
update_status(0, arrangestr); // update_status(0, arrangestr);
arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); // arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info();
try { // try {
arr::BedShapeHint hint; // arr::BedShapeHint hint;
// TODO: from Sasha from GUI or // // TODO: from Sasha from GUI or
hint.type = arr::BedShapeType::WHO_KNOWS; // hint.type = arr::BedShapeType::WHO_KNOWS;
arr::arrange(model, // arr::arrange(model,
wti, // wti,
min_obj_distance, // min_obj_distance,
bed, // bed,
hint, // hint,
false, // create many piles not just one pile // false, // create many piles not just one pile
[this](unsigned st) { // [this](unsigned st) {
if (st > 0) // if (st > 0)
update_status(count - int(st), arrangestr); // update_status(count - int(st), arrangestr);
}, // },
[this]() { return was_canceled(); }); // [this]() { return was_canceled(); });
} catch (std::exception & /*e*/) { // } catch (std::exception & /*e*/) {
GUI::show_error(plater().q, // GUI::show_error(plater().q,
L("Could not arrange model objects! " // L("Could not arrange model objects! "
"Some geometries may be invalid.")); // "Some geometries may be invalid."));
} // }
update_status(count, // update_status(count,
was_canceled() ? _(L("Arranging canceled.")) // was_canceled() ? _(L("Arranging canceled."))
: _(L("Arranging done."))); // : _(L("Arranging done.")));
// it remains to move the wipe tower: // // it remains to move the wipe tower:
view3D->get_canvas3d()->arrange_wipe_tower(wti); // view3D->get_canvas3d()->arrange_wipe_tower(wti);
} }
void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
{ {
int obj_idx = plater().get_selected_object_idx(); // int obj_idx = plater().get_selected_object_idx();
if (obj_idx < 0) { return; } // if (obj_idx < 0) { return; }
ModelObject *o = plater().model.objects[size_t(obj_idx)]; // ModelObject *o = plater().model.objects[size_t(obj_idx)];
auto r = sla::find_best_rotation( // auto r = sla::find_best_rotation(
*o, // *o,
.005f, // .005f,
[this](unsigned s) { // [this](unsigned s) {
if (s < 100) // if (s < 100)
update_status(int(s), // update_status(int(s),
_(L("Searching for optimal orientation"))); // _(L("Searching for optimal orientation")));
}, // },
[this]() { return was_canceled(); }); // [this]() { return was_canceled(); });
const auto *bed_shape_opt = // const auto *bed_shape_opt =
plater().config->opt<ConfigOptionPoints>("bed_shape"); // plater().config->opt<ConfigOptionPoints>("bed_shape");
assert(bed_shape_opt); // assert(bed_shape_opt);
auto & bedpoints = bed_shape_opt->values; // auto & bedpoints = bed_shape_opt->values;
Polyline bed; // Polyline bed;
bed.points.reserve(bedpoints.size()); // bed.points.reserve(bedpoints.size());
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); // for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
double mindist = 6.0; // FIXME // double mindist = 6.0; // FIXME
if (!was_canceled()) { // if (!was_canceled()) {
for(ModelInstance * oi : o->instances) { // for(ModelInstance * oi : o->instances) {
oi->set_rotation({r[X], r[Y], r[Z]}); // oi->set_rotation({r[X], r[Y], r[Z]});
auto trmatrix = oi->get_transformation().get_matrix(); // auto trmatrix = oi->get_transformation().get_matrix();
Polygon trchull = o->convex_hull_2d(trmatrix); // Polygon trchull = o->convex_hull_2d(trmatrix);
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); // MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
double r = rotbb.angle_to_X(); // double r = rotbb.angle_to_X();
// The box should be landscape // // The box should be landscape
if(rotbb.width() < rotbb.height()) r += PI / 2; // if(rotbb.width() < rotbb.height()) r += PI / 2;
Vec3d rt = oi->get_rotation(); rt(Z) += r; // Vec3d rt = oi->get_rotation(); rt(Z) += r;
oi->set_rotation(rt); // oi->set_rotation(rt);
} // }
arr::WipeTowerInfo wti; // useless in SLA context // arr::WipeTowerInfo wti; // useless in SLA context
arr::find_new_position(plater().model, // arr::find_new_position(plater().model,
o->instances, // o->instances,
coord_t(mindist / SCALING_FACTOR), // coord_t(mindist / SCALING_FACTOR),
bed, // bed,
wti); // wti);
// Correct the z offset of the object which was corrupted be // // Correct the z offset of the object which was corrupted be
// the rotation // // the rotation
o->ensure_on_bed(); // o->ensure_on_bed();
} // }
update_status(100, // update_status(100,
was_canceled() ? _(L("Orientation search canceled.")) // was_canceled() ? _(L("Orientation search canceled."))
: _(L("Orientation found."))); // : _(L("Orientation found.")));
} }
void Plater::priv::split_object() void Plater::priv::split_object()