Merge branch 'master' into fs_dir_per_glyph_SPE-1597

# Conflicts:
#	src/libslic3r/Polygon.hpp
This commit is contained in:
Filip Sykala - NTB T15p 2023-08-17 15:46:25 +02:00
commit 0c9cb02cf1
125 changed files with 19179 additions and 5057 deletions

View File

@ -1,7 +1,8 @@
#add_subdirectory(slasupporttree) #add_subdirectory(slasupporttree)
#add_subdirectory(openvdb) #add_subdirectory(openvdb)
# add_subdirectory(meshboolean) # add_subdirectory(meshboolean)
add_subdirectory(its_neighbor_index) #add_subdirectory(its_neighbor_index)
# add_subdirectory(opencsg) # add_subdirectory(opencsg)
#add_subdirectory(aabb-evaluation) #add_subdirectory(aabb-evaluation)
add_subdirectory(wx_gl_test) #add_subdirectory(wx_gl_test)
add_subdirectory(print_arrange_polys)

View File

@ -0,0 +1,7 @@
add_executable(print_arrange_polys main.cpp)
target_link_libraries(print_arrange_polys libslic3r admesh)
if (WIN32)
prusaslicer_copy_dlls(print_arrange_polys)
endif()

View File

@ -0,0 +1,103 @@
#include <iostream>
#include <ostream>
#include <libslic3r/TriangleMesh.hpp>
#include <boost/filesystem.hpp>
void print_arrange_polygons(const std::string &dirpath, std::ostream &out)
{
using namespace Slic3r;
boost::filesystem::path p = dirpath; //"/home/quarky/Workspace/printing/Original-Prusa-i3-MK3/Printed-Parts/stl/";
if (!boost::filesystem::exists(p) || !boost::filesystem::is_directory(p))
return;
for (const auto& entry : boost::filesystem::directory_iterator(p)) {
if (!boost::filesystem::is_regular_file(entry)) {
continue;
}
TriangleMesh mesh;
mesh.ReadSTLFile(entry.path().c_str());
ExPolygons outline = mesh.horizontal_projection();
out << "// " << entry.path().filename() << ": " << std::endl;
for (const ExPolygon &expoly : outline) {
out << "MyPoly{\n"; // Start of polygon
out << "\t{\n"; // Start of contour
for (const auto& point : expoly.contour.points) {
out << " {" << point.x() << ", " << point.y() << "},\n"; // Print point coordinates
}
out << " },\n"; // End of contour
out << " {\n"; // start of holes
for (const auto& hole : expoly.holes) {
out << " {\n"; // Start of hole
for (const auto& point : hole.points) {
out << " {" << point.x() << ", " << point.y() << "},\n"; // Print point coordinates
}
out << " },\n"; // End of hole Polygon
}
out << " }\n"; // end of holes Polygons
out << "},\n"; // End of ExPolygon
}
}
}
void print_arrange_items(const std::string &dirpath, std::ostream &out)
{
using namespace Slic3r;
boost::filesystem::path p = dirpath;
if (!boost::filesystem::exists(p) || !boost::filesystem::is_directory(p))
return;
for (const auto& entry : boost::filesystem::directory_iterator(p)) {
if (!boost::filesystem::is_regular_file(entry)) {
continue;
}
TriangleMesh mesh;
mesh.ReadSTLFile(entry.path().c_str());
ExPolygons outline = mesh.horizontal_projection();
out << "ExPolygons{ " << "// " << entry.path().filename() << ":\n";
for (const ExPolygon &expoly : outline) {
out << " MyPoly{\n"; // Start of polygon
out << " {\n"; // Start of contour
for (const auto& point : expoly.contour.points) {
out << " {" << point.x() << ", " << point.y() << "},\n"; // Print point coordinates
}
out << " },\n"; // End of contour
out << " {\n"; // start of holes
for (const auto& hole : expoly.holes) {
out << " {\n"; // Start of hole
for (const auto& point : hole.points) {
out << " {" << point.x() << ", " << point.y() << "},\n"; // Print point coordinates
}
out << " },\n"; // End of hole Polygon
}
out << " }\n"; // end of holes Polygons
out << " },\n"; // End of ExPolygon
}
out << "},\n";
}
}
int main(int argc, const char *argv[])
{
if (argc <= 1)
return -1;
std::string dirpath = argv[1];
print_arrange_items(dirpath, std::cout);
return 0;
}

View File

@ -314,10 +314,10 @@ int CLI::run(int argc, char **argv)
// Loop through transform options. // Loop through transform options.
bool user_center_specified = false; bool user_center_specified = false;
Points bed = get_bed_shape(m_print_config); arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(m_print_config));
ArrangeParams arrange_cfg; arr2::ArrangeSettings arrange_cfg;
arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config)); arrange_cfg.set_distance_from_objects(min_object_distance(m_print_config));
for (auto const &opt_key : m_transforms) { for (auto const &opt_key : m_transforms) {
if (opt_key == "merge") { if (opt_key == "merge") {
Model m; Model m;
@ -330,7 +330,7 @@ int CLI::run(int argc, char **argv)
if (this->has_print_action()) if (this->has_print_action())
arrange_objects(m, bed, arrange_cfg); arrange_objects(m, bed, arrange_cfg);
else else
arrange_objects(m, InfiniteBed{}, arrange_cfg); arrange_objects(m, arr2::InfiniteBed{}, arrange_cfg);
} }
m_models.clear(); m_models.clear();
m_models.emplace_back(std::move(m)); m_models.emplace_back(std::move(m));
@ -576,7 +576,7 @@ int CLI::run(int argc, char **argv)
if (! m_config.opt_bool("dont_arrange")) { if (! m_config.opt_bool("dont_arrange")) {
if (user_center_specified) { if (user_center_specified) {
Vec2d c = m_config.option<ConfigOptionPoint>("center")->value; Vec2d c = m_config.option<ConfigOptionPoint>("center")->value;
arrange_objects(model, InfiniteBed{scaled(c)}, arrange_cfg); arrange_objects(model, arr2::InfiniteBed{scaled(c)}, arrange_cfg);
} else } else
arrange_objects(model, bed, arrange_cfg); arrange_objects(model, bed, arrange_cfg);
} }

View File

@ -18,11 +18,10 @@ set(LIBNEST2D_SRCFILES
include/libnest2d/optimizers/nlopt/simplex.hpp include/libnest2d/optimizers/nlopt/simplex.hpp
include/libnest2d/optimizers/nlopt/subplex.hpp include/libnest2d/optimizers/nlopt/subplex.hpp
include/libnest2d/optimizers/nlopt/genetic.hpp include/libnest2d/optimizers/nlopt/genetic.hpp
src/libnest2d.cpp
) )
add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES}) add_library(libnest2d INTERFACE)
target_include_directories(libnest2d PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) target_include_directories(libnest2d INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(libnest2d PUBLIC NLopt::nlopt TBB::tbb TBB::tbbmalloc Boost::boost libslic3r) target_link_libraries(libnest2d INTERFACE NLopt::nlopt TBB::tbb TBB::tbbmalloc Boost::boost libslic3r)
target_compile_definitions(libnest2d PUBLIC LIBNEST2D_THREADING_tbb LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_libslic3r) target_compile_definitions(libnest2d INTERFACE LIBNEST2D_THREADING_tbb LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_libslic3r)

View File

@ -243,6 +243,12 @@ inline void translate(Slic3r::ExPolygon& sh, const Slic3r::Point& offs)
sh.translate(offs); sh.translate(offs);
} }
template<>
inline void translate(Slic3r::Polygon& sh, const Slic3r::Point& offs)
{
sh.translate(offs);
}
#define DISABLE_BOOST_ROTATE #define DISABLE_BOOST_ROTATE
template<> template<>
inline void rotate(Slic3r::ExPolygon& sh, const Radians& rads) inline void rotate(Slic3r::ExPolygon& sh, const Radians& rads)
@ -250,6 +256,12 @@ inline void rotate(Slic3r::ExPolygon& sh, const Radians& rads)
sh.rotate(rads); sh.rotate(rads);
} }
template<>
inline void rotate(Slic3r::Polygon& sh, const Radians& rads)
{
sh.rotate(rads);
}
} // namespace shapelike } // namespace shapelike
namespace nfp { namespace nfp {

View File

@ -15,12 +15,19 @@ namespace Slic3r {
// The stored pointer is not checked for being null when dereferenced. // The stored pointer is not checked for being null when dereferenced.
// //
// This is a movable only object due to the fact that it can possibly hold // This is a movable only object due to the fact that it can possibly hold
// a unique_ptr which a non-copy. // a unique_ptr which can only be moved.
//
// Drawbacks:
// No custom deleters are supported when storing a unique_ptr, but overloading
// std::default_delete for a particular type could be a workaround
//
// raw array types are problematic, since std::default_delete also does not
// support them well.
template<class T> template<class T>
class AnyPtr { class AnyPtr {
enum { RawPtr, UPtr, ShPtr, WkPtr }; enum { RawPtr, UPtr, ShPtr };
boost::variant<T*, std::unique_ptr<T>, std::shared_ptr<T>, std::weak_ptr<T>> ptr; boost::variant<T*, std::unique_ptr<T>, std::shared_ptr<T>> ptr;
template<class Self> static T *get_ptr(Self &&s) template<class Self> static T *get_ptr(Self &&s)
{ {
@ -28,91 +35,119 @@ class AnyPtr {
case RawPtr: return boost::get<T *>(s.ptr); case RawPtr: return boost::get<T *>(s.ptr);
case UPtr: return boost::get<std::unique_ptr<T>>(s.ptr).get(); case UPtr: return boost::get<std::unique_ptr<T>>(s.ptr).get();
case ShPtr: return boost::get<std::shared_ptr<T>>(s.ptr).get(); case ShPtr: return boost::get<std::shared_ptr<T>>(s.ptr).get();
case WkPtr: {
auto shptr = boost::get<std::weak_ptr<T>>(s.ptr).lock();
return shptr.get();
}
} }
return nullptr; return nullptr;
} }
public: template<class TT> friend class AnyPtr;
template<class TT = T, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr(TT *p = nullptr) : ptr{p}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr(std::unique_ptr<TT> p) : ptr{std::unique_ptr<T>(std::move(p))}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr(std::shared_ptr<TT> p) : ptr{std::shared_ptr<T>(std::move(p))}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
AnyPtr(std::weak_ptr<TT> p) : ptr{std::weak_ptr<T>(std::move(p))}
{}
~AnyPtr() = default; template<class TT>
using SimilarPtrOnly = std::enable_if_t<std::is_convertible_v<TT*, T*>>;
public:
AnyPtr() noexcept = default;
AnyPtr(T *p) noexcept: ptr{p} {}
AnyPtr(std::nullptr_t) noexcept {};
template<class TT, class = SimilarPtrOnly<TT>>
AnyPtr(TT *p) noexcept : ptr{p}
{}
template<class TT = T, class = SimilarPtrOnly<TT>>
AnyPtr(std::unique_ptr<TT> p) noexcept : ptr{std::unique_ptr<T>(std::move(p))}
{}
template<class TT = T, class = SimilarPtrOnly<TT>>
AnyPtr(std::shared_ptr<TT> p) noexcept : ptr{std::shared_ptr<T>(std::move(p))}
{}
AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {} AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {}
template<class TT, class = SimilarPtrOnly<TT>>
AnyPtr(AnyPtr<TT> &&other) noexcept
{
this->operator=(std::move(other));
}
AnyPtr(const AnyPtr &other) = delete; AnyPtr(const AnyPtr &other) = delete;
AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; } AnyPtr &operator=(AnyPtr &&other) noexcept
{
ptr = std::move(other.ptr);
return *this;
}
AnyPtr &operator=(const AnyPtr &other) = delete; AnyPtr &operator=(const AnyPtr &other) = delete;
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>> template<class TT, class = SimilarPtrOnly<TT>>
AnyPtr &operator=(TT *p) { ptr = p; return *this; } AnyPtr& operator=(AnyPtr<TT> &&other) noexcept
{
switch (other.ptr.which()) {
case RawPtr: *this = boost::get<TT *>(other.ptr); break;
case UPtr: *this = std::move(boost::get<std::unique_ptr<TT>>(other.ptr)); break;
case ShPtr: *this = std::move(boost::get<std::shared_ptr<TT>>(other.ptr)); break;
}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>> return *this;
AnyPtr &operator=(std::unique_ptr<TT> p) { ptr = std::move(p); return *this; } }
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>> template<class TT, class = SimilarPtrOnly<TT>>
AnyPtr &operator=(std::shared_ptr<TT> p) { ptr = p; return *this; } AnyPtr &operator=(TT *p) noexcept
{
ptr = static_cast<T *>(p);
return *this;
}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>> template<class TT, class = SimilarPtrOnly<TT>>
AnyPtr &operator=(std::weak_ptr<TT> p) { ptr = std::move(p); return *this; } AnyPtr &operator=(std::unique_ptr<TT> p) noexcept
{
ptr = std::unique_ptr<T>(std::move(p));
return *this;
}
const T &operator*() const { return *get_ptr(*this); } template<class TT, class = SimilarPtrOnly<TT>>
T &operator*() { return *get_ptr(*this); } AnyPtr &operator=(std::shared_ptr<TT> p) noexcept
{
ptr = std::shared_ptr<T>(std::move(p));
return *this;
}
T *operator->() { return get_ptr(*this); } const T &operator*() const noexcept { return *get_ptr(*this); }
const T *operator->() const { return get_ptr(*this); } T &operator*() noexcept { return *get_ptr(*this); }
T *get() { return get_ptr(*this); } T *operator->() noexcept { return get_ptr(*this); }
const T *get() const { return get_ptr(*this); } const T *operator->() const noexcept { return get_ptr(*this); }
operator bool() const T *get() noexcept { return get_ptr(*this); }
const T *get() const noexcept { return get_ptr(*this); }
operator bool() const noexcept
{ {
switch (ptr.which()) { switch (ptr.which()) {
case RawPtr: return bool(boost::get<T *>(ptr)); case RawPtr: return bool(boost::get<T *>(ptr));
case UPtr: return bool(boost::get<std::unique_ptr<T>>(ptr)); case UPtr: return bool(boost::get<std::unique_ptr<T>>(ptr));
case ShPtr: return bool(boost::get<std::shared_ptr<T>>(ptr)); case ShPtr: return bool(boost::get<std::shared_ptr<T>>(ptr));
case WkPtr: {
auto shptr = boost::get<std::weak_ptr<T>>(ptr).lock();
return bool(shptr);
}
} }
return false; return false;
} }
// If the stored pointer is a shared or weak pointer, returns a reference // If the stored pointer is a shared pointer, returns a reference
// counted copy. Empty shared pointer is returned otherwise. // counted copy. Empty shared pointer is returned otherwise.
std::shared_ptr<T> get_shared_cpy() const std::shared_ptr<T> get_shared_cpy() const noexcept
{ {
std::shared_ptr<T> ret; std::shared_ptr<T> ret;
switch (ptr.which()) { if (ptr.which() == ShPtr)
case ShPtr: ret = boost::get<std::shared_ptr<T>>(ptr); break; ret = boost::get<std::shared_ptr<T>>(ptr);
case WkPtr: ret = boost::get<std::weak_ptr<T>>(ptr).lock(); break;
default:
;
}
return ret; return ret;
} }
// If the underlying pointer is unique, convert to shared pointer // If the underlying pointer is unique, convert to shared pointer
void convert_unique_to_shared() void convert_unique_to_shared() noexcept
{ {
if (ptr.which() == UPtr) if (ptr.which() == UPtr)
ptr = std::shared_ptr<T>{std::move(boost::get<std::unique_ptr<T>>(ptr))}; ptr = std::shared_ptr<T>{std::move(boost::get<std::unique_ptr<T>>(ptr))};
@ -125,6 +160,7 @@ public:
} }
}; };
} // namespace Slic3r } // namespace Slic3r
#endif // ANYPTR_HPP #endif // ANYPTR_HPP

View File

@ -12,6 +12,7 @@
#include <ClipperUtils.hpp> #include <ClipperUtils.hpp>
#include <boost/geometry/index/rtree.hpp> #include <boost/geometry/index/rtree.hpp>
#include <boost/container/small_vector.hpp>
#if defined(_MSC_VER) && defined(__clang__) #if defined(_MSC_VER) && defined(__clang__)
#define BOOST_NO_CXX17_HDR_STRING_VIEW #define BOOST_NO_CXX17_HDR_STRING_VIEW
@ -258,7 +259,7 @@ protected:
auto& index = isBig(item.area()) ? spatindex : smalls_spatindex; auto& index = isBig(item.area()) ? spatindex : smalls_spatindex;
// Query the spatial index for the neighbors // Query the spatial index for the neighbors
std::vector<SpatElement> result; boost::container::small_vector<SpatElement, 100> result;
result.reserve(index.size()); result.reserve(index.size());
index.query(query, std::back_inserter(result)); index.query(query, std::back_inserter(result));

View File

@ -0,0 +1,260 @@
#ifndef ARRANGE2_HPP
#define ARRANGE2_HPP
#include "Scene.hpp"
#include "Items/MutableItemTraits.hpp"
#include "Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/MinAreaBoundingBox.hpp"
namespace Slic3r { namespace arr2 {
template<class ArrItem> class Arranger
{
public:
class Ctl : public ArrangeTaskCtl {
public:
virtual void on_packed(ArrItem &item) {};
};
virtual ~Arranger() = default;
virtual void arrange(std::vector<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
Ctl &ctl) = 0;
void arrange(std::vector<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
ArrangeTaskCtl &ctl);
void arrange(std::vector<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
Ctl &&ctl)
{
arrange(items, fixed, bed, ctl);
}
void arrange(std::vector<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
ArrangeTaskCtl &&ctl)
{
arrange(items, fixed, bed, ctl);
}
static std::unique_ptr<Arranger> create(const ArrangeSettingsView &settings);
};
template<class ArrItem> using ArrangerCtl = typename Arranger<ArrItem>::Ctl;
template<class ArrItem>
class DefaultArrangerCtl : public Arranger<ArrItem>::Ctl {
ArrangeTaskCtl *taskctl = nullptr;
public:
DefaultArrangerCtl() = default;
explicit DefaultArrangerCtl(ArrangeTaskBase::Ctl &ctl) : taskctl{&ctl} {}
void update_status(int st) override
{
if (taskctl)
taskctl->update_status(st);
}
bool was_canceled() const override
{
if (taskctl)
return taskctl->was_canceled();
return false;
}
};
template<class ArrItem>
void Arranger<ArrItem>::arrange(std::vector<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
ArrangeTaskCtl &ctl)
{
arrange(items, fixed, bed, DefaultArrangerCtl<ArrItem>{ctl});
}
template<class ArrItem> class ArrangeableToItemConverter
{
public:
virtual ~ArrangeableToItemConverter() = default;
virtual ArrItem convert(const Arrangeable &arrbl, coord_t offs = 0) const = 0;
// Returns the extent of simplification that the converter utilizes when
// creating arrange items. Zero shall mean no simplification at all.
virtual coord_t simplification_tolerance() const { return 0; }
static std::unique_ptr<ArrangeableToItemConverter> create(
ArrangeSettingsView::GeometryHandling geometry_handling,
coord_t safety_d);
static std::unique_ptr<ArrangeableToItemConverter> create(
const Scene &sc)
{
return create(sc.settings().get_geometry_handling(),
scaled(sc.settings().get_distance_from_objects()));
}
};
template<class DStore, class = WritableDataStoreOnly<DStore>>
class AnyWritableDataStore: public AnyWritable
{
DStore &dstore;
public:
AnyWritableDataStore(DStore &store): dstore{store} {}
void write(std::string_view key, std::any d) override
{
set_data(dstore, std::string{key}, std::move(d));
}
};
template<class ArrItem>
class BasicItemConverter : public ArrangeableToItemConverter<ArrItem>
{
coord_t m_safety_d;
coord_t m_simplify_tol;
public:
BasicItemConverter(coord_t safety_d = 0, coord_t simpl_tol = 0)
: m_safety_d{safety_d}, m_simplify_tol{simpl_tol}
{}
coord_t safety_dist() const noexcept { return m_safety_d; }
coord_t simplification_tolerance() const override
{
return m_simplify_tol;
}
};
template<class ArrItem>
class ConvexItemConverter : public BasicItemConverter<ArrItem>
{
public:
using BasicItemConverter<ArrItem>::BasicItemConverter;
ArrItem convert(const Arrangeable &arrbl, coord_t offs) const override;
};
template<class ArrItem>
class AdvancedItemConverter : public BasicItemConverter<ArrItem>
{
protected:
virtual ArrItem get_arritem(const Arrangeable &arrbl, coord_t eps) const;
public:
using BasicItemConverter<ArrItem>::BasicItemConverter;
ArrItem convert(const Arrangeable &arrbl, coord_t offs) const override;
};
template<class ArrItem>
class BalancedItemConverter : public AdvancedItemConverter<ArrItem>
{
protected:
ArrItem get_arritem(const Arrangeable &arrbl, coord_t offs) const override;
public:
using AdvancedItemConverter<ArrItem>::AdvancedItemConverter;
};
template<class ArrItem, class En = void> struct ImbueableItemTraits_
{
static constexpr const char *Key = "object_id";
static void imbue_id(ArrItem &itm, const ObjectID &id)
{
set_arbitrary_data(itm, Key, id);
}
static std::optional<ObjectID> retrieve_id(const ArrItem &itm)
{
std::optional<ObjectID> ret;
auto idptr = get_data<const ObjectID>(itm, Key);
if (idptr)
ret = *idptr;
return ret;
}
};
template<class ArrItem>
using ImbueableItemTraits = ImbueableItemTraits_<StripCVRef<ArrItem>>;
template<class ArrItem>
void imbue_id(ArrItem &itm, const ObjectID &id)
{
ImbueableItemTraits<ArrItem>::imbue_id(itm, id);
}
template<class ArrItem>
std::optional<ObjectID> retrieve_id(const ArrItem &itm)
{
return ImbueableItemTraits<ArrItem>::retrieve_id(itm);
}
template<class ArrItem>
bool apply_arrangeitem(const ArrItem &itm, ArrangeableModel &mdl)
{
bool ret = false;
if (auto id = retrieve_id(itm)) {
mdl.visit_arrangeable(*id, [&itm, &ret](Arrangeable &arrbl) {
if ((ret = arrbl.assign_bed(get_bed_index(itm))))
arrbl.transform(unscaled(get_translation(itm)), get_rotation(itm));
});
}
return ret;
}
template<class ArrItem>
double get_min_area_bounding_box_rotation(const ArrItem &itm)
{
return MinAreaBoundigBox{envelope_convex_hull(itm),
MinAreaBoundigBox::pcConvex}
.angle_to_X();
}
template<class ArrItem>
double get_fit_into_bed_rotation(const ArrItem &itm, const RectangleBed &bed)
{
double ret = 0.;
auto bbsz = envelope_bounding_box(itm).size();
auto binbb = bounding_box(bed);
auto binbbsz = binbb.size();
if (bbsz.x() >= binbbsz.x() || bbsz.y() >= binbbsz.y())
ret = fit_into_box_rotation(envelope_convex_hull(itm), binbb);
return ret;
}
template<class ArrItem>
auto get_corrected_bed(const ExtendedBed &bed,
const ArrangeableToItemConverter<ArrItem> &converter)
{
auto bedcpy = bed;
visit_bed([tol = -converter.simplification_tolerance()](auto &rawbed) {
rawbed = offset(rawbed, tol);
}, bedcpy);
return bedcpy;
}
}} // namespace Slic3r::arr2
#endif // ARRANGE2_HPP

View File

@ -0,0 +1,485 @@
#ifndef ARRANGEIMPL_HPP
#define ARRANGEIMPL_HPP
#include <random>
#include "Arrange.hpp"
#include "Core/ArrangeBase.hpp"
#include "Core/ArrangeFirstFit.hpp"
#include "Core/NFP/PackStrategyNFP.hpp"
#include "Core/NFP/Kernels/TMArrangeKernel.hpp"
#include "Core/NFP/Kernels/GravityKernel.hpp"
#include "Core/NFP/RectangleOverfitPackingStrategy.hpp"
#include "Core/Beds.hpp"
#include "Items/MutableItemTraits.hpp"
#include "SegmentedRectangleBed.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#ifndef NDEBUG
#include "Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp"
#endif
namespace Slic3r { namespace arr2 {
// arrange overload for SegmentedRectangleBed which is exactly what is used
// by XL printers.
template<class It,
class ConstIt,
class SelectionStrategy,
class PackStrategy, class...SBedArgs>
void arrange(SelectionStrategy &&selstrategy,
PackStrategy &&packingstrategy,
const Range<It> &items,
const Range<ConstIt> &fixed,
const SegmentedRectangleBed<SBedArgs...> &bed)
{
// Dispatch:
arrange(std::forward<SelectionStrategy>(selstrategy),
std::forward<PackStrategy>(packingstrategy), items, fixed,
RectangleBed{bed.bb}, SelStrategyTag<SelectionStrategy>{});
size_t beds = get_bed_count(crange(items));
size_t fixed_beds = std::max(beds, get_bed_count(fixed));
std::vector<bool> fixed_is_empty(fixed_beds, true);
std::vector<BoundingBox> pilebb(beds);
for (auto &itm : items) {
auto bedidx = get_bed_index(itm);
if (bedidx >= 0) {
pilebb[bedidx].merge(fixed_bounding_box(itm));
if (is_wipe_tower(itm))
fixed_is_empty[bedidx] = false;
}
}
for (auto &fxitm : fixed) {
auto bedidx = get_bed_index(fxitm);
if (bedidx >= 0 || is_wipe_tower(fxitm))
fixed_is_empty[bedidx] = false;
}
auto bedbb = bounding_box(bed);
auto piecesz = unscaled(bedbb).size();
piecesz.x() /= bed.segments_x();
piecesz.y() /= bed.segments_y();
using Pivots = RectPivots;
Pivots pivot = bed.alignment();
for (size_t bedidx = 0; bedidx < beds; ++bedidx) {
if (! fixed_is_empty[bedidx])
continue;
BoundingBox bb;
auto pilesz = unscaled(pilebb[bedidx]).size();
bb.max.x() = scaled(std::ceil(pilesz.x() / piecesz.x()) * piecesz.x());
bb.max.y() = scaled(std::ceil(pilesz.y() / piecesz.y()) * piecesz.y());
switch (pivot) {
case Pivots::BottomLeft:
bb.translate(bedbb.min - bb.min);
break;
case Pivots::TopRight:
bb.translate(bedbb.max - bb.max);
break;
case Pivots::BottomRight: {
Point bedref{bedbb.max.x(), bedbb.min.y()};
Point bbref {bb.max.x(), bb.min.y()};
bb.translate(bedref - bbref);
break;
}
case Pivots::TopLeft: {
Point bedref{bedbb.min.x(), bedbb.max.y()};
Point bbref {bb.min.x(), bb.max.y()};
bb.translate(bedref - bbref);
break;
}
case Pivots::Center: {
bb.translate(bedbb.center() - bb.center());
break;
}
default:
;
}
Vec2crd d = bb.center() - pilebb[bedidx].center();
auto pilebbx = pilebb[bedidx];
pilebbx.translate(d);
Point corr{0, 0};
corr.x() = -std::min(0, pilebbx.min.x() - bedbb.min.x())
-std::max(0, pilebbx.max.x() - bedbb.max.x());
corr.y() = -std::min(0, pilebbx.min.y() - bedbb.min.y())
-std::max(0, pilebbx.max.y() - bedbb.max.y());
d += corr;
for (auto &itm : items)
if (get_bed_index(itm) == static_cast<int>(bedidx) && !is_wipe_tower(itm))
translate(itm, d);
}
}
using VariantKernel =
boost::variant<TMArrangeKernel, GravityKernel>;
template<> struct KernelTraits_<VariantKernel> {
template<class ArrItem>
static double placement_fitness(const VariantKernel &kernel,
const ArrItem &itm,
const Vec2crd &transl)
{
double ret = NaNd;
boost::apply_visitor(
[&](auto &k) { ret = k.placement_fitness(itm, transl); }, kernel);
return ret;
}
template<class ArrItem, class Bed, class Ctx, class RemIt>
static bool on_start_packing(VariantKernel &kernel,
ArrItem &itm,
const Bed &bed,
const Ctx &packing_context,
const Range<RemIt> &remaining_items)
{
bool ret = false;
boost::apply_visitor([&](auto &k) {
ret = k.on_start_packing(itm, bed, packing_context, remaining_items);
}, kernel);
return ret;
}
template<class ArrItem>
static bool on_item_packed(VariantKernel &kernel, ArrItem &itm)
{
bool ret = false;
boost::apply_visitor([&](auto &k) { ret = k.on_item_packed(itm); },
kernel);
return ret;
}
};
template<class ArrItem>
struct firstfit::ItemArrangedVisitor<ArrItem, DataStoreOnly<ArrItem>> {
template<class Bed, class PIt, class RIt>
static void on_arranged(ArrItem &itm,
const Bed &bed,
const Range<PIt> &packed,
const Range<RIt> &remaining)
{
using OnArrangeCb = std::function<void(StripCVRef<ArrItem> &)>;
auto cb = get_data<OnArrangeCb>(itm, "on_arranged");
if (cb) {
(*cb)(itm);
}
}
};
inline RectPivots xlpivots_to_rect_pivots(ArrangeSettingsView::XLPivots xlpivot)
{
if (xlpivot == arr2::ArrangeSettingsView::xlpRandom) {
// means it should be random
std::random_device rd{};
std::mt19937 rng(rd());
std::uniform_int_distribution<std::mt19937::result_type>
dist(0, arr2::ArrangeSettingsView::xlpRandom - 1);
xlpivot = static_cast<ArrangeSettingsView::XLPivots>(dist(rng));
}
RectPivots rectpivot = RectPivots::Center;
switch(xlpivot) {
case arr2::ArrangeSettingsView::xlpCenter: rectpivot = RectPivots::Center; break;
case arr2::ArrangeSettingsView::xlpFrontLeft: rectpivot = RectPivots::BottomLeft; break;
case arr2::ArrangeSettingsView::xlpFrontRight: rectpivot = RectPivots::BottomRight; break;
case arr2::ArrangeSettingsView::xlpRearLeft: rectpivot = RectPivots::TopLeft; break;
case arr2::ArrangeSettingsView::xlpRearRight: rectpivot = RectPivots::TopRight; break;
default:
;
}
return rectpivot;
}
template<class It, class Bed>
void fill_rotations(const Range<It> &items,
const Bed &bed,
const ArrangeSettingsView &settings)
{
if (!settings.is_rotation_enabled())
return;
for (auto &itm : items) {
if (is_wipe_tower(itm)) // Rotating the wipe tower is currently problematic
continue;
// Use the minimum bounding box rotation as a starting point.
auto minbbr = get_min_area_bounding_box_rotation(itm);
std::vector<double> rotations =
{minbbr,
minbbr + PI / 4., minbbr + PI / 2.,
minbbr + PI, minbbr + 3 * PI / 4.};
// Add the original rotation of the item if minbbr
// is not already the original rotation (zero)
if (std::abs(minbbr) > 0.)
rotations.emplace_back(0.);
// Also try to find the rotation that fits the item
// into a rectangular bed, given that it cannot fit,
// and there exists a rotation which can fit.
if constexpr (std::is_convertible_v<Bed, RectangleBed>) {
double fitbrot = get_fit_into_bed_rotation(itm, bed);
if (std::abs(fitbrot) > 0.)
rotations.emplace_back(fitbrot);
}
set_allowed_rotations(itm, rotations);
}
}
// An arranger put together to fulfill all the requirements of PrusaSlicer based
// on the supplied ArrangeSettings
template<class ArrItem>
class DefaultArranger: public Arranger<ArrItem> {
ArrangeSettings m_settings;
static constexpr auto Accuracy = 1.;
template<class It, class FixIt, class Bed>
void arrange_(
const Range<It> &items,
const Range<FixIt> &fixed,
const Bed &bed,
ArrangerCtl<ArrItem> &ctl)
{
auto cmpfn = [](const auto &itm1, const auto &itm2) {
int pa = get_priority(itm1);
int pb = get_priority(itm2);
return pa == pb ? envelope_area(itm1) > envelope_area(itm2) :
pa > pb;
};
auto on_arranged = [&ctl](auto &itm, auto &bed, auto &ctx, auto &rem) {
ctl.update_status(rem.size());
ctl.on_packed(itm);
firstfit::DefaultOnArrangedFn{}(itm, bed, ctx, rem);
};
auto stop_cond = [&ctl] { return ctl.was_canceled(); };
firstfit::SelectionStrategy sel{cmpfn, on_arranged, stop_cond};
constexpr auto ep = ex_tbb;
VariantKernel basekernel;
switch (m_settings.get_arrange_strategy()) {
default:
[[fallthrough]];
case ArrangeSettingsView::asAuto:
basekernel = TMArrangeKernel{items.size(), area(bed)};
break;
case ArrangeSettingsView::asPullToCenter:
basekernel = GravityKernel{};
break;
}
#ifndef NDEBUG
SVGDebugOutputKernelWrapper<VariantKernel> kernel{bounding_box(bed), basekernel};
#else
auto & kernel = basekernel;
#endif
fill_rotations(items, bed, m_settings);
bool with_wipe_tower = std::any_of(items.begin(), items.end(),
[](auto &itm) {
return is_wipe_tower(itm);
});
// With rectange bed, and no fixed items, let's use an infinite bed
// with RectangleOverfitKernelWrapper. It produces better results than
// a pure RectangleBed with inner-fit polygon calculation.
if (!with_wipe_tower &&
m_settings.get_arrange_strategy() == ArrangeSettingsView::asAuto &&
std::is_convertible_v<Bed, RectangleBed>) {
PackStrategyNFP base_strategy{std::move(kernel), ep, Accuracy, stop_cond};
RectangleOverfitPackingStrategy final_strategy{std::move(base_strategy)};
arr2::arrange(sel, final_strategy, items, fixed, bed);
} else {
PackStrategyNFP ps{std::move(kernel), ep, Accuracy, stop_cond};
arr2::arrange(sel, ps, items, fixed, bed);
}
}
public:
explicit DefaultArranger(const ArrangeSettingsView &settings)
{
m_settings.set_from(settings);
}
void arrange(
std::vector<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
ArrangerCtl<ArrItem> &ctl) override
{
visit_bed([this, &items, &fixed, &ctl](auto rawbed) {
if constexpr (IsSegmentedBed<decltype(rawbed)>)
rawbed.pivot = xlpivots_to_rect_pivots(
m_settings.get_xl_alignment());
arrange_(range(items), crange(fixed), rawbed, ctl);
}, bed);
}
};
template<class ArrItem>
std::unique_ptr<Arranger<ArrItem>> Arranger<ArrItem>::create(
const ArrangeSettingsView &settings)
{
// Currently all that is needed is handled by DefaultArranger
return std::make_unique<DefaultArranger<ArrItem>>(settings);
}
template<class ArrItem>
ArrItem ConvexItemConverter<ArrItem>::convert(const Arrangeable &arrbl,
coord_t offs) const
{
auto bed_index = arrbl.get_bed_index();
Polygon outline = arrbl.convex_outline();
Polygon envelope = arrbl.convex_envelope();
coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.));
if (infl != 0) {
outline = Geometry::convex_hull(offset(outline, infl));
if (! envelope.empty())
envelope = Geometry::convex_hull(offset(envelope, infl));
}
ArrItem ret;
set_convex_shape(ret, outline);
if (! envelope.empty())
set_convex_envelope(ret, envelope);
set_bed_index(ret, bed_index);
set_priority(ret, arrbl.priority());
imbue_id(ret, arrbl.id());
if constexpr (IsWritableDataStore<ArrItem>)
arrbl.imbue_data(AnyWritableDataStore{ret});
return ret;
}
template<class ArrItem>
ArrItem AdvancedItemConverter<ArrItem>::convert(const Arrangeable &arrbl,
coord_t offs) const
{
auto bed_index = arrbl.get_bed_index();
ArrItem ret = get_arritem(arrbl, offs);
set_bed_index(ret, bed_index);
set_priority(ret, arrbl.priority());
imbue_id(ret, arrbl.id());
if constexpr (IsWritableDataStore<ArrItem>)
arrbl.imbue_data(AnyWritableDataStore{ret});
return ret;
}
template<class ArrItem>
ArrItem AdvancedItemConverter<ArrItem>::get_arritem(const Arrangeable &arrbl,
coord_t offs) const
{
coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.));
auto outline = arrbl.full_outline();
auto envelope = arrbl.full_envelope();
if (infl != 0) {
outline = offset_ex(outline, infl);
if (! envelope.empty())
envelope = offset_ex(envelope, infl);
}
auto simpl_tol = static_cast<double>(this->simplification_tolerance());
if (simpl_tol > 0)
{
outline = expolygons_simplify(outline, simpl_tol);
if (!envelope.empty())
envelope = expolygons_simplify(envelope, simpl_tol);
}
ArrItem ret;
set_shape(ret, outline);
if (! envelope.empty())
set_envelope(ret, envelope);
return ret;
}
template<class ArrItem>
ArrItem BalancedItemConverter<ArrItem>::get_arritem(const Arrangeable &arrbl,
coord_t offs) const
{
ArrItem ret = AdvancedItemConverter<ArrItem>::get_arritem(arrbl, offs);
set_convex_envelope(ret, envelope_convex_hull(ret));
return ret;
}
template<class ArrItem>
std::unique_ptr<ArrangeableToItemConverter<ArrItem>>
ArrangeableToItemConverter<ArrItem>::create(
ArrangeSettingsView::GeometryHandling gh,
coord_t safety_d)
{
std::unique_ptr<ArrangeableToItemConverter<ArrItem>> ret;
constexpr coord_t SimplifyTol = scaled(.2);
switch(gh) {
case arr2::ArrangeSettingsView::ghConvex:
ret = std::make_unique<ConvexItemConverter<ArrItem>>(safety_d);
break;
case arr2::ArrangeSettingsView::ghBalanced:
ret = std::make_unique<BalancedItemConverter<ArrItem>>(safety_d, SimplifyTol);
break;
case arr2::ArrangeSettingsView::ghAdvanced:
ret = std::make_unique<AdvancedItemConverter<ArrItem>>(safety_d, SimplifyTol);
break;
default:
;
}
return ret;
}
}} // namespace Slic3r::arr2
#endif // ARRANGEIMPL_HPP

View File

@ -0,0 +1,197 @@
#include "ArrangeSettingsDb_AppCfg.hpp"
namespace Slic3r {
ArrangeSettingsDb_AppCfg::ArrangeSettingsDb_AppCfg(AppConfig *appcfg) : m_appcfg{appcfg}
{
m_settings_fff.postfix = "_fff";
m_settings_fff_seq.postfix = "_fff_seq_print";
m_settings_sla.postfix = "_sla";
std::string dist_fff_str =
m_appcfg->get("arrange", "min_object_distance_fff");
std::string dist_bed_fff_str =
m_appcfg->get("arrange", "min_bed_distance_fff");
std::string dist_fff_seq_print_str =
m_appcfg->get("arrange", "min_object_distance_fff_seq_print");
std::string dist_bed_fff_seq_print_str =
m_appcfg->get("arrange", "min_bed_distance_fff_seq_print");
std::string dist_sla_str =
m_appcfg->get("arrange", "min_object_distance_sla");
std::string dist_bed_sla_str =
m_appcfg->get("arrange", "min_bed_distance_sla");
std::string en_rot_fff_str =
m_appcfg->get("arrange", "enable_rotation_fff");
std::string en_rot_fff_seqp_str =
m_appcfg->get("arrange", "enable_rotation_fff_seq_print");
std::string en_rot_sla_str =
m_appcfg->get("arrange", "enable_rotation_sla");
// std::string alignment_fff_str =
// m_appcfg->get("arrange", "alignment_fff");
// std::string alignment_fff_seqp_str =
// m_appcfg->get("arrange", "alignment_fff_seq_pring");
// std::string alignment_sla_str =
// m_appcfg->get("arrange", "alignment_sla");
// Override default alignment and save save/load it to a temporary slot "alignment_xl"
std::string alignment_xl_str =
m_appcfg->get("arrange", "alignment_xl");
std::string geom_handling_str =
m_appcfg->get("arrange", "geometry_handling");
std::string strategy_str =
m_appcfg->get("arrange", "arrange_strategy");
if (!dist_fff_str.empty())
m_settings_fff.vals.d_obj = string_to_float_decimal_point(dist_fff_str);
if (!dist_bed_fff_str.empty())
m_settings_fff.vals.d_bed = string_to_float_decimal_point(dist_bed_fff_str);
if (!dist_fff_seq_print_str.empty())
m_settings_fff_seq.vals.d_obj = string_to_float_decimal_point(dist_fff_seq_print_str);
if (!dist_bed_fff_seq_print_str.empty())
m_settings_fff_seq.vals.d_bed = string_to_float_decimal_point(dist_bed_fff_seq_print_str);
if (!dist_sla_str.empty())
m_settings_sla.vals.d_obj = string_to_float_decimal_point(dist_sla_str);
if (!dist_bed_sla_str.empty())
m_settings_sla.vals.d_bed = string_to_float_decimal_point(dist_bed_sla_str);
if (!en_rot_fff_str.empty())
m_settings_fff.vals.rotations = (en_rot_fff_str == "1" || en_rot_fff_str == "yes");
if (!en_rot_fff_seqp_str.empty())
m_settings_fff_seq.vals.rotations = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes");
if (!en_rot_sla_str.empty())
m_settings_sla.vals.rotations = (en_rot_sla_str == "1" || en_rot_sla_str == "yes");
// if (!alignment_sla_str.empty())
// m_arrange_settings_sla.alignment = std::stoi(alignment_sla_str);
// if (!alignment_fff_str.empty())
// m_arrange_settings_fff.alignment = std::stoi(alignment_fff_str);
// if (!alignment_fff_seqp_str.empty())
// m_arrange_settings_fff_seq_print.alignment = std::stoi(alignment_fff_seqp_str);
// Override default alignment and save save/load it to a temporary slot "alignment_xl"
ArrangeSettingsView::XLPivots arr_alignment = ArrangeSettingsView::xlpFrontLeft;
if (!alignment_xl_str.empty()) {
int align_val = std::stoi(alignment_xl_str);
if (align_val >= 0 && align_val < ArrangeSettingsView::xlpCount)
arr_alignment =
static_cast<ArrangeSettingsView::XLPivots>(align_val);
}
m_settings_sla.vals.xl_align = arr_alignment ;
m_settings_fff.vals.xl_align = arr_alignment ;
m_settings_fff_seq.vals.xl_align = arr_alignment ;
ArrangeSettingsView::GeometryHandling geom_handl = arr2::ArrangeSettingsView::ghConvex;
if (!geom_handling_str.empty()) {
int gh = std::stoi(geom_handling_str);
if(gh >= 0 && gh < ArrangeSettingsView::GeometryHandling::ghCount)
geom_handl = static_cast<ArrangeSettingsView::GeometryHandling>(gh);
}
m_settings_sla.vals.geom_handling = geom_handl;
m_settings_fff.vals.geom_handling = geom_handl;
m_settings_fff_seq.vals.geom_handling = geom_handl;
ArrangeSettingsView::ArrangeStrategy arr_strategy = arr2::ArrangeSettingsView::asAuto;
if (!strategy_str.empty()) {
int strateg = std::stoi(strategy_str);
if(strateg >= 0 && strateg < ArrangeSettingsView::ArrangeStrategy::asCount)
arr_strategy = static_cast<ArrangeSettingsView::ArrangeStrategy>(strateg);
}
m_settings_sla.vals.arr_strategy = arr_strategy;
m_settings_fff.vals.arr_strategy = arr_strategy;
m_settings_fff_seq.vals.arr_strategy = arr_strategy;
}
void ArrangeSettingsDb_AppCfg::distance_from_obj_range(float &min,
float &max) const
{
min = get_slot(this).dobj_range.minval;
max = get_slot(this).dobj_range.maxval;
}
void ArrangeSettingsDb_AppCfg::distance_from_bed_range(float &min,
float &max) const
{
min = get_slot(this).dbed_range.minval;
max = get_slot(this).dbed_range.maxval;
}
arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_distance_from_objects(float v)
{
Slot &slot = get_slot(this);
slot.vals.d_obj = v;
m_appcfg->set("arrange", "min_object_distance" + slot.postfix,
float_to_string_decimal_point(v));
return *this;
}
arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_distance_from_bed(float v)
{
Slot &slot = get_slot(this);
slot.vals.d_bed = v;
m_appcfg->set("arrange", "min_bed_distance" + slot.postfix,
float_to_string_decimal_point(v));
return *this;
}
arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_rotation_enabled(bool v)
{
Slot &slot = get_slot(this);
slot.vals.rotations = v;
m_appcfg->set("arrange", "enable_rotation" + slot.postfix, v ? "1" : "0");
return *this;
}
arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_xl_alignment(XLPivots v)
{
m_settings_fff.vals.xl_align = v;
m_appcfg->set("arrange", "alignment_xl", std::to_string(v));
return *this;
}
arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_geometry_handling(GeometryHandling v)
{
m_settings_fff.vals.geom_handling = v;
m_appcfg->set("arrange", "geometry_handling", std::to_string(v));
return *this;
}
arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_arrange_strategy(ArrangeStrategy v)
{
m_settings_fff.vals.arr_strategy = v;
m_appcfg->set("arrange", "arrange_strategy", std::to_string(v));
return *this;
}
} // namespace Slic3r

View File

@ -0,0 +1,91 @@
#ifndef ARRANGESETTINGSDB_APPCFG_HPP
#define ARRANGESETTINGSDB_APPCFG_HPP
#include "ArrangeSettingsView.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
class ArrangeSettingsDb_AppCfg: public arr2::ArrangeSettingsDb
{
public:
enum Slots { slotFFF, slotFFFSeqPrint, slotSLA };
private:
AppConfig *m_appcfg;
Slots m_current_slot = slotFFF;
struct FloatRange { float minval = 0.f, maxval = 100.f; };
struct Slot
{
Values vals;
Values defaults;
FloatRange dobj_range, dbed_range;
std::string postfix;
};
// Settings and their defaults are stored separately for fff,
// sla and fff sequential mode
Slot m_settings_fff, m_settings_fff_seq, m_settings_sla;
template<class Self>
static auto & get_slot(Self *self, Slots slot) {
switch(slot) {
case slotFFF: return self->m_settings_fff;
case slotFFFSeqPrint: return self->m_settings_fff_seq;
case slotSLA: return self->m_settings_sla;
}
return self->m_settings_fff;
}
template<class Self> static auto &get_slot(Self *self)
{
return get_slot(self, self->m_current_slot);
}
template<class Self>
static auto& get_ref(Self *self) { return get_slot(self).vals; }
public:
explicit ArrangeSettingsDb_AppCfg(AppConfig *appcfg);
float get_distance_from_objects() const override { return get_ref(this).d_obj; }
float get_distance_from_bed() const override { return get_ref(this).d_bed; }
bool is_rotation_enabled() const override { return get_ref(this).rotations; }
XLPivots get_xl_alignment() const override { return m_settings_fff.vals.xl_align; }
GeometryHandling get_geometry_handling() const override { return m_settings_fff.vals.geom_handling; }
ArrangeStrategy get_arrange_strategy() const override { return m_settings_fff.vals.arr_strategy; }
void distance_from_obj_range(float &min, float &max) const override;
void distance_from_bed_range(float &min, float &max) const override;
ArrangeSettingsDb& set_distance_from_objects(float v) override;
ArrangeSettingsDb& set_distance_from_bed(float v) override;
ArrangeSettingsDb& set_rotation_enabled(bool v) override;
ArrangeSettingsDb& set_xl_alignment(XLPivots v) override;
ArrangeSettingsDb& set_geometry_handling(GeometryHandling v) override;
ArrangeSettingsDb& set_arrange_strategy(ArrangeStrategy v) override;
Values get_defaults() const override { return get_slot(this).defaults; }
void set_active_slot(Slots slot) noexcept { m_current_slot = slot; }
void set_distance_from_obj_range(Slots slot, float min, float max)
{
get_slot(this, slot).dobj_range = FloatRange{min, max};
}
void set_distance_from_bed_range(Slots slot, float min, float max)
{
get_slot(this, slot).dbed_range = FloatRange{min, max};
}
Values &get_defaults(Slots slot) { return get_slot(this, slot).defaults; }
};
} // namespace Slic3r
#endif // ARRANGESETTINGSDB_APPCFG_HPP

View File

@ -0,0 +1,118 @@
#ifndef ARRANGESETTINGSVIEW_HPP
#define ARRANGESETTINGSVIEW_HPP
namespace Slic3r { namespace arr2 {
class ArrangeSettingsView
{
public:
enum GeometryHandling { ghConvex, ghBalanced, ghAdvanced, ghCount };
enum ArrangeStrategy { asAuto, asPullToCenter, asCount };
enum XLPivots {
xlpCenter,
xlpRearLeft,
xlpFrontLeft,
xlpFrontRight,
xlpRearRight,
xlpRandom,
xlpCount
};
virtual ~ArrangeSettingsView() = default;
virtual float get_distance_from_objects() const = 0;
virtual float get_distance_from_bed() const = 0;
virtual bool is_rotation_enabled() const = 0;
virtual XLPivots get_xl_alignment() const = 0;
virtual GeometryHandling get_geometry_handling() const = 0;
virtual ArrangeStrategy get_arrange_strategy() const = 0;
};
class ArrangeSettingsDb: public ArrangeSettingsView
{
public:
virtual void distance_from_obj_range(float &min, float &max) const = 0;
virtual void distance_from_bed_range(float &min, float &max) const = 0;
virtual ArrangeSettingsDb& set_distance_from_objects(float v) = 0;
virtual ArrangeSettingsDb& set_distance_from_bed(float v) = 0;
virtual ArrangeSettingsDb& set_rotation_enabled(bool v) = 0;
virtual ArrangeSettingsDb& set_xl_alignment(XLPivots v) = 0;
virtual ArrangeSettingsDb& set_geometry_handling(GeometryHandling v) = 0;
virtual ArrangeSettingsDb& set_arrange_strategy(ArrangeStrategy v) = 0;
struct Values {
float d_obj = 6.f, d_bed = 0.f;
bool rotations = false;
XLPivots xl_align = XLPivots::xlpFrontLeft;
GeometryHandling geom_handling = GeometryHandling::ghConvex;
ArrangeStrategy arr_strategy = ArrangeStrategy::asAuto;
Values() = default;
Values(const ArrangeSettingsView &sv)
{
d_bed = sv.get_distance_from_bed();
d_obj = sv.get_distance_from_objects();
arr_strategy = sv.get_arrange_strategy();
geom_handling = sv.get_geometry_handling();
rotations = sv.is_rotation_enabled();
xl_align = sv.get_xl_alignment();
}
};
virtual Values get_defaults() const { return {}; }
ArrangeSettingsDb& set_from(const ArrangeSettingsView &sv)
{
set_distance_from_bed(sv.get_distance_from_bed());
set_distance_from_objects(sv.get_distance_from_objects());
set_arrange_strategy(sv.get_arrange_strategy());
set_geometry_handling(sv.get_geometry_handling());
set_rotation_enabled(sv.is_rotation_enabled());
set_xl_alignment(sv.get_xl_alignment());
return *this;
}
};
class ArrangeSettings: public Slic3r::arr2::ArrangeSettingsDb
{
ArrangeSettingsDb::Values m_v = {};
public:
explicit ArrangeSettings(
const ArrangeSettingsDb::Values &v = {})
: m_v{v}
{}
explicit ArrangeSettings(const ArrangeSettingsView &v)
: m_v{v}
{}
float get_distance_from_objects() const override { return m_v.d_obj; }
float get_distance_from_bed() const override { return m_v.d_bed; }
bool is_rotation_enabled() const override { return m_v.rotations; }
XLPivots get_xl_alignment() const override { return m_v.xl_align; }
GeometryHandling get_geometry_handling() const override { return m_v.geom_handling; }
ArrangeStrategy get_arrange_strategy() const override { return m_v.arr_strategy; }
void distance_from_obj_range(float &min, float &max) const override { min = 0.f; max = 100.f; }
void distance_from_bed_range(float &min, float &max) const override { min = 0.f; max = 100.f; }
ArrangeSettings& set_distance_from_objects(float v) override { m_v.d_obj = v; return *this; }
ArrangeSettings& set_distance_from_bed(float v) override { m_v.d_bed = v; return *this; }
ArrangeSettings& set_rotation_enabled(bool v) override { m_v.rotations = v; return *this; }
ArrangeSettings& set_xl_alignment(XLPivots v) override { m_v.xl_align = v; return *this; }
ArrangeSettings& set_geometry_handling(GeometryHandling v) override { m_v.geom_handling = v; return *this; }
ArrangeSettings& set_arrange_strategy(ArrangeStrategy v) override { m_v.arr_strategy = v; return *this; }
auto & values() const { return m_v; }
auto & values() { return m_v; }
};
}} // namespace Slic3r::arr2
#endif // ARRANGESETTINGSVIEW_HPP

View File

@ -0,0 +1,258 @@
#ifndef ARRANGEBASE_HPP
#define ARRANGEBASE_HPP
#include <iterator>
#include <type_traits>
#include "ArrangeItemTraits.hpp"
#include "PackingContext.hpp"
#include "libslic3r/Point.hpp"
namespace Slic3r { namespace arr2 {
namespace detail_is_const_it {
template<class It, class En = void>
struct IsConstIt_ { static constexpr bool value = false; };
template<class It>
using iterator_category_t = typename std::iterator_traits<It>::iterator_category;
template<class It>
using iterator_reference_t = typename std::iterator_traits<It>::reference;
template<class It>
struct IsConstIt_ <It, std::enable_if_t<std::is_class_v<iterator_category_t<It>>> >
{
static constexpr bool value =
std::is_const_v<std::remove_reference_t<iterator_reference_t<It>>>;
};
} // namespace detail_is_const_it
template<class It>
static constexpr bool IsConstIterator = detail_is_const_it::IsConstIt_<It>::value;
template<class It>
constexpr bool is_const_iterator(const It &it) noexcept { return IsConstIterator<It>; }
// The pack() function will use tag dispatching, based on the given strategy
// object that is used as its first argument.
// This tag is derived for a packing strategy as default, and will be used
// to cast a compile error.
struct UnimplementedPacking {};
// PackStrategyTag_ needs to be specialized for any valid packing strategy class
template<class PackStrategy> struct PackStrategyTag_ {
using Tag = UnimplementedPacking;
};
// Helper metafunc to derive packing strategy tag from a strategy object.
template<class Strategy>
using PackStrategyTag =
typename PackStrategyTag_<remove_cvref_t<Strategy>>::Tag;
template<class PackStrategy, class En = void> struct PackStrategyTraits_ {
template<class ArrItem> using Context = DefaultPackingContext<ArrItem>;
template<class ArrItem, class Bed>
static Context<ArrItem> create_context(PackStrategy &ps,
const Bed &bed,
int bed_index)
{
return {};
}
};
template<class PS> using PackStrategyTraits = PackStrategyTraits_<StripCVRef<PS>>;
template<class PS, class ArrItem>
using PackStrategyContext =
typename PackStrategyTraits<PS>::template Context<StripCVRef<ArrItem>>;
template<class ArrItem, class PackStrategy, class Bed>
PackStrategyContext<PackStrategy, ArrItem> create_context(PackStrategy &&ps,
const Bed &bed,
int bed_index)
{
return PackStrategyTraits<PackStrategy>::template create_context<
StripCVRef<ArrItem>>(ps, bed, bed_index);
}
// Function to pack one item into a bed.
// strategy parameter holds clue to what packing strategy to use. This function
// needs to be overloaded for the strategy tag belonging to the given
// strategy.
// 'bed' parameter is the type of bed into which the new item should be packed.
// See beds.hpp for valid bed classes.
// 'item' parameter is the item to be packed. After succesful arrangement
// (see return value) the item will have it's translation and rotation
// set correctly. If the function returns false, the translation and
// rotation of the input item might be changed to arbitrary values.
// 'fixed_items' paramter holds a range of ArrItem type objects that are already
// on the bed and need to be avoided by the newly packed item.
// 'remaining_items' is a range of ArrItem type objects that are intended to be
// packed in the future. This information can be leveradged by
// the packing strategy to make more intelligent placement
// decisions for the input item.
template<class Strategy, class Bed, class ArrItem, class RemIt>
bool pack(Strategy &&strategy,
const Bed &bed,
ArrItem &item,
const PackStrategyContext<Strategy, ArrItem> &context,
const Range<RemIt> &remaining_items)
{
static_assert(IsConstIterator<RemIt>, "Remaining item iterator is not const!");
// Dispatch:
return pack(std::forward<Strategy>(strategy), bed, item, context,
remaining_items, PackStrategyTag<Strategy>{});
}
// Overload without fixed items:
template<class Strategy, class Bed, class ArrItem>
bool pack(Strategy &&strategy, const Bed &bed, ArrItem &item)
{
std::vector<ArrItem> dummy;
auto context = create_context<ArrItem>(strategy, bed, PhysicalBedId);
return pack(std::forward<Strategy>(strategy), bed, item, context,
crange(dummy));
}
// Overload when strategy is unkown, yields compile error:
template<class Strategy, class Bed, class ArrItem, class RemIt>
bool pack(Strategy &&strategy,
const Bed &bed,
ArrItem &item,
const PackStrategyContext<Strategy, ArrItem> &context,
const Range<RemIt> &remaining_items,
const UnimplementedPacking &)
{
static_assert(always_false<Strategy>::value,
"Packing unimplemented for this placement strategy");
return false;
}
// Helper function to remove unpackable items from the input container.
template<class PackStrategy, class Container, class Bed, class StopCond>
void remove_unpackable_items(PackStrategy &&ps,
Container &c,
const Bed &bed,
const StopCond &stopcond)
{
// Safety test: try to pack each item into an empty bed. If it fails
// then it should be removed from the list
auto it = c.begin();
while (it != c.end() && !stopcond()) {
StripCVRef<decltype(*it)> &itm = *it;
auto cpy{itm};
if (!pack(ps, bed, cpy)) {
set_bed_index(itm, Unarranged);
it = c.erase(it);
} else
it++;
}
}
// arrange() function will use tag dispatching based on the selection strategy
// given as its first argument.
// This tag is derived for a selection strategy as default, and will be used
// to cast a compile error.
struct UnimplementedSelection {};
// SelStrategyTag_ needs to be specialized for any valid selection strategy class
template<class SelStrategy> struct SelStrategyTag_ {
using Tag = UnimplementedSelection;
};
// Helper metafunc to derive the selection strategy tag from a strategy object.
template<class Strategy>
using SelStrategyTag = typename SelStrategyTag_<remove_cvref_t<Strategy>>::Tag;
// Main function to start the arrangement. Takes a selection and a packing
// strategy object as the first two parameters. An implementation
// (function overload) must exist for this function that takes the coresponding
// selection strategy tag belonging to the given selstrategy argument.
//
// items parameter is a range of arrange items to arrange.
// fixed parameter is a range of arrange items that have fixed position and will
// not move during the arrangement but need to be avoided by the
// moving items.
// bed parameter is the type of bed into which the items need to fit.
template<class It,
class ConstIt,
class TBed,
class SelectionStrategy,
class PackStrategy>
void arrange(SelectionStrategy &&selstrategy,
PackStrategy &&packingstrategy,
const Range<It> &items,
const Range<ConstIt> &fixed,
const TBed &bed)
{
static_assert(IsConstIterator<ConstIt>, "Fixed item iterator is not const!");
// Dispatch:
arrange(std::forward<SelectionStrategy>(selstrategy),
std::forward<PackStrategy>(packingstrategy), items, fixed, bed,
SelStrategyTag<SelectionStrategy>{});
}
template<class It, class TBed, class SelectionStrategy, class PackStrategy>
void arrange(SelectionStrategy &&selstrategy,
PackStrategy &&packingstrategy,
const Range<It> &items,
const TBed &bed)
{
std::vector<typename std::iterator_traits<It>::value_type> dummy;
arrange(std::forward<SelectionStrategy>(selstrategy),
std::forward<PackStrategy>(packingstrategy), items, crange(dummy),
bed);
}
// Overload for unimplemented selection strategy, yields compile error:
template<class It,
class ConstIt,
class TBed,
class SelectionStrategy,
class PackStrategy>
void arrange(SelectionStrategy &&selstrategy,
PackStrategy &&packingstrategy,
const Range<It> &items,
const Range<ConstIt> &fixed,
const TBed &bed,
const UnimplementedSelection &)
{
static_assert(always_false<SelectionStrategy>::value,
"Arrange unimplemented for this selection strategy");
}
template<class It>
size_t get_bed_count(const Range<It> &items)
{
auto it = std::max_element(items.begin(),
items.end(),
[](auto &i1, auto &i2) {
return get_bed_index(i1) < get_bed_index(i2);
});
size_t beds = 0;
if (it != items.end())
beds = get_bed_index(*it) + 1;
return beds;
}
struct DefaultStopCondition {
constexpr bool operator()() const noexcept { return false; }
};
}} // namespace Slic3r::arr2
#endif // ARRANGEBASE_HPP

View File

@ -0,0 +1,161 @@
#ifndef ARRANGEFIRSTFIT_HPP
#define ARRANGEFIRSTFIT_HPP
#include <iterator>
#include <libslic3r/Arrange/Core/ArrangeBase.hpp>
namespace Slic3r { namespace arr2 { namespace firstfit {
struct SelectionTag {};
// Can be specialized by Items
template<class ArrItem, class En = void>
struct ItemArrangedVisitor {
template<class Bed, class PIt, class RIt>
static void on_arranged(ArrItem &itm,
const Bed &bed,
const Range<PIt> &packed_items,
const Range<RIt> &remaining_items)
{}
};
// Use the the visitor baked into the ArrItem type by default
struct DefaultOnArrangedFn {
template<class ArrItem, class Bed, class PIt, class RIt>
void operator()(ArrItem &itm,
const Bed &bed,
const Range<PIt> &packed,
const Range<RIt> &remaining)
{
ItemArrangedVisitor<StripCVRef<ArrItem>>::on_arranged(itm, bed, packed,
remaining);
}
};
struct DefaultItemCompareFn {
template<class ArrItem>
bool operator() (const ArrItem &ia, const ArrItem &ib)
{
return get_priority(ia) > get_priority(ib);
}
};
template<class CompareFn = DefaultItemCompareFn,
class OnArrangedFn = DefaultOnArrangedFn,
class StopCondition = DefaultStopCondition>
struct SelectionStrategy
{
CompareFn cmpfn;
OnArrangedFn on_arranged_fn;
StopCondition cancel_fn;
SelectionStrategy(CompareFn cmp = {},
OnArrangedFn on_arranged = {},
StopCondition stopcond = {})
: cmpfn{cmp},
on_arranged_fn{std::move(on_arranged)},
cancel_fn{std::move(stopcond)}
{}
};
} // namespace firstfit
template<class... Args> struct SelStrategyTag_<firstfit::SelectionStrategy<Args...>> {
using Tag = firstfit::SelectionTag;
};
template<class It,
class ConstIt,
class TBed,
class SelStrategy,
class PackStrategy>
void arrange(
SelStrategy &&sel,
PackStrategy &&ps,
const Range<It> &items,
const Range<ConstIt> &fixed,
const TBed &bed,
const firstfit::SelectionTag &)
{
using ArrItem = typename std::iterator_traits<It>::value_type;
using ArrItemRef = std::reference_wrapper<ArrItem>;
auto sorted_items = reserve_vector<ArrItemRef>(items.size());
for (auto &itm : items) {
set_bed_index(itm, Unarranged);
sorted_items.emplace_back(itm);
}
int max_bed_idx = get_bed_count(fixed);
using Context = PackStrategyContext<PackStrategy, ArrItem>;
auto bed_contexts = reserve_vector<Context>(max_bed_idx + 1);
for (auto &itm : fixed) {
if (get_bed_index(itm) >= 0) {
auto bedidx = static_cast<size_t>(get_bed_index(itm));
while (bed_contexts.size() <= bedidx)
bed_contexts.emplace_back(
create_context<ArrItem>(ps, bed, bedidx));
add_fixed_item(bed_contexts[bedidx], itm);
}
}
if constexpr (!std::is_null_pointer_v<decltype(sel.cmpfn)>) {
std::stable_sort(sorted_items.begin(), sorted_items.end(), sel.cmpfn);
}
auto is_cancelled = [&sel]() {
return sel.cancel_fn();
};
remove_unpackable_items(ps, sorted_items, bed, [&is_cancelled]() {
return is_cancelled();
});
auto it = sorted_items.begin();
using SConstIt = typename std::vector<ArrItemRef>::const_iterator;
while (it != sorted_items.end() && !is_cancelled()) {
bool was_packed = false;
size_t j = 0;
while (!was_packed && !is_cancelled()) {
for (; j < bed_contexts.size() && !was_packed && !is_cancelled(); j++) {
set_bed_index(*it, int(j));
auto remaining = Range{std::next(static_cast<SConstIt>(it)),
sorted_items.cend()};
was_packed = pack(ps, bed, *it, bed_contexts[j], remaining);
if(was_packed) {
add_packed_item(bed_contexts[j], *it);
auto packed_range = Range{sorted_items.cbegin(),
static_cast<SConstIt>(it)};
sel.on_arranged_fn(*it, bed, packed_range, remaining);
} else {
set_bed_index(*it, Unarranged);
}
}
if (!was_packed) {
bed_contexts.emplace_back(
create_context<ArrItem>(ps, bed, bed_contexts.size()));
j = bed_contexts.size() - 1;
}
}
++it;
}
}
}} // namespace Slic3r::arr2
#endif // ARRANGEFIRSTFIT_HPP

View File

@ -0,0 +1,113 @@
#ifndef ARRANGE_ITEM_TRAITS_HPP
#define ARRANGE_ITEM_TRAITS_HPP
#include <libslic3r/Point.hpp>
namespace Slic3r { namespace arr2 {
// A logical bed representing an object not being arranged. Either the arrange
// has not yet successfully run on this ArrangePolygon or it could not fit the
// object due to overly large size or invalid geometry.
const constexpr int Unarranged = -1;
const constexpr int PhysicalBedId = 0;
// Basic interface of an arrange item. This struct can be specialized for any
// type that is arrangeable.
template<class ArrItem, class En = void> struct ArrangeItemTraits_ {
static Vec2crd get_translation(const ArrItem &ap)
{
return ap.get_translation();
}
static double get_rotation(const ArrItem &ap)
{
return ap.get_rotation();
}
static int get_bed_index(const ArrItem &ap) { return ap.get_bed_index(); }
static int get_priority(const ArrItem &ap) { return ap.get_priority(); }
// Setters:
static void set_translation(ArrItem &ap, const Vec2crd &v)
{
ap.set_translation(v);
}
static void set_rotation(ArrItem &ap, double v) { ap.set_rotation(v); }
static void set_bed_index(ArrItem &ap, int v) { ap.set_bed_index(v); }
};
template<class T> using ArrangeItemTraits = ArrangeItemTraits_<StripCVRef<T>>;
// Getters:
template<class T> Vec2crd get_translation(const T &itm)
{
return ArrangeItemTraits<T>::get_translation(itm);
}
template<class T> double get_rotation(const T &itm)
{
return ArrangeItemTraits<T>::get_rotation(itm);
}
template<class T> int get_bed_index(const T &itm)
{
return ArrangeItemTraits<T>::get_bed_index(itm);
}
template<class T> int get_priority(const T &itm)
{
return ArrangeItemTraits<T>::get_priority(itm);
}
// Setters:
template<class T> void set_translation(T &itm, const Vec2crd &v)
{
ArrangeItemTraits<T>::set_translation(itm, v);
}
template<class T> void set_rotation(T &itm, double v)
{
ArrangeItemTraits<T>::set_rotation(itm, v);
}
template<class T> void set_bed_index(T &itm, int v)
{
ArrangeItemTraits<T>::set_bed_index(itm, v);
}
// Helper functions for arrange items
template<class ArrItem> bool is_arranged(const ArrItem &ap)
{
return get_bed_index(ap) > Unarranged;
}
template<class ArrItem> bool is_fixed(const ArrItem &ap)
{
return get_bed_index(ap) >= PhysicalBedId;
}
template<class ArrItem> bool is_on_physical_bed(const ArrItem &ap)
{
return get_bed_index(ap) == PhysicalBedId;
}
template<class ArrItem> void translate(ArrItem &ap, const Vec2crd &t)
{
set_translation(ap, get_translation(ap) + t);
}
template<class ArrItem> void rotate(ArrItem &ap, double rads)
{
set_rotation(ap, get_rotation(ap) + rads);
}
}} // namespace Slic3r::arr2
#endif // ARRANGE_ITEM_HPP

View File

@ -0,0 +1,129 @@
#include "Beds.hpp"
namespace Slic3r { namespace arr2 {
BoundingBox bounding_box(const InfiniteBed &bed)
{
BoundingBox ret;
using C = coord_t;
// It is important for Mx and My to be strictly less than half of the
// range of type C. width(), height() and area() will not overflow this way.
C Mx = C((std::numeric_limits<C>::lowest() + 2 * bed.center.x()) / 4.01);
C My = C((std::numeric_limits<C>::lowest() + 2 * bed.center.y()) / 4.01);
ret.max = bed.center - Point{Mx, My};
ret.min = bed.center + Point{Mx, My};
return ret;
}
Polygon to_rectangle(const BoundingBox &bb)
{
Polygon ret;
ret.points = {
bb.min,
Point{bb.max.x(), bb.min.y()},
bb.max,
Point{bb.min.x(), bb.max.y()}
};
return ret;
}
Polygon approximate_circle_with_polygon(const arr2::CircleBed &bed, int nedges)
{
Polygon ret;
double angle_incr = (2 * M_PI) / nedges; // Angle increment for each edge
double angle = 0; // Starting angle
// Loop to generate vertices for each edge
for (int i = 0; i < nedges; i++) {
// Calculate coordinates of the vertices using trigonometry
auto x = bed.center().x() + static_cast<coord_t>(bed.radius() * std::cos(angle));
auto y = bed.center().y() + static_cast<coord_t>(bed.radius() * std::sin(angle));
// Add vertex to the vector
ret.points.emplace_back(x, y);
// Update the angle for the next iteration
angle += angle_incr;
}
return ret;
}
inline coord_t width(const BoundingBox &box)
{
return box.max.x() - box.min.x();
}
inline coord_t height(const BoundingBox &box)
{
return box.max.y() - box.min.y();
}
inline double poly_area(const Points &pts)
{
return std::abs(Polygon::area(pts));
}
inline double distance_to(const Point &p1, const Point &p2)
{
double dx = p2.x() - p1.x();
double dy = p2.y() - p1.y();
return std::sqrt(dx * dx + dy * dy);
}
static CircleBed to_circle(const Point &center, const Points &points)
{
std::vector<double> vertex_distances;
double avg_dist = 0;
for (const Point &pt : points) {
double distance = distance_to(center, pt);
vertex_distances.push_back(distance);
avg_dist += distance;
}
avg_dist /= vertex_distances.size();
CircleBed ret(center, avg_dist);
for (auto el : vertex_distances) {
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
ret = {};
break;
}
}
return ret;
}
template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
{
if (bed.empty())
return fn(InfiniteBed{});
else if (bed.size() == 1)
return fn(InfiniteBed{bed.front()});
else {
auto bb = BoundingBox(bed);
CircleBed circ = to_circle(bb.center(), bed);
auto parea = poly_area(bed);
if ((1.0 - parea / area(bb)) < 1e-3) {
return fn(RectangleBed{bb});
} else if (!std::isnan(circ.radius()))
return fn(circ);
else
return fn(IrregularBed{{ExPolygon(bed)}});
}
}
ArrangeBed to_arrange_bed(const Points &bedpts)
{
ArrangeBed ret;
call_with_bed(bedpts, [&](const auto &bed) { ret = bed; });
return ret;
}
}} // namespace Slic3r::arr2

View File

@ -0,0 +1,191 @@
#ifndef BEDS_HPP
#define BEDS_HPP
#include <numeric>
#include <libslic3r/Point.hpp>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/BoundingBox.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <boost/variant.hpp>
namespace Slic3r { namespace arr2 {
// Bed types to be used with arrangement. Most generic bed is a simple polygon
// with holes, but other special bed types are also valid, like a bed without
// boundaries, or a special case of a rectangular or circular bed which leaves
// a lot of room for optimizations.
// Representing an unbounded bed.
struct InfiniteBed {
Point center;
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
};
BoundingBox bounding_box(const InfiniteBed &bed);
inline InfiniteBed offset(const InfiniteBed &bed, coord_t) { return bed; }
struct RectangleBed {
BoundingBox bb;
explicit RectangleBed(const BoundingBox &bedbb) : bb{bedbb} {}
explicit RectangleBed(coord_t w, coord_t h, Point c = {0, 0}):
bb{{c.x() - w / 2, c.y() - h / 2}, {c.x() + w / 2, c.y() + h / 2}}
{}
coord_t width() const { return bb.size().x(); }
coord_t height() const { return bb.size().y(); }
};
inline BoundingBox bounding_box(const RectangleBed &bed) { return bed.bb; }
inline RectangleBed offset(RectangleBed bed, coord_t v)
{
bed.bb.offset(v);
return bed;
}
Polygon to_rectangle(const BoundingBox &bb);
inline Polygon to_rectangle(const RectangleBed &bed)
{
return to_rectangle(bed.bb);
}
class CircleBed {
Point m_center;
double m_radius;
public:
CircleBed(): m_center(0, 0), m_radius(NaNd) {}
explicit CircleBed(const Point& c, double r)
: m_center(c)
, m_radius(r)
{}
double radius() const { return m_radius; }
const Point& center() const { return m_center; }
};
// Function to approximate a circle with a convex polygon
Polygon approximate_circle_with_polygon(const CircleBed &bed, int nedges = 24);
inline BoundingBox bounding_box(const CircleBed &bed)
{
auto r = static_cast<coord_t>(std::round(bed.radius()));
Point R{r, r};
return {bed.center() - R, bed.center() + R};
}
inline CircleBed offset(const CircleBed &bed, coord_t v)
{
return CircleBed{bed.center(), bed.radius() + v};
}
struct IrregularBed { ExPolygons poly; };
inline BoundingBox bounding_box(const IrregularBed &bed)
{
return get_extents(bed.poly);
}
inline IrregularBed offset(IrregularBed bed, coord_t v)
{
bed.poly = offset_ex(bed.poly, v);
return bed;
}
using ArrangeBed =
boost::variant<InfiniteBed, RectangleBed, CircleBed, IrregularBed>;
inline BoundingBox bounding_box(const ArrangeBed &bed)
{
BoundingBox ret;
auto visitor = [&ret](const auto &b) { ret = bounding_box(b); };
boost::apply_visitor(visitor, bed);
return ret;
}
inline ArrangeBed offset(ArrangeBed bed, coord_t v)
{
auto visitor = [v](auto &b) { b = offset(b, v); };
boost::apply_visitor(visitor, bed);
return bed;
}
inline double area(const BoundingBox &bb)
{
auto bbsz = bb.size();
return double(bbsz.x()) * bbsz.y();
}
inline double area(const RectangleBed &bed)
{
auto bbsz = bed.bb.size();
return double(bbsz.x()) * bbsz.y();
}
inline double area(const InfiniteBed &bed)
{
return std::numeric_limits<double>::infinity();
}
inline double area(const IrregularBed &bed)
{
return std::accumulate(bed.poly.begin(), bed.poly.end(), 0.,
[](double s, auto &p) { return s + p.area(); });
}
inline double area(const CircleBed &bed)
{
return bed.radius() * bed.radius() * PI;
}
inline double area(const ArrangeBed &bed)
{
double ret = 0.;
auto visitor = [&ret](auto &b) { ret = area(b); };
boost::apply_visitor(visitor, bed);
return ret;
}
inline ExPolygons to_expolygons(const InfiniteBed &bed)
{
return {ExPolygon{to_rectangle(RectangleBed{scaled(1000.), scaled(1000.)})}};
}
inline ExPolygons to_expolygons(const RectangleBed &bed)
{
return {ExPolygon{to_rectangle(bed)}};
}
inline ExPolygons to_expolygons(const CircleBed &bed)
{
return {ExPolygon{approximate_circle_with_polygon(bed)}};
}
inline ExPolygons to_expolygons(const IrregularBed &bed) { return bed.poly; }
inline ExPolygons to_expolygons(const ArrangeBed &bed)
{
ExPolygons ret;
auto visitor = [&ret](const auto &b) { ret = to_expolygons(b); };
boost::apply_visitor(visitor, bed);
return ret;
}
ArrangeBed to_arrange_bed(const Points &bedpts);
} // namespace arr2
inline BoundingBox &bounding_box(BoundingBox &bb) { return bb; }
inline const BoundingBox &bounding_box(const BoundingBox &bb) { return bb; }
inline BoundingBox bounding_box(const Polygon &p) { return get_extents(p); }
} // namespace Slic3r
#endif // BEDS_HPP

View File

@ -0,0 +1,78 @@
#ifndef DATASTORETRAITS_HPP
#define DATASTORETRAITS_HPP
#include <string_view>
#include "libslic3r/libslic3r.h"
namespace Slic3r { namespace arr2 {
// Some items can be containers of arbitrary data stored under string keys.
template<class ArrItem, class En = void> struct DataStoreTraits_
{
static constexpr bool Implemented = false;
template<class T> static const T *get(const ArrItem &, const std::string &key)
{
return nullptr;
}
// Same as above just not const.
template<class T> static T *get(ArrItem &, const std::string &key)
{
return nullptr;
}
static bool has_key(const ArrItem &itm, const std::string &key)
{
return false;
}
};
template<class ArrItem, class En = void> struct WritableDataStoreTraits_
{
static constexpr bool Implemented = false;
template<class T> static void set(ArrItem &, const std::string &key, T &&data)
{
}
};
template<class T> using DataStoreTraits = DataStoreTraits_<StripCVRef<T>>;
template<class T> constexpr bool IsDataStore = DataStoreTraits<StripCVRef<T>>::Implemented;
template<class T, class TT = T> using DataStoreOnly = std::enable_if_t<IsDataStore<T>, TT>;
template<class T, class ArrItem>
const T *get_data(const ArrItem &itm, const std::string &key)
{
return DataStoreTraits<ArrItem>::template get<T>(itm, key);
}
template<class ArrItem>
bool has_key(const ArrItem &itm, const std::string &key)
{
return DataStoreTraits<ArrItem>::has_key(itm, key);
}
template<class T, class ArrItem>
T *get_data(ArrItem &itm, const std::string &key)
{
return DataStoreTraits<ArrItem>::template get<T>(itm, key);
}
template<class T> using WritableDataStoreTraits = WritableDataStoreTraits_<StripCVRef<T>>;
template<class T> constexpr bool IsWritableDataStore = WritableDataStoreTraits<StripCVRef<T>>::Implemented;
template<class T, class TT = T> using WritableDataStoreOnly = std::enable_if_t<IsWritableDataStore<T>, TT>;
template<class T, class ArrItem>
void set_data(ArrItem &itm, const std::string &key, T &&data)
{
WritableDataStoreTraits<ArrItem>::template set(itm, key, std::forward<T>(data));
}
template<class T> constexpr bool IsReadWritableDataStore = IsDataStore<T> && IsWritableDataStore<T>;
template<class T, class TT = T> using ReadWritableDataStoreOnly = std::enable_if_t<IsReadWritableDataStore<T>, TT>;
}} // namespace Slic3r::arr2
#endif // DATASTORETRAITS_HPP

View File

@ -0,0 +1,110 @@
#ifndef CIRCULAR_EDGEITERATOR_HPP
#define CIRCULAR_EDGEITERATOR_HPP
#include <libslic3r/Polygon.hpp>
#include <libslic3r/Line.hpp>
namespace Slic3r {
// Circular iterator over a polygon yielding individual edges as Line objects
// if flip_lines is true, the orientation of each line is flipped (not the
// direction of traversal)
template<bool flip_lines = false>
class CircularEdgeIterator_ {
const Polygon *m_poly = nullptr;
size_t m_i = 0;
size_t m_c = 0; // counting how many times the iterator has circled over
public:
// i: vertex position of first line's starting vertex
// poly: target polygon
CircularEdgeIterator_(size_t i, const Polygon &poly)
: m_poly{&poly}
, m_i{!poly.empty() ? i % poly.size() : 0}
, m_c{!poly.empty() ? i / poly.size() : 0}
{}
explicit CircularEdgeIterator_ (const Polygon &poly)
: CircularEdgeIterator_(0, poly) {}
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = Line;
using pointer = Line*;
using reference = Line&;
CircularEdgeIterator_ & operator++()
{
assert (m_poly);
++m_i;
if (m_i == m_poly->size()) { // faster than modulo (?)
m_i = 0;
++m_c;
}
return *this;
}
CircularEdgeIterator_ operator++(int)
{
auto cpy = *this; ++(*this); return cpy;
}
Line operator*() const
{
size_t nx = m_i == m_poly->size() - 1 ? 0 : m_i + 1;
Line ret;
if constexpr (flip_lines)
ret = Line((*m_poly)[nx], (*m_poly)[m_i]);
else
ret = Line((*m_poly)[m_i], (*m_poly)[nx]);
return ret;
}
Line operator->() const { return *(*this); }
bool operator==(const CircularEdgeIterator_& other) const
{
return m_i == other.m_i && m_c == other.m_c;
}
bool operator!=(const CircularEdgeIterator_& other) const
{
return !(*this == other);
}
CircularEdgeIterator_& operator +=(size_t dist)
{
m_i = (m_i + dist) % m_poly->size();
m_c = (m_i + (m_c * m_poly->size()) + dist) / m_poly->size();
return *this;
}
CircularEdgeIterator_ operator +(size_t dist)
{
auto cpy = *this;
cpy += dist;
return cpy;
}
};
using CircularEdgeIterator = CircularEdgeIterator_<>;
using CircularReverseEdgeIterator = CircularEdgeIterator_<true>;
inline Range<CircularEdgeIterator> line_range(const Polygon &poly)
{
return Range{CircularEdgeIterator{0, poly}, CircularEdgeIterator{poly.size(), poly}};
}
inline Range<CircularReverseEdgeIterator> line_range_flp(const Polygon &poly)
{
return Range{CircularReverseEdgeIterator{0, poly}, CircularReverseEdgeIterator{poly.size(), poly}};
}
} // namespace Slic3r
#endif // CIRCULAR_EDGEITERATOR_HPP

View File

@ -0,0 +1,92 @@
#include "EdgeCache.hpp"
#include "CircularEdgeIterator.hpp"
namespace Slic3r { namespace arr2 {
void EdgeCache::create_cache(const ExPolygon &sh)
{
m_contour.distances.reserve(sh.contour.size());
m_holes.reserve(sh.holes.size());
m_contour.poly = &sh.contour;
fill_distances(sh.contour, m_contour.distances);
for (const Polygon &hole : sh.holes) {
auto &hc = m_holes.emplace_back();
hc.poly = &hole;
fill_distances(hole, hc.distances);
}
}
Vec2crd EdgeCache::coords(const ContourCache &cache, double distance) const
{
assert(cache.poly);
return arr2::coords(*cache.poly, cache.distances, distance);
}
void EdgeCache::sample_contour(double accuracy, std::vector<ContourLocation> &samples)
{
const auto N = m_contour.distances.size();
const auto S = stride(N, accuracy);
samples.reserve(N / S + 1);
for(size_t i = 0; i < N; i += S) {
samples.emplace_back(
ContourLocation{0, m_contour.distances[i] / m_contour.distances.back()});
}
for (size_t hidx = 1; hidx <= m_holes.size(); ++hidx) {
auto& hc = m_holes[hidx - 1];
const auto NH = hc.distances.size();
const auto SH = stride(NH, accuracy);
samples.reserve(samples.size() + NH / SH + 1);
for (size_t i = 0; i < NH; i += SH) {
samples.emplace_back(
ContourLocation{hidx, hc.distances[i] / hc.distances.back()});
}
}
}
Vec2crd coords(const Polygon &poly, const std::vector<double> &distances, double distance)
{
assert(poly.size() > 1 && distance >= .0 && distance <= 1.0);
// distance is from 0.0 to 1.0, we scale it up to the full length of
// the circumference
double d = distance * distances.back();
// Magic: we find the right edge in log time
auto it = std::lower_bound(distances.begin(), distances.end(), d);
assert(it != distances.end());
auto idx = it - distances.begin(); // get the index of the edge
auto &pts = poly.points;
auto edge = idx == long(pts.size() - 1) ? Line(pts.back(), pts.front()) :
Line(pts[idx], pts[idx + 1]);
// Get the remaining distance on the target edge
auto ed = d - (idx > 0 ? *std::prev(it) : 0 );
double t = ed / edge.length();
Vec2d n {double(edge.b.x()) - edge.a.x(), double(edge.b.y()) - edge.a.y()};
Vec2crd ret = (edge.a.cast<double>() + t * n).cast<coord_t>();
return ret;
}
void fill_distances(const Polygon &poly, std::vector<double> &distances)
{
distances.reserve(poly.size());
double dist = 0.;
auto lrange = line_range(poly);
for (const Line &l : lrange) {
dist += l.length();
distances.emplace_back(dist);
}
}
}} // namespace Slic3r::arr2

View File

@ -0,0 +1,70 @@
#ifndef EDGECACHE_HPP
#define EDGECACHE_HPP
#include <vector>
#include <libslic3r/ExPolygon.hpp>
namespace Slic3r { namespace arr2 {
// Position on the circumference of an ExPolygon.
// countour_id: 0th is contour, 1..N are holes
// dist: position given as a floating point number within <0., 1.>
struct ContourLocation { size_t contour_id; double dist; };
void fill_distances(const Polygon &poly, std::vector<double> &distances);
Vec2crd coords(const Polygon &poly, const std::vector<double>& distances, double distance);
// A class for getting a point on the circumference of the polygon (in log time)
//
// This is a transformation of the provided polygon to be able to pinpoint
// locations on the circumference. The optimizer will pass a floating point
// value e.g. within <0,1> and we have to transform this value quickly into a
// coordinate on the circumference. By definition 0 should yield the first
// vertex and 1.0 would be the last (which should coincide with first).
//
// We also have to make this work for the holes of the captured polygon.
class EdgeCache {
struct ContourCache {
const Polygon *poly;
std::vector<double> distances;
} m_contour;
std::vector<ContourCache> m_holes;
void create_cache(const ExPolygon& sh);
Vec2crd coords(const ContourCache& cache, double distance) const;
public:
explicit EdgeCache(const ExPolygon *sh)
{
create_cache(*sh);
}
// Given coeff for accuracy <0., 1.>, return the number of vertices to skip
// when fetching corners.
static inline size_t stride(const size_t N, double accuracy)
{
return static_cast<coord_t>(
std::round(N / std::pow(N, std::pow(accuracy, 1./3.)))
);
}
void sample_contour(double accuracy, std::vector<ContourLocation> &samples);
Vec2crd coords(const ContourLocation &loc) const
{
assert(loc.contour_id <= m_holes.size());
return loc.contour_id > 0 ?
coords(m_holes[loc.contour_id - 1], loc.dist) :
coords(m_contour, loc.dist);
}
};
}} // namespace Slic3r::arr2
#endif // EDGECACHE_HPP

View File

@ -0,0 +1,61 @@
#ifndef COMPACTIFYKERNEL_HPP
#define COMPACTIFYKERNEL_HPP
#include <numeric>
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include <libslic3r/Geometry/ConvexHull.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include "KernelUtils.hpp"
namespace Slic3r { namespace arr2 {
struct CompactifyKernel {
ExPolygons merged_pile;
template<class ArrItem>
double placement_fitness(const ArrItem &itm, const Vec2crd &transl) const
{
auto pile = merged_pile;
ExPolygons itm_tr = to_expolygons(envelope_outline(itm));
for (auto &p : itm_tr)
p.translate(transl);
append(pile, std::move(itm_tr));
pile = union_ex(pile);
Polygon chull = Geometry::convex_hull(pile);
return -(chull.area());
}
template<class ArrItem, class Bed, class Context, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Context &packing_context,
const Range<RemIt> & /*remaining_items*/)
{
bool ret = find_initial_position(itm, bounding_box(bed).center(), bed,
packing_context);
merged_pile.clear();
for (const auto &gitm : all_items_range(packing_context)) {
append(merged_pile, to_expolygons(fixed_outline(gitm)));
}
merged_pile = union_ex(merged_pile);
return ret;
}
template<class ArrItem>
bool on_item_packed(ArrItem &itm) { return true; }
};
}} // namespace Slic3r::arr2
#endif // COMPACTIFYKERNEL_HPP

View File

@ -0,0 +1,58 @@
#ifndef GRAVITYKERNEL_HPP
#define GRAVITYKERNEL_HPP
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include "KernelUtils.hpp"
namespace Slic3r { namespace arr2 {
struct GravityKernel {
std::optional<Vec2crd> sink;
std::optional<Vec2crd> item_sink;
Vec2d active_sink;
GravityKernel(Vec2crd gravity_center) : sink{gravity_center} {}
GravityKernel() = default;
template<class ArrItem>
double placement_fitness(const ArrItem &itm, const Vec2crd &transl) const
{
Vec2d center = unscaled(envelope_centroid(itm));
center += unscaled(transl);
return - (center - active_sink).squaredNorm();
}
template<class ArrItem, class Bed, class Ctx, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Ctx &packing_context,
const Range<RemIt> & /*remaining_items*/)
{
bool ret = false;
item_sink = get_gravity_sink(itm);
if (!sink) {
sink = bounding_box(bed).center();
}
if (item_sink)
active_sink = unscaled(*item_sink);
else
active_sink = unscaled(*sink);
ret = find_initial_position(itm, scaled(active_sink), bed, packing_context);
return ret;
}
template<class ArrItem> bool on_item_packed(ArrItem &itm) { return true; }
};
}} // namespace Slic3r::arr2
#endif // GRAVITYKERNEL_HPP

View File

@ -0,0 +1,57 @@
#ifndef KERNELTRAITS_HPP
#define KERNELTRAITS_HPP
#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp"
namespace Slic3r { namespace arr2 {
// An arrangement kernel that specifies the object function to the arrangement
// optimizer and additional callback functions to be able to track the state
// of the arranged pile during arrangement.
template<class Kernel, class En = void> struct KernelTraits_
{
// Has to return a score value marking the quality of the arrangement. The
// higher this value is, the better a particular placement of the item is.
// parameter transl is the translation needed for the item to be moved to
// the candidate position.
// To discard the item, return NaN as score for every translation.
template<class ArrItem>
static double placement_fitness(const Kernel &k,
const ArrItem &itm,
const Vec2crd &transl)
{
return k.placement_fitness(itm, transl);
}
// Called whenever a new item is about to be processed by the optimizer.
// The current state of the arrangement can be saved by the kernel: the
// already placed items and the remaining items that need to fit into a
// particular bed.
// Returns true if the item is can be packed immediately, false if it
// should be processed further. This way, a kernel have the power to
// choose an initial position for the item that is not on the NFP.
template<class ArrItem, class Bed, class Ctx, class RemIt>
static bool on_start_packing(Kernel &k,
ArrItem &itm,
const Bed &bed,
const Ctx &packing_context,
const Range<RemIt> &remaining_items)
{
return k.on_start_packing(itm, bed, packing_context, remaining_items);
}
// Called when an item has been succesfully packed. itm should have the
// final translation and rotation already set.
// Can return false to discard the item after the optimization.
template<class ArrItem>
static bool on_item_packed(Kernel &k, ArrItem &itm)
{
return k.on_item_packed(itm);
}
};
template<class K> using KernelTraits = KernelTraits_<StripCVRef<K>>;
}} // namespace Slic3r::arr2
#endif // KERNELTRAITS_HPP

View File

@ -0,0 +1,75 @@
#ifndef ARRANGEKERNELUTILS_HPP
#define ARRANGEKERNELUTILS_HPP
#include <type_traits>
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include "libslic3r/Arrange/Core/DataStoreTraits.hpp"
namespace Slic3r { namespace arr2 {
template<class Itm, class Bed, class Context>
bool find_initial_position(Itm &itm,
const Vec2crd &sink,
const Bed &bed,
const Context &packing_context)
{
bool ret = false;
if constexpr (std::is_convertible_v<Bed, RectangleBed> ||
std::is_convertible_v<Bed, InfiniteBed> ||
std::is_convertible_v<Bed, CircleBed>)
{
if (all_items_range(packing_context).empty()) {
auto rotations = allowed_rotations(itm);
auto chull = envelope_convex_hull(itm);
for (double rot : rotations) {
auto chullcpy = chull;
chullcpy.rotate(rot);
auto bbitm = bounding_box(chullcpy);
Vec2crd cb = sink;
Vec2crd ci = bbitm.center();
Vec2crd d = cb - ci;
bbitm.translate(d);
if (bounding_box(bed).contains(bbitm)) {
rotate(itm, rot);
translate(itm, d);
ret = true;
break;
}
}
}
}
return ret;
}
template<class ArrItem> std::optional<Vec2crd> get_gravity_sink(const ArrItem &itm)
{
constexpr const char * SinkKey = "sink";
std::optional<Vec2crd> ret;
auto ptr = get_data<Vec2crd>(itm, SinkKey);
if (ptr)
ret = *ptr;
return ret;
}
template<class ArrItem> bool is_wipe_tower(const ArrItem &itm)
{
constexpr const char * Key = "is_wipe_tower";
return has_key(itm, Key);
}
}} // namespace Slic3r::arr2
#endif // ARRANGEKERNELUTILS_HPP

View File

@ -0,0 +1,94 @@
#ifndef RECTANGLEOVERFITKERNELWRAPPER_HPP
#define RECTANGLEOVERFITKERNELWRAPPER_HPP
#include "KernelTraits.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
namespace Slic3r { namespace arr2 {
// This is a kernel wrapper that will apply a penality to the object function
// if the result cannot fit into the given rectangular bounds. This can be used
// to arrange into rectangular boundaries without calculating the IFP of the
// rectangle bed. Note that after the arrangement, what is garanteed is that
// the resulting pile will fit into the rectangular boundaries, but it will not
// be within the given rectangle. The items need to be moved afterwards manually.
// Use RectangeOverfitPackingStrategy to automate this post process step.
template<class Kernel>
struct RectangleOverfitKernelWrapper {
Kernel &k;
BoundingBox binbb;
BoundingBox pilebb;
RectangleOverfitKernelWrapper(Kernel &kern, const BoundingBox &limits)
: k{kern}
, binbb{limits}
{}
double overfit(const BoundingBox &itmbb) const
{
auto fullbb = pilebb;
fullbb.merge(itmbb);
auto fullbbsz = fullbb.size();
auto binbbsz = binbb.size();
auto wdiff = fullbbsz.x() - binbbsz.x() - SCALED_EPSILON;
auto hdiff = fullbbsz.y() - binbbsz.y() - SCALED_EPSILON;
double miss = .0;
if (wdiff > 0)
miss += double(wdiff);
if (hdiff > 0)
miss += double(hdiff);
miss = miss > 0? miss : 0;
return miss;
}
template<class ArrItem>
double placement_fitness(const ArrItem &item, const Vec2crd &transl) const
{
double score = KernelTraits<Kernel>::placement_fitness(k, item, transl);
auto itmbb = envelope_bounding_box(item);
itmbb.translate(transl);
double miss = overfit(itmbb);
score -= miss * miss;
return score;
}
template<class ArrItem, class Bed, class Ctx, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Ctx &packing_context,
const Range<RemIt> &remaining_items)
{
pilebb = BoundingBox{};
for (auto &fitm : all_items_range(packing_context))
pilebb.merge(fixed_bounding_box(fitm));
return KernelTraits<Kernel>::on_start_packing(k, itm, RectangleBed{binbb},
packing_context,
remaining_items);
}
template<class ArrItem>
bool on_item_packed(ArrItem &itm)
{
bool ret = KernelTraits<Kernel>::on_item_packed(k, itm);
double miss = overfit(envelope_bounding_box(itm));
if (miss > 0.)
ret = false;
return ret;
}
};
}} // namespace Slic3r::arr2
#endif // RECTANGLEOVERFITKERNELWRAPPER_H

View File

@ -0,0 +1,96 @@
#ifndef SVGDEBUGOUTPUTKERNELWRAPPER_HPP
#define SVGDEBUGOUTPUTKERNELWRAPPER_HPP
#include <memory>
#include "KernelTraits.hpp"
#include "libslic3r/Arrange/Core/PackingContext.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include <libslic3r/SVG.hpp>
namespace Slic3r { namespace arr2 {
template<class Kernel>
struct SVGDebugOutputKernelWrapper {
Kernel &k;
std::unique_ptr<Slic3r::SVG> svg;
BoundingBox drawbounds;
template<class... Args>
SVGDebugOutputKernelWrapper(const BoundingBox &bounds, Kernel &kern)
: k{kern}, drawbounds{bounds}
{}
template<class ArrItem, class Bed, class Context, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Context &packing_context,
const Range<RemIt> &rem)
{
using namespace Slic3r;
bool ret = KernelTraits<Kernel>::on_start_packing(k, itm, bed,
packing_context,
rem);
if (arr2::get_bed_index(itm) < 0)
return ret;
svg.reset();
auto bounds = drawbounds;
auto fixed = all_items_range(packing_context);
svg = std::make_unique<SVG>(std::string("arrange_bed") +
std::to_string(
arr2::get_bed_index(itm)) +
"_" + std::to_string(fixed.size()) +
".svg",
bounds, 0, false);
svg->draw(ExPolygon{arr2::to_rectangle(drawbounds)}, "blue", .2f);
auto nfp = calculate_nfp(itm, packing_context, bed);
svg->draw_outline(nfp);
svg->draw(nfp, "green", 0.2f);
for (const auto &fixeditm : fixed) {
ExPolygons fixeditm_outline = to_expolygons(fixed_outline(fixeditm));
svg->draw_outline(fixeditm_outline);
svg->draw(fixeditm_outline, "yellow", 0.5f);
}
return ret;
}
template<class ArrItem>
double placement_fitness(const ArrItem &item, const Vec2crd &transl) const
{
return KernelTraits<Kernel>::placement_fitness(k, item, transl);
}
template<class ArrItem>
bool on_item_packed(ArrItem &itm)
{
using namespace Slic3r;
using namespace Slic3r::arr2;
bool ret = KernelTraits<Kernel>::on_item_packed(k, itm);
if (svg) {
ExPolygons itm_outline = to_expolygons(fixed_outline(itm));
svg->draw_outline(itm_outline);
svg->draw(itm_outline, "grey");
svg->Close();
}
return ret;
}
};
}} // namespace Slic3r::arr2
#endif // SVGDEBUGOUTPUTKERNELWRAPPER_HPP

View File

@ -0,0 +1,270 @@
#ifndef TMARRANGEKERNEL_HPP
#define TMARRANGEKERNEL_HPP
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include "KernelUtils.hpp"
#include <boost/geometry/index/rtree.hpp>
#include <libslic3r/BoostAdapter.hpp>
namespace Slic3r { namespace arr2 {
// Summon the spatial indexing facilities from boost
namespace bgi = boost::geometry::index;
using SpatElement = std::pair<BoundingBox, unsigned>;
using SpatIndex = bgi::rtree<SpatElement, bgi::rstar<16, 4> >;
class TMArrangeKernel {
SpatIndex m_rtree; // spatial index for the normal (bigger) objects
SpatIndex m_smallsrtree; // spatial index for only the smaller items
BoundingBox m_pilebb;
double m_bin_area = NaNd;
double m_norm;
size_t m_rem_cnt = 0;
size_t m_item_cnt = 0;
struct ItemStats { double area = 0.; BoundingBox bb; };
std::vector<ItemStats> m_itemstats;
// A coefficient used in separating bigger items and smaller items.
static constexpr double BigItemTreshold = 0.02;
template<class T> ArithmeticOnly<T, double> norm(T val) const
{
return double(val) / m_norm;
}
// Treat big items (compared to the print bed) differently
bool is_big(double a) const { return a / m_bin_area > BigItemTreshold; }
protected:
std::optional<Point> sink;
std::optional<Point> item_sink;
Point active_sink;
const BoundingBox & pilebb() const { return m_pilebb; }
public:
TMArrangeKernel() = default;
TMArrangeKernel(Vec2crd gravity_center, size_t itm_cnt, double bedarea = NaNd)
: sink{gravity_center}
, m_bin_area(bedarea)
, m_item_cnt{itm_cnt}
{}
TMArrangeKernel(size_t itm_cnt, double bedarea = NaNd)
: m_bin_area(bedarea), m_item_cnt{itm_cnt}
{}
template<class ArrItem>
double placement_fitness(const ArrItem &item, const Vec2crd &transl) const
{
// Candidate item bounding box
auto ibb = envelope_bounding_box(item);
ibb.translate(transl);
auto itmcntr = envelope_centroid(item);
itmcntr += transl;
// Calculate the full bounding box of the pile with the candidate item
auto fullbb = m_pilebb;
fullbb.merge(ibb);
// The bounding box of the big items (they will accumulate in the center
// of the pile
BoundingBox bigbb;
if(m_rtree.empty()) {
bigbb = fullbb;
}
else {
auto boostbb = m_rtree.bounds();
boost::geometry::convert(boostbb, bigbb);
}
// Will hold the resulting score
double score = 0;
// Density is the pack density: how big is the arranged pile
double density = 0;
// Distinction of cases for the arrangement scene
enum e_cases {
// This branch is for big items in a mixed (big and small) scene
// OR for all items in a small-only scene.
BIG_ITEM,
// This branch is for the last big item in a mixed scene
LAST_BIG_ITEM,
// For small items in a mixed scene.
SMALL_ITEM,
WIPE_TOWER,
} compute_case;
bool is_wt = is_wipe_tower(item);
bool bigitems = is_big(envelope_area(item)) || m_rtree.empty();
if (is_wt)
compute_case = WIPE_TOWER;
else if (bigitems && m_rem_cnt > 0)
compute_case = BIG_ITEM;
else if (bigitems && m_rem_cnt == 0)
compute_case = LAST_BIG_ITEM;
else
compute_case = SMALL_ITEM;
switch (compute_case) {
case WIPE_TOWER: {
score = (unscaled(itmcntr) - unscaled(active_sink)).squaredNorm();
break;
}
case BIG_ITEM: {
const Point& minc = ibb.min; // bottom left corner
const Point& maxc = ibb.max; // top right corner
// top left and bottom right corners
Point top_left{minc.x(), maxc.y()};
Point bottom_right{maxc.x(), minc.y()};
// Now the distance of the gravity center will be calculated to the
// five anchor points and the smallest will be chosen.
std::array<double, 5> dists;
auto cc = fullbb.center(); // The gravity center
dists[0] = (minc - cc).cast<double>().norm();
dists[1] = (maxc - cc).cast<double>().norm();
dists[2] = (itmcntr - cc).template cast<double>().norm();
dists[3] = (top_left - cc).cast<double>().norm();
dists[4] = (bottom_right - cc).cast<double>().norm();
// The smalles distance from the arranged pile center:
double dist = norm(*(std::min_element(dists.begin(), dists.end())));
double bindist = norm((ibb.center() - active_sink).template cast<double>().norm());
dist = 0.8 * dist + 0.2 * bindist;
// Prepare a variable for the alignment score.
// This will indicate: how well is the candidate item
// aligned with 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;
auto query = bgi::intersects(ibb);
auto& index = is_big(envelope_area(item)) ? m_rtree : m_smallsrtree;
// Query the spatial index for the neighbors
std::vector<SpatElement> result;
result.reserve(index.size());
index.query(query, std::back_inserter(result));
// now get the score for the best alignment
for(auto& e : result) {
auto idx = e.second;
const ItemStats& p = m_itemstats[idx];
auto parea = p.area;
if(std::abs(1.0 - parea / fixed_area(item)) < 1e-6) {
auto bb = p.bb;
bb.merge(ibb);
auto bbarea = area(bb);
auto ascore = 1.0 - (fixed_area(item) + parea) / bbarea;
if(ascore < alignment_score)
alignment_score = ascore;
}
}
auto fullbbsz = fullbb.size();
density = std::sqrt(norm(fullbbsz.x()) * norm(fullbbsz.y()));
double R = double(m_rem_cnt) / (m_item_cnt);
// 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 neighbors
if (result.empty())
score = 0.50 * dist + 0.50 * density;
else
// Let the density matter more when fewer objects remain
score = 0.50 * dist + (1.0 - R) * 0.20 * density +
0.30 * alignment_score;
break;
}
case LAST_BIG_ITEM: {
score = norm((itmcntr - m_pilebb.center()).template cast<double>().norm());
break;
}
case SMALL_ITEM: {
// 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 = norm((itmcntr - bigbb.center()).template cast<double>().norm());
break;
}
}
return -score;
}
template<class ArrItem, class Bed, class Context, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Context &packing_context,
const Range<RemIt> &remaining_items)
{
item_sink = get_gravity_sink(itm);
if (!sink) {
sink = bounding_box(bed).center();
}
if (item_sink)
active_sink = *item_sink;
else
active_sink = *sink;
auto fixed = all_items_range(packing_context);
bool ret = find_initial_position(itm, active_sink, bed, packing_context);
m_rem_cnt = remaining_items.size();
if (m_item_cnt == 0)
m_item_cnt = m_rem_cnt + fixed.size() + 1;
if (std::isnan(m_bin_area))
m_bin_area = area(bed);
m_norm = std::sqrt(m_bin_area);
m_itemstats.clear();
m_itemstats.reserve(fixed.size());
m_rtree.clear();
m_smallsrtree.clear();
m_pilebb = {};
unsigned idx = 0;
for (auto &fixitem : fixed) {
auto fixitmbb = fixed_bounding_box(fixitem);
m_itemstats.emplace_back(ItemStats{fixed_area(fixitem), fixitmbb});
m_pilebb.merge(fixitmbb);
if(is_big(fixed_area(fixitem)))
m_rtree.insert({fixitmbb, idx});
m_smallsrtree.insert({fixitmbb, idx});
idx++;
}
return ret;
}
template<class ArrItem>
bool on_item_packed(ArrItem &itm) { return true; }
};
}} // namespace Slic3r::arr2
#endif // TMARRANGEKERNEL_HPP

View File

@ -0,0 +1,418 @@
#ifndef NFP_CPP
#define NFP_CPP
#include "NFP.hpp"
#include "CircularEdgeIterator.hpp"
#include "NFPConcave_Tesselate.hpp"
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
namespace Slic3r { using LargeInt = __int128; }
#else
#include <boost/multiprecision/integer.hpp>
namespace Slic3r { using LargeInt = boost::multiprecision::int128_t; }
#endif
#include <boost/rational.hpp>
namespace Slic3r {
static bool line_cmp(const Line& e1, const Line& e2)
{
using Ratio = boost::rational<LargeInt>;
const Vec<2, int64_t> ax(1, 0); // Unit vector for the X axis
Vec<2, int64_t> p1 = (e1.b - e1.a).cast<int64_t>();
Vec<2, int64_t> p2 = (e2.b - e2.a).cast<int64_t>();
// Quadrant mapping array. The quadrant of a vector can be determined
// from the dot product of the vector and its perpendicular pair
// with the unit vector X axis. The products will carry the values
// lcos = dot(p, ax) = l * cos(phi) and
// lsin = -dotperp(p, ax) = l * sin(phi) where
// l is the length of vector p. From the signs of these values we can
// construct an index which has the sign of lcos as MSB and the
// sign of lsin as LSB. This index can be used to retrieve the actual
// quadrant where vector p resides using the following map:
// (+ is 0, - is 1)
// cos | sin | decimal | quadrant
// + | + | 0 | 0
// + | - | 1 | 3
// - | + | 2 | 1
// - | - | 3 | 2
std::array<int, 4> quadrants {0, 3, 1, 2 };
std::array<int, 2> q {0, 0}; // Quadrant indices for p1 and p2
using TDots = std::array<int64_t, 2>;
TDots lcos { p1.dot(ax), p2.dot(ax) };
TDots lsin { -dotperp(p1, ax), -dotperp(p2, ax) };
// Construct the quadrant indices for p1 and p2
for(size_t i = 0; i < 2; ++i) {
if (lcos[i] == 0)
q[i] = lsin[i] > 0 ? 1 : 3;
else if (lsin[i] == 0)
q[i] = lcos[i] > 0 ? 0 : 2;
else
q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)];
}
if (q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant
auto lsq1 = p1.squaredNorm(); // squared magnitudes, avoid sqrt
auto lsq2 = p2.squaredNorm(); // squared magnitudes, avoid sqrt
// We will actually compare l^2 * cos^2(phi) which saturates the
// cos function. But with the quadrant info we can get the sign back
int sign = q[0] == 1 || q[0] == 2 ? -1 : 1;
// If Ratio is an actual rational type, there is no precision loss
auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0];
auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1];
return q[0] < 2 ? pcos1 > pcos2 : pcos1 < pcos2;
}
// If in different quadrants, compare the quadrant indices only.
return q[0] < q[1];
}
static inline bool vsort(const Vec2crd& v1, const Vec2crd& v2)
{
return v1.y() == v2.y() ? v1.x() < v2.x() : v1.y() < v2.y();
}
ExPolygons ifp_convex(const arr2::RectangleBed &obed, const Polygon &convexpoly)
{
ExPolygon ret;
auto sbox = bounding_box(convexpoly);
auto sboxsize = sbox.size();
coord_t sheight = sboxsize.y();
coord_t swidth = sboxsize.x();
Point sliding_top = reference_vertex(convexpoly);
auto leftOffset = sliding_top.x() - sbox.min.x();
auto rightOffset = sliding_top.x() - sbox.max.x();
coord_t topOffset = 0;
auto bottomOffset = sheight;
auto bedbb = obed.bb;
// bedbb.offset(1);
auto bedsz = bedbb.size();
auto boxWidth = bedsz.x();
auto boxHeight = bedsz.y();
auto bedMinx = bedbb.min.x();
auto bedMiny = bedbb.min.y();
auto bedMaxx = bedbb.max.x();
auto bedMaxy = bedbb.max.y();
Polygon innerNfp{ Point{bedMinx + leftOffset, bedMaxy + topOffset},
Point{bedMaxx + rightOffset, bedMaxy + topOffset},
Point{bedMaxx + rightOffset, bedMiny + bottomOffset},
Point{bedMinx + leftOffset, bedMiny + bottomOffset},
Point{bedMinx + leftOffset, bedMaxy + topOffset} };
if (sheight <= boxHeight && swidth <= boxWidth)
ret.contour = std::move(innerNfp);
return {ret};
}
Polygon ifp_convex_convex(const Polygon &fixed, const Polygon &movable)
{
auto subnfps = reserve_polygons(fixed.size());
// For each edge of the bed polygon, determine the nfp of convexpoly and
// the zero area polygon formed by the edge. The union of all these sub-nfps
// will contain a hole that is the actual ifp.
auto lrange = line_range(fixed);
for (const Line &l : lrange) { // Older mac compilers generate warnging if line_range is called in-place
Polygon fixed = {l.a, l.b};
subnfps.emplace_back(nfp_convex_convex_legacy(fixed, movable));
}
// Do the union and then keep only the holes (should be only one or zero, if
// the convexpoly cannot fit into the bed)
Polygons ifp = union_(subnfps);
Polygon ret;
// find the first hole
auto it = std::find_if(ifp.begin(), ifp.end(), [](const Polygon &subifp){
return subifp.is_clockwise();
});
if (it != ifp.end()) {
ret = std::move(*it);
std::reverse(ret.begin(), ret.end());
}
return ret;
}
ExPolygons ifp_convex(const arr2::CircleBed &bed, const Polygon &convexpoly)
{
Polygon circle = approximate_circle_with_polygon(bed);
return {ExPolygon{ifp_convex_convex(circle, convexpoly)}};
}
ExPolygons ifp_convex(const arr2::IrregularBed &bed, const Polygon &convexpoly)
{
auto bb = get_extents(bed.poly);
bb.offset(scaled(1.));
Polygon rect = arr2::to_rectangle(bb);
ExPolygons blueprint = diff_ex(rect, bed.poly);
Polygons ifp;
for (const ExPolygon &part : blueprint) {
Polygons triangles = Slic3r::convex_decomposition_tess(part);
for (const Polygon &tr : triangles) {
Polygon subifp = nfp_convex_convex_legacy(tr, convexpoly);
ifp.emplace_back(std::move(subifp));
}
}
ifp = union_(ifp);
Polygons ret;
std::copy_if(ifp.begin(), ifp.end(), std::back_inserter(ret),
[](const Polygon &p) { return p.is_clockwise(); });
for (Polygon &p : ret)
std::reverse(p.begin(), p.end());
return to_expolygons(ret);
}
Vec2crd reference_vertex(const Polygon &poly)
{
Vec2crd ret{std::numeric_limits<coord_t>::min(),
std::numeric_limits<coord_t>::min()};
auto it = std::max_element(poly.points.begin(), poly.points.end(), vsort);
if (it != poly.points.end())
ret = std::max(ret, static_cast<const Vec2crd &>(*it), vsort);
return ret;
}
Vec2crd reference_vertex(const ExPolygon &expoly)
{
return reference_vertex(expoly.contour);
}
Vec2crd reference_vertex(const Polygons &outline)
{
Vec2crd ret{std::numeric_limits<coord_t>::min(),
std::numeric_limits<coord_t>::min()};
for (const Polygon &poly : outline)
ret = std::max(ret, reference_vertex(poly), vsort);
return ret;
}
Vec2crd reference_vertex(const ExPolygons &outline)
{
Vec2crd ret{std::numeric_limits<coord_t>::min(),
std::numeric_limits<coord_t>::min()};
for (const ExPolygon &expoly : outline)
ret = std::max(ret, reference_vertex(expoly), vsort);
return ret;
}
Vec2crd min_vertex(const Polygon &poly)
{
Vec2crd ret{std::numeric_limits<coord_t>::max(),
std::numeric_limits<coord_t>::max()};
auto it = std::min_element(poly.points.begin(), poly.points.end(), vsort);
if (it != poly.points.end())
ret = std::min(ret, static_cast<const Vec2crd&>(*it), vsort);
return ret;
}
// Find the vertex corresponding to the edge with minimum angle to X axis.
// Only usable with CircularEdgeIterator<> template.
template<class It> It find_min_anglex_edge(It from)
{
bool found = false;
auto it = from;
while (!found ) {
found = !line_cmp(*it, *std::next(it));
++it;
}
return it;
}
// Only usable if both fixed and movable polygon is convex. In that case,
// their edges are already sorted by angle to X axis, only the starting
// (lowest X axis) edge needs to be found first.
void nfp_convex_convex(const Polygon &fixed, const Polygon &movable, Polygon &poly)
{
if (fixed.empty() || movable.empty())
return;
// Clear poly and adjust its capacity. Nothing happens if poly is
// already sufficiently large and and empty.
poly.clear();
poly.points.reserve(fixed.size() + movable.size());
// Find starting positions on the fixed and moving polygons
auto it_fx = find_min_anglex_edge(CircularEdgeIterator{fixed});
auto it_mv = find_min_anglex_edge(CircularReverseEdgeIterator{movable});
// End positions are at the same vertex after completing one full circle
auto end_fx = it_fx + fixed.size();
auto end_mv = it_mv + movable.size();
// Pos zero is just fine as starting point:
poly.points.emplace_back(0, 0);
// Output iterator adapter for std::merge
struct OutItAdaptor {
using value_type [[maybe_unused]] = Line;
using difference_type [[maybe_unused]] = std::ptrdiff_t;
using pointer [[maybe_unused]] = Line*;
using reference [[maybe_unused]] = Line& ;
using iterator_category [[maybe_unused]] = std::output_iterator_tag;
Polygon *outpoly;
OutItAdaptor(Polygon &out) : outpoly{&out} {}
OutItAdaptor &operator *() { return *this; }
void operator=(const Line &l)
{
// Yielding l.b, offsetted so that l.a touches the last vertex in
// in outpoly
outpoly->points.emplace_back(l.b + outpoly->back() - l.a);
}
OutItAdaptor& operator++() { return *this; };
};
// Use std algo to merge the edges from the two polygons
std::merge(it_fx, end_fx, it_mv, end_mv, OutItAdaptor{poly}, line_cmp);
}
Polygon nfp_convex_convex(const Polygon &fixed, const Polygon &movable)
{
Polygon ret;
nfp_convex_convex(fixed, movable, ret);
return ret;
}
static void buildPolygon(const std::vector<Line>& edgelist,
Polygon& rpoly,
Point& top_nfp)
{
auto& rsh = rpoly.points;
rsh.reserve(2 * edgelist.size());
// Add the two vertices from the first edge into the final polygon.
rsh.emplace_back(edgelist.front().a);
rsh.emplace_back(edgelist.front().b);
// Sorting function for the nfp reference vertex search
// the reference (rightmost top) vertex so far
top_nfp = *std::max_element(std::cbegin(rsh), std::cend(rsh), vsort);
auto tmp = std::next(std::begin(rsh));
// Construct final nfp by placing each edge to the end of the previous
for(auto eit = std::next(edgelist.begin()); eit != edgelist.end(); ++eit) {
auto d = *tmp - eit->a;
Vec2crd p = eit->b + d;
rsh.emplace_back(p);
// Set the new reference vertex
if (vsort(top_nfp, p))
top_nfp = p;
tmp = std::next(tmp);
}
}
Polygon nfp_convex_convex_legacy(const Polygon &fixed, const Polygon &movable)
{
assert (!fixed.empty());
assert (!movable.empty());
Polygon rsh; // Final nfp placeholder
Point max_nfp;
std::vector<Line> edgelist;
auto cap = fixed.points.size() + movable.points.size();
// Reserve the needed memory
edgelist.reserve(cap);
rsh.points.reserve(cap);
auto add_edge = [&edgelist](const Point &v1, const Point &v2) {
Line e{v1, v2};
if ((e.b - e.a).cast<int64_t>().squaredNorm() > 0)
edgelist.emplace_back(e);
};
Point max_fixed = fixed.points.front();
{ // place all edges from fixed into edgelist
auto first = std::cbegin(fixed);
auto next = std::next(first);
while(next != std::cend(fixed)) {
add_edge(*(first), *(next));
max_fixed = std::max(max_fixed, *first, vsort);
++first; ++next;
}
add_edge(*std::crbegin(fixed), *std::cbegin(fixed));
max_fixed = std::max(max_fixed, *std::crbegin(fixed), vsort);
}
Point max_movable = movable.points.front();
Point min_movable = movable.points.front();
{ // place all edges from movable into edgelist
auto first = std::cbegin(movable);
auto next = std::next(first);
while(next != std::cend(movable)) {
add_edge(*(next), *(first));
min_movable = std::min(min_movable, *first, vsort);
max_movable = std::max(max_movable, *first, vsort);
++first; ++next;
}
add_edge(*std::cbegin(movable), *std::crbegin(movable));
min_movable = std::min(min_movable, *std::crbegin(movable), vsort);
max_movable = std::max(max_movable, *std::crbegin(movable), vsort);
}
std::sort(edgelist.begin(), edgelist.end(), line_cmp);
buildPolygon(edgelist, rsh, max_nfp);
auto dtouch = max_fixed - min_movable;
auto top_other = max_movable + dtouch;
auto dnfp = top_other - max_nfp;
rsh.translate(dnfp);
return rsh;
}
} // namespace Slic3r
#endif // NFP_CPP

View File

@ -0,0 +1,50 @@
#ifndef NFP_HPP
#define NFP_HPP
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/Arrange/Core/Beds.hpp>
namespace Slic3r {
template<class Unit = int64_t, class T>
Unit dotperp(const Vec<2, T> &a, const Vec<2, T> &b)
{
return Unit(a.x()) * Unit(b.y()) - Unit(a.y()) * Unit(b.x());
}
// Convex-Convex nfp in linear time (fixed.size() + movable.size()),
// no memory allocations (if out param is used).
// FIXME: Currently broken for very sharp triangles.
Polygon nfp_convex_convex(const Polygon &fixed, const Polygon &movable);
void nfp_convex_convex(const Polygon &fixed, const Polygon &movable, Polygon &out);
Polygon nfp_convex_convex_legacy(const Polygon &fixed, const Polygon &movable);
Polygon ifp_convex_convex(const Polygon &fixed, const Polygon &movable);
ExPolygons ifp_convex(const arr2::RectangleBed &bed, const Polygon &convexpoly);
ExPolygons ifp_convex(const arr2::CircleBed &bed, const Polygon &convexpoly);
ExPolygons ifp_convex(const arr2::IrregularBed &bed, const Polygon &convexpoly);
inline ExPolygons ifp_convex(const arr2::InfiniteBed &bed, const Polygon &convexpoly)
{
return {};
}
inline ExPolygons ifp_convex(const arr2::ArrangeBed &bed, const Polygon &convexpoly)
{
ExPolygons ret;
auto visitor = [&ret, &convexpoly](const auto &b) { ret = ifp_convex(b, convexpoly); };
boost::apply_visitor(visitor, bed);
return ret;
}
Vec2crd reference_vertex(const Polygon &outline);
Vec2crd reference_vertex(const ExPolygon &outline);
Vec2crd reference_vertex(const Polygons &outline);
Vec2crd reference_vertex(const ExPolygons &outline);
Vec2crd min_vertex(const Polygon &outline);
} // namespace Slic3r
#endif // NFP_HPP

View File

@ -0,0 +1,196 @@
#ifndef NFPARRANGEITEMTRAITS_HPP
#define NFPARRANGEITEMTRAITS_HPP
#include <numeric>
#include "libslic3r/Arrange/Core/ArrangeBase.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/BoundingBox.hpp"
namespace Slic3r { namespace arr2 {
// Additional methods that an ArrangeItem object has to implement in order
// to be usable with PackStrategyNFP.
template<class ArrItem, class En = void> struct NFPArrangeItemTraits_
{
template<class Context, class Bed, class StopCond = DefaultStopCondition>
static ExPolygons calculate_nfp(const ArrItem &item,
const Context &packing_context,
const Bed &bed,
StopCond stop_condition = {})
{
static_assert(always_false<ArrItem>::value,
"NFP unimplemented for this item type.");
return {};
}
static Vec2crd reference_vertex(const ArrItem &item)
{
return item.reference_vertex();
}
static BoundingBox envelope_bounding_box(const ArrItem &itm)
{
return itm.envelope_bounding_box();
}
static BoundingBox fixed_bounding_box(const ArrItem &itm)
{
return itm.fixed_bounding_box();
}
static const Polygons & envelope_outline(const ArrItem &itm)
{
return itm.envelope_outline();
}
static const Polygons & fixed_outline(const ArrItem &itm)
{
return itm.fixed_outline();
}
static const Polygon & envelope_convex_hull(const ArrItem &itm)
{
return itm.envelope_convex_hull();
}
static const Polygon & fixed_convex_hull(const ArrItem &itm)
{
return itm.fixed_convex_hull();
}
static double envelope_area(const ArrItem &itm)
{
return itm.envelope_area();
}
static double fixed_area(const ArrItem &itm)
{
return itm.fixed_area();
}
static auto allowed_rotations(const ArrItem &)
{
return std::array{0.};
}
static Vec2crd fixed_centroid(const ArrItem &itm)
{
return fixed_bounding_box(itm).center();
}
static Vec2crd envelope_centroid(const ArrItem &itm)
{
return envelope_bounding_box(itm).center();
}
};
template<class T>
using NFPArrangeItemTraits = NFPArrangeItemTraits_<StripCVRef<T>>;
template<class ArrItem,
class Context,
class Bed,
class StopCond = DefaultStopCondition>
ExPolygons calculate_nfp(const ArrItem &itm,
const Context &context,
const Bed &bed,
StopCond stopcond = {})
{
return NFPArrangeItemTraits<ArrItem>::calculate_nfp(itm, context, bed,
std::move(stopcond));
}
template<class ArrItem> Vec2crd reference_vertex(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::reference_vertex(itm);
}
template<class ArrItem> BoundingBox envelope_bounding_box(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::envelope_bounding_box(itm);
}
template<class ArrItem> BoundingBox fixed_bounding_box(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::fixed_bounding_box(itm);
}
template<class ArrItem> decltype(auto) envelope_convex_hull(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::envelope_convex_hull(itm);
}
template<class ArrItem> decltype(auto) fixed_convex_hull(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::fixed_convex_hull(itm);
}
template<class ArrItem> decltype(auto) envelope_outline(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::envelope_outline(itm);
}
template<class ArrItem> decltype(auto) fixed_outline(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::fixed_outline(itm);
}
template<class ArrItem> double envelope_area(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::envelope_area(itm);
}
template<class ArrItem> double fixed_area(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::fixed_area(itm);
}
template<class ArrItem> Vec2crd fixed_centroid(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::fixed_centroid(itm);
}
template<class ArrItem> Vec2crd envelope_centroid(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::envelope_centroid(itm);
}
template<class ArrItem>
auto allowed_rotations(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::allowed_rotations(itm);
}
template<class It>
BoundingBox bounding_box(const Range<It> &itms) noexcept
{
auto pilebb =
std::accumulate(itms.begin(), itms.end(), BoundingBox{},
[](BoundingBox bb, const auto &itm) {
bb.merge(fixed_bounding_box(itm));
return bb;
});
return pilebb;
}
template<class It>
BoundingBox bounding_box_on_bedidx(const Range<It> &itms, int bed_index) noexcept
{
auto pilebb =
std::accumulate(itms.begin(), itms.end(), BoundingBox{},
[bed_index](BoundingBox bb, const auto &itm) {
if (bed_index == get_bed_index(itm))
bb.merge(fixed_bounding_box(itm));
return bb;
});
return pilebb;
}
}} // namespace Slic3r::arr2
#endif // ARRANGEITEMTRAITSNFP_HPP

View File

@ -0,0 +1,111 @@
#include "NFP.hpp"
#include "NFPConcave_CGAL.hpp"
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/partition_2.h>
#include <CGAL/Partition_traits_2.h>
#include <CGAL/property_map.h>
#include <CGAL/Polygon_vertical_decomposition_2.h>
#include "libslic3r/ClipperUtils.hpp"
namespace Slic3r {
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using Partition_traits_2 = CGAL::Partition_traits_2<K, CGAL::Pointer_property_map<K::Point_2>::type >;
using Point_2 = Partition_traits_2::Point_2;
using Polygon_2 = Partition_traits_2::Polygon_2; // a polygon of indices
ExPolygons nfp_concave_concave_cgal(const ExPolygon &fixed, const ExPolygon &movable)
{
Polygons fixed_decomp = convex_decomposition_cgal(fixed);
Polygons movable_decomp = convex_decomposition_cgal(movable);
auto refs_mv = reserve_vector<Vec2crd>(movable_decomp.size());
for (const Polygon &p : movable_decomp)
refs_mv.emplace_back(reference_vertex(p));
auto nfps = reserve_polygons(fixed_decomp.size() *movable_decomp.size());
Vec2crd ref_whole = reference_vertex(movable);
for (const Polygon &fixed_part : fixed_decomp) {
size_t mvi = 0;
for (const Polygon &movable_part : movable_decomp) {
Polygon subnfp = nfp_convex_convex(fixed_part, movable_part);
const Vec2crd &ref_mp = refs_mv[mvi];
auto d = ref_whole - ref_mp;
subnfp.translate(d);
nfps.emplace_back(subnfp);
mvi++;
}
}
return union_ex(nfps);
}
// TODO: holes
Polygons convex_decomposition_cgal(const ExPolygon &expoly)
{
CGAL::Polygon_vertical_decomposition_2<K> decomp;
CGAL::Polygon_2<K> contour;
for (auto &p : expoly.contour.points)
contour.push_back({unscaled(p.x()), unscaled(p.y())});
CGAL::Polygon_with_holes_2<K> cgalpoly{contour};
for (const Polygon &h : expoly.holes) {
CGAL::Polygon_2<K> hole;
for (auto &p : h.points)
hole.push_back({unscaled(p.x()), unscaled(p.y())});
cgalpoly.add_hole(hole);
}
std::vector<CGAL::Polygon_2<K>> out;
decomp(cgalpoly, std::back_inserter(out));
Polygons ret;
for (auto &pwh : out) {
Polygon poly;
for (auto &p : pwh)
poly.points.emplace_back(scaled(p.x()), scaled(p.y()));
ret.emplace_back(std::move(poly));
}
return ret; //convex_decomposition_cgal(expoly.contour);
}
Polygons convex_decomposition_cgal(const Polygon &poly)
{
auto pts = reserve_vector<K::Point_2>(poly.size());
for (const Point &p : poly.points)
pts.emplace_back(unscaled(p.x()), unscaled(p.y()));
Partition_traits_2 traits(CGAL::make_property_map(pts));
Polygon_2 polyidx;
for (size_t i = 0; i < pts.size(); ++i)
polyidx.push_back(i);
std::vector<Polygon_2> outp;
CGAL::optimal_convex_partition_2(polyidx.vertices_begin(),
polyidx.vertices_end(),
std::back_inserter(outp),
traits);
Polygons ret;
for (const Polygon_2& poly : outp){
Polygon r;
for(Point_2 p : poly.container())
r.points.emplace_back(scaled(pts[p].x()), scaled(pts[p].y()));
ret.emplace_back(std::move(r));
}
return ret;
}
} // namespace Slic3r

View File

@ -0,0 +1,14 @@
#ifndef NFPCONCAVE_CGAL_HPP
#define NFPCONCAVE_CGAL_HPP
#include <libslic3r/ExPolygon.hpp>
namespace Slic3r {
Polygons convex_decomposition_cgal(const Polygon &expoly);
Polygons convex_decomposition_cgal(const ExPolygon &expoly);
ExPolygons nfp_concave_concave_cgal(const ExPolygon &fixed, const ExPolygon &movable);
} // namespace Slic3r
#endif // NFPCONCAVE_CGAL_HPP

View File

@ -0,0 +1,70 @@
#include "NFPConcave_Tesselate.hpp"
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Tesselate.hpp>
#include "NFP.hpp"
namespace Slic3r {
Polygons convex_decomposition_tess(const Polygon &expoly)
{
return convex_decomposition_tess(ExPolygon{expoly});
}
Polygons convex_decomposition_tess(const ExPolygon &expoly)
{
std::vector<Vec2d> tr = Slic3r::triangulate_expolygon_2d(expoly);
auto ret = Slic3r::reserve_polygons(tr.size() / 3);
for (size_t i = 0; i < tr.size(); i += 3) {
ret.emplace_back(
Polygon{scaled(tr[i]), scaled(tr[i + 1]), scaled(tr[i + 2])});
}
return ret;
}
Polygons convex_decomposition_tess(const ExPolygons &expolys)
{
constexpr size_t AvgTriangleCountGuess = 50;
auto ret = reserve_polygons(AvgTriangleCountGuess * expolys.size());
for (const ExPolygon &expoly : expolys) {
Polygons convparts = convex_decomposition_tess(expoly);
std::move(convparts.begin(), convparts.end(), std::back_inserter(ret));
}
return ret;
}
ExPolygons nfp_concave_concave_tess(const ExPolygon &fixed,
const ExPolygon &movable)
{
Polygons fixed_decomp = convex_decomposition_tess(fixed);
Polygons movable_decomp = convex_decomposition_tess(movable);
auto refs_mv = reserve_vector<Vec2crd>(movable_decomp.size());
for (const Polygon &p : movable_decomp)
refs_mv.emplace_back(reference_vertex(p));
auto nfps = reserve_polygons(fixed_decomp.size() * movable_decomp.size());
Vec2crd ref_whole = reference_vertex(movable);
for (const Polygon &fixed_part : fixed_decomp) {
size_t mvi = 0;
for (const Polygon &movable_part : movable_decomp) {
Polygon subnfp = nfp_convex_convex(fixed_part, movable_part);
const Vec2crd &ref_mp = refs_mv[mvi];
auto d = ref_whole - ref_mp;
subnfp.translate(d);
nfps.emplace_back(subnfp);
mvi++;
}
}
return union_ex(nfps);
}
} // namespace Slic3r

View File

@ -0,0 +1,15 @@
#ifndef NFPCONCAVE_TESSELATE_HPP
#define NFPCONCAVE_TESSELATE_HPP
#include <libslic3r/ExPolygon.hpp>
namespace Slic3r {
Polygons convex_decomposition_tess(const Polygon &expoly);
Polygons convex_decomposition_tess(const ExPolygon &expoly);
Polygons convex_decomposition_tess(const ExPolygons &expolys);
ExPolygons nfp_concave_concave_tess(const ExPolygon &fixed, const ExPolygon &movable);
} // namespace Slic3r
#endif // NFPCONCAVE_TESSELATE_HPP

View File

@ -0,0 +1,285 @@
#ifndef PACKSTRATEGYNFP_HPP
#define PACKSTRATEGYNFP_HPP
#include "libslic3r/Arrange/Core/ArrangeBase.hpp"
#include "EdgeCache.hpp"
#include "Kernels/KernelTraits.hpp"
#include "NFPArrangeItemTraits.hpp"
#include "libslic3r/Optimize/NLoptOptimizer.hpp"
#include "libslic3r/Execution/ExecutionSeq.hpp"
namespace Slic3r { namespace arr2 {
struct NFPPackingTag{};
struct DummyArrangeKernel
{
template<class ArrItem>
double placement_fitness(const ArrItem &itm, const Vec2crd &dest_pos) const
{
return NaNd;
}
template<class ArrItem, class Bed, class Context, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Context &packing_context,
const Range<RemIt> &remaining_items)
{
return true;
}
template<class ArrItem> bool on_item_packed(ArrItem &itm) { return true; }
};
template<class Strategy> using OptAlg = typename Strategy::OptAlg;
template<class ArrangeKernel = DummyArrangeKernel,
class ExecPolicy = ExecutionSeq,
class OptMethod = opt::AlgNLoptSubplex,
class StopCond = DefaultStopCondition>
struct PackStrategyNFP {
using OptAlg = OptMethod;
ArrangeKernel kernel;
ExecPolicy ep;
double accuracy = 1.;
opt::Optimizer<OptMethod> solver;
StopCond stop_condition;
PackStrategyNFP(opt::Optimizer<OptMethod> slv,
ArrangeKernel k = {},
ExecPolicy execpolicy = {},
double accur = 1.,
StopCond stop_cond = {})
: kernel{std::move(k)},
ep{std::move(execpolicy)},
accuracy{accur},
solver{std::move(slv)},
stop_condition{std::move(stop_cond)}
{}
PackStrategyNFP(ArrangeKernel k = {},
ExecPolicy execpolicy = {},
double accur = 1.,
StopCond stop_cond = {})
: PackStrategyNFP{opt::Optimizer<OptMethod>{}, std::move(k),
std::move(execpolicy), accur, std::move(stop_cond)}
{
// Defaults for AlgNLoptSubplex
auto iters = static_cast<unsigned>(std::floor(1000 * accuracy));
auto optparams =
opt::StopCriteria{}.max_iterations(iters).rel_score_diff(
1e-20) /*.abs_score_diff(1e-20)*/;
solver.set_criteria(optparams);
}
};
template<class...Args>
struct PackStrategyTag_<PackStrategyNFP<Args...>>
{
using Tag = NFPPackingTag;
};
template<class ArrItem, class Bed, class PStrategy>
double pick_best_spot_on_nfp_verts_only(ArrItem &item,
const ExPolygons &nfp,
const Bed &bed,
const PStrategy &strategy)
{
using KernelT = KernelTraits<decltype(strategy.kernel)>;
auto score = -std::numeric_limits<double>::infinity();
Vec2crd orig_tr = get_translation(item);
Vec2crd translation{0, 0};
auto eval_fitness = [&score, &strategy, &item, &translation,
&orig_tr](const Vec2crd &p) {
set_translation(item, orig_tr);
Vec2crd ref_v = reference_vertex(item);
Vec2crd tr = p - ref_v;
double fitness = KernelT::placement_fitness(strategy.kernel, item, tr);
if (fitness > score) {
score = fitness;
translation = tr;
}
};
for (const ExPolygon &expoly : nfp) {
for (const Point &p : expoly.contour) {
eval_fitness(p);
}
for (const Polygon &h : expoly.holes)
for (const Point &p : h.points)
eval_fitness(p);
}
set_translation(item, orig_tr + translation);
return score;
}
struct CornerResult
{
size_t contour_id;
opt::Result<1> oresult;
};
template<class ArrItem, class Bed, class... Args>
double pick_best_spot_on_nfp(ArrItem &item,
const ExPolygons &nfp,
const Bed &bed,
const PackStrategyNFP<Args...> &strategy)
{
auto &ex_policy = strategy.ep;
using KernelT = KernelTraits<decltype(strategy.kernel)>;
auto score = -std::numeric_limits<double>::infinity();
Vec2crd orig_tr = get_translation(item);
Vec2crd translation{0, 0};
Vec2crd ref_v = reference_vertex(item);
auto edge_caches = reserve_vector<EdgeCache>(nfp.size());
auto sample_sets = reserve_vector<std::vector<ContourLocation>>(
nfp.size());
for (const ExPolygon &expoly : nfp) {
edge_caches.emplace_back(EdgeCache{&expoly});
edge_caches.back().sample_contour(strategy.accuracy,
sample_sets.emplace_back());
}
auto nthreads = execution::max_concurrency(ex_policy);
std::vector<CornerResult> gresults(edge_caches.size());
auto resultcmp = [](auto &a, auto &b) {
return a.oresult.score < b.oresult.score;
};
execution::for_each(
ex_policy, size_t(0), edge_caches.size(),
[&](size_t edge_cache_idx) {
auto &ec_contour = edge_caches[edge_cache_idx];
auto &corners = sample_sets[edge_cache_idx];
std::vector<CornerResult> results(corners.size());
auto cornerfn = [&](size_t i) {
ContourLocation cr = corners[i];
auto objfn = [&](opt::Input<1> &in) {
Vec2crd p = ec_contour.coords(ContourLocation{cr.contour_id, in[0]});
Vec2crd tr = p - ref_v;
return KernelT::placement_fitness(strategy.kernel, item, tr);
};
// Assuming that solver is a lightweight object
auto solver = strategy.solver;
solver.to_max();
auto oresult = solver.optimize(objfn,
opt::initvals({cr.dist}),
opt::bounds({{0., 1.}}));
results[i] = CornerResult{cr.contour_id, oresult};
};
execution::for_each(ex_policy, size_t(0), results.size(),
cornerfn, nthreads);
auto it = std::max_element(results.begin(), results.end(),
resultcmp);
if (it != results.end())
gresults[edge_cache_idx] = *it;
},
nthreads);
auto it = std::max_element(gresults.begin(), gresults.end(), resultcmp);
if (it != gresults.end()) {
score = it->oresult.score;
size_t path_id = std::distance(gresults.begin(), it);
size_t contour_id = it->contour_id;
double dist = it->oresult.optimum[0];
Vec2crd pos = edge_caches[path_id].coords(ContourLocation{contour_id, dist});
Vec2crd tr = pos - ref_v;
set_translation(item, orig_tr + tr);
}
return score;
}
template<class Strategy, class ArrItem, class Bed, class RemIt>
bool pack(Strategy &strategy,
const Bed &bed,
ArrItem &item,
const PackStrategyContext<Strategy, ArrItem> &packing_context,
const Range<RemIt> &remaining_items,
const NFPPackingTag &)
{
using KernelT = KernelTraits<decltype(strategy.kernel)>;
// The kernel might pack the item immediately
bool packed = KernelT::on_start_packing(strategy.kernel, item, bed,
packing_context, remaining_items);
double orig_rot = get_rotation(item);
double final_rot = 0.;
double final_score = -std::numeric_limits<double>::infinity();
Vec2crd orig_tr = get_translation(item);
Vec2crd final_tr = orig_tr;
bool cancelled = strategy.stop_condition();
const auto & rotations = allowed_rotations(item);
// Check all rotations but only if item is not already packed
for (auto rot_it = rotations.begin();
!cancelled && !packed && rot_it != rotations.end(); ++rot_it) {
double rot = *rot_it;
set_rotation(item, orig_rot + rot);
set_translation(item, orig_tr);
auto nfp = calculate_nfp(item, packing_context, bed,
strategy.stop_condition);
double score = NaNd;
if (!nfp.empty()) {
score = pick_best_spot_on_nfp(item, nfp, bed, strategy);
cancelled = strategy.stop_condition();
if (score > final_score) {
final_score = score;
final_rot = rot;
final_tr = get_translation(item);
}
}
}
// If the score is not valid, and the item is not already packed, or
// the packing was cancelled asynchronously by stop condition, then
// discard the packing
bool is_score_valid = !std::isnan(final_score) && !std::isinf(final_score);
packed = !cancelled && (packed || is_score_valid);
if (packed) {
set_translation(item, final_tr);
set_rotation(item, orig_rot + final_rot);
// Finally, consult the kernel if the packing is sane
packed = KernelT::on_item_packed(strategy.kernel, item);
}
return packed;
}
}} // namespace Slic3r::arr2
#endif // PACKSTRATEGYNFP_HPP

View File

@ -0,0 +1,141 @@
#ifndef RECTANGLEOVERFITPACKINGSTRATEGY_HPP
#define RECTANGLEOVERFITPACKINGSTRATEGY_HPP
#include "Kernels/RectangleOverfitKernelWrapper.hpp"
#include "libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
namespace Slic3r { namespace arr2 {
using PostAlignmentFn = std::function<Vec2crd(const BoundingBox &bedbb,
const BoundingBox &pilebb)>;
struct CenterAlignmentFn {
Vec2crd operator() (const BoundingBox &bedbb,
const BoundingBox &pilebb)
{
return bedbb.center() - pilebb.center();
}
};
template<class ArrItem>
struct RectangleOverfitPackingContext : public DefaultPackingContext<ArrItem>
{
BoundingBox limits;
int bed_index;
PostAlignmentFn post_alignment_fn;
explicit RectangleOverfitPackingContext(const BoundingBox limits,
int bedidx,
PostAlignmentFn alignfn = CenterAlignmentFn{})
: limits{limits}, bed_index{bedidx}, post_alignment_fn{alignfn}
{}
void align_pile()
{
// Here, the post alignment can be safely done. No throwing
// functions are called!
if (fixed_items_range(*this).empty()) {
auto itms = packed_items_range(*this);
auto pilebb = bounding_box(itms);
for (auto &itm : itms) {
translate(itm, post_alignment_fn(limits, pilebb));
}
}
}
~RectangleOverfitPackingContext() { align_pile(); }
};
// With rectange bed, and no fixed items, an infinite bed with
// RectangleOverfitKernelWrapper can produce better results than a pure
// RectangleBed with inner-fit polygon calculation.
template<class ...Args>
struct RectangleOverfitPackingStrategy {
PackStrategyNFP<Args...> base_strategy;
PostAlignmentFn post_alignment_fn = CenterAlignmentFn{};
template<class ArrItem>
using Context = RectangleOverfitPackingContext<ArrItem>;
RectangleOverfitPackingStrategy(PackStrategyNFP<Args...> s,
PostAlignmentFn post_align_fn)
: base_strategy{std::move(s)}, post_alignment_fn{post_align_fn}
{}
RectangleOverfitPackingStrategy(PackStrategyNFP<Args...> s)
: base_strategy{std::move(s)}
{}
};
struct RectangleOverfitPackingStrategyTag {};
template<class... Args>
struct PackStrategyTag_<RectangleOverfitPackingStrategy<Args...>> {
using Tag = RectangleOverfitPackingStrategyTag;
};
template<class... Args>
struct PackStrategyTraits_<RectangleOverfitPackingStrategy<Args...>> {
template<class ArrItem>
using Context = typename RectangleOverfitPackingStrategy<
Args...>::template Context<StripCVRef<ArrItem>>;
template<class ArrItem, class Bed>
static Context<ArrItem> create_context(
RectangleOverfitPackingStrategy<Args...> &ps,
const Bed &bed,
int bed_index)
{
return Context<ArrItem>{bounding_box(bed), bed_index,
ps.post_alignment_fn};
}
};
template<class ArrItem>
struct PackingContextTraits_<RectangleOverfitPackingContext<ArrItem>>
: public PackingContextTraits_<DefaultPackingContext<ArrItem>>
{
static void add_packed_item(RectangleOverfitPackingContext<ArrItem> &ctx, ArrItem &itm)
{
ctx.add_packed_item(itm);
// to prevent coords going out of range
ctx.align_pile();
}
};
template<class Strategy, class ArrItem, class Bed, class RemIt>
bool pack(Strategy &strategy,
const Bed &bed,
ArrItem &item,
const PackStrategyContext<Strategy, ArrItem> &packing_context,
const Range<RemIt> &remaining_items,
const RectangleOverfitPackingStrategyTag &)
{
bool ret = false;
if (fixed_items_range(packing_context).empty()) {
auto &base = strategy.base_strategy;
PackStrategyNFP modded_strategy{
base.solver,
RectangleOverfitKernelWrapper{base.kernel, packing_context.limits},
base.ep, base.accuracy};
ret = pack(modded_strategy,
InfiniteBed{packing_context.limits.center()}, item,
packing_context, remaining_items, NFPPackingTag{});
} else {
ret = pack(strategy.base_strategy, bed, item, packing_context,
remaining_items, NFPPackingTag{});
}
return ret;
}
}} // namespace Slic3r::arr2
#endif // RECTANGLEOVERFITPACKINGSTRATEGY_HPP

View File

@ -0,0 +1,124 @@
#ifndef PACKINGCONTEXT_HPP
#define PACKINGCONTEXT_HPP
#include "ArrangeItemTraits.hpp"
namespace Slic3r { namespace arr2 {
template<class Ctx, class En = void>
struct PackingContextTraits_ {
template<class ArrItem>
static void add_fixed_item(Ctx &ctx, const ArrItem &itm)
{
ctx.add_fixed_item(itm);
}
template<class ArrItem>
static void add_packed_item(Ctx &ctx, ArrItem &itm)
{
ctx.add_packed_item(itm);
}
// returns a range of all packed items in the context ctx
static auto all_items_range(const Ctx &ctx)
{
return ctx.all_items_range();
}
static auto fixed_items_range(const Ctx &ctx)
{
return ctx.fixed_items_range();
}
static auto packed_items_range(const Ctx &ctx)
{
return ctx.packed_items_range();
}
static auto packed_items_range(Ctx &ctx)
{
return ctx.packed_items_range();
}
};
template<class Ctx, class ArrItem>
void add_fixed_item(Ctx &ctx, const ArrItem &itm)
{
PackingContextTraits_<StripCVRef<Ctx>>::add_fixed_item(ctx, itm);
}
template<class Ctx, class ArrItem>
void add_packed_item(Ctx &ctx, ArrItem &itm)
{
PackingContextTraits_<StripCVRef<Ctx>>::add_packed_item(ctx, itm);
}
template<class Ctx>
auto all_items_range(const Ctx &ctx)
{
return PackingContextTraits_<StripCVRef<Ctx>>::all_items_range(ctx);
}
template<class Ctx>
auto fixed_items_range(const Ctx &ctx)
{
return PackingContextTraits_<StripCVRef<Ctx>>::fixed_items_range(ctx);
}
template<class Ctx>
auto packed_items_range(Ctx &&ctx)
{
return PackingContextTraits_<StripCVRef<Ctx>>::packed_items_range(ctx);
}
template<class ArrItem>
class DefaultPackingContext {
using ArrItemRaw = StripCVRef<ArrItem>;
std::vector<std::reference_wrapper<const ArrItemRaw>> m_fixed;
std::vector<std::reference_wrapper<ArrItemRaw>> m_packed;
std::vector<std::reference_wrapper<const ArrItemRaw>> m_items;
public:
DefaultPackingContext() = default;
template<class It>
explicit DefaultPackingContext(const Range<It> &fixed_items)
{
std::copy(fixed_items.begin(), fixed_items.end(), std::back_inserter(m_fixed));
std::copy(fixed_items.begin(), fixed_items.end(), std::back_inserter(m_items));
}
auto all_items_range() const noexcept { return crange(m_items); }
auto fixed_items_range() const noexcept { return crange(m_fixed); }
auto packed_items_range() const noexcept { return crange(m_packed); }
auto packed_items_range() noexcept { return range(m_packed); }
void add_fixed_item(const ArrItem &itm)
{
m_fixed.emplace_back(itm);
m_items.emplace_back(itm);
}
void add_packed_item(ArrItem &itm)
{
m_packed.emplace_back(itm);
m_items.emplace_back(itm);
}
};
template<class It>
auto default_context(const Range<It> &items)
{
using ArrItem = StripCVRef<typename std::iterator_traits<It>::value_type>;
return DefaultPackingContext<ArrItem>{items};
}
template<class Cont, class ArrItem = typename Cont::value_type>
auto default_context(const Cont &container)
{
return DefaultPackingContext<ArrItem>{crange(container)};
}
}} // namespace Slic3r::arr2
#endif // PACKINGCONTEXT_HPP

View File

@ -0,0 +1,91 @@
#ifndef ARBITRARYDATASTORE_HPP
#define ARBITRARYDATASTORE_HPP
#include <string>
#include <map>
#include <any>
#include "libslic3r/Arrange/Core/DataStoreTraits.hpp"
namespace Slic3r { namespace arr2 {
// An associative container able to store and retrieve any data type.
// Based on std::any
class ArbitraryDataStore {
std::map<std::string, std::any> m_data;
public:
template<class T> void add(const std::string &key, T &&data)
{
m_data[key] = std::any{std::forward<T>(data)};
}
void add(const std::string &key, std::any &&data)
{
m_data[key] = std::move(data);
}
// Return nullptr if the key does not exist or the stored data has a
// type other then T. Otherwise returns a pointer to the stored data.
template<class T> const T *get(const std::string &key) const
{
auto it = m_data.find(key);
return it != m_data.end() ? std::any_cast<T>(&(it->second)) :
nullptr;
}
// Same as above just not const.
template<class T> T *get(const std::string &key)
{
auto it = m_data.find(key);
return it != m_data.end() ? std::any_cast<T>(&(it->second)) : nullptr;
}
bool has_key(const std::string &key) const
{
auto it = m_data.find(key);
return it != m_data.end();
}
};
// Some items can be containers of arbitrary data stored under string keys.
template<> struct DataStoreTraits_<ArbitraryDataStore>
{
static constexpr bool Implemented = true;
template<class T>
static const T *get(const ArbitraryDataStore &s, const std::string &key)
{
return s.get<T>(key);
}
// Same as above just not const.
template<class T>
static T *get(ArbitraryDataStore &s, const std::string &key)
{
return s.get<T>(key);
}
template<class T>
static bool has_key(ArbitraryDataStore &s, const std::string &key)
{
return s.has_key(key);
}
};
template<> struct WritableDataStoreTraits_<ArbitraryDataStore>
{
static constexpr bool Implemented = true;
template<class T>
static void set(ArbitraryDataStore &store,
const std::string &key,
T &&data)
{
store.add(key, std::forward<T>(data));
}
};
}} // namespace Slic3r::arr2
#endif // ARBITRARYDATASTORE_HPP

View File

@ -0,0 +1,205 @@
#include "ArrangeItem.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp"
#include "libslic3r/Arrange/ArrangeImpl.hpp"
#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp"
#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp"
#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
namespace Slic3r { namespace arr2 {
const Polygons &DecomposedShape::transformed_outline() const
{
constexpr auto sc = scaled<double>(1.) * scaled<double>(1.);
if (!m_transformed_outline_valid) {
m_transformed_outline = contours();
for (Polygon &poly : m_transformed_outline) {
poly.rotate(rotation());
poly.translate(translation());
}
m_area = std::accumulate(m_transformed_outline.begin(),
m_transformed_outline.end(), 0.,
[sc](double s, const auto &p) {
return s + p.area() / sc;
});
m_convex_hull = Geometry::convex_hull(m_transformed_outline);
m_bounding_box = get_extents(m_convex_hull);
m_transformed_outline_valid = true;
}
return m_transformed_outline;
}
const Polygon &DecomposedShape::convex_hull() const
{
if (!m_transformed_outline_valid)
transformed_outline();
return m_convex_hull;
}
const BoundingBox &DecomposedShape::bounding_box() const
{
if (!m_transformed_outline_valid)
transformed_outline();
return m_bounding_box;
}
const Vec2crd &DecomposedShape::reference_vertex() const
{
if (!m_reference_vertex_valid) {
m_reference_vertex = Slic3r::reference_vertex(transformed_outline());
m_refs.clear();
m_mins.clear();
m_refs.reserve(m_transformed_outline.size());
m_mins.reserve(m_transformed_outline.size());
for (auto &poly : m_transformed_outline) {
m_refs.emplace_back(Slic3r::reference_vertex(poly));
m_mins.emplace_back(Slic3r::min_vertex(poly));
}
m_reference_vertex_valid = true;
}
return m_reference_vertex;
}
const Vec2crd &DecomposedShape::reference_vertex(size_t i) const
{
if (!m_reference_vertex_valid) {
reference_vertex();
}
return m_refs[i];
}
const Vec2crd &DecomposedShape::min_vertex(size_t idx) const
{
if (!m_reference_vertex_valid) {
reference_vertex();
}
return m_mins[idx];
}
Vec2crd DecomposedShape::centroid() const
{
constexpr double area_sc = scaled<double>(1.) * scaled(1.);
if (!m_centroid_valid) {
double total_area = 0.0;
Vec2d cntr = Vec2d::Zero();
for (const Polygon& poly : transformed_outline()) {
double parea = poly.area() / area_sc;
Vec2d pcntr = unscaled(poly.centroid());
total_area += parea;
cntr += pcntr * parea;
}
cntr /= total_area;
m_centroid = scaled(cntr);
m_centroid_valid = true;
}
return m_centroid;
}
DecomposedShape decompose(const ExPolygons &shape)
{
return DecomposedShape{convex_decomposition_tess(shape)};
}
DecomposedShape decompose(const Polygon &shape)
{
Polygons convex_shapes;
bool is_convex = polygon_is_convex(shape);
if (is_convex) {
convex_shapes.emplace_back(shape);
} else {
convex_shapes = convex_decomposition_tess(shape);
}
return DecomposedShape{std::move(convex_shapes)};
}
ArrangeItem::ArrangeItem(const ExPolygons &shape)
: m_shape{decompose(shape)}, m_envelope{&m_shape}
{}
ArrangeItem::ArrangeItem(Polygon shape)
: m_shape{decompose(shape)}, m_envelope{&m_shape}
{}
ArrangeItem::ArrangeItem(const ArrangeItem &other)
{
this->operator= (other);
}
ArrangeItem::ArrangeItem(ArrangeItem &&other) noexcept
{
this->operator=(std::move(other));
}
ArrangeItem &ArrangeItem::operator=(const ArrangeItem &other)
{
m_shape = other.m_shape;
m_datastore = other.m_datastore;
m_bed_idx = other.m_bed_idx;
m_priority = other.m_priority;
if (other.m_envelope.get() == &other.m_shape)
m_envelope = &m_shape;
else
m_envelope = std::make_unique<DecomposedShape>(other.envelope());
return *this;
}
void ArrangeItem::set_shape(DecomposedShape shape)
{
m_shape = std::move(shape);
m_envelope = &m_shape;
}
void ArrangeItem::set_envelope(DecomposedShape envelope)
{
m_envelope = std::make_unique<DecomposedShape>(std::move(envelope));
// Initial synch of transformations of envelope and shape.
// They need to be in synch all the time
m_envelope->translation(m_shape.translation());
m_envelope->rotation(m_shape.rotation());
}
ArrangeItem &ArrangeItem::operator=(ArrangeItem &&other) noexcept
{
m_shape = std::move(other.m_shape);
m_datastore = std::move(other.m_datastore);
m_bed_idx = other.m_bed_idx;
m_priority = other.m_priority;
if (other.m_envelope.get() == &other.m_shape)
m_envelope = &m_shape;
else
m_envelope = std::move(other.m_envelope);
return *this;
}
template struct ImbueableItemTraits_<ArrangeItem>;
template class ArrangeableToItemConverter<ArrangeItem>;
template struct ArrangeTask<ArrangeItem>;
template struct FillBedTask<ArrangeItem>;
template struct MultiplySelectionTask<ArrangeItem>;
template class Arranger<ArrangeItem>;
}} // namespace Slic3r::arr2

View File

@ -0,0 +1,480 @@
#ifndef ARRANGEITEM_HPP
#define ARRANGEITEM_HPP
#include <optional>
#include <boost/variant.hpp>
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/AnyPtr.hpp"
#include "libslic3r/Arrange/Core/PackingContext.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/NFP/NFP.hpp"
#include "libslic3r/Arrange/Items/MutableItemTraits.hpp"
#include "libslic3r/Arrange/Arrange.hpp"
#include "libslic3r/Arrange/Tasks/ArrangeTask.hpp"
#include "libslic3r/Arrange/Tasks/FillBedTask.hpp"
#include "libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp"
#include "libslic3r/Arrange/Items/ArbitraryDataStore.hpp"
#include <libslic3r/ClipperUtils.hpp>
namespace Slic3r { namespace arr2 {
inline bool check_polygons_are_convex(const Polygons &pp) {
return std::all_of(pp.begin(), pp.end(), [](const Polygon &p) {
return polygon_is_convex(p);
});
}
// A class that stores a set of polygons that are garanteed to be all convex.
// They collectively represent a decomposition of a more complex shape into
// its convex part. Note that this class only stores the result of the decomp,
// does not do the job itself. In debug mode, an explicit check is done for
// each component to be convex.
//
// Additionally class stores a translation vector and a rotation angle for the
// stored polygon, plus additional privitives that are all cached cached after
// appying a the transformations. The caching is not thread safe!
class DecomposedShape
{
Polygons m_shape;
Vec2crd m_translation{0, 0}; // The translation of the poly
double m_rotation{0.0}; // The rotation of the poly in radians
mutable Polygons m_transformed_outline;
mutable bool m_transformed_outline_valid = false;
mutable Point m_reference_vertex;
mutable std::vector<Point> m_refs;
mutable std::vector<Point> m_mins;
mutable bool m_reference_vertex_valid = false;
mutable Point m_centroid;
mutable bool m_centroid_valid = false;
mutable Polygon m_convex_hull;
mutable BoundingBox m_bounding_box;
mutable double m_area = 0;
public:
DecomposedShape() = default;
explicit DecomposedShape(Polygon sh)
{
m_shape.emplace_back(std::move(sh));
assert(check_polygons_are_convex(m_shape));
}
explicit DecomposedShape(std::initializer_list<Point> pts)
: DecomposedShape(Polygon{pts})
{}
explicit DecomposedShape(Polygons sh) : m_shape{std::move(sh)}
{
assert(check_polygons_are_convex(m_shape));
}
const Polygons &contours() const { return m_shape; }
const Vec2crd &translation() const { return m_translation; }
double rotation() const { return m_rotation; }
void translation(const Vec2crd &v)
{
m_translation = v;
m_transformed_outline_valid = false;
m_reference_vertex_valid = false;
m_centroid_valid = false;
}
void rotation(double v)
{
m_rotation = v;
m_transformed_outline_valid = false;
m_reference_vertex_valid = false;
m_centroid_valid = false;
}
const Polygons &transformed_outline() const;
const Polygon &convex_hull() const;
const BoundingBox &bounding_box() const;
// The cached reference vertex in the context of NFP creation. Always
// refers to the leftmost upper vertex.
const Vec2crd &reference_vertex() const;
const Vec2crd &reference_vertex(size_t idx) const;
// Also for NFP calculations, the rightmost lowest vertex of the shape.
const Vec2crd &min_vertex(size_t idx) const;
double area_unscaled() const
{
// update cache
transformed_outline();
return m_area;
}
Vec2crd centroid() const;
};
DecomposedShape decompose(const ExPolygons &polys);
DecomposedShape decompose(const Polygon &p);
class ArrangeItem
{
private:
DecomposedShape m_shape; // Shape of item when it's not moving
AnyPtr<DecomposedShape> m_envelope; // Possibly different shape when packed
ArbitraryDataStore m_datastore;
int m_bed_idx{Unarranged}; // To which logical bed does this item belong
int m_priority{0}; // For sorting
public:
ArrangeItem() = default;
explicit ArrangeItem(DecomposedShape shape)
: m_shape(std::move(shape)), m_envelope{&m_shape}
{}
explicit ArrangeItem(DecomposedShape shape, DecomposedShape envelope)
: m_shape(std::move(shape))
, m_envelope{std::make_unique<DecomposedShape>(std::move(envelope))}
{}
explicit ArrangeItem(const ExPolygons &shape);
explicit ArrangeItem(Polygon shape);
explicit ArrangeItem(std::initializer_list<Point> pts)
: ArrangeItem(Polygon{pts})
{}
ArrangeItem(const ArrangeItem &);
ArrangeItem(ArrangeItem &&) noexcept;
ArrangeItem & operator=(const ArrangeItem &);
ArrangeItem & operator=(ArrangeItem &&) noexcept;
int bed_idx() const { return m_bed_idx; }
int priority() const { return m_priority; }
void bed_idx(int v) { m_bed_idx = v; }
void priority(int v) { m_priority = v; }
const ArbitraryDataStore &datastore() const { return m_datastore; }
ArbitraryDataStore &datastore() { return m_datastore; }
const DecomposedShape & shape() const { return m_shape; }
void set_shape(DecomposedShape shape);
const DecomposedShape & envelope() const { return *m_envelope; }
void set_envelope(DecomposedShape envelope);
const Vec2crd &translation() const { return m_shape.translation(); }
double rotation() const { return m_shape.rotation(); }
void translation(const Vec2crd &v)
{
m_shape.translation(v);
m_envelope->translation(v);
}
void rotation(double v)
{
m_shape.rotation(v);
m_envelope->rotation(v);
}
void update_caches() const
{
m_shape.reference_vertex();
m_envelope->reference_vertex();
m_shape.centroid();
m_envelope->centroid();
}
};
template<> struct ArrangeItemTraits_<ArrangeItem>
{
static const Vec2crd &get_translation(const ArrangeItem &itm)
{
return itm.translation();
}
static double get_rotation(const ArrangeItem &itm)
{
return itm.rotation();
}
static int get_bed_index(const ArrangeItem &itm)
{
return itm.bed_idx();
}
static int get_priority(const ArrangeItem &itm)
{
return itm.priority();
}
// Setters:
static void set_translation(ArrangeItem &itm, const Vec2crd &v)
{
itm.translation(v);
}
static void set_rotation(ArrangeItem &itm, double v)
{
itm.rotation(v);
}
static void set_bed_index(ArrangeItem &itm, int v)
{
itm.bed_idx(v);
}
};
// Some items can be containers of arbitrary data stored under string keys.
template<> struct DataStoreTraits_<ArrangeItem>
{
static constexpr bool Implemented = true;
template<class T>
static const T *get(const ArrangeItem &itm, const std::string &key)
{
return itm.datastore().get<T>(key);
}
// Same as above just not const.
template<class T>
static T *get(ArrangeItem &itm, const std::string &key)
{
return itm.datastore().get<T>(key);
}
static bool has_key(const ArrangeItem &itm, const std::string &key)
{
return itm.datastore().has_key(key);
}
};
template<> struct WritableDataStoreTraits_<ArrangeItem>
{
static constexpr bool Implemented = true;
template<class T>
static void set(ArrangeItem &itm,
const std::string &key,
T &&data)
{
itm.datastore().add(key, std::forward<T>(data));
}
};
template<class FixedIt, class StopCond = DefaultStopCondition>
static Polygons calculate_nfp_unnormalized(const ArrangeItem &item,
const Range<FixedIt> &fixed_items,
StopCond &&stop_cond = {})
{
size_t cap = 0;
for (const ArrangeItem &fixitem : fixed_items) {
const Polygons &outlines = fixitem.shape().transformed_outline();
cap += outlines.size();
}
const Polygons &item_outlines = item.envelope().transformed_outline();
auto nfps = reserve_polygons(cap * item_outlines.size());
Vec2crd ref_whole = item.envelope().reference_vertex();
Polygon subnfp;
for (const ArrangeItem &fixed : fixed_items) {
// fixed_polys should already be a set of strictly convex polygons,
// as ArrangeItem stores convex-decomposed polygons
const Polygons & fixed_polys = fixed.shape().transformed_outline();
for (const Polygon &fixed_poly : fixed_polys) {
Point max_fixed = Slic3r::reference_vertex(fixed_poly);
for (size_t mi = 0; mi < item_outlines.size(); ++mi) {
const Polygon &movable = item_outlines[mi];
const Vec2crd &mref = item.envelope().reference_vertex(mi);
subnfp = nfp_convex_convex_legacy(fixed_poly, movable);
Vec2crd min_movable = item.envelope().min_vertex(mi);
Vec2crd dtouch = max_fixed - min_movable;
Vec2crd top_other = mref + dtouch;
Vec2crd max_nfp = Slic3r::reference_vertex(subnfp);
auto dnfp = top_other - max_nfp;
auto d = ref_whole - mref + dnfp;
subnfp.translate(d);
nfps.emplace_back(subnfp);
}
if (stop_cond())
break;
nfps = union_(nfps);
}
if (stop_cond()) {
nfps.clear();
break;
}
}
return nfps;
}
template<> struct NFPArrangeItemTraits_<ArrangeItem> {
template<class Context, class Bed, class StopCond>
static ExPolygons calculate_nfp(const ArrangeItem &item,
const Context &packing_context,
const Bed &bed,
StopCond &&stopcond)
{
auto static_items = all_items_range(packing_context);
Polygons nfps = arr2::calculate_nfp_unnormalized(item, static_items, stopcond);
ExPolygons nfp_ex;
if (!stopcond()) {
if constexpr (!std::is_convertible_v<Bed, InfiniteBed>) {
ExPolygons ifpbed = ifp_convex(bed, item.envelope().convex_hull());
nfp_ex = diff_ex(ifpbed, nfps);
} else {
nfp_ex = union_ex(nfps);
}
}
item.update_caches();
return nfp_ex;
}
static const Vec2crd& reference_vertex(const ArrangeItem &item)
{
return item.envelope().reference_vertex();
}
static BoundingBox envelope_bounding_box(const ArrangeItem &itm)
{
return itm.envelope().bounding_box();
}
static BoundingBox fixed_bounding_box(const ArrangeItem &itm)
{
return itm.shape().bounding_box();
}
static double envelope_area(const ArrangeItem &itm)
{
return itm.envelope().area_unscaled() * scaled<double>(1.) *
scaled<double>(1.);
}
static double fixed_area(const ArrangeItem &itm)
{
return itm.shape().area_unscaled() * scaled<double>(1.) *
scaled<double>(1.);
}
static const Polygons & envelope_outline(const ArrangeItem &itm)
{
return itm.envelope().transformed_outline();
}
static const Polygons & fixed_outline(const ArrangeItem &itm)
{
return itm.shape().transformed_outline();
}
static const Polygon & envelope_convex_hull(const ArrangeItem &itm)
{
return itm.envelope().convex_hull();
}
static const Polygon & fixed_convex_hull(const ArrangeItem &itm)
{
return itm.shape().convex_hull();
}
static const std::vector<double>& allowed_rotations(const ArrangeItem &itm)
{
static const std::vector<double> ret_zero = {0.};
const std::vector<double> * ret_ptr = &ret_zero;
auto rots = get_data<std::vector<double>>(itm, "rotations");
if (rots) {
ret_ptr = rots;
}
return *ret_ptr;
}
static Vec2crd fixed_centroid(const ArrangeItem &itm)
{
return itm.shape().centroid();
}
static Vec2crd envelope_centroid(const ArrangeItem &itm)
{
return itm.envelope().centroid();
}
};
template<> struct IsMutableItem_<ArrangeItem>: public std::true_type {};
template<>
struct MutableItemTraits_<ArrangeItem> {
static void set_priority(ArrangeItem &itm, int p) { itm.priority(p); }
static void set_convex_shape(ArrangeItem &itm, const Polygon &shape)
{
itm.set_shape(DecomposedShape{shape});
}
static void set_shape(ArrangeItem &itm, const ExPolygons &shape)
{
itm.set_shape(decompose(shape));
}
static void set_convex_envelope(ArrangeItem &itm, const Polygon &envelope)
{
itm.set_envelope(DecomposedShape{envelope});
}
static void set_envelope(ArrangeItem &itm, const ExPolygons &envelope)
{
itm.set_envelope(decompose(envelope));
}
template<class T>
static void set_arbitrary_data(ArrangeItem &itm, const std::string &key, T &&data)
{
set_data(itm, key, std::forward<T>(data));
}
static void set_allowed_rotations(ArrangeItem &itm, const std::vector<double> &rotations)
{
set_data(itm, "rotations", rotations);
}
};
extern template struct ImbueableItemTraits_<ArrangeItem>;
extern template class ArrangeableToItemConverter<ArrangeItem>;
extern template struct ArrangeTask<ArrangeItem>;
extern template struct FillBedTask<ArrangeItem>;
extern template struct MultiplySelectionTask<ArrangeItem>;
extern template class Arranger<ArrangeItem>;
}} // namespace Slic3r::arr2
#endif // ARRANGEITEM_HPP

View File

@ -0,0 +1,136 @@
#ifndef MutableItemTraits_HPP
#define MutableItemTraits_HPP
#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/DataStoreTraits.hpp"
#include "libslic3r/ExPolygon.hpp"
namespace Slic3r { namespace arr2 {
template<class Itm> struct IsMutableItem_ : public std::false_type
{};
// Using this interface to set up any arrange item. Provides default
// implementation but it needs to be explicitly switched on with
// IsMutableItem_ or completely reimplement a specialization.
template<class Itm, class En = void> struct MutableItemTraits_
{
static_assert(IsMutableItem_<Itm>::value, "Not a Writable item type!");
static void set_priority(Itm &itm, int p) { itm.set_priority(p); }
static void set_convex_shape(Itm &itm, const Polygon &shape)
{
itm.set_convex_shape(shape);
}
static void set_shape(Itm &itm, const ExPolygons &shape)
{
itm.set_shape(shape);
}
static void set_convex_envelope(Itm &itm, const Polygon &envelope)
{
itm.set_convex_envelope(envelope);
}
static void set_envelope(Itm &itm, const ExPolygons &envelope)
{
itm.set_envelope(envelope);
}
template<class T>
static void set_arbitrary_data(Itm &itm, const std::string &key, T &&data)
{
if constexpr (IsWritableDataStore<Itm>)
set_data(itm, key, std::forward<T>(data));
}
static void set_allowed_rotations(Itm &itm,
const std::vector<double> &rotations)
{
itm.set_allowed_rotations(rotations);
}
};
template<class T>
using MutableItemTraits = MutableItemTraits_<StripCVRef<T>>;
template<class T> constexpr bool IsMutableItem = IsMutableItem_<T>::value;
template<class T, class TT = T>
using MutableItemOnly = std::enable_if_t<IsMutableItem<T>, TT>;
template<class Itm> void set_priority(Itm &itm, int p)
{
MutableItemTraits<Itm>::set_priority(itm, p);
}
template<class Itm> void set_convex_shape(Itm &itm, const Polygon &shape)
{
MutableItemTraits<Itm>::set_convex_shape(itm, shape);
}
template<class Itm> void set_shape(Itm &itm, const ExPolygons &shape)
{
MutableItemTraits<Itm>::set_shape(itm, shape);
}
template<class Itm>
void set_convex_envelope(Itm &itm, const Polygon &envelope)
{
MutableItemTraits<Itm>::set_convex_envelope(itm, envelope);
}
template<class Itm> void set_envelope(Itm &itm, const ExPolygons &envelope)
{
MutableItemTraits<Itm>::set_envelope(itm, envelope);
}
template<class T, class Itm>
void set_arbitrary_data(Itm &itm, const std::string &key, T &&data)
{
MutableItemTraits<Itm>::set_arbitrary_data(itm, key, std::forward<T>(data));
}
template<class Itm>
void set_allowed_rotations(Itm &itm, const std::vector<double> &rotations)
{
MutableItemTraits<Itm>::set_allowed_rotations(itm, rotations);
}
template<class ArrItem> int raise_priority(ArrItem &itm)
{
int ret = get_priority(itm) + 1;
set_priority(itm, ret);
return ret;
}
template<class ArrItem> int reduce_priority(ArrItem &itm)
{
int ret = get_priority(itm) - 1;
set_priority(itm, ret);
return ret;
}
template<class It> int lowest_priority(const Range<It> &item_range)
{
auto minp_it = std::min_element(item_range.begin(),
item_range.end(),
[](auto &itm1, auto &itm2) {
return get_priority(itm1) <
get_priority(itm2);
});
int min_priority = 0;
if (minp_it != item_range.end())
min_priority = get_priority(*minp_it);
return min_priority;
}
}} // namespace Slic3r::arr2
#endif // MutableItemTraits_HPP

View File

@ -0,0 +1,24 @@
#include "SimpleArrangeItem.hpp"
#include "libslic3r/Arrange/ArrangeImpl.hpp"
#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp"
#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp"
#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp"
namespace Slic3r { namespace arr2 {
Polygon SimpleArrangeItem::outline() const
{
Polygon ret = shape();
ret.rotate(m_rotation);
ret.translate(m_translation);
return ret;
}
template class ArrangeableToItemConverter<SimpleArrangeItem>;
template struct ArrangeTask<SimpleArrangeItem>;
template struct FillBedTask<SimpleArrangeItem>;
template struct MultiplySelectionTask<SimpleArrangeItem>;
template class Arranger<SimpleArrangeItem>;
}} // namespace Slic3r::arr2

View File

@ -0,0 +1,218 @@
#ifndef SIMPLEARRANGEITEM_HPP
#define SIMPLEARRANGEITEM_HPP
#include "libslic3r/Arrange/Core/PackingContext.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/NFP/NFP.hpp"
#include "libslic3r/Arrange/Arrange.hpp"
#include "libslic3r/Arrange/Tasks/ArrangeTask.hpp"
#include "libslic3r/Arrange/Tasks/FillBedTask.hpp"
#include "libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#include "MutableItemTraits.hpp"
namespace Slic3r { namespace arr2 {
class SimpleArrangeItem {
Polygon m_shape;
Vec2crd m_translation = Vec2crd::Zero();
double m_rotation = 0.;
int m_priority = 0;
int m_bed_idx = Unarranged;
std::vector<double> m_allowed_rotations = {0.};
ObjectID m_obj_id;
public:
explicit SimpleArrangeItem(Polygon chull = {}): m_shape{std::move(chull)} {}
void set_shape(Polygon chull) { m_shape = std::move(chull); }
const Vec2crd& get_translation() const noexcept { return m_translation; }
double get_rotation() const noexcept { return m_rotation; }
int get_priority() const noexcept { return m_priority; }
int get_bed_index() const noexcept { return m_bed_idx; }
void set_translation(const Vec2crd &v) { m_translation = v; }
void set_rotation(double v) noexcept { m_rotation = v; }
void set_priority(int v) noexcept { m_priority = v; }
void set_bed_index(int v) noexcept { m_bed_idx = v; }
const Polygon &shape() const { return m_shape; }
Polygon outline() const;
const auto &allowed_rotations() const noexcept
{
return m_allowed_rotations;
}
void set_allowed_rotations(std::vector<double> rots)
{
m_allowed_rotations = std::move(rots);
}
void set_object_id(const ObjectID &id) noexcept { m_obj_id = id; }
const ObjectID & get_object_id() const noexcept { return m_obj_id; }
};
template<> struct NFPArrangeItemTraits_<SimpleArrangeItem>
{
template<class Context, class Bed, class StopCond>
static ExPolygons calculate_nfp(const SimpleArrangeItem &item,
const Context &packing_context,
const Bed &bed,
StopCond &&stop_cond)
{
auto fixed_items = all_items_range(packing_context);
auto nfps = reserve_polygons(fixed_items.size());
for (const SimpleArrangeItem &fixed_part : fixed_items) {
Polygon subnfp = nfp_convex_convex_legacy(fixed_part.outline(),
item.outline());
nfps.emplace_back(subnfp);
if (stop_cond()) {
nfps.clear();
break;
}
}
ExPolygons nfp_ex;
if (!stop_cond()) {
if constexpr (!std::is_convertible_v<Bed, InfiniteBed>) {
ExPolygons ifpbed = ifp_convex(bed, item.outline());
nfp_ex = diff_ex(ifpbed, nfps);
} else {
nfp_ex = union_ex(nfps);
}
}
return nfp_ex;
}
static Vec2crd reference_vertex(const SimpleArrangeItem &item)
{
return Slic3r::reference_vertex(item.outline());
}
static BoundingBox envelope_bounding_box(const SimpleArrangeItem &itm)
{
return get_extents(itm.outline());
}
static BoundingBox fixed_bounding_box(const SimpleArrangeItem &itm)
{
return get_extents(itm.outline());
}
static Polygons envelope_outline(const SimpleArrangeItem &itm)
{
return {itm.outline()};
}
static Polygons fixed_outline(const SimpleArrangeItem &itm)
{
return {itm.outline()};
}
static Polygon envelope_convex_hull(const SimpleArrangeItem &itm)
{
return Geometry::convex_hull(itm.outline());
}
static Polygon fixed_convex_hull(const SimpleArrangeItem &itm)
{
return Geometry::convex_hull(itm.outline());
}
static double envelope_area(const SimpleArrangeItem &itm)
{
return itm.shape().area();
}
static double fixed_area(const SimpleArrangeItem &itm)
{
return itm.shape().area();
}
static const auto& allowed_rotations(const SimpleArrangeItem &itm) noexcept
{
return itm.allowed_rotations();
}
static Vec2crd fixed_centroid(const SimpleArrangeItem &itm) noexcept
{
return itm.outline().centroid();
}
static Vec2crd envelope_centroid(const SimpleArrangeItem &itm) noexcept
{
return itm.outline().centroid();
}
};
template<> struct IsMutableItem_<SimpleArrangeItem>: public std::true_type {};
template<>
struct MutableItemTraits_<SimpleArrangeItem> {
static void set_priority(SimpleArrangeItem &itm, int p) { itm.set_priority(p); }
static void set_convex_shape(SimpleArrangeItem &itm, const Polygon &shape)
{
itm.set_shape(shape);
}
static void set_shape(SimpleArrangeItem &itm, const ExPolygons &shape)
{
itm.set_shape(Geometry::convex_hull(shape));
}
static void set_convex_envelope(SimpleArrangeItem &itm, const Polygon &envelope)
{
itm.set_shape(envelope);
}
static void set_envelope(SimpleArrangeItem &itm, const ExPolygons &envelope)
{
itm.set_shape(Geometry::convex_hull(envelope));
}
template<class T>
static void set_data(SimpleArrangeItem &itm, const std::string &key, T &&data)
{}
static void set_allowed_rotations(SimpleArrangeItem &itm, const std::vector<double> &rotations)
{
itm.set_allowed_rotations(rotations);
}
};
template<> struct ImbueableItemTraits_<SimpleArrangeItem>
{
static void imbue_id(SimpleArrangeItem &itm, const ObjectID &id)
{
itm.set_object_id(id);
}
static std::optional<ObjectID> retrieve_id(const SimpleArrangeItem &itm)
{
std::optional<ObjectID> ret;
if (itm.get_object_id().valid())
ret = itm.get_object_id();
return ret;
}
};
extern template class ArrangeableToItemConverter<SimpleArrangeItem>;
extern template struct ArrangeTask<SimpleArrangeItem>;
extern template struct FillBedTask<SimpleArrangeItem>;
extern template struct MultiplySelectionTask<SimpleArrangeItem>;
extern template class Arranger<SimpleArrangeItem>;
}} // namespace Slic3r::arr2
#endif // SIMPLEARRANGEITEM_HPP

View File

@ -0,0 +1,79 @@
#ifndef TRAFOONLYARRANGEITEM_HPP
#define TRAFOONLYARRANGEITEM_HPP
#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Items/ArbitraryDataStore.hpp"
#include "libslic3r/Arrange/Items/MutableItemTraits.hpp"
namespace Slic3r { namespace arr2 {
class TrafoOnlyArrangeItem {
int m_bed_idx = Unarranged;
int m_priority = 0;
Vec2crd m_translation = Vec2crd::Zero();
double m_rotation = 0.;
ArbitraryDataStore m_datastore;
public:
TrafoOnlyArrangeItem() = default;
template<class ArrItm>
explicit TrafoOnlyArrangeItem(const ArrItm &other)
: m_bed_idx{arr2::get_bed_index(other)},
m_priority{arr2::get_priority(other)},
m_translation(arr2::get_translation(other)),
m_rotation{arr2::get_rotation(other)}
{}
const Vec2crd& get_translation() const noexcept { return m_translation; }
double get_rotation() const noexcept { return m_rotation; }
int get_bed_index() const noexcept { return m_bed_idx; }
int get_priority() const noexcept { return m_priority; }
const ArbitraryDataStore &datastore() const noexcept { return m_datastore; }
ArbitraryDataStore &datastore() { return m_datastore; }
};
template<> struct DataStoreTraits_<TrafoOnlyArrangeItem>
{
static constexpr bool Implemented = true;
template<class T>
static const T *get(const TrafoOnlyArrangeItem &itm, const std::string &key)
{
return itm.datastore().get<T>(key);
}
template<class T>
static T *get(TrafoOnlyArrangeItem &itm, const std::string &key)
{
return itm.datastore().get<T>(key);
}
static bool has_key(const TrafoOnlyArrangeItem &itm, const std::string &key)
{
return itm.datastore().has_key(key);
}
};
template<> struct IsMutableItem_<TrafoOnlyArrangeItem>: public std::true_type {};
template<> struct WritableDataStoreTraits_<TrafoOnlyArrangeItem>
{
static constexpr bool Implemented = true;
template<class T>
static void set(TrafoOnlyArrangeItem &itm,
const std::string &key,
T &&data)
{
set_data(itm.datastore(), key, std::forward<T>(data));
}
};
} // namespace arr2
} // namespace Slic3r
#endif // TRAFOONLYARRANGEITEM_HPP

View File

@ -0,0 +1,64 @@
#include "Scene.hpp"
#include "Items/ArrangeItem.hpp"
#include "Tasks/ArrangeTask.hpp"
#include "Tasks/FillBedTask.hpp"
namespace Slic3r { namespace arr2 {
std::vector<ObjectID> Scene::selected_ids() const
{
auto items = reserve_vector<ObjectID>(model().arrangeable_count());
model().for_each_arrangeable([ &items](auto &arrbl) mutable {
if (arrbl.is_selected())
items.emplace_back(arrbl.id());
});
return items;
}
using DefaultArrangeItem = ArrangeItem;
std::unique_ptr<ArrangeTaskBase> ArrangeTaskBase::create(Tasks task_type, const Scene &sc)
{
std::unique_ptr<ArrangeTaskBase> ret;
switch(task_type) {
case Tasks::Arrange:
ret = ArrangeTask<ArrangeItem>::create(sc);
break;
case Tasks::FillBed:
ret = FillBedTask<ArrangeItem>::create(sc);
break;
default:
;
}
return ret;
}
std::set<ObjectID> selected_geometry_ids(const Scene &sc)
{
std::set<ObjectID> result;
std::vector<ObjectID> selected_ids = sc.selected_ids();
for (const ObjectID &id : selected_ids) {
sc.model().visit_arrangeable(id, [&result](const Arrangeable &arrbl) {
auto id = arrbl.geometry_id();
if (id.valid())
result.insert(arrbl.geometry_id());
});
}
return result;
}
void arrange(Scene &scene, ArrangeTaskCtl &ctl)
{
auto task = ArrangeTaskBase::create(Tasks::Arrange, scene);
auto result = task->process(ctl);
result->apply_on(scene.model());
}
}} // namespace Slic3r::arr2

View File

@ -0,0 +1,377 @@
#ifndef ARR2_SCENE_HPP
#define ARR2_SCENE_HPP
#include <any>
#include <string_view>
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/AnyPtr.hpp"
#include "libslic3r/Arrange/ArrangeSettingsView.hpp"
#include "libslic3r/Arrange/SegmentedRectangleBed.hpp"
namespace Slic3r { namespace arr2 {
// This module contains all the necessary high level interfaces for
// arrangement. No dependency on the rest of libslic3r is intoduced here. (No
// Model, ModelObject, etc...) except for ObjectID.
// An interface that allows to store arbitrary data (std::any) under a specific
// key in an object implementing the interface. This is later used to pass
// arbitrary parameters from any arrangeable object down to the arrangement core.
class AnyWritable
{
public:
virtual ~AnyWritable() = default;
virtual void write(std::string_view key, std::any d) = 0;
};
// The interface that captures the objects which are actually moved around.
// Implementations must provide means to extract the 2D outline that is used
// by the arrangement core.
class Arrangeable
{
public:
virtual ~Arrangeable() = default;
// ID is implementation specific, must uniquely identify an Arrangeable
// object.
virtual ObjectID id() const = 0;
// This is different than id(), and identifies an underlying group into
// which the Arrangeable belongs. Can be used to group arrangeables sharing
// the same outline.
virtual ObjectID geometry_id() const = 0;
// Outline extraction can be a demanding operation, so there is a separate
// method the extract the full outline of an object and the convex hull only
// It will depend on the arrangement config to choose which one is called.
// convex_outline might be considerably faster than calling full_outline()
// and then calculating the convex hull from that.
virtual ExPolygons full_outline() const = 0;
virtual Polygon convex_outline() const = 0;
// Envelope is the boundary that an arrangeble object might have which
// is used when the object is being placed or moved around. Once it is
// placed, the outline (convex or full) will be used to determine the
// boundaries instead of the envelope. This concept can be used to
// implement arranging objects with support structures that can overlap,
// but never touch the actual object. In this case, full envelope would
// return the silhouette of the object with supports (pad, brim, etc...) and
// outline would be the actual object boundary.
virtual ExPolygons full_envelope() const { return {}; }
virtual Polygon convex_envelope() const { return {}; }
// Write the transformations determined by the arrangement into the object
virtual void transform(const Vec2d &transl, double rot) = 0;
// An arrangeable can be printable or unprintable, they should not be on
// the same bed. (See arrange tasks)
virtual bool is_printable() const { return true; }
// An arrangeable can be selected or not, this will determine if treated
// as static objects or movable ones.
virtual bool is_selected() const { return true; }
// Determines the order in which the objects are arranged. Higher priority
// objects are arranged first.
virtual int priority() const { return 0; }
// Any implementation specific properties can be passed to the arrangement
// core by overriding this method. This implies that the specific Arranger
// will be able to interpret these properties. An example usage is to mark
// special objects (like a wipe tower)
virtual void imbue_data(AnyWritable &datastore) const {}
// for convinience to pass an AnyWritable created in the same expression
// as the method call
void imbue_data(AnyWritable &&datastore) const { imbue_data(datastore); }
// An Arrangeable might reside on a logical bed instead of the real one
// in case that the arrangement can not fit it onto the real bed. Handling
// of logical beds is also implementation specific and are specified with
// the next two methods:
// Returns the bed index on which the given Arrangeable is sitting.
virtual int get_bed_index() const = 0;
// Assign the Arrangeable to the given bed index. Note that this
// method can return false, indicating that the given bed is not available
// to be occupied.
virtual bool assign_bed(int bed_idx) = 0;
};
// Arrangeable objects are provided by an ArrangeableModel which is also able to
// create new arrangeables given a prototype id to copy.
class ArrangeableModel
{
public:
virtual ~ArrangeableModel() = default;
// Visit all arrangeable in this model and call the provided visitor
virtual void for_each_arrangeable(std::function<void(Arrangeable &)>) = 0;
virtual void for_each_arrangeable(std::function<void(const Arrangeable&)>) const = 0;
// Visit a specific arrangeable identified by it's id
virtual void visit_arrangeable(const ObjectID &id, std::function<void(const Arrangeable &)>) const = 0;
virtual void visit_arrangeable(const ObjectID &id, std::function<void(Arrangeable &)>) = 0;
// Add a new arrangeable which is a copy of the one matching prototype_id
// Return the new object id or an invalid id if the new object was not
// created.
virtual ObjectID add_arrangeable(const ObjectID &prototype_id) = 0;
size_t arrangeable_count() const
{
size_t cnt = 0;
for_each_arrangeable([&cnt](auto &) { ++cnt; });
return cnt;
}
};
// The special bed type used by XL printers
using XLBed = SegmentedRectangleBed<std::integral_constant<size_t, 4>,
std::integral_constant<size_t, 4>>;
// ExtendedBed is a variant type holding all bed types supported by the
// arrange core and the additional XLBed
template<class... Args> struct ExtendedBed_
{
using Type =
boost::variant<XLBed, /* insert other types if needed*/ Args...>;
};
template<class... Args> struct ExtendedBed_<boost::variant<Args...>>
{
using Type = boost::variant<XLBed, Args...>;
};
using ExtendedBed = typename ExtendedBed_<ArrangeBed>::Type;
template<class BedFn> void visit_bed(BedFn &&fn, const ExtendedBed &bed)
{
boost::apply_visitor(fn, bed);
}
template<class BedFn> void visit_bed(BedFn &&fn, ExtendedBed &bed)
{
boost::apply_visitor(fn, bed);
}
class Scene;
// SceneBuilderBase is intended for Scene construction. A simple constructor
// is not enough here to capture all the possible ways of constructing a Scene.
// Subclasses of SceneBuilderBase can add more domain specific methods and
// overloads. An rvalue object of this class is handed over to the Scene
// constructor which can then establish itself using the provided builder.
// A little CRTP is used to implement fluent interface returning Subclass
// references.
template<class Subclass>
class SceneBuilderBase
{
protected:
AnyPtr<ArrangeableModel> m_arrangeable_model;
AnyPtr<const ArrangeSettingsView> m_settings;
ExtendedBed m_bed = arr2::InfiniteBed{};
coord_t m_brims_offs = 0;
coord_t m_skirt_offs = 0;
public:
virtual ~SceneBuilderBase() = default;
SceneBuilderBase() = default;
SceneBuilderBase(const SceneBuilderBase &) = delete;
SceneBuilderBase& operator=(const SceneBuilderBase &) = delete;
SceneBuilderBase(SceneBuilderBase &&) = default;
SceneBuilderBase& operator=(SceneBuilderBase &&) = default;
// All setters return an rvalue reference so that at the end, the
// build_scene method can be called fluently
Subclass &&set_arrange_settings(AnyPtr<const ArrangeSettingsView> settings)
{
m_settings = std::move(settings);
return std::move(static_cast<Subclass&>(*this));
}
Subclass &&set_arrange_settings(const ArrangeSettingsView &settings)
{
m_settings = std::make_unique<ArrangeSettings>(settings);
return std::move(static_cast<Subclass&>(*this));
}
Subclass &&set_bed(const Points &pts)
{
m_bed = arr2::to_arrange_bed(pts);
return std::move(static_cast<Subclass&>(*this));
}
Subclass && set_bed(const arr2::ArrangeBed &bed)
{
m_bed = bed;
return std::move(static_cast<Subclass&>(*this));
}
Subclass &&set_bed(const XLBed &bed)
{
m_bed = bed;
return std::move(static_cast<Subclass&>(*this));
}
Subclass &&set_arrangeable_model(AnyPtr<ArrangeableModel> model)
{
m_arrangeable_model = std::move(model);
return std::move(static_cast<Subclass&>(*this));
}
// Can only be called on an rvalue instance (hence the && at the end),
// the method will potentially move its content into sc
virtual void build_scene(Scene &sc) &&;
};
class BasicSceneBuilder: public SceneBuilderBase<BasicSceneBuilder> {};
// The Scene class captures all data needed to do an arrangement.
class Scene
{
template <class Sub> friend class SceneBuilderBase;
// These fields always need to be initialized to valid objects after
// construction of Scene which is ensured by the SceneBuilder
AnyPtr<ArrangeableModel> m_amodel;
AnyPtr<const ArrangeSettingsView> m_settings;
ExtendedBed m_bed;
public:
// Can only be built from an rvalue SceneBuilder, as it's content will
// potentially be moved to the constructed ArrangeScene object
template<class Sub>
explicit Scene(SceneBuilderBase<Sub> &&bld)
{
std::move(bld).build_scene(*this);
}
const ArrangeableModel &model() const noexcept { return *m_amodel; }
ArrangeableModel &model() noexcept { return *m_amodel; }
const ArrangeSettingsView &settings() const noexcept { return *m_settings; }
template<class BedFn> void visit_bed(BedFn &&fn) const
{
arr2::visit_bed(fn, m_bed);
}
const ExtendedBed & bed() const { return m_bed; }
std::vector<ObjectID> selected_ids() const;
};
std::set<ObjectID> selected_geometry_ids(const Scene &sc);
class EmptyArrangeableModel: public ArrangeableModel
{
public:
void for_each_arrangeable(std::function<void(Arrangeable &)>) override {}
void for_each_arrangeable(std::function<void(const Arrangeable&)>) const override {}
void visit_arrangeable(const ObjectID &id, std::function<void(const Arrangeable &)>) const override {}
void visit_arrangeable(const ObjectID &id, std::function<void(Arrangeable &)>) override {}
ObjectID add_arrangeable(const ObjectID &prototype_id) override { return {}; }
};
template<class Subclass>
void SceneBuilderBase<Subclass>::build_scene(Scene &sc) &&
{
if (!m_arrangeable_model)
m_arrangeable_model = std::make_unique<EmptyArrangeableModel>();
if (!m_settings)
m_settings = std::make_unique<arr2::ArrangeSettings>();
coord_t inset = std::max(scaled(m_settings->get_distance_from_bed()),
m_skirt_offs + m_brims_offs);
coord_t md = scaled(m_settings->get_distance_from_objects());
md = md / 2 - inset;
visit_bed([md](auto &rawbed) { rawbed = offset(rawbed, md); }, m_bed);
sc.m_settings = std::move(m_settings);
sc.m_amodel = std::move(m_arrangeable_model);
sc.m_bed = std::move(m_bed);
}
// Arrange tasks produce an object implementing this interface. The arrange
// result can be applied to an ArrangeableModel which may or may not succeed.
// The ArrangeableModel could be in a different state (it's objects may have
// changed or removed) than it was at the time of arranging.
class ArrangeResult
{
public:
virtual ~ArrangeResult() = default;
virtual bool apply_on(ArrangeableModel &mdlwt) = 0;
};
enum class Tasks { Arrange, FillBed };
class ArrangeTaskCtl
{
public:
virtual ~ArrangeTaskCtl() = default;
virtual void update_status(int st) = 0;
virtual bool was_canceled() const = 0;
};
class DummyCtl : public ArrangeTaskCtl
{
public:
void update_status(int) override {}
bool was_canceled() const override { return false; }
};
class ArrangeTaskBase
{
public:
using Ctl = ArrangeTaskCtl;
virtual ~ArrangeTaskBase() = default;
[[nodiscard]] virtual std::unique_ptr<ArrangeResult> process(Ctl &ctl) = 0;
[[nodiscard]] virtual int item_count_to_process() const = 0;
[[nodiscard]] static std::unique_ptr<ArrangeTaskBase> create(
Tasks task_type, const Scene &sc);
[[nodiscard]] std::unique_ptr<ArrangeResult> process(Ctl &&ctl)
{
return process(ctl);
}
[[nodiscard]] std::unique_ptr<ArrangeResult> process()
{
return process(DummyCtl{});
}
};
void arrange(Scene &scene, ArrangeTaskCtl &ctl);
inline void arrange(Scene &scene, ArrangeTaskCtl &&ctl = DummyCtl{})
{
arrange(scene, ctl);
}
} // namespace arr2
} // namespace Slic3r
#endif // ARR2_SCENE_HPP

View File

@ -0,0 +1,869 @@
#ifndef SCENEBUILDER_CPP
#define SCENEBUILDER_CPP
#include "SceneBuilder.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "Core/ArrangeItemTraits.hpp"
#include "Geometry/ConvexHull.hpp"
namespace Slic3r { namespace arr2 {
coord_t get_skirt_inset(const Print &fffprint)
{
float skirt_inset = 0.f;
if (fffprint.has_skirt()) {
float skirtflow = fffprint.objects().empty()
? 0
: fffprint.skirt_flow().width();
skirt_inset = fffprint.config().skirts.value * skirtflow
+ fffprint.config().skirt_distance.value;
}
return scaled(skirt_inset);
}
coord_t brim_offset(const PrintObject &po)
{
const BrimType brim_type = po.config().brim_type.value;
const float brim_separation = po.config().brim_separation.getFloat();
const float brim_width = po.config().brim_width.getFloat();
const bool has_outer_brim = brim_type == BrimType::btOuterOnly ||
brim_type == BrimType::btOuterAndInner;
// How wide is the brim? (in scaled units)
return has_outer_brim ? scaled(brim_width + brim_separation) : 0;
}
size_t model_instance_count (const Model &m)
{
return std::accumulate(m.objects.begin(),
m.objects.end(),
size_t(0),
[](size_t s, const Slic3r::ModelObject *mo) {
return s + mo->instances.size();
});
}
void transform_instance(ModelInstance &mi,
const Vec2d &transl_unscaled,
double rot,
const Transform3d &physical_tr)
{
auto trafo = mi.get_transformation().get_matrix();
auto tr = Transform3d::Identity();
tr.translate(to_3d(transl_unscaled, 0.));
trafo = physical_tr.inverse() * tr * Eigen::AngleAxisd(rot, Vec3d::UnitZ()) * physical_tr * trafo;
mi.set_transformation(Geometry::Transformation{trafo});
mi.invalidate_object_bounding_box();
}
BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate)
{
BoundingBoxf3 bb;
const Transform3d inst_matrix
= dont_translate ? mi.get_transformation().get_matrix_no_offset()
: mi.get_transformation().get_matrix();
for (ModelVolume *v : mi.get_object()->volumes) {
if (v->is_model_part()) {
bb.merge(v->mesh().transformed_bounding_box(inst_matrix
* v->get_matrix()));
}
}
return bb;
}
ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr)
{
ExPolygons outline;
for (const ModelVolume *v : inst.get_object()->volumes) {
Polygons vol_outline;
vol_outline = project_mesh(v->mesh().its,
tr * inst.get_matrix() * v->get_matrix(),
[] {});
switch (v->type()) {
case ModelVolumeType::MODEL_PART:
outline = union_ex(outline, vol_outline);
break;
case ModelVolumeType::NEGATIVE_VOLUME:
outline = diff_ex(outline, vol_outline);
break;
default:;
}
}
return outline;
}
Polygon extract_convex_outline(const ModelInstance &inst, const Transform3d &tr)
{
return inst.get_object()->convex_hull_2d(tr * inst.get_matrix());
}
inline static bool is_infinite_bed(const ExtendedBed &ebed) noexcept
{
bool ret = false;
visit_bed(
[&ret](auto &rawbed) {
ret = std::is_convertible_v<decltype(rawbed), InfiniteBed>;
},
ebed);
return ret;
}
void SceneBuilder::set_brim_and_skirt()
{
if (!m_fff_print)
return;
m_brims_offs = 0;
for (const PrintObject *po : m_fff_print->objects()) {
if (po) {
m_brims_offs = std::max(m_brims_offs, brim_offset(*po));
}
}
m_skirt_offs = get_skirt_inset(*m_fff_print);
}
void SceneBuilder::build_scene(Scene &sc) &&
{
if (m_sla_print && !m_fff_print) {
m_arrangeable_model = std::make_unique<ArrangeableSLAPrint>(m_sla_print.get(), *this);
} else {
m_arrangeable_model = std::make_unique<ArrangeableSlicerModel>(*this);
}
if (m_fff_print && !m_sla_print) {
if (is_infinite_bed(m_bed)) {
set_bed(*m_fff_print);
} else {
set_brim_and_skirt();
}
}
std::move(*this).SceneBuilderBase<SceneBuilder>::build_scene(sc);
}
void SceneBuilder::build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel)
{
if (!m_model)
m_model = std::make_unique<Model>();
if (!m_selection)
m_selection = std::make_unique<FixedSelection>(*m_model);
if (!m_vbed_handler) {
m_vbed_handler = VirtualBedHandler::create(m_bed);
}
if (!m_wipetower_handler) {
m_wipetower_handler = std::make_unique<MissingWipeTowerHandler>();
}
amodel.m_vbed_handler = std::move(m_vbed_handler);
amodel.m_model = std::move(m_model);
amodel.m_selmask = std::move(m_selection);
amodel.m_wth = std::move(m_wipetower_handler);
amodel.m_wth->set_selection_predicate(
[&amodel] { return amodel.m_selmask->is_wipe_tower(); });
}
int XStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const
{
int bedidx = 0;
auto stride_s = stride_scaled();
if (stride_s > 0) {
double bedx = unscaled(m_start);
auto instance_bb = obj.bounding_box();
auto reference_pos_x = (instance_bb.min.x() - bedx);
auto stride = unscaled(stride_s);
bedidx = static_cast<int>(std::floor(reference_pos_x / stride));
}
return bedidx;
}
bool XStriderVBedHandler::assign_bed(VBedPlaceable &obj, int bed_index)
{
bool ret = false;
auto stride_s = stride_scaled();
if (bed_index == 0 || (bed_index > 0 && stride_s > 0)) {
auto current_bed_index = get_bed_index(obj);
auto stride = unscaled(stride_s);
auto transl = Vec2d{(bed_index - current_bed_index) * stride, 0.};
obj.displace(transl, 0.);
ret = true;
}
return ret;
}
Transform3d XStriderVBedHandler::get_physical_bed_trafo(int bed_index) const
{
auto stride_s = stride_scaled();
auto tr = Transform3d::Identity();
tr.translate(Vec3d{-bed_index * unscaled(stride_s), 0., 0.});
return tr;
}
int YStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const
{
int bedidx = 0;
auto stride_s = stride_scaled();
if (stride_s > 0) {
double ystart = unscaled(m_start);
auto instance_bb = obj.bounding_box();
auto reference_pos_y = (instance_bb.min.y() - ystart);
auto stride = unscaled(stride_s);
bedidx = static_cast<int>(std::floor(reference_pos_y / stride));
}
return bedidx;
}
bool YStriderVBedHandler::assign_bed(VBedPlaceable &obj, int bed_index)
{
bool ret = false;
auto stride_s = stride_scaled();
if (bed_index == 0 || (bed_index > 0 && stride_s > 0)) {
auto current_bed_index = get_bed_index(obj);
auto stride = unscaled(stride_s);
auto transl = Vec2d{0., (bed_index - current_bed_index) * stride};
obj.displace(transl, 0.);
ret = true;
}
return ret;
}
Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const
{
auto stride_s = stride_scaled();
auto tr = Transform3d::Identity();
tr.translate(Vec3d{0., -bed_index * unscaled(stride_s), 0.});
return tr;
}
const int GridStriderVBedHandler::ColsOutside =
static_cast<int>(std::sqrt(std::numeric_limits<int>::max()));
Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const
{
Vec2i ret{bed_idx % ColsOutside, bed_idx / ColsOutside};
return ret;
}
int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const
{
assert(crd.x() < ColsOutside - 1 && crd.y() < ColsOutside - 1);
return crd.y() * ColsOutside + crd.x();
}
int GridStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const
{
Vec2i crd = {m_xstrider.get_bed_index(obj), m_ystrider.get_bed_index(obj)};
return grid2raw(crd);
}
bool GridStriderVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx)
{
Vec2i crd = raw2grid(bed_idx);
bool retx = m_xstrider.assign_bed(inst, crd.x());
bool rety = m_ystrider.assign_bed(inst, crd.y());
return retx && rety;
}
Transform3d GridStriderVBedHandler::get_physical_bed_trafo(int bed_idx) const
{
Vec2i crd = raw2grid(bed_idx);
Transform3d ret = m_xstrider.get_physical_bed_trafo(crd.x()) *
m_ystrider.get_physical_bed_trafo(crd.y());
return ret;
}
FixedSelection::FixedSelection(const Model &m) : m_wp{true}
{
m_seldata.resize(m.objects.size());
for (size_t i = 0; i < m.objects.size(); ++i) {
m_seldata[i].resize(m.objects[i]->instances.size(), true);
}
}
FixedSelection::FixedSelection(const SelectionMask &other)
{
auto obj_sel = other.selected_objects();
m_seldata.reserve(obj_sel.size());
for (int oidx = 0; oidx < static_cast<int>(obj_sel.size()); ++oidx)
m_seldata.emplace_back(other.selected_instances(oidx));
}
std::vector<bool> FixedSelection::selected_objects() const
{
auto ret = Slic3r::reserve_vector<bool>(m_seldata.size());
std::transform(m_seldata.begin(),
m_seldata.end(),
std::back_inserter(ret),
[](auto &a) {
return std::any_of(a.begin(), a.end(), [](bool b) {
return b;
});
});
return ret;
}
static std::vector<size_t> find_true_indices(const std::vector<bool> &v)
{
auto ret = reserve_vector<size_t>(v.size());
for (size_t i = 0; i < v.size(); ++i)
if (v[i])
ret.emplace_back(i);
return ret;
}
std::vector<size_t> selected_object_indices(const SelectionMask &sm)
{
auto sel = sm.selected_objects();
return find_true_indices(sel);
}
std::vector<size_t> selected_instance_indices(int obj_idx, const SelectionMask &sm)
{
auto sel = sm.selected_instances(obj_idx);
return find_true_indices(sel);
}
SceneBuilder::SceneBuilder() = default;
SceneBuilder::~SceneBuilder() = default;
SceneBuilder::SceneBuilder(SceneBuilder &&) = default;
SceneBuilder& SceneBuilder::operator=(SceneBuilder&&) = default;
SceneBuilder &&SceneBuilder::set_model(AnyPtr<Model> mdl)
{
m_model = std::move(mdl);
return std::move(*this);
}
SceneBuilder &&SceneBuilder::set_model(Model &mdl)
{
m_model = &mdl;
return std::move(*this);
}
SceneBuilder &&SceneBuilder::set_fff_print(AnyPtr<const Print> mdl_print)
{
m_fff_print = std::move(mdl_print);
return std::move(*this);
}
SceneBuilder &&SceneBuilder::set_sla_print(AnyPtr<const SLAPrint> mdl_print)
{
m_sla_print = std::move(mdl_print);
return std::move(*this);
}
SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg)
{
Points bedpts = get_bed_shape(cfg);
if (is_XL_printer(cfg)) {
m_bed = XLBed{get_extents(bedpts)};
} else {
m_bed = arr2::to_arrange_bed(bedpts);
}
return std::move(*this);
}
SceneBuilder &&SceneBuilder::set_bed(const Print &print)
{
Points bedpts = get_bed_shape(print.config());
if (is_XL_printer(print.config())) {
m_bed = XLBed{get_extents(bedpts)};
} else {
m_bed = arr2::to_arrange_bed(bedpts);
}
set_brim_and_skirt();
return std::move(*this);
}
SceneBuilder &&SceneBuilder::set_sla_print(const SLAPrint *slaprint)
{
m_sla_print = slaprint;
return std::move(*this);
}
int ArrangeableWipeTowerBase::get_bed_index() const { return PhysicalBedId; }
bool ArrangeableWipeTowerBase::assign_bed(int bed_idx)
{
return bed_idx == PhysicalBedId;
}
bool PhysicalOnlyVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx)
{
return bed_idx == PhysicalBedId;
}
ArrangeableSlicerModel::ArrangeableSlicerModel(SceneBuilder &builder)
{
builder.build_arrangeable_slicer_model(*this);
}
ArrangeableSlicerModel::~ArrangeableSlicerModel() = default;
void ArrangeableSlicerModel::for_each_arrangeable(
std::function<void(Arrangeable &)> fn)
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
}
void ArrangeableSlicerModel::for_each_arrangeable(
std::function<void(const Arrangeable &)> fn) const
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
}
ObjectID ArrangeableSlicerModel::add_arrangeable(const ObjectID &prototype_id)
{
ObjectID ret;
auto [inst, pos] = find_instance_by_id(*m_model, prototype_id);
if (inst) {
auto new_inst = inst->get_object()->add_instance(*inst);
if (new_inst) {
ret = new_inst->id();
}
}
return ret;
}
template<class Self, class Fn>
void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn)
{
InstPos pos;
for (auto *obj : self.m_model->objects) {
for (auto *inst : obj->instances) {
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), self.m_selmask.get(), pos};
fn(ainst);
++pos.inst_idx;
}
pos.inst_idx = 0;
++pos.obj_idx;
}
}
template<class Self, class Fn>
void ArrangeableSlicerModel::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn)
{
if (id == self.m_model->wipe_tower.id()) {
self.m_wth->visit(fn);
return;
}
auto [inst, pos] = find_instance_by_id(*self.m_model, id);
if (inst) {
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), self.m_selmask.get(), pos};
fn(ainst);
}
}
void ArrangeableSlicerModel::visit_arrangeable(
const ObjectID &id, std::function<void(const Arrangeable &)> fn) const
{
visit_arrangeable_(*this, id, fn);
}
void ArrangeableSlicerModel::visit_arrangeable(
const ObjectID &id, std::function<void(Arrangeable &)> fn)
{
visit_arrangeable_(*this, id, fn);
}
template<class Self, class Fn>
void ArrangeableSLAPrint::for_each_arrangeable_(Self &&self, Fn &&fn)
{
InstPos pos;
for (auto *obj : self.m_model->objects) {
for (auto *inst : obj->instances) {
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(),
self.m_selmask.get(), pos};
auto obj_id = inst->get_object()->id();
const SLAPrintObject *po =
self.m_slaprint->get_print_object_by_model_object_id(obj_id);
if (po) {
auto &vbh = self.m_vbed_handler;
auto phtr = vbh->get_physical_bed_trafo(vbh->get_bed_index(VBedPlaceableMI{*inst}));
ArrangeableSLAPrintObject ainst_po{po, &ainst, phtr * inst->get_matrix()};
fn(ainst_po);
} else {
fn(ainst);
}
++pos.inst_idx;
}
pos.inst_idx = 0;
++pos.obj_idx;
}
}
void ArrangeableSLAPrint::for_each_arrangeable(
std::function<void(Arrangeable &)> fn)
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
}
void ArrangeableSLAPrint::for_each_arrangeable(
std::function<void(const Arrangeable &)> fn) const
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
}
template<class Self, class Fn>
void ArrangeableSLAPrint::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn)
{
auto [inst, pos] = find_instance_by_id(*self.m_model, id);
if (inst) {
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(),
self.m_selmask.get(), pos};
auto obj_id = inst->get_object()->id();
const SLAPrintObject *po =
self.m_slaprint->get_print_object_by_model_object_id(obj_id);
if (po) {
auto &vbh = self.m_vbed_handler;
auto phtr = vbh->get_physical_bed_trafo(vbh->get_bed_index(VBedPlaceableMI{*inst}));
ArrangeableSLAPrintObject ainst_po{po, &ainst, phtr * inst->get_matrix()};
fn(ainst_po);
} else {
fn(ainst);
}
}
}
void ArrangeableSLAPrint::visit_arrangeable(
const ObjectID &id, std::function<void(const Arrangeable &)> fn) const
{
visit_arrangeable_(*this, id, fn);
}
void ArrangeableSLAPrint::visit_arrangeable(
const ObjectID &id, std::function<void(Arrangeable &)> fn)
{
visit_arrangeable_(*this, id, fn);
}
template<class InstPtr, class VBedHPtr>
ExPolygons ArrangeableModelInstance<InstPtr, VBedHPtr>::full_outline() const
{
int bedidx = m_vbedh->get_bed_index(*this);
auto tr = m_vbedh->get_physical_bed_trafo(bedidx);
return extract_full_outline(*m_mi, tr);
}
template<class InstPtr, class VBedHPtr>
Polygon ArrangeableModelInstance<InstPtr, VBedHPtr>::convex_outline() const
{
int bedidx = m_vbedh->get_bed_index(*this);
auto tr = m_vbedh->get_physical_bed_trafo(bedidx);
return extract_convex_outline(*m_mi, tr);
}
template<class InstPtr, class VBedHPtr>
bool ArrangeableModelInstance<InstPtr, VBedHPtr>::is_selected() const
{
bool ret = false;
if (m_selmask) {
auto sel = m_selmask->selected_instances(m_pos_within_model.obj_idx);
if (m_pos_within_model.inst_idx < sel.size() &&
sel[m_pos_within_model.inst_idx])
ret = true;
}
return ret;
}
template<class InstPtr, class VBedHPtr>
void ArrangeableModelInstance<InstPtr, VBedHPtr>::transform(const Vec2d &transl, double rot)
{
if constexpr (!std::is_const_v<InstPtr> && !std::is_const_v<VBedHPtr>) {
int bedidx = m_vbedh->get_bed_index(*this);
auto physical_trafo = m_vbedh->get_physical_bed_trafo(bedidx);
transform_instance(*m_mi, transl, rot, physical_trafo);
}
}
template<class InstPtr, class VBedHPtr>
bool ArrangeableModelInstance<InstPtr, VBedHPtr>::assign_bed(int bed_idx)
{
bool ret = false;
if constexpr (!std::is_const_v<InstPtr> && !std::is_const_v<VBedHPtr>)
ret = m_vbedh->assign_bed(*this, bed_idx);
return ret;
}
template class ArrangeableModelInstance<ModelInstance, VirtualBedHandler>;
template class ArrangeableModelInstance<const ModelInstance, const VirtualBedHandler>;
ExPolygons ArrangeableSLAPrintObject::full_outline() const
{
ExPolygons ret;
auto laststep = m_po->last_completed_step();
if (laststep < slaposCount && laststep > slaposSupportTree) {
Polygons polys;
auto omesh = m_po->get_mesh_to_print();
auto &smesh = m_po->support_mesh();
Transform3d trafo_instance = m_inst_trafo * m_po->trafo().inverse();
if (omesh) {
Polygons ptmp = project_mesh(*omesh, trafo_instance, [] {});
std::move(ptmp.begin(), ptmp.end(), std::back_inserter(polys));
}
Polygons ptmp = project_mesh(smesh.its, trafo_instance, [] {});
std::move(ptmp.begin(), ptmp.end(), std::back_inserter(polys));
ret = union_ex(polys);
} else {
ret = m_arrbl->full_outline();
}
return ret;
}
ExPolygons ArrangeableSLAPrintObject::full_envelope() const
{
ExPolygons ret = full_outline();
auto laststep = m_po->last_completed_step();
if (laststep < slaposCount && laststep > slaposSupportTree) {
auto &pmesh = m_po->pad_mesh();
if (!pmesh.empty()) {
Transform3d trafo_instance = m_inst_trafo * m_po->trafo().inverse();
Polygons ptmp = project_mesh(pmesh.its, trafo_instance, [] {});
ret = union_ex(ret, ptmp);
}
}
return ret;
}
Polygon ArrangeableSLAPrintObject::convex_outline() const
{
Polygons polys;
polys.emplace_back(m_arrbl->convex_outline());
auto laststep = m_po->last_completed_step();
if (laststep < slaposCount && laststep > slaposSupportTree) {
auto omesh = m_po->get_mesh_to_print();
auto &smesh = m_po->support_mesh();
Transform3f trafo_instance = m_inst_trafo.cast<float>();
trafo_instance = trafo_instance * m_po->trafo().cast<float>().inverse();
Polygons polys;
polys.reserve(3);
auto zlvl = -m_po->get_elevation();
if (omesh) {
polys.emplace_back(
its_convex_hull_2d_above(*omesh, trafo_instance, zlvl));
}
polys.emplace_back(
its_convex_hull_2d_above(smesh.its, trafo_instance, zlvl));
}
return Geometry::convex_hull(polys);
}
Polygon ArrangeableSLAPrintObject::convex_envelope() const
{
Polygons polys;
polys.emplace_back(convex_outline());
auto laststep = m_po->last_completed_step();
if (laststep < slaposCount && laststep > slaposSupportTree) {
auto &pmesh = m_po->pad_mesh();
if (!pmesh.empty()) {
Transform3f trafo_instance = m_inst_trafo.cast<float>();
trafo_instance = trafo_instance * m_po->trafo().cast<float>().inverse();
auto zlvl = -m_po->get_elevation();
polys.emplace_back(
its_convex_hull_2d_above(pmesh.its, trafo_instance, zlvl));
}
}
return Geometry::convex_hull(polys);
}
DuplicableModel::DuplicableModel(AnyPtr<Model> mdl, AnyPtr<VirtualBedHandler> vbh, const BoundingBox &bedbb)
: m_model{std::move(mdl)}, m_vbh{std::move(vbh)}, m_duplicates(1), m_bedbb{bedbb}
{
}
DuplicableModel::~DuplicableModel() = default;
ObjectID DuplicableModel::add_arrangeable(const ObjectID &prototype_id)
{
ObjectID ret;
if (prototype_id.valid()) {
size_t idx = prototype_id.id - 1;
if (idx < m_duplicates.size()) {
ModelDuplicate md = m_duplicates[idx];
md.id = m_duplicates.size();
ret = md.id.id + 1;
m_duplicates.emplace_back(std::move(md));
}
}
return ret;
}
void DuplicableModel::apply_duplicates()
{
for (ModelObject *o : m_model->objects) {
// make a copy of the pointers in order to avoid recursion
// when appending their copies
ModelInstancePtrs instances = o->instances;
o->instances.clear();
for (const ModelInstance *i : instances) {
for (const ModelDuplicate &md : m_duplicates) {
ModelInstance *instance = o->add_instance(*i);
arr2::transform_instance(*instance, md.tr, md.rot);
}
}
for (auto *i : instances)
delete i;
instances.clear();
o->invalidate_bounding_box();
}
}
template<class Mdl, class Dup, class VBH>
ObjectID ArrangeableFullModel<Mdl, Dup, VBH>::geometry_id() const { return m_mdl->id(); }
template<class Mdl, class Dup, class VBH>
ExPolygons ArrangeableFullModel<Mdl, Dup, VBH>::full_outline() const
{
auto ret = reserve_vector<ExPolygon>(arr2::model_instance_count(*m_mdl));
auto transl = Transform3d::Identity();
transl.translate(to_3d(m_dup->tr, 0.));
Transform3d trafo = transl* Eigen::AngleAxisd(m_dup->rot, Vec3d::UnitZ());
for (auto *mo : m_mdl->objects) {
for (auto *mi : mo->instances) {
auto expolys = arr2::extract_full_outline(*mi, trafo);
std::move(expolys.begin(), expolys.end(), std::back_inserter(ret));
}
}
return ret;
}
template<class Mdl, class Dup, class VBH>
Polygon ArrangeableFullModel<Mdl, Dup, VBH>::convex_outline() const
{
auto ret = reserve_polygons(arr2::model_instance_count(*m_mdl));
auto transl = Transform3d::Identity();
transl.translate(to_3d(m_dup->tr, 0.));
Transform3d trafo = transl* Eigen::AngleAxisd(m_dup->rot, Vec3d::UnitZ());
for (auto *mo : m_mdl->objects) {
for (auto *mi : mo->instances) {
ret.emplace_back(arr2::extract_convex_outline(*mi, trafo));
}
}
return Geometry::convex_hull(ret);
}
template class ArrangeableFullModel<Model, ModelDuplicate, VirtualBedHandler>;
template class ArrangeableFullModel<const Model, const ModelDuplicate, const VirtualBedHandler>;
std::unique_ptr<VirtualBedHandler> VirtualBedHandler::create(const ExtendedBed &bed)
{
std::unique_ptr<VirtualBedHandler> ret;
if (is_infinite_bed(bed)) {
ret = std::make_unique<PhysicalOnlyVBedHandler>();
} else {
// The gap between logical beds expressed in ratio of
// the current bed width.
constexpr double LogicalBedGap = 1. / 10.;
BoundingBox bedbb;
visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed);
auto bedwidth = bedbb.size().x();
coord_t xgap = LogicalBedGap * bedwidth;
ret = std::make_unique<GridStriderVBedHandler>(bedbb, xgap);
}
return ret;
}
}} // namespace Slic3r::arr2
#endif // SCENEBUILDER_CPP

View File

@ -0,0 +1,669 @@
#ifndef SCENEBUILDER_HPP
#define SCENEBUILDER_HPP
#include "Scene.hpp"
#include "Core/ArrangeItemTraits.hpp"
namespace Slic3r {
class Model;
class ModelInstance;
class ModelWipeTower;
class Print;
class SLAPrint;
class SLAPrintObject;
class PrintObject;
class DynamicPrintConfig;
namespace arr2 {
using SelectionPredicate = std::function<bool()>;
class WipeTowerHandler
{
public:
virtual ~WipeTowerHandler() = default;
virtual void visit(std::function<void(Arrangeable &)>) = 0;
virtual void visit(std::function<void(const Arrangeable &)>) const = 0;
virtual void set_selection_predicate(SelectionPredicate pred) = 0;
};
class VBedPlaceable {
public:
virtual ~VBedPlaceable() = default;
virtual BoundingBoxf bounding_box() const = 0;
virtual void displace(const Vec2d &transl, double rot) = 0;
};
// An interface to handle virtual beds for ModelInstances. A ModelInstance
// may be assigned to a logical bed identified by an integer index value (zero
// is the actual physical bed). The ModelInstance may still be outside of it's
// bed, regardless of being assigned to it. The handler object should provide
// means to read the assigned bed index of a ModelInstance, to assign a
// different bed index and to provide a trafo that maps it to the physical bed
// given a logical bed index. The reason is that the arrangement expects items
// to be in the coordinate system of the physical bed.
class VirtualBedHandler
{
public:
virtual ~VirtualBedHandler() = default;
// Returns the bed index on which the given ModelInstance is sitting.
virtual int get_bed_index(const VBedPlaceable &obj) const = 0;
// The returned trafo can be used to move the outline of the ModelInstance
// to the coordinate system of the physical bed, should that differ from
// the coordinate space of a logical bed.
virtual Transform3d get_physical_bed_trafo(int bed_index) const = 0;
// Assign the ModelInstance to the given bed index. Note that this
// method can return false, indicating that the given bed is not available
// to be occupied (e.g. the handler has a limited amount of logical bed)
virtual bool assign_bed(VBedPlaceable &obj, int bed_idx) = 0;
bool assign_bed(VBedPlaceable &&obj, int bed_idx)
{
return assign_bed(obj, bed_idx);
}
static std::unique_ptr<VirtualBedHandler> create(const ExtendedBed &bed);
};
class SelectionMask
{
public:
virtual ~SelectionMask() = default;
virtual std::vector<bool> selected_objects() const = 0;
virtual std::vector<bool> selected_instances(int obj_id) const = 0;
virtual bool is_wipe_tower() const = 0;
};
class FixedSelection : public Slic3r::arr2::SelectionMask
{
std::vector<std::vector<bool>> m_seldata;
bool m_wp = false;
public:
FixedSelection() = default;
explicit FixedSelection(std::initializer_list<std::vector<bool>> seld,
bool wp = false)
: m_seldata{std::move(seld)}, m_wp{wp}
{}
explicit FixedSelection(const Model &m);
explicit FixedSelection(const SelectionMask &other);
std::vector<bool> selected_objects() const override;
std::vector<bool> selected_instances(int obj_id) const override
{
return obj_id < int(m_seldata.size()) ? m_seldata[obj_id] :
std::vector<bool>{};
}
bool is_wipe_tower() const override { return m_wp; }
};
struct ArrangeableWipeTowerBase: public Arrangeable
{
ObjectID oid;
Polygon poly;
SelectionPredicate selection_pred;
ArrangeableWipeTowerBase(
const ObjectID &objid,
Polygon shape,
SelectionPredicate selection_predicate = [] { return false; })
: oid{objid},
poly{std::move(shape)},
selection_pred{std::move(selection_predicate)}
{}
ObjectID id() const override { return oid; }
ObjectID geometry_id() const override { return {}; }
ExPolygons full_outline() const override
{
auto cpy = poly;
return {ExPolygon{std::move(cpy)}};
}
Polygon convex_outline() const override
{
return poly;
}
bool is_selected() const override
{
return selection_pred();
}
int get_bed_index() const override;
bool assign_bed(int /*bed_idx*/) override;
int priority() const override { return 1; }
void transform(const Vec2d &transl, double rot) override {}
void imbue_data(AnyWritable &datastore) const override
{
datastore.write("is_wipe_tower", {});
}
};
class SceneBuilder;
struct InstPos { size_t obj_idx = 0, inst_idx = 0; };
class ArrangeableSlicerModel: public ArrangeableModel
{
protected:
AnyPtr<Model> m_model;
AnyPtr<WipeTowerHandler> m_wth;
AnyPtr<VirtualBedHandler> m_vbed_handler;
AnyPtr<const SelectionMask> m_selmask;
private:
friend class SceneBuilder;
template<class Self, class Fn>
static void for_each_arrangeable_(Self &&self, Fn &&fn);
template<class Self, class Fn>
static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn);
public:
explicit ArrangeableSlicerModel(SceneBuilder &builder);
~ArrangeableSlicerModel();
void for_each_arrangeable(std::function<void(Arrangeable &)>) override;
void for_each_arrangeable(std::function<void(const Arrangeable&)>) const override;
void visit_arrangeable(const ObjectID &id, std::function<void(const Arrangeable &)>) const override;
void visit_arrangeable(const ObjectID &id, std::function<void(Arrangeable &)>) override;
ObjectID add_arrangeable(const ObjectID &prototype_id) override;
Model & get_model() { return *m_model; }
const Model &get_model() const { return *m_model; }
};
class SceneBuilder: public SceneBuilderBase<SceneBuilder>
{
protected:
AnyPtr<Model> m_model;
AnyPtr<WipeTowerHandler> m_wipetower_handler;
AnyPtr<VirtualBedHandler> m_vbed_handler;
AnyPtr<const SelectionMask> m_selection;
AnyPtr<const SLAPrint> m_sla_print;
AnyPtr<const Print> m_fff_print;
void set_brim_and_skirt();
public:
SceneBuilder();
~SceneBuilder();
SceneBuilder(SceneBuilder&&);
SceneBuilder& operator=(SceneBuilder&&);
SceneBuilder && set_model(AnyPtr<Model> mdl);
SceneBuilder && set_model(Model &mdl);
SceneBuilder && set_fff_print(AnyPtr<const Print> fffprint);
SceneBuilder && set_sla_print(AnyPtr<const SLAPrint> mdl_print);
using SceneBuilderBase<SceneBuilder>::set_bed;
SceneBuilder &&set_bed(const DynamicPrintConfig &cfg);
SceneBuilder &&set_bed(const Print &print);
SceneBuilder && set_wipe_tower_handler(WipeTowerHandler &wth)
{
m_wipetower_handler = &wth;
return std::move(*this);
}
SceneBuilder && set_wipe_tower_handler(AnyPtr<WipeTowerHandler> wth)
{
m_wipetower_handler = std::move(wth);
return std::move(*this);
}
SceneBuilder && set_virtual_bed_handler(AnyPtr<VirtualBedHandler> vbedh)
{
m_vbed_handler = std::move(vbedh);
return std::move(*this);
}
SceneBuilder && set_sla_print(const SLAPrint *slaprint);
SceneBuilder && set_selection(AnyPtr<const SelectionMask> sel)
{
m_selection = std::move(sel);
return std::move(*this);
}
// Can only be called on an rvalue instance (hence the && at the end),
// the method will potentially move its content into sc
void build_scene(Scene &sc) && override;
void build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel);
};
struct MissingWipeTowerHandler : public WipeTowerHandler
{
void visit(std::function<void(Arrangeable &)>) override {}
void visit(std::function<void(const Arrangeable &)>) const override {}
void set_selection_predicate(std::function<bool()>) override {}
};
// Only a physical bed, non-zero bed index values are discarded.
class PhysicalOnlyVBedHandler final : public VirtualBedHandler
{
public:
using VirtualBedHandler::assign_bed;
int get_bed_index(const VBedPlaceable &obj) const override { return 0; }
Transform3d get_physical_bed_trafo(int bed_index) const override
{
return Transform3d::Identity();
}
bool assign_bed(VBedPlaceable &inst, int bed_idx) override;
};
// A virtual bed handler implementation, that defines logical beds to be created
// on the right side of the physical bed along the X axis in a row
class XStriderVBedHandler final : public VirtualBedHandler
{
coord_t m_stride_scaled;
coord_t m_start;
public:
explicit XStriderVBedHandler(const BoundingBox &bedbb, coord_t xgap)
: m_stride_scaled{bedbb.size().x() + 2 * std::max(0, xgap)},
m_start{bedbb.min.x() - std::max(0, xgap)}
{
}
coord_t stride_scaled() const { return m_stride_scaled; }
// Can return negative indices when the instance is to the left of the
// physical bed
int get_bed_index(const VBedPlaceable &obj) const override;
// Only positive beds are accepted
bool assign_bed(VBedPlaceable &inst, int bed_idx) override;
using VirtualBedHandler::assign_bed;
Transform3d get_physical_bed_trafo(int bed_index) const override;
};
// Same as XStriderVBedHandler only that it lays out vbeds on the Y axis
class YStriderVBedHandler final : public VirtualBedHandler
{
coord_t m_stride_scaled;
coord_t m_start;
public:
coord_t stride_scaled() const { return m_stride_scaled; }
explicit YStriderVBedHandler(const BoundingBox &bedbb, coord_t ygap)
: m_stride_scaled{bedbb.size().y() + 2 * std::max(0, ygap)}
, m_start{bedbb.min.y() - std::max(0, ygap)}
{}
int get_bed_index(const VBedPlaceable &obj) const override;
bool assign_bed(VBedPlaceable &inst, int bed_idx) override;
Transform3d get_physical_bed_trafo(int bed_index) const override;
};
class GridStriderVBedHandler: public VirtualBedHandler
{
// This vbed handler defines a grid of virtual beds with a large number
// of columns so that it behaves as XStrider for regular cases.
// The goal is to handle objects residing at world coordinates
// not representable with scaled coordinates. Combining XStrider with
// YStrider takes care of the X and Y axis to be mapped into the physical
// bed's coordinate region (which is representable in scaled coords)
static const int ColsOutside;
XStriderVBedHandler m_xstrider;
YStriderVBedHandler m_ystrider;
public:
GridStriderVBedHandler(const BoundingBox &bedbb,
coord_t gap)
: m_xstrider{bedbb, gap}
, m_ystrider{bedbb, gap}
{}
Vec2i raw2grid(int bedidx) const;
int grid2raw(const Vec2i &crd) const;
int get_bed_index(const VBedPlaceable &obj) const override;
bool assign_bed(VBedPlaceable &inst, int bed_idx) override;
Transform3d get_physical_bed_trafo(int bed_index) const override;
};
std::vector<size_t> selected_object_indices(const SelectionMask &sm);
std::vector<size_t> selected_instance_indices(int obj_idx, const SelectionMask &sm);
coord_t get_skirt_inset(const Print &fffprint);
coord_t brim_offset(const PrintObject &po);
// unscaled coords are necessary to be able to handle bigger coordinate range
// than what is available with scaled coords. This is useful when working with
// virtual beds.
void transform_instance(ModelInstance &mi,
const Vec2d &transl_unscaled,
double rot,
const Transform3d &physical_tr = Transform3d::Identity());
BoundingBoxf3 instance_bounding_box(const ModelInstance &mi,
bool dont_translate = false);
ExPolygons extract_full_outline(const ModelInstance &inst,
const Transform3d &tr = Transform3d::Identity());
Polygon extract_convex_outline(const ModelInstance &inst,
const Transform3d &tr = Transform3d::Identity());
size_t model_instance_count (const Model &m);
class VBedPlaceableMI : public VBedPlaceable
{
ModelInstance *m_mi;
public:
explicit VBedPlaceableMI(ModelInstance &mi) : m_mi{&mi} {}
BoundingBoxf bounding_box() const override { return to_2d(instance_bounding_box(*m_mi)); }
void displace(const Vec2d &transl, double rot) override
{
transform_instance(*m_mi, transl, rot);
}
};
template<class InstPtr, class VBedHPtr>
class ArrangeableModelInstance : public Arrangeable, VBedPlaceable
{
InstPtr *m_mi;
VBedHPtr *m_vbedh;
const SelectionMask *m_selmask;
InstPos m_pos_within_model;
public:
explicit ArrangeableModelInstance(InstPtr *mi,
VBedHPtr *vbedh,
const SelectionMask *selmask,
const InstPos &pos)
: m_mi{mi}, m_vbedh{vbedh}, m_selmask{selmask}, m_pos_within_model{pos}
{
assert(m_mi != nullptr && m_vbedh != nullptr);
}
// Arrangeable:
ObjectID id() const override { return m_mi->id(); }
ObjectID geometry_id() const override { return m_mi->get_object()->id(); }
ExPolygons full_outline() const override;
Polygon convex_outline() const override;
bool is_printable() const override { return m_mi->printable; }
bool is_selected() const override;
void transform(const Vec2d &tr, double rot) override;
int get_bed_index() const override { return m_vbedh->get_bed_index(*this); }
bool assign_bed(int bed_idx) override;
// VBedPlaceable:
BoundingBoxf bounding_box() const override { return to_2d(instance_bounding_box(*m_mi)); }
void displace(const Vec2d &transl, double rot) override
{
if constexpr (!std::is_const_v<InstPtr>)
transform_instance(*m_mi, transl, rot);
}
};
extern template class ArrangeableModelInstance<ModelInstance, VirtualBedHandler>;
extern template class ArrangeableModelInstance<const ModelInstance, const VirtualBedHandler>;
class ArrangeableSLAPrintObject : public Arrangeable
{
const SLAPrintObject *m_po;
Arrangeable *m_arrbl;
Transform3d m_inst_trafo;
public:
ArrangeableSLAPrintObject(const SLAPrintObject *po,
Arrangeable *arrbl,
const Transform3d &inst_tr = Transform3d::Identity())
: m_po{po}, m_arrbl{arrbl}, m_inst_trafo{inst_tr}
{}
ObjectID id() const override { return m_arrbl->id(); }
ObjectID geometry_id() const override { return m_arrbl->geometry_id(); }
ExPolygons full_outline() const override;
ExPolygons full_envelope() const override;
Polygon convex_outline() const override;
Polygon convex_envelope() const override;
void transform(const Vec2d &transl, double rot) override
{
m_arrbl->transform(transl, rot);
}
int get_bed_index() const override { return m_arrbl->get_bed_index(); }
bool assign_bed(int bedidx) override
{
return m_arrbl->assign_bed(bedidx);
}
bool is_printable() const override { return m_arrbl->is_printable(); }
bool is_selected() const override { return m_arrbl->is_selected(); }
int priority() const override { return m_arrbl->priority(); }
};
class ArrangeableSLAPrint : public ArrangeableSlicerModel {
const SLAPrint *m_slaprint;
friend class SceneBuilder;
template<class Self, class Fn>
static void for_each_arrangeable_(Self &&self, Fn &&fn);
template<class Self, class Fn>
static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn);
public:
explicit ArrangeableSLAPrint(const SLAPrint *slaprint,
SceneBuilder &builder)
: m_slaprint{slaprint}, ArrangeableSlicerModel{builder}
{
assert(slaprint != nullptr);
}
void for_each_arrangeable(std::function<void(Arrangeable &)>) override;
void for_each_arrangeable(
std::function<void(const Arrangeable &)>) const override;
void visit_arrangeable(
const ObjectID &id,
std::function<void(const Arrangeable &)>) const override;
void visit_arrangeable(const ObjectID &id,
std::function<void(Arrangeable &)>) override;
};
template<class Mdl>
auto find_instance_by_id(Mdl &&model, const ObjectID &id)
{
std::remove_reference_t<
decltype(std::declval<Mdl>().objects[0]->instances[0])>
ret = nullptr;
InstPos pos;
for (auto * obj : model.objects) {
for (auto *inst : obj->instances) {
if (inst->id() == id) {
ret = inst;
break;
}
++pos.inst_idx;
}
if (ret)
break;
++pos.obj_idx;
pos.inst_idx = 0;
}
return std::make_pair(ret, pos);
}
struct ModelDuplicate
{
ObjectID id;
Vec2d tr = Vec2d::Zero();
double rot = 0.;
int bed_idx = Unarranged;
};
// Implementing the Arrangeable interface with the whole Model being one outline
// with all its objects and instances.
template<class Mdl, class Dup, class VBH>
class ArrangeableFullModel: public Arrangeable, VBedPlaceable
{
Mdl *m_mdl;
Dup *m_dup;
VBH *m_vbh;
public:
explicit ArrangeableFullModel(Mdl *mdl,
Dup *md,
VBH *vbh)
: m_mdl{mdl}, m_dup{md}, m_vbh{vbh}
{
assert(m_mdl != nullptr);
}
ObjectID id() const override { return m_dup->id.id + 1; }
ObjectID geometry_id() const override;
ExPolygons full_outline() const override;
Polygon convex_outline() const override;
bool is_printable() const override { return true; }
bool is_selected() const override { return m_dup->id == 0; }
int get_bed_index() const override
{
return m_vbh->get_bed_index(*this);
}
void transform(const Vec2d &tr, double rot) override
{
if constexpr (!std::is_const_v<Mdl> && !std::is_const_v<Dup>) {
m_dup->tr += tr;
m_dup->rot += rot;
}
}
bool assign_bed(int bed_idx) override
{
bool ret = false;
if constexpr (!std::is_const_v<VBH> && !std::is_const_v<Dup>) {
if ((ret = m_vbh->assign_bed(*this, bed_idx)))
m_dup->bed_idx = bed_idx;
}
return ret;
}
BoundingBoxf bounding_box() const override { return unscaled(get_extents(convex_outline())); }
void displace(const Vec2d &transl, double rot) override
{
transform(transl, rot);
}
};
extern template class ArrangeableFullModel<Model, ModelDuplicate, VirtualBedHandler>;
extern template class ArrangeableFullModel<const Model, const ModelDuplicate, const VirtualBedHandler>;
class DuplicableModel: public ArrangeableModel {
AnyPtr<Model> m_model;
AnyPtr<VirtualBedHandler> m_vbh;
std::vector<ModelDuplicate> m_duplicates;
BoundingBox m_bedbb;
template<class Self, class Fn>
static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn)
{
if (id.valid()) {
size_t idx = id.id - 1;
if (idx < self.m_duplicates.size()) {
auto &md = self.m_duplicates[idx];
ArrangeableFullModel arrbl{self.m_model.get(), &md, self.m_vbh.get()};
fn(arrbl);
}
}
}
public:
explicit DuplicableModel(AnyPtr<Model> mdl,
AnyPtr<VirtualBedHandler> vbh,
const BoundingBox &bedbb);
~DuplicableModel();
void for_each_arrangeable(std::function<void(Arrangeable &)> fn) override
{
for (ModelDuplicate &md : m_duplicates) {
ArrangeableFullModel arrbl{m_model.get(), &md, m_vbh.get()};
fn(arrbl);
}
}
void for_each_arrangeable(std::function<void(const Arrangeable&)> fn) const override
{
for (const ModelDuplicate &md : m_duplicates) {
ArrangeableFullModel arrbl{m_model.get(), &md, m_vbh.get()};
fn(arrbl);
}
}
void visit_arrangeable(const ObjectID &id, std::function<void(const Arrangeable &)> fn) const override
{
visit_arrangeable_(*this, id, fn);
}
void visit_arrangeable(const ObjectID &id, std::function<void(Arrangeable &)> fn) override
{
visit_arrangeable_(*this, id, fn);
}
ObjectID add_arrangeable(const ObjectID &prototype_id) override;
void apply_duplicates();
};
} // namespace arr2
} // namespace Slic3r
#endif // SCENEBUILDER_HPP

View File

@ -0,0 +1,105 @@
#ifndef SEGMENTEDRECTANGLEBED_HPP
#define SEGMENTEDRECTANGLEBED_HPP
#include "libslic3r/Arrange/Core/Beds.hpp"
namespace Slic3r { namespace arr2 {
enum class RectPivots {
Center, BottomLeft, BottomRight, TopLeft, TopRight
};
template<class T> struct IsSegmentedBed_ : public std::false_type {};
template<class T> constexpr bool IsSegmentedBed = IsSegmentedBed_<StripCVRef<T>>::value;
template<class SegX = void, class SegY = void, class Pivot = void>
struct SegmentedRectangleBed {
Vec<2, size_t> segments = Vec<2, size_t>::Ones();
BoundingBox bb;
RectPivots pivot = RectPivots::Center;
SegmentedRectangleBed() = default;
SegmentedRectangleBed(const BoundingBox &bb,
size_t segments_x,
size_t segments_y,
const RectPivots pivot = RectPivots::Center)
: segments{segments_x, segments_y}, bb{bb}, pivot{pivot}
{}
size_t segments_x() const noexcept { return segments.x(); }
size_t segments_y() const noexcept { return segments.y(); }
auto alignment() const noexcept { return pivot; }
};
template<size_t SegX, size_t SegY>
struct SegmentedRectangleBed<std::integral_constant<size_t, SegX>,
std::integral_constant<size_t, SegY>>
{
BoundingBox bb;
RectPivots pivot = RectPivots::Center;
SegmentedRectangleBed() = default;
explicit SegmentedRectangleBed(const BoundingBox &b,
const RectPivots pivot = RectPivots::Center)
: bb{b}
{}
size_t segments_x() const noexcept { return SegX; }
size_t segments_y() const noexcept { return SegY; }
auto alignment() const noexcept { return pivot; }
};
template<size_t SegX, size_t SegY, RectPivots pivot>
struct SegmentedRectangleBed<std::integral_constant<size_t, SegX>,
std::integral_constant<size_t, SegY>,
std::integral_constant<RectPivots, pivot>>
{
BoundingBox bb;
SegmentedRectangleBed() = default;
explicit SegmentedRectangleBed(const BoundingBox &b) : bb{b} {}
size_t segments_x() const noexcept { return SegX; }
size_t segments_y() const noexcept { return SegY; }
auto alignment() const noexcept { return pivot; }
};
template<class... Args>
struct IsSegmentedBed_<SegmentedRectangleBed<Args...>>
: public std::true_type {};
template<class... Args>
auto offset(const SegmentedRectangleBed<Args...> &bed, coord_t val_scaled)
{
auto cpy = bed;
cpy.bb.offset(val_scaled);
return cpy;
}
template<class...Args>
auto bounding_box(const SegmentedRectangleBed<Args...> &bed)
{
return bed.bb;
}
template<class...Args>
auto area(const SegmentedRectangleBed<Args...> &bed)
{
return arr2::area(bed.bb);
}
template<class...Args>
ExPolygons to_expolygons(const SegmentedRectangleBed<Args...> &bed)
{
return to_expolygons(RectangleBed{bed.bb});
}
}} // namespace Slic3r::arr2
#endif // SEGMENTEDRECTANGLEBED_HPP

View File

@ -0,0 +1,81 @@
#ifndef ARRANGETASK_HPP
#define ARRANGETASK_HPP
#include "libslic3r/Arrange/Arrange.hpp"
#include "libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp"
namespace Slic3r { namespace arr2 {
struct ArrangeTaskResult : public ArrangeResult
{
std::vector<TrafoOnlyArrangeItem> items;
bool apply_on(ArrangeableModel &mdl) override
{
bool ret = true;
for (auto &itm : items) {
if (is_arranged(itm))
ret = ret && apply_arrangeitem(itm, mdl);
}
return ret;
}
template<class ArrItem>
void add_item(const ArrItem &itm)
{
items.emplace_back(itm);
if (auto id = retrieve_id(itm))
imbue_id(items.back(), *id);
}
template<class It>
void add_items(const Range<It> &items_range)
{
for (auto &itm : items_range)
add_item(itm);
}
};
template<class ArrItem> struct ArrangeTask : public ArrangeTaskBase
{
struct ArrangeSet
{
std::vector<ArrItem> selected, unselected;
} printable, unprintable;
ExtendedBed bed;
ArrangeSettings settings;
static std::unique_ptr<ArrangeTask> create(
const Scene &sc,
const ArrangeableToItemConverter<ArrItem> &converter);
static std::unique_ptr<ArrangeTask> create(const Scene &sc)
{
auto conv = ArrangeableToItemConverter<ArrItem>::create(sc);
return create(sc, *conv);
}
std::unique_ptr<ArrangeResult> process(Ctl &ctl) override
{
return process_native(ctl);
}
std::unique_ptr<ArrangeTaskResult> process_native(Ctl &ctl);
std::unique_ptr<ArrangeTaskResult> process_native(Ctl &&ctl)
{
return process_native(ctl);
}
int item_count_to_process() const override
{
return static_cast<int>(printable.selected.size() +
unprintable.selected.size());
}
};
} // namespace arr2
} // namespace Slic3r
#endif // ARRANGETASK_HPP

View File

@ -0,0 +1,134 @@
#ifndef ARRANGETASK_IMPL_HPP
#define ARRANGETASK_IMPL_HPP
#include <random>
#include "ArrangeTask.hpp"
namespace Slic3r { namespace arr2 {
// Prepare the selected and unselected items separately. If nothing is
// selected, behaves as if everything would be selected.
template<class ArrItem>
void extract_selected(ArrangeTask<ArrItem> &task,
const ArrangeableModel &mdl,
const ArrangeableToItemConverter<ArrItem> &itm_conv)
{
// Go through the objects and check if inside the selection
mdl.for_each_arrangeable(
[&task, &itm_conv](const Arrangeable &arrbl) {
bool selected = arrbl.is_selected();
bool printable = arrbl.is_printable();
auto itm = itm_conv.convert(arrbl, selected ? 0 : -SCALED_EPSILON);
auto &container_parent = printable ? task.printable :
task.unprintable;
auto &container = selected ?
container_parent.selected :
container_parent.unselected;
container.emplace_back(std::move(itm));
});
// If the selection was empty arrange everything
if (task.printable.selected.empty() && task.unprintable.selected.empty()) {
task.printable.selected.swap(task.printable.unselected);
task.unprintable.selected.swap(task.unprintable.unselected);
}
}
template<class ArrItem>
std::unique_ptr<ArrangeTask<ArrItem>> ArrangeTask<ArrItem>::create(
const Scene &sc, const ArrangeableToItemConverter<ArrItem> &converter)
{
auto task = std::make_unique<ArrangeTask<ArrItem>>();
task->settings.set_from(sc.settings());
task->bed = get_corrected_bed(sc.bed(), converter);
extract_selected(*task, sc.model(), converter);
return task;
}
// Remove all items on the physical bed (not occupyable for unprintable items)
// and shift all items to the next lower bed index, so that arrange will think
// that logical bed no. 1 is the physical one
template<class ItemCont>
void prepare_fixed_unselected(ItemCont &items, int shift)
{
for (auto &itm : items)
set_bed_index(itm, get_bed_index(itm) - shift);
items.erase(std::remove_if(items.begin(), items.end(),
[](auto &itm) { return !is_arranged(itm); }),
items.end());
}
template<class ArrItem>
std::unique_ptr<ArrangeTaskResult>
ArrangeTask<ArrItem>::process_native(Ctl &ctl)
{
auto result = std::make_unique<ArrangeTaskResult>();
auto arranger = Arranger<ArrItem>::create(settings);
class TwoStepArrangeCtl: public Ctl
{
Ctl &parent;
ArrangeTask &self;
public:
TwoStepArrangeCtl(Ctl &p, ArrangeTask &slf) : parent{p}, self{slf} {}
void update_status(int remaining) override
{
parent.update_status(remaining + self.unprintable.selected.size());
}
bool was_canceled() const override { return parent.was_canceled(); }
} subctl{ctl, *this};
auto fixed_items = printable.unselected;
// static (unselected) unprintable objects should not be overlapped by
// movable and printable objects
std::copy(unprintable.unselected.begin(),
unprintable.unselected.end(),
std::back_inserter(fixed_items));
arranger->arrange(printable.selected, fixed_items, bed, subctl);
// Unprintable items should go to the first bed not containing any printable
// items
auto beds = std::max(get_bed_count(crange(printable.selected)),
get_bed_count(crange(printable.unselected)));
// If there are no printables, leave the physical bed empty
beds = std::max(beds, size_t{1});
prepare_fixed_unselected(unprintable.unselected, beds);
arranger->arrange(unprintable.selected, unprintable.unselected, bed, ctl);
result->add_items(crange(printable.selected));
for (auto &itm : unprintable.selected) {
if (is_arranged(itm)) {
int bedidx = get_bed_index(itm) + beds;
arr2::set_bed_index(itm, bedidx);
}
result->add_item(itm);
}
return result;
}
} // namespace arr2
} // namespace Slic3r
#endif //ARRANGETASK_IMPL_HPP

View File

@ -0,0 +1,53 @@
#ifndef FILLBEDTASK_HPP
#define FILLBEDTASK_HPP
#include "MultiplySelectionTask.hpp"
#include "libslic3r/Arrange/Arrange.hpp"
namespace Slic3r { namespace arr2 {
struct FillBedTaskResult: public MultiplySelectionTaskResult {};
template<class ArrItem>
struct FillBedTask: public ArrangeTaskBase
{
std::optional<ArrItem> prototype_item;
std::vector<ArrItem> selected, unselected;
ArrangeSettings settings;
ExtendedBed bed;
size_t selected_existing_count = 0;
std::unique_ptr<FillBedTaskResult> process_native(Ctl &ctl);
std::unique_ptr<FillBedTaskResult> process_native(Ctl &&ctl)
{
return process_native(ctl);
}
std::unique_ptr<ArrangeResult> process(Ctl &ctl) override
{
return process_native(ctl);
}
int item_count_to_process() const override
{
return selected.size();
}
static std::unique_ptr<FillBedTask> create(
const Scene &sc,
const ArrangeableToItemConverter<ArrItem> &converter);
static std::unique_ptr<FillBedTask> create(const Scene &sc)
{
auto conv = ArrangeableToItemConverter<ArrItem>::create(sc);
return create(sc, *conv);
}
};
} // namespace arr2
} // namespace Slic3r
#endif // FILLBEDTASK_HPP

View File

@ -0,0 +1,194 @@
#ifndef FILLBEDTASKIMPL_HPP
#define FILLBEDTASKIMPL_HPP
#include "FillBedTask.hpp"
#include "Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
namespace Slic3r { namespace arr2 {
template<class ArrItem>
int calculate_items_needed_to_fill_bed(const ExtendedBed &bed,
const ArrItem &prototype_item,
size_t prototype_count,
const std::vector<ArrItem> &fixed)
{
double poly_area = fixed_area(prototype_item);
auto area_sum_fn = [](double s, const auto &itm) {
return s + (get_bed_index(itm) == 0) * fixed_area(itm);
};
double unsel_area = std::accumulate(fixed.begin(),
fixed.end(),
0.,
area_sum_fn);
double fixed_area = unsel_area + prototype_count * poly_area;
double bed_area = 0.;
visit_bed([&bed_area] (auto &realbed) { bed_area = area(realbed); }, bed);
// This is the maximum number of items,
// the real number will always be close but less.
auto needed_items = static_cast<int>(
std::ceil((bed_area - fixed_area) / poly_area));
return needed_items;
}
template<class ArrItem>
void extract(FillBedTask<ArrItem> &task,
const Scene &scene,
const ArrangeableToItemConverter<ArrItem> &itm_conv)
{
task.prototype_item = {};
auto selected_ids = scene.selected_ids();
if (selected_ids.empty())
return;
std::set<ObjectID> selected_objects = selected_geometry_ids(scene);
if (selected_objects.size() != 1)
return;
ObjectID prototype_geometry_id = *(selected_objects.begin());
auto set_prototype_item = [&task, &itm_conv](const Arrangeable &arrbl) {
if (arrbl.is_printable())
task.prototype_item = itm_conv.convert(arrbl);
};
scene.model().visit_arrangeable(selected_ids.front(), set_prototype_item);
if (!task.prototype_item)
return;
// Workaround for missing items when arranging the same geometry only:
// Injecting a number of items but with slightly shrinked shape, so that
// they can fill the emerging holes. Priority is set to lowest so that
// these filler items will only be inserted as the last ones.
ArrItem prototype_item_shrinked;
scene.model().visit_arrangeable(selected_ids.front(),
[&prototype_item_shrinked, &itm_conv](const Arrangeable &arrbl) {
if (arrbl.is_printable())
prototype_item_shrinked = itm_conv.convert(arrbl, -SCALED_EPSILON);
});
set_bed_index(*task.prototype_item, Unarranged);
auto collect_task_items = [&prototype_geometry_id, &task,
&itm_conv](const Arrangeable &arrbl) {
if (arrbl.geometry_id() == prototype_geometry_id) {
if (arrbl.is_printable()) {
auto itm = itm_conv.convert(arrbl);
raise_priority(itm);
task.selected.emplace_back(std::move(itm));
}
} else {
auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON);
task.unselected.emplace_back(std::move(itm));
}
};
// Set the lowest priority to the shrinked prototype (hole filler) item
set_priority(prototype_item_shrinked,
lowest_priority(range(task.selected)) - 1);
scene.model().for_each_arrangeable(collect_task_items);
int needed_items = calculate_items_needed_to_fill_bed(task.bed,
*task.prototype_item,
task.selected.size(),
task.unselected);
task.selected_existing_count = task.selected.size();
task.selected.reserve(task.selected.size() + needed_items);
std::fill_n(std::back_inserter(task.selected), needed_items,
*task.prototype_item);
// Add as many filler items as there are needed items. Most of them will
// be discarded anyways.
std::fill_n(std::back_inserter(task.selected), needed_items,
prototype_item_shrinked);
}
template<class ArrItem>
std::unique_ptr<FillBedTask<ArrItem>> FillBedTask<ArrItem>::create(
const Scene &sc, const ArrangeableToItemConverter<ArrItem> &converter)
{
auto task = std::make_unique<FillBedTask<ArrItem>>();
task->settings.set_from(sc.settings());
task->bed = get_corrected_bed(sc.bed(), converter);
extract(*task, sc, converter);
return task;
}
template<class ArrItem>
std::unique_ptr<FillBedTaskResult> FillBedTask<ArrItem>::process_native(
Ctl &ctl)
{
auto result = std::make_unique<FillBedTaskResult>();
if (!prototype_item)
return result;
result->prototype_id = retrieve_id(*prototype_item).value_or(ObjectID{});
class FillBedCtl: public ArrangerCtl<ArrItem>
{
ArrangeTaskCtl &parent;
FillBedTask &self;
bool do_stop = false;
public:
FillBedCtl(ArrangeTaskCtl &p, FillBedTask &slf) : parent{p}, self{slf} {}
void update_status(int remaining) override
{
parent.update_status(remaining);
}
bool was_canceled() const override
{
return parent.was_canceled() || do_stop;
}
void on_packed(ArrItem &itm) override
{
// Stop at the first filler that is not on the physical bed
do_stop = get_bed_index(itm) > PhysicalBedId && get_priority(itm) < 0;
}
} subctl(ctl, *this);
auto arranger = Arranger<ArrItem>::create(settings);
arranger->arrange(selected, unselected, bed, subctl);
auto arranged_range = Range{selected.begin(),
selected.begin() + selected_existing_count};
result->add_arranged_items(arranged_range);
auto to_add_range = Range{selected.begin() + selected_existing_count,
selected.end()};
for (auto &itm : to_add_range)
if (get_bed_index(itm) == PhysicalBedId)
result->add_new_item(itm);
return result;
}
} // namespace arr2
} // namespace Slic3r
#endif // FILLBEDTASKIMPL_HPP

View File

@ -0,0 +1,108 @@
#ifndef MULTIPLYSELECTIONTASK_HPP
#define MULTIPLYSELECTIONTASK_HPP
#include "libslic3r/Arrange/Arrange.hpp"
#include "libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp"
namespace Slic3r { namespace arr2 {
struct MultiplySelectionTaskResult: public ArrangeResult {
ObjectID prototype_id;
std::vector<TrafoOnlyArrangeItem> arranged_items;
std::vector<TrafoOnlyArrangeItem> to_add;
bool apply_on(ArrangeableModel &mdl) override
{
bool ret = prototype_id.valid();
if (!ret)
return ret;
for (auto &itm : to_add) {
auto id = mdl.add_arrangeable(prototype_id);
imbue_id(itm, id);
ret = ret && apply_arrangeitem(itm, mdl);
}
for (auto &itm : arranged_items) {
if (is_arranged(itm))
ret = ret && apply_arrangeitem(itm, mdl);
}
return ret;
}
template<class ArrItem>
void add_arranged_item(const ArrItem &itm)
{
arranged_items.emplace_back(itm);
if (auto id = retrieve_id(itm))
imbue_id(arranged_items.back(), *id);
}
template<class It>
void add_arranged_items(const Range<It> &items_range)
{
arranged_items.reserve(items_range.size());
for (auto &itm : items_range)
add_arranged_item(itm);
}
template<class ArrItem> void add_new_item(const ArrItem &itm)
{
to_add.emplace_back(itm);
}
template<class It> void add_new_items(const Range<It> &items_range)
{
to_add.reserve(items_range.size());
for (auto &itm : items_range) {
to_add.emplace_back(itm);
}
}
};
template<class ArrItem>
struct MultiplySelectionTask: public ArrangeTaskBase
{
std::optional<ArrItem> prototype_item;
std::vector<ArrItem> selected, unselected;
ArrangeSettings settings;
ExtendedBed bed;
size_t selected_existing_count = 0;
std::unique_ptr<MultiplySelectionTaskResult> process_native(Ctl &ctl);
std::unique_ptr<MultiplySelectionTaskResult> process_native(Ctl &&ctl)
{
return process_native(ctl);
}
std::unique_ptr<ArrangeResult> process(Ctl &ctl) override
{
return process_native(ctl);
}
int item_count_to_process() const override
{
return selected.size();
}
static std::unique_ptr<MultiplySelectionTask> create(
const Scene &sc,
size_t multiply_count,
const ArrangeableToItemConverter<ArrItem> &converter);
static std::unique_ptr<MultiplySelectionTask> create(const Scene &sc,
size_t multiply_count)
{
auto conv = ArrangeableToItemConverter<ArrItem>::create(sc);
return create(sc, multiply_count, *conv);
}
};
}} // namespace Slic3r::arr2
#endif // MULTIPLYSELECTIONTASK_HPP

View File

@ -0,0 +1,120 @@
#ifndef MULTIPLYSELECTIONTASKIMPL_HPP
#define MULTIPLYSELECTIONTASKIMPL_HPP
#include "MultiplySelectionTask.hpp"
namespace Slic3r { namespace arr2 {
template<class ArrItem>
std::unique_ptr<MultiplySelectionTask<ArrItem>> MultiplySelectionTask<ArrItem>::create(
const Scene &scene, size_t count, const ArrangeableToItemConverter<ArrItem> &itm_conv)
{
auto task_ptr = std::make_unique<MultiplySelectionTask<ArrItem>>();
auto &task = *task_ptr;
task.settings.set_from(scene.settings());
task.bed = get_corrected_bed(scene.bed(), itm_conv);
task.prototype_item = {};
auto selected_ids = scene.selected_ids();
if (selected_ids.empty())
return task_ptr;
std::set<ObjectID> selected_objects = selected_geometry_ids(scene);
if (selected_objects.size() != 1)
return task_ptr;
ObjectID prototype_geometry_id = *(selected_objects.begin());
auto set_prototype_item = [&task, &itm_conv](const Arrangeable &arrbl) {
if (arrbl.is_printable())
task.prototype_item = itm_conv.convert(arrbl);
};
scene.model().visit_arrangeable(selected_ids.front(), set_prototype_item);
if (!task.prototype_item)
return task_ptr;
set_bed_index(*task.prototype_item, Unarranged);
auto collect_task_items = [&prototype_geometry_id, &task,
&itm_conv](const Arrangeable &arrbl) {
if (arrbl.geometry_id() == prototype_geometry_id) {
if (arrbl.is_printable()) {
auto itm = itm_conv.convert(arrbl);
raise_priority(itm);
task.selected.emplace_back(std::move(itm));
}
} else {
auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON);
task.unselected.emplace_back(std::move(itm));
}
};
scene.model().for_each_arrangeable(collect_task_items);
task.selected_existing_count = task.selected.size();
task.selected.reserve(task.selected.size() + count);
std::fill_n(std::back_inserter(task.selected), count, *task.prototype_item);
return task_ptr;
}
template<class ArrItem>
std::unique_ptr<MultiplySelectionTaskResult>
MultiplySelectionTask<ArrItem>::process_native(Ctl &ctl)
{
auto result = std::make_unique<MultiplySelectionTaskResult>();
if (!prototype_item)
return result;
result->prototype_id = retrieve_id(*prototype_item).value_or(ObjectID{});
class MultiplySelectionCtl: public ArrangerCtl<ArrItem>
{
ArrangeTaskCtl &parent;
MultiplySelectionTask<ArrItem> &self;
public:
MultiplySelectionCtl(ArrangeTaskCtl &p, MultiplySelectionTask<ArrItem> &slf)
: parent{p}, self{slf} {}
void update_status(int remaining) override
{
parent.update_status(remaining);
}
bool was_canceled() const override
{
return parent.was_canceled();
}
} subctl(ctl, *this);
auto arranger = Arranger<ArrItem>::create(settings);
arranger->arrange(selected, unselected, bed, subctl);
auto arranged_range = Range{selected.begin(),
selected.begin() + selected_existing_count};
result->add_arranged_items(arranged_range);
auto to_add_range = Range{selected.begin() + selected_existing_count,
selected.end()};
result->add_new_items(to_add_range);
return result;
}
}} // namespace Slic3r::arr2
#endif // MULTIPLYSELECTIONTASKIMPL_HPP

View File

@ -3,6 +3,8 @@
#include <libslic3r/Point.hpp> #include <libslic3r/Point.hpp>
#include <libslic3r/BoundingBox.hpp> #include <libslic3r/BoundingBox.hpp>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/Polyline.hpp>
#include <boost/geometry.hpp> #include <boost/geometry.hpp>
@ -126,13 +128,146 @@ struct indexed_access<BB3<T>, 1, d> {
} }
}; };
}
} /* ************************************************************************** */
/* Segment concept adaptaion ************************************************ */
/* ************************************************************************** */
template<> struct tag<Slic3r::Line> {
using type = segment_tag;
};
template<> struct point_type<Slic3r::Line> {
using type = Slic3r::Point;
};
template<> struct indexed_access<Slic3r::Line, 0, 0> {
static inline coord_t get(Slic3r::Line const& l) { return l.a.x(); }
static inline void set(Slic3r::Line &l, coord_t c) { l.a.x() = c; }
};
template<> struct indexed_access<Slic3r::Line, 0, 1> {
static inline coord_t get(Slic3r::Line const& l) { return l.a.y(); }
static inline void set(Slic3r::Line &l, coord_t c) { l.a.y() = c; }
};
template<> struct indexed_access<Slic3r::Line, 1, 0> {
static inline coord_t get(Slic3r::Line const& l) { return l.b.x(); }
static inline void set(Slic3r::Line &l, coord_t c) { l.b.x() = c; }
};
template<> struct indexed_access<Slic3r::Line, 1, 1> {
static inline coord_t get(Slic3r::Line const& l) { return l.b.y(); }
static inline void set(Slic3r::Line &l, coord_t c) { l.b.y() = c; }
};
/* ************************************************************************** */
/* Polyline concept adaptation ********************************************** */
/* ************************************************************************** */
template<> struct tag<Slic3r::Polyline> {
using type = linestring_tag;
};
/* ************************************************************************** */
/* Polygon concept adaptation *********************************************** */
/* ************************************************************************** */
// Ring implementation /////////////////////////////////////////////////////////
// Boost would refer to ClipperLib::Path (alias Slic3r::ExPolygon) as a ring
template<> struct tag<Slic3r::Polygon> {
using type = ring_tag;
};
template<> struct point_order<Slic3r::Polygon> {
static const order_selector value = counterclockwise;
};
// All our Paths should be closed for the bin packing application
template<> struct closure<Slic3r::Polygon> {
static const constexpr closure_selector value = closure_selector::open;
};
// Polygon implementation //////////////////////////////////////////////////////
template<> struct tag<Slic3r::ExPolygon> {
using type = polygon_tag;
};
template<> struct exterior_ring<Slic3r::ExPolygon> {
static inline Slic3r::Polygon& get(Slic3r::ExPolygon& p)
{
return p.contour;
}
static inline Slic3r::Polygon const& get(Slic3r::ExPolygon const& p)
{
return p.contour;
}
};
template<> struct ring_const_type<Slic3r::ExPolygon> {
using type = const Slic3r::Polygon&;
};
template<> struct ring_mutable_type<Slic3r::ExPolygon> {
using type = Slic3r::Polygon&;
};
template<> struct interior_const_type<Slic3r::ExPolygon> {
using type = const Slic3r::Polygons&;
};
template<> struct interior_mutable_type<Slic3r::ExPolygon> {
using type = Slic3r::Polygons&;
};
template<>
struct interior_rings<Slic3r::ExPolygon> {
static inline Slic3r::Polygons& get(Slic3r::ExPolygon& p) { return p.holes; }
static inline const Slic3r::Polygons& get(Slic3r::ExPolygon const& p)
{
return p.holes;
}
};
/* ************************************************************************** */
/* MultiPolygon concept adaptation ****************************************** */
/* ************************************************************************** */
template<> struct tag<Slic3r::ExPolygons> {
using type = multi_polygon_tag;
};
}} // namespace geometry::traits
template<> struct range_value<std::vector<Slic3r::Vec2d>> { template<> struct range_value<std::vector<Slic3r::Vec2d>> {
using type = Slic3r::Vec2d; using type = Slic3r::Vec2d;
}; };
template<>
struct range_value<Slic3r::Polyline> {
using type = Slic3r::Point;
};
// This is an addition to the ring implementation of Polygon concept
template<>
struct range_value<Slic3r::Polygon> {
using type = Slic3r::Point;
};
template<>
struct range_value<Slic3r::Polygons> {
using type = Slic3r::Polygon;
};
template<>
struct range_value<Slic3r::ExPolygons> {
using type = Slic3r::ExPolygon;
};
} // namespace boost } // namespace boost
#endif // SLABOOSTADAPTER_HPP #endif // SLABOOSTADAPTER_HPP

View File

@ -54,8 +54,8 @@ public:
return ! (this->max.x() < other.min.x() || this->min.x() > other.max.x() || return ! (this->max.x() < other.min.x() || this->min.x() > other.max.x() ||
this->max.y() < other.min.y() || this->min.y() > other.max.y()); this->max.y() < other.min.y() || this->min.y() > other.max.y());
} }
bool operator==(const BoundingBoxBase<PointType, PointsType> &rhs) { return this->min == rhs.min && this->max == rhs.max; } bool operator==(const BoundingBoxBase<PointType, PointsType> &rhs) const noexcept { return this->min == rhs.min && this->max == rhs.max; }
bool operator!=(const BoundingBoxBase<PointType, PointsType> &rhs) { return ! (*this == rhs); } bool operator!=(const BoundingBoxBase<PointType, PointsType> &rhs) const noexcept { return ! (*this == rhs); }
private: private:
// to access construct() // to access construct()
@ -192,6 +192,7 @@ public:
BoundingBox() : BoundingBoxBase<Point, Points>() {} BoundingBox() : BoundingBoxBase<Point, Points>() {}
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point, Points>(pmin, pmax) {} BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point, Points>(pmin, pmax) {}
BoundingBox(const BoundingBoxBase<Vec2crd> &bb): BoundingBox(bb.min, bb.max) {}
BoundingBox(const Points &points) : BoundingBoxBase<Point, Points>(points) {} BoundingBox(const Points &points) : BoundingBoxBase<Point, Points>(points) {}
BoundingBox inflated(coordf_t delta) const throw() { BoundingBox out(*this); out.offset(delta); return out; } BoundingBox inflated(coordf_t delta) const throw() { BoundingBox out(*this); out.offset(delta); return out; }
@ -215,6 +216,7 @@ public:
BoundingBoxf() : BoundingBoxBase<Vec2d>() {} BoundingBoxf() : BoundingBoxBase<Vec2d>() {}
BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {} BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {}
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {} BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {}
BoundingBoxf(const BoundingBoxBase<Vec2d> &bb): BoundingBoxf{bb.min, bb.max} {}
}; };
class BoundingBoxf3 : public BoundingBox3Base<Vec3d> class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
@ -239,17 +241,23 @@ inline bool empty(const BoundingBox3Base<PointType> &bb)
inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; } inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; }
template<class T = coord_t> template<class T = coord_t, class Tin>
BoundingBoxBase<Vec<2, T>> scaled(const BoundingBoxf &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; } BoundingBoxBase<Vec<2, T>> scaled(const BoundingBoxBase<Vec<2, Tin>> &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; }
template<class T = coord_t> template<class T = coord_t>
BoundingBox3Base<Vec<3, T>> scaled(const BoundingBoxf3 &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; } BoundingBoxBase<Vec<2, T>> scaled(const BoundingBox &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; }
template<class T = coord_t, class Tin>
BoundingBox3Base<Vec<3, T>> scaled(const BoundingBox3Base<Vec<3, Tin>> &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; }
template<class T = double, class Tin>
BoundingBoxBase<Vec<2, T>> unscaled(const BoundingBoxBase<Vec<2, Tin>> &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; }
template<class T = double> template<class T = double>
BoundingBoxBase<Vec<2, T>> unscaled(const BoundingBox &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; } BoundingBoxBase<Vec<2, T>> unscaled(const BoundingBox &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; }
template<class T = double> template<class T = double, class Tin>
BoundingBox3Base<Vec<3, T>> unscaled(const BoundingBox3 &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; } BoundingBox3Base<Vec<3, T>> unscaled(const BoundingBox3Base<Vec<3, Tin>> &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; }
template<class Tout, class Tin> template<class Tout, class Tin>
auto cast(const BoundingBoxBase<Tin> &b) auto cast(const BoundingBoxBase<Tin> &b)
@ -298,6 +306,19 @@ inline double bbox_point_distance_squared(const BoundingBox &bbox, const Point &
coord_t(0)); coord_t(0));
} }
template<class T>
BoundingBoxBase<Vec<2, T>> to_2d(const BoundingBox3Base<Vec<3, T>> &bb)
{
return {to_2d(bb.min), to_2d(bb.max)};
}
template<class Tout, class T>
BoundingBoxBase<Vec<2, Tout>> to_2d(const BoundingBox3Base<Vec<3, T>> &bb)
{
return {to_2d(bb.min), to_2d(bb.max)};
}
} // namespace Slic3r } // namespace Slic3r
// Serialization through the Cereal library // Serialization through the Cereal library

View File

@ -216,8 +216,55 @@ set(SLIC3R_SOURCES
MeasureUtils.hpp MeasureUtils.hpp
CustomGCode.cpp CustomGCode.cpp
CustomGCode.hpp CustomGCode.hpp
Arrange.hpp Arrange/Arrange.hpp
Arrange.cpp Arrange/ArrangeImpl.hpp
Arrange/Items/ArrangeItem.hpp
Arrange/Items/ArrangeItem.cpp
Arrange/Items/SimpleArrangeItem.hpp
Arrange/Items/SimpleArrangeItem.cpp
Arrange/Items/TrafoOnlyArrangeItem.hpp
Arrange/Items/MutableItemTraits.hpp
Arrange/Items/ArbitraryDataStore.hpp
Arrange/ArrangeSettingsView.hpp
Arrange/ArrangeSettingsDb_AppCfg.hpp
Arrange/ArrangeSettingsDb_AppCfg.cpp
Arrange/Scene.hpp
Arrange/Scene.cpp
Arrange/SceneBuilder.hpp
Arrange/SceneBuilder.cpp
Arrange/Tasks/ArrangeTask.hpp
Arrange/Tasks/ArrangeTaskImpl.hpp
Arrange/Tasks/FillBedTask.hpp
Arrange/Tasks/FillBedTaskImpl.hpp
Arrange/Tasks/MultiplySelectionTask.hpp
Arrange/Tasks/MultiplySelectionTaskImpl.hpp
Arrange/SegmentedRectangleBed.hpp
Arrange/Core/ArrangeItemTraits.hpp
Arrange/Core/DataStoreTraits.hpp
Arrange/Core/ArrangeBase.hpp
Arrange/Core/PackingContext.hpp
Arrange/Core/ArrangeFirstFit.hpp
Arrange/Core/Beds.hpp
Arrange/Core/Beds.cpp
Arrange/Core/NFP/NFP.hpp
Arrange/Core/NFP/NFP.cpp
Arrange/Core/NFP/NFPConcave_CGAL.hpp
Arrange/Core/NFP/NFPConcave_CGAL.cpp
Arrange/Core/NFP/NFPConcave_Tesselate.hpp
Arrange/Core/NFP/NFPConcave_Tesselate.cpp
Arrange/Core/NFP/EdgeCache.hpp
Arrange/Core/NFP/EdgeCache.cpp
Arrange/Core/NFP/CircularEdgeIterator.hpp
Arrange/Core/NFP/NFPArrangeItemTraits.hpp
Arrange/Core/NFP/PackStrategyNFP.hpp
Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp
Arrange/Core/NFP/Kernels/KernelTraits.hpp
Arrange/Core/NFP/Kernels/GravityKernel.hpp
Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp
Arrange/Core/NFP/Kernels/CompactifyKernel.hpp
Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp
Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp
Arrange/Core/NFP/Kernels/KernelUtils.hpp
MultiPoint.cpp MultiPoint.cpp
MultiPoint.hpp MultiPoint.hpp
MutablePriorityQueue.hpp MutablePriorityQueue.hpp
@ -433,6 +480,10 @@ set(SLIC3R_SOURCES
add_library(libslic3r STATIC ${SLIC3R_SOURCES}) add_library(libslic3r STATIC ${SLIC3R_SOURCES})
if (WIN32)
target_compile_definitions(libslic3r PUBLIC NOMINMAX)
endif()
foreach(_source IN ITEMS ${SLIC3R_SOURCES}) foreach(_source IN ITEMS ${SLIC3R_SOURCES})
get_filename_component(_source_path "${_source}" PATH) get_filename_component(_source_path "${_source}" PATH)
string(REPLACE "/" "\\" _group_path "${_source_path}") string(REPLACE "/" "\\" _group_path "${_source_path}")

View File

@ -773,8 +773,16 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Sli
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative). // May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type) Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type)
{ return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); } { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, ClipperLib::PolyFillType fill_type)
{ return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ApplySafetyOffset::No, fill_type); }
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject) Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject)
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &subject2)
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(subject2), ClipperLib::pftNonZero)); }
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &subject2)
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(subject2), ClipperLib::pftNonZero)); }
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &subject2)
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ClipperLib::pftNonZero)); }
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject) Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject)
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }

View File

@ -498,7 +498,11 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &subject2); Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &subject2);
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative). // May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero); Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero);
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero);
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject); Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject);
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &subject2);
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &subject2);
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &subject2);
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject); Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject);
// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed. // Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed.

View File

@ -16,7 +16,9 @@
// The standard Windows includes. // The standard Windows includes.
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX #define NOMINMAX
#endif
#include <Windows.h> #include <Windows.h>
#include <shellapi.h> #include <shellapi.h>

View File

@ -3,11 +3,10 @@
#include <vector> #include <vector>
#include "../Polygon.hpp" #include "../ExPolygon.hpp"
namespace Slic3r { namespace Slic3r {
class ExPolygon;
using ExPolygons = std::vector<ExPolygon>; using ExPolygons = std::vector<ExPolygon>;
namespace Geometry { namespace Geometry {
@ -16,7 +15,9 @@ Pointf3s convex_hull(Pointf3s points);
Polygon convex_hull(Points points); Polygon convex_hull(Points points);
Polygon convex_hull(const Polygons &polygons); Polygon convex_hull(const Polygons &polygons);
Polygon convex_hull(const ExPolygons &expolygons); Polygon convex_hull(const ExPolygons &expolygons);
Polygon convex_hulll(const Polylines &polylines); Polygon convex_hull(const Polylines &polylines);
inline Polygon convex_hull(const Polygon &poly) { return convex_hull(poly.points); }
inline Polygon convex_hull(const ExPolygon &poly) { return convex_hull(poly.contour.points); }
// Returns true if the intersection of the two convex polygons A and B // Returns true if the intersection of the two convex polygons A and B
// is not an empty set. // is not an empty set.

View File

@ -38,6 +38,32 @@ template<class L> using Scalar = typename Traits<remove_cvref_t<L>>::Scalar;
template<class L> auto get_a(L &&l) { return Traits<remove_cvref_t<L>>::get_a(l); } template<class L> auto get_a(L &&l) { return Traits<remove_cvref_t<L>>::get_a(l); }
template<class L> auto get_b(L &&l) { return Traits<remove_cvref_t<L>>::get_b(l); } template<class L> auto get_b(L &&l) { return Traits<remove_cvref_t<L>>::get_b(l); }
template<class L> auto sqlength(L &&l)
{
return (get_b(l) - get_a(l)).squaredNorm();
}
template<class Scalar, class L>
auto sqlength(L &&l)
{
return (get_b(l).template cast<Scalar>() - get_a(l).template cast<Scalar>()).squaredNorm();
}
template<class L, class = std::enable_if_t<Dim<L> == 2> >
auto angle_to_x(const L &l)
{
auto dx = double(get_b(l).x()) - get_a(l).x();
auto dy = double(get_b(l).y()) - get_a(l).y();
double a = std::atan2(dy, dx);
auto s = std::signbit(a);
if(s)
a += 2. * PI;
return a;
}
// Distance to the closest point of line. // Distance to the closest point of line.
template<class L> template<class L>
inline double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, Vec<Dim<L>, Scalar<L>> *nearest_point) inline double distance_to_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, Vec<Dim<L>, Scalar<L>> *nearest_point)
@ -162,7 +188,7 @@ public:
void translate(double x, double y) { this->translate(Point(x, y)); } void translate(double x, double y) { this->translate(Point(x, y)); }
void rotate(double angle, const Point &center) { this->a.rotate(angle, center); this->b.rotate(angle, center); } void rotate(double angle, const Point &center) { this->a.rotate(angle, center); this->b.rotate(angle, center); }
void reverse() { std::swap(this->a, this->b); } void reverse() { std::swap(this->a, this->b); }
double length() const { return (b - a).cast<double>().norm(); } double length() const { return (b.cast<double>() - a.cast<double>()).norm(); }
Point midpoint() const { return (this->a + this->b) / 2; } Point midpoint() const { return (this->a + this->b) / 2; }
bool intersection_infinite(const Line &other, Point* point) const; bool intersection_infinite(const Line &other, Point* point) const;
bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; } bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; }

View File

@ -1,6 +1,7 @@
#include "MinAreaBoundingBox.hpp" #include "MinAreaBoundingBox.hpp"
#include <libslic3r/ExPolygon.hpp> #include <libslic3r/ExPolygon.hpp>
#include <BoundingBox.hpp>
#if defined(_MSC_VER) && defined(__clang__) #if defined(_MSC_VER) && defined(__clang__)
#define BOOST_NO_CXX17_HDR_STRING_VIEW #define BOOST_NO_CXX17_HDR_STRING_VIEW
@ -103,4 +104,16 @@ void remove_collinear_points(ExPolygon &p)
{ {
p = libnest2d::removeCollinearPoints<ExPolygon>(p, Unit(0)); p = libnest2d::removeCollinearPoints<ExPolygon>(p, Unit(0));
} }
double fit_into_box_rotation(const Polygon &shape, const BoundingBox &bb)
{
using namespace libnest2d;
_Box<Point> box{{bb.min.x(), bb.min.y()}, {bb.max.x(), bb.max.y()}};
return fitIntoBoxRotation<Polygon, TCompute<Polygon>, Rational>(shape,
box,
EPSILON);
}
} // namespace Slic3r } // namespace Slic3r

View File

@ -50,6 +50,8 @@ public:
const Point& axis() const { return m_axis; } const Point& axis() const { return m_axis; }
}; };
} double fit_into_box_rotation(const Polygon &shape, const BoundingBox &box);
} // namespace Slic3r
#endif // MINAREABOUNDINGBOX_HPP #endif // MINAREABOUNDINGBOX_HPP

View File

@ -1062,7 +1062,8 @@ Polygon ModelObject::convex_hull_2d(const Transform3d& trafo_instance) const
tbb::parallel_for(tbb::blocked_range<size_t>(0, volumes.size()), [&](const tbb::blocked_range<size_t>& range) { tbb::parallel_for(tbb::blocked_range<size_t>(0, volumes.size()), [&](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); ++i) { for (size_t i = range.begin(); i < range.end(); ++i) {
const ModelVolume* v = volumes[i]; const ModelVolume* v = volumes[i];
chs.emplace_back(its_convex_hull_2d_above(v->mesh().its, (trafo_instance * v->get_matrix()).cast<float>(), 0.0f)); if (v->is_model_part())
chs.emplace_back(its_convex_hull_2d_above(v->mesh().its, (trafo_instance * v->get_matrix()).cast<float>(), 0.0f));
} }
}); });
@ -1997,38 +1998,6 @@ void ModelInstance::transform_polygon(Polygon* polygon) const
polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin
} }
arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
{
// static const double SIMPLIFY_TOLERANCE_MM = 0.1;
Polygon p = get_object()->convex_hull_2d(this->get_matrix());
// if (!p.points.empty()) {
// Polygons pp{p};
// pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM));
// if (!pp.empty()) p = pp.front();
// }
arrangement::ArrangePolygon ret;
ret.poly.contour = std::move(p);
ret.translation = Vec2crd::Zero();
ret.rotation = 0.;
return ret;
}
void ModelInstance::apply_arrange_result(const Vec2d &offs, double rotation)
{
// write the transformation data into the model instance
auto trafo = get_transformation().get_matrix();
auto tr = Transform3d::Identity();
tr.translate(to_3d(unscaled(offs), 0.));
trafo = tr * Eigen::AngleAxisd(rotation, Vec3d::UnitZ()) * trafo;
m_transformation.set_matrix(trafo);
this->object->invalidate_bounding_box();
}
indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const
{ {
TriangleSelector selector(mv.mesh()); TriangleSelector selector(mv.mesh());

View File

@ -11,7 +11,6 @@
#include "SLA/SupportPoint.hpp" #include "SLA/SupportPoint.hpp"
#include "SLA/Hollowing.hpp" #include "SLA/Hollowing.hpp"
#include "TriangleMesh.hpp" #include "TriangleMesh.hpp"
#include "Arrange.hpp"
#include "CustomGCode.hpp" #include "CustomGCode.hpp"
#include "enum_bitmask.hpp" #include "enum_bitmask.hpp"
#include "TextConfiguration.hpp" #include "TextConfiguration.hpp"
@ -1155,11 +1154,7 @@ public:
bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); } bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); }
// Getting the input polygon for arrange void invalidate_object_bounding_box() { object->invalidate_bounding_box(); }
arrangement::ArrangePolygon get_arrange_polygon() const;
// Apply the arrange result on the ModelInstance
void apply_arrange_result(const Vec2d& offs, double rotation);
protected: protected:
friend class Print; friend class Print;

View File

@ -1,77 +1,17 @@
#include "ModelArrange.hpp" #include "ModelArrange.hpp"
#include <libslic3r/Arrange/SceneBuilder.hpp>
#include <libslic3r/Model.hpp> #include <libslic3r/Model.hpp>
#include <libslic3r/Geometry/ConvexHull.hpp> #include <libslic3r/Geometry/ConvexHull.hpp>
#include "Arrange/Core/ArrangeItemTraits.hpp"
#include "Arrange/Items/ArrangeItem.hpp"
#include "MTUtils.hpp" #include "MTUtils.hpp"
namespace Slic3r { namespace Slic3r {
arrangement::ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances)
{
size_t count = 0;
for (auto obj : model.objects) count += obj->instances.size();
ArrangePolygons input;
input.reserve(count);
instances.clear(); instances.reserve(count);
for (ModelObject *mo : model.objects)
for (ModelInstance *minst : mo->instances) {
input.emplace_back(minst->get_arrange_polygon());
instances.emplace_back(minst);
}
return input;
}
bool apply_arrange_polys(ArrangePolygons &input, ModelInstancePtrs &instances, VirtualBedFn vfn)
{
bool ret = true;
for(size_t i = 0; i < input.size(); ++i) {
if (input[i].bed_idx != 0) { ret = false; if (vfn) vfn(input[i]); }
if (input[i].bed_idx >= 0)
instances[i]->apply_arrange_result(input[i].translation.cast<double>(),
input[i].rotation);
}
return ret;
}
Slic3r::arrangement::ArrangePolygon get_arrange_poly(const Model &model)
{
ArrangePolygon ap;
Points &apts = ap.poly.contour.points;
for (const ModelObject *mo : model.objects)
for (const ModelInstance *minst : mo->instances) {
ArrangePolygon obj_ap = minst->get_arrange_polygon();
ap.poly.contour.rotate(obj_ap.rotation);
ap.poly.contour.translate(obj_ap.translation.x(), obj_ap.translation.y());
const Points &pts = obj_ap.poly.contour.points;
std::copy(pts.begin(), pts.end(), std::back_inserter(apts));
}
apts = std::move(Geometry::convex_hull(apts).points);
return ap;
}
void duplicate(Model &model, Slic3r::arrangement::ArrangePolygons &copies, VirtualBedFn vfn)
{
for (ModelObject *o : model.objects) {
// make a copy of the pointers in order to avoid recursion when appending their copies
ModelInstancePtrs instances = o->instances;
o->instances.clear();
for (const ModelInstance *i : instances) {
for (arrangement::ArrangePolygon &ap : copies) {
if (ap.bed_idx != 0) vfn(ap);
ModelInstance *instance = o->add_instance(*i);
Vec2d pos = unscale(ap.translation);
instance->set_offset(instance->get_offset() + to_3d(pos, 0.));
}
}
o->invalidate_bounding_box();
}
}
void duplicate_objects(Model &model, size_t copies_num) void duplicate_objects(Model &model, size_t copies_num)
{ {
for (ModelObject *o : model.objects) { for (ModelObject *o : model.objects) {
@ -83,4 +23,49 @@ void duplicate_objects(Model &model, size_t copies_num)
} }
} }
bool arrange_objects(Model &model,
const arr2::ArrangeBed &bed,
const arr2::ArrangeSettingsView &settings)
{
arr2::Scene scene{arr2::SceneBuilder{}
.set_bed(bed)
.set_arrange_settings(settings)
.set_model(model)};
auto task = arr2::ArrangeTaskBase::create(arr2::Tasks::Arrange, scene);
auto result = task->process();
return result->apply_on(scene.model());
}
void duplicate_objects(Model &model,
size_t copies_num,
const arr2::ArrangeBed &bed,
const arr2::ArrangeSettingsView &settings)
{
duplicate_objects(model, copies_num);
arrange_objects(model, bed, settings);
}
void duplicate(Model &model,
size_t copies_num,
const arr2::ArrangeBed &bed,
const arr2::ArrangeSettingsView &settings)
{
auto vbh = arr2::VirtualBedHandler::create(bed);
arr2::DuplicableModel dup_model{&model, std::move(vbh), bounding_box(bed)};
arr2::Scene scene{arr2::BasicSceneBuilder{}
.set_arrangeable_model(&dup_model)
.set_arrange_settings(&settings)
.set_bed(bed)};
if (copies_num >= 1)
copies_num -= 1;
auto task = arr2::MultiplySelectionTask<arr2::ArrangeItem>::create(scene, copies_num);
auto result = task->process_native(arr2::DummyCtl{});
if (result->apply_on(scene.model()))
dup_model.apply_duplicates();
}
} // namespace Slic3r } // namespace Slic3r

View File

@ -1,7 +1,7 @@
#ifndef MODELARRANGE_HPP #ifndef MODELARRANGE_HPP
#define MODELARRANGE_HPP #define MODELARRANGE_HPP
#include <libslic3r/Arrange.hpp> #include <libslic3r/Arrange/Scene.hpp>
namespace Slic3r { namespace Slic3r {
@ -9,63 +9,23 @@ class Model;
class ModelInstance; class ModelInstance;
using ModelInstancePtrs = std::vector<ModelInstance*>; using ModelInstancePtrs = std::vector<ModelInstance*>;
using arrangement::ArrangePolygon; //void duplicate(Model &model, ArrangePolygons &copies, VirtualBedFn);
using arrangement::ArrangePolygons;
using arrangement::ArrangeParams;
using arrangement::InfiniteBed;
using arrangement::CircleBed;
// Do something with ArrangePolygons in virtual beds
using VirtualBedFn = std::function<void(arrangement::ArrangePolygon&)>;
[[noreturn]] inline void throw_if_out_of_bed(arrangement::ArrangePolygon&)
{
throw Slic3r::RuntimeError("Objects could not fit on the bed");
}
ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances);
ArrangePolygon get_arrange_poly(const Model &model);
bool apply_arrange_polys(ArrangePolygons &polys, ModelInstancePtrs &instances, VirtualBedFn);
void duplicate(Model &model, ArrangePolygons &copies, VirtualBedFn);
void duplicate_objects(Model &model, size_t copies_num); void duplicate_objects(Model &model, size_t copies_num);
template<class TBed> bool arrange_objects(Model &model,
bool arrange_objects(Model & model, const arr2::ArrangeBed &bed,
const TBed & bed, const arr2::ArrangeSettingsView &settings);
const ArrangeParams &params,
VirtualBedFn vfn = throw_if_out_of_bed)
{
ModelInstancePtrs instances;
auto&& input = get_arrange_polys(model, instances);
arrangement::arrange(input, bed, params);
return apply_arrange_polys(input, instances, vfn);
}
template<class TBed>
void duplicate(Model & model,
size_t copies_num,
const TBed & bed,
const ArrangeParams &params,
VirtualBedFn vfn = throw_if_out_of_bed)
{
ArrangePolygons copies(copies_num, get_arrange_poly(model));
arrangement::arrange(copies, bed, params);
duplicate(model, copies, vfn);
}
template<class TBed>
void duplicate_objects(Model & model, void duplicate_objects(Model & model,
size_t copies_num, size_t copies_num,
const TBed & bed, const arr2::ArrangeBed &bed,
const ArrangeParams &params, const arr2::ArrangeSettingsView &settings);
VirtualBedFn vfn = throw_if_out_of_bed)
{
duplicate_objects(model, copies_num);
arrange_objects(model, bed, params, vfn);
}
} void duplicate(Model & model,
size_t copies_num,
const arr2::ArrangeBed &bed,
const arr2::ArrangeSettingsView &settings);
} // namespace Slic3r
#endif // MODELARRANGE_HPP #endif // MODELARRANGE_HPP

View File

@ -1271,18 +1271,23 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
static void cut_segmented_layers(const std::vector<ExPolygons> &input_expolygons, static void cut_segmented_layers(const std::vector<ExPolygons> &input_expolygons,
std::vector<std::vector<ExPolygons>> &segmented_regions, std::vector<std::vector<ExPolygons>> &segmented_regions,
const float cut_width, const float cut_width,
const float interlocking_depth,
const std::function<void()> &throw_on_cancel_callback) const std::function<void()> &throw_on_cancel_callback)
{ {
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - begin"; BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &throw_on_cancel_callback](const tbb::blocked_range<size_t>& range) { const float interlocking_cut_width = interlocking_depth > 0.f ? std::max(cut_width - interlocking_depth, 0.f) : 0.f;
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &interlocking_cut_width, &throw_on_cancel_callback](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback(); throw_on_cancel_callback();
const size_t num_extruders_plus_one = segmented_regions[layer_idx].size(); const float region_cut_width = (layer_idx % 2 == 0 && interlocking_cut_width > 0.f) ? interlocking_cut_width : cut_width;
std::vector<ExPolygons> segmented_regions_cuts(num_extruders_plus_one); // Indexed by extruder_id const size_t num_extruders_plus_one = segmented_regions[layer_idx].size();
for (size_t extruder_idx = 0; extruder_idx < num_extruders_plus_one; ++extruder_idx) if (region_cut_width > 0.f) {
if (const ExPolygons &ex_polygons = segmented_regions[layer_idx][extruder_idx]; !ex_polygons.empty()) std::vector<ExPolygons> segmented_regions_cuts(num_extruders_plus_one); // Indexed by extruder_id
segmented_regions_cuts[extruder_idx] = diff_ex(ex_polygons, offset_ex(input_expolygons[layer_idx], cut_width)); for (size_t extruder_idx = 0; extruder_idx < num_extruders_plus_one; ++extruder_idx)
segmented_regions[layer_idx] = std::move(segmented_regions_cuts); if (const ExPolygons &ex_polygons = segmented_regions[layer_idx][extruder_idx]; !ex_polygons.empty())
segmented_regions_cuts[extruder_idx] = diff_ex(ex_polygons, offset_ex(input_expolygons[layer_idx], -region_cut_width));
segmented_regions[layer_idx] = std::move(segmented_regions_cuts);
}
} }
}); // end of parallel_for }); // end of parallel_for
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end"; BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end";
@ -1891,8 +1896,8 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - end"; BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - end";
throw_on_cancel_callback(); throw_on_cancel_callback();
if (auto w = print_object.config().mmu_segmented_region_max_width; w > 0.f) { if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f) {
cut_segmented_layers(input_expolygons, segmented_regions, float(-scale_(w)), throw_on_cancel_callback); cut_segmented_layers(input_expolygons, segmented_regions, float(scale_(max_width)), float(scale_(interlocking_depth)), throw_on_cancel_callback);
throw_on_cancel_callback(); throw_on_cancel_callback();
} }

View File

@ -90,6 +90,12 @@ public:
inline auto end() const { return points.end(); } inline auto end() const { return points.end(); }
inline auto cbegin() const { return points.begin(); } inline auto cbegin() const { return points.begin(); }
inline auto cend() const { return points.end(); } inline auto cend() const { return points.end(); }
inline auto rbegin() { return points.rbegin(); }
inline auto rbegin() const { return points.rbegin(); }
inline auto rend() { return points.rend(); }
inline auto rend() const { return points.rend(); }
inline auto crbegin()const { return points.crbegin(); }
inline auto crend() const { return points.crend(); }
}; };
class MultiPoint3 class MultiPoint3

View File

@ -1,4 +1,3 @@
#define NOMINMAX
#include "OpenVDBUtils.hpp" #include "OpenVDBUtils.hpp"
#ifdef _MSC_VER #ifdef _MSC_VER

View File

@ -13,6 +13,8 @@
#include <utility> #include <utility>
#include <libslic3r/libslic3r.h>
#include "Optimizer.hpp" #include "Optimizer.hpp"
namespace Slic3r { namespace opt { namespace Slic3r { namespace opt {
@ -104,29 +106,6 @@ struct NLoptRAII { // Helper RAII class for nlopt_opt
~NLoptRAII() { nlopt_destroy(ptr); } ~NLoptRAII() { nlopt_destroy(ptr); }
}; };
// Map a generic function to each argument following the mapping function
template<class Fn, class...Args>
Fn for_each_argument(Fn &&fn, Args&&...args)
{
// see https://www.fluentcpp.com/2019/03/05/for_each_arg-applying-a-function-to-each-argument-of-a-function-in-cpp/
(fn(std::forward<Args>(args)),...);
return fn;
}
// Call fn on each element of the input tuple tup.
template<class Fn, class Tup>
Fn for_each_in_tuple(Fn fn, Tup &&tup)
{
auto mpfn = [&fn](auto&...pack) {
for_each_argument(fn, pack...);
};
std::apply(mpfn, tup);
return fn;
}
// Wrap each element of the tuple tup into a wrapper class W and return // Wrap each element of the tuple tup into a wrapper class W and return
// a new tuple with each element being of type W<T_i> where T_i is the type of // a new tuple with each element being of type W<T_i> where T_i is the type of
// i-th element of tup. // i-th element of tup.

View File

@ -1,5 +1,5 @@
#ifndef OPTIMIZER_HPP #ifndef PRUSASLICER_OPTIMIZER_HPP
#define OPTIMIZER_HPP #define PRUSASLICER_OPTIMIZER_HPP
#include <utility> #include <utility>
#include <tuple> #include <tuple>
@ -10,6 +10,11 @@
#include <cassert> #include <cassert>
#include <optional> #include <optional>
#ifdef WIN32
#undef min
#undef max
#endif
namespace Slic3r { namespace opt { namespace Slic3r { namespace opt {
template<class T, class O = T> template<class T, class O = T>

View File

@ -283,6 +283,12 @@ struct PolygonPoint
}; };
using PolygonPoints = std::vector<PolygonPoint>; using PolygonPoints = std::vector<PolygonPoint>;
// To replace reserve_vector where it's used for Polygons
template<class I> IntegerOnly<I, Polygons> reserve_polygons(I cap)
{
return reserve_vector<Polygon, I, typename Polygons::allocator_type>(cap);
}
} // Slic3r } // Slic3r
// start Boost // start Boost

View File

@ -78,6 +78,9 @@ public:
void split_at(const Point &point, Polyline* p1, Polyline* p2) const; void split_at(const Point &point, Polyline* p1, Polyline* p2) const;
bool is_straight() const; bool is_straight() const;
bool is_closed() const { return this->points.front() == this->points.back(); } bool is_closed() const { return this->points.front() == this->points.back(); }
using iterator = Points::iterator;
using const_iterator = Points::const_iterator;
}; };
inline bool operator==(const Polyline &lhs, const Polyline &rhs) { return lhs.points == rhs.points; } inline bool operator==(const Polyline &lhs, const Polyline &rhs) { return lhs.points == rhs.points; }

View File

@ -7,7 +7,9 @@
#ifdef _MSC_VER #ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX #define NOMINMAX
#endif
#include <Windows.h> #include <Windows.h>
#endif /* _MSC_VER */ #endif /* _MSC_VER */
@ -456,7 +458,7 @@ static std::vector<std::string> s_Preset_print_options {
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits", "mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits",
"perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
"wall_distribution_count", "min_feature_size", "min_bead_width" "wall_distribution_count", "min_feature_size", "min_bead_width"
}; };

View File

@ -1621,6 +1621,17 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert; def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.)); def->set_default_value(new ConfigOptionFloat(0.));
def = this->add("mmu_segmented_region_interlocking_depth", coFloat);
def->label = L("Interlocking depth of a segmented region");
def->tooltip = L("Interlocking depth of a segmented region. It will be ignored if "
"\"mmu_segmented_region_max_width\" is zero or if \"mmu_segmented_region_interlocking_depth\""
"is bigger then \"mmu_segmented_region_max_width\". Zero disables this feature.");
def->sidetext = L("mm (zero to disable)");
def->min = 0;
def->category = L("Advanced");
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.));
def = this->add("ironing", coBool); def = this->add("ironing", coBool);
def->label = L("Enable ironing"); def->label = L("Enable ironing");
def->tooltip = L("Enable ironing of the top layers with the hot print head for smooth surface"); def->tooltip = L("Enable ironing of the top layers with the hot print head for smooth surface");
@ -4651,9 +4662,11 @@ std::string validate(const FullPrintConfig &cfg)
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CACHE_ELEMENT_DEFINITION, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_SEQ)) \ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CACHE_ELEMENT_DEFINITION, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_SEQ)) \
int print_config_static_initializer() { \ int print_config_static_initializer() { \
/* Putting a trace here to avoid the compiler to optimize out this function. */ \ /* Putting a trace here to avoid the compiler to optimize out this function. */ \
BOOST_LOG_TRIVIAL(trace) << "Initializing StaticPrintConfigs"; \ /*BOOST_LOG_TRIVIAL(trace) << "Initializing StaticPrintConfigs";*/ \
/* Tamas: alternative solution through a static volatile int. Boost log pollutes stdout and prevents tests from generating clean output */ \
static volatile int ret = 1; \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CACHE_ELEMENT_INITIALIZATION, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_SEQ)) \ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CACHE_ELEMENT_INITIALIZATION, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_SEQ)) \
return 1; \ return ret; \
} }
PRINT_CONFIG_CACHE_INITIALIZE(( PRINT_CONFIG_CACHE_INITIALIZE((
PrintObjectConfig, PrintRegionConfig, MachineEnvelopeConfig, GCodeConfig, PrintConfig, FullPrintConfig, PrintObjectConfig, PrintRegionConfig, MachineEnvelopeConfig, GCodeConfig, PrintConfig, FullPrintConfig,
@ -4949,15 +4962,6 @@ Points get_bed_shape(const DynamicPrintConfig &config)
return to_points(bed_shape_opt->values); return to_points(bed_shape_opt->values);
} }
void get_bed_shape(const DynamicPrintConfig &cfg, arrangement::ArrangeBed &out)
{
if (is_XL_printer(cfg)) {
out = arrangement::SegmentedRectangleBed{get_extents(get_bed_shape(cfg)), 4, 4};
} else {
out = arrangement::to_arrange_bed(get_bed_shape(cfg));
}
}
Points get_bed_shape(const PrintConfig &cfg) Points get_bed_shape(const PrintConfig &cfg)
{ {
return to_points(cfg.bed_shape.values); return to_points(cfg.bed_shape.values);

View File

@ -19,7 +19,6 @@
#include "libslic3r.h" #include "libslic3r.h"
#include "Config.hpp" #include "Config.hpp"
#include "SLA/SupportTreeStrategies.hpp" #include "SLA/SupportTreeStrategies.hpp"
#include "libslic3r/Arrange.hpp"
#include <boost/preprocessor/facilities/empty.hpp> #include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp> #include <boost/preprocessor/punctuation/comma_if.hpp>
@ -500,6 +499,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, interface_shells)) ((ConfigOptionBool, interface_shells))
((ConfigOptionFloat, layer_height)) ((ConfigOptionFloat, layer_height))
((ConfigOptionFloat, mmu_segmented_region_max_width)) ((ConfigOptionFloat, mmu_segmented_region_max_width))
((ConfigOptionFloat, mmu_segmented_region_interlocking_depth))
((ConfigOptionFloat, raft_contact_distance)) ((ConfigOptionFloat, raft_contact_distance))
((ConfigOptionFloat, raft_expansion)) ((ConfigOptionFloat, raft_expansion))
((ConfigOptionPercent, raft_first_layer_density)) ((ConfigOptionPercent, raft_first_layer_density))
@ -1200,8 +1200,6 @@ Points get_bed_shape(const DynamicPrintConfig &cfg);
Points get_bed_shape(const PrintConfig &cfg); Points get_bed_shape(const PrintConfig &cfg);
Points get_bed_shape(const SLAPrinterConfig &cfg); Points get_bed_shape(const SLAPrinterConfig &cfg);
void get_bed_shape(const DynamicPrintConfig &cfg, arrangement::ArrangeBed &out);
std::string get_sla_suptree_prefix(const DynamicPrintConfig &config); std::string get_sla_suptree_prefix(const DynamicPrintConfig &config);
// ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp. // ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp.

View File

@ -670,6 +670,7 @@ bool PrintObject::invalidate_state_by_config_options(
} else if ( } else if (
opt_key == "layer_height" opt_key == "layer_height"
|| opt_key == "mmu_segmented_region_max_width" || opt_key == "mmu_segmented_region_max_width"
|| opt_key == "mmu_segmented_region_interlocking_depth"
|| opt_key == "raft_layers" || opt_key == "raft_layers"
|| opt_key == "raft_contact_distance" || opt_key == "raft_contact_distance"
|| opt_key == "slice_closing_radius" || opt_key == "slice_closing_radius"

View File

@ -1,9 +1,6 @@
#define NOMINMAX
#include <libslic3r/SLA/SupportTreeBuilder.hpp> #include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <libslic3r/SLA/SupportTreeUtils.hpp> #include <libslic3r/SLA/SupportTreeUtils.hpp>
#include <libslic3r/SLA/SupportTreeMesher.hpp> #include <libslic3r/SLA/SupportTreeMesher.hpp>
//#include <libslic3r/SLA/Contour3D.hpp>
namespace Slic3r { namespace Slic3r {
namespace sla { namespace sla {

View File

@ -321,7 +321,8 @@ template<class T, class I, class... Args> // Arbitrary allocator can be used
IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity) IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity)
{ {
std::vector<T, Args...> ret; std::vector<T, Args...> ret;
if (capacity > I(0)) ret.reserve(size_t(capacity)); if (capacity > I(0))
ret.reserve(size_t(capacity));
return ret; return ret;
} }
@ -330,6 +331,18 @@ IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity)
template<class T> template<class T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>; using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
namespace detail_strip_ref_wrappers {
template<class T> struct StripCVRef_ { using type = remove_cvref_t<T>; };
template<class T> struct StripCVRef_<std::reference_wrapper<T>>
{
using type = std::remove_cv_t<T>;
};
} // namespace detail
// Removes reference wrappers as well
template<class T> using StripCVRef =
typename detail_strip_ref_wrappers::StripCVRef_<remove_cvref_t<T>>::type;
// A very simple range concept implementation with iterator-like objects. // A very simple range concept implementation with iterator-like objects.
// This should be replaced by std::ranges::subrange (C++20) // This should be replaced by std::ranges::subrange (C++20)
template<class It> class Range template<class It> class Range
@ -358,6 +371,48 @@ template<class Cont> auto range(Cont &&cont)
return Range{std::begin(cont), std::end(cont)}; return Range{std::begin(cont), std::end(cont)};
} }
template<class Cont> auto crange(Cont &&cont)
{
return Range{std::cbegin(cont), std::cend(cont)};
}
template<class IntType = int, class = IntegerOnly<IntType, void>>
class IntIterator {
IntType m_val;
public:
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = IntType;
using pointer = IntType*; // or also value_type*
using reference = IntType&; // or also value_type&
IntIterator(IntType v): m_val{v} {}
IntIterator & operator++() { ++m_val; return *this; }
IntIterator operator++(int) { auto cpy = *this; ++m_val; return cpy; }
IntIterator & operator--() { --m_val; return *this; }
IntIterator operator--(int) { auto cpy = *this; --m_val; return cpy; }
IntType operator*() const { return m_val; }
IntType operator->() const { return m_val; }
bool operator==(const IntIterator& other) const
{
return m_val == other.m_val;
}
bool operator!=(const IntIterator& other) const
{
return !(*this == other);
}
};
template<class IntType, class = IntegerOnly<IntType>>
auto range(IntType from, IntType to)
{
return Range{IntIterator{from}, IntIterator{to}};
}
template<class T, class = FloatingOnly<T>> template<class T, class = FloatingOnly<T>>
constexpr T NaN = std::numeric_limits<T>::quiet_NaN(); constexpr T NaN = std::numeric_limits<T>::quiet_NaN();
@ -385,6 +440,32 @@ inline IntegerOnly<I, I> fast_round_up(double a)
template<class T> using SamePair = std::pair<T, T>; template<class T> using SamePair = std::pair<T, T>;
// Helper to be used in static_assert.
template<class T> struct always_false { enum { value = false }; };
// Map a generic function to each argument following the mapping function
template<class Fn, class...Args>
Fn for_each_argument(Fn &&fn, Args&&...args)
{
// see https://www.fluentcpp.com/2019/03/05/for_each_arg-applying-a-function-to-each-argument-of-a-function-in-cpp/
(fn(std::forward<Args>(args)),...);
return fn;
}
// Call fn on each element of the input tuple tup.
template<class Fn, class Tup>
Fn for_each_in_tuple(Fn fn, Tup &&tup)
{
auto mpfn = [&fn](auto&...pack) {
for_each_argument(fn, pack...);
};
std::apply(mpfn, tup);
return fn;
}
} // namespace Slic3r } // namespace Slic3r
#endif // _libslic3r_h_ #endif // _libslic3r_h_

View File

@ -8,6 +8,8 @@ set(SLIC3R_GUI_SOURCES
pchheader.hpp pchheader.hpp
GUI/AboutDialog.cpp GUI/AboutDialog.cpp
GUI/AboutDialog.hpp GUI/AboutDialog.hpp
GUI/ArrangeSettingsDialogImgui.hpp
GUI/ArrangeSettingsDialogImgui.cpp
GUI/SysInfoDialog.cpp GUI/SysInfoDialog.cpp
GUI/SysInfoDialog.hpp GUI/SysInfoDialog.hpp
GUI/KBShortcutsDialog.cpp GUI/KBShortcutsDialog.cpp
@ -196,8 +198,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Jobs/BusyCursorJob.hpp GUI/Jobs/BusyCursorJob.hpp
GUI/Jobs/CancellableJob.hpp GUI/Jobs/CancellableJob.hpp
GUI/Jobs/PlaterWorker.hpp GUI/Jobs/PlaterWorker.hpp
GUI/Jobs/ArrangeJob.hpp GUI/Jobs/ArrangeJob2.hpp
GUI/Jobs/ArrangeJob.cpp GUI/Jobs/ArrangeJob2.cpp
GUI/Jobs/CreateFontNameImageJob.cpp GUI/Jobs/CreateFontNameImageJob.cpp
GUI/Jobs/CreateFontNameImageJob.hpp GUI/Jobs/CreateFontNameImageJob.hpp
GUI/Jobs/CreateFontStyleImagesJob.cpp GUI/Jobs/CreateFontStyleImagesJob.cpp
@ -206,8 +208,6 @@ set(SLIC3R_GUI_SOURCES
GUI/Jobs/EmbossJob.hpp GUI/Jobs/EmbossJob.hpp
GUI/Jobs/RotoptimizeJob.hpp GUI/Jobs/RotoptimizeJob.hpp
GUI/Jobs/RotoptimizeJob.cpp GUI/Jobs/RotoptimizeJob.cpp
GUI/Jobs/FillBedJob.hpp
GUI/Jobs/FillBedJob.cpp
GUI/Jobs/SLAImportJob.hpp GUI/Jobs/SLAImportJob.hpp
GUI/Jobs/SLAImportJob.cpp GUI/Jobs/SLAImportJob.cpp
GUI/Jobs/ProgressIndicator.hpp GUI/Jobs/ProgressIndicator.hpp

View File

@ -0,0 +1,131 @@
#include "ArrangeSettingsDialogImgui.hpp"
#include "I18N.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/GUI.hpp"
namespace Slic3r { namespace GUI {
struct Settings {
float d_obj;
float d_bed;
bool rotations;
int xl_align;
int geom_handling;
int arr_strategy;
};
static void read_settings(Settings &s, const arr2::ArrangeSettingsDb *db)
{
assert(db);
s.d_obj = db->get_distance_from_objects();
s.d_bed = db->get_distance_from_bed();
s.rotations = db->is_rotation_enabled();
s.xl_align = db->get_xl_alignment();
s.geom_handling = db->get_geometry_handling();
s.arr_strategy = db->get_arrange_strategy();
}
ArrangeSettingsDialogImgui::ArrangeSettingsDialogImgui(
ImGuiWrapper *imgui, AnyPtr<arr2::ArrangeSettingsDb> db)
: m_imgui{imgui}, m_db{std::move(db)}
{}
void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y)
{
assert(m_imgui && m_db);
m_imgui->set_next_window_pos(pos_x, pos_y, ImGuiCond_Always, 0.5f, 0.0f);
m_imgui->begin(_L("Arrange options"),
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoCollapse);
Settings settings;
read_settings(settings, m_db.get());
m_imgui->text(GUI::format_wxstr(
_L("Press %1%left mouse button to enter the exact value"),
shortkey_ctrl_prefix()));
float dobj_min, dobj_max;
float dbed_min, dbed_max;
m_db->distance_from_obj_range(dobj_min, dobj_max);
m_db->distance_from_bed_range(dbed_min, dbed_max);
if(dobj_min > settings.d_obj) {
settings.d_obj = std::max(dobj_min, settings.d_obj);
m_db->set_distance_from_objects(settings.d_obj);
}
if (dbed_min > settings.d_bed) {
settings.d_bed = std::max(dbed_min, settings.d_bed);
m_db->set_distance_from_bed(settings.d_bed);
}
if (m_imgui->slider_float(_L("Spacing"), &settings.d_obj, dobj_min,
dobj_max, "%5.2f")) {
settings.d_obj = std::max(dobj_min, settings.d_obj);
m_db->set_distance_from_objects(settings.d_obj);
}
if (m_imgui->slider_float(_L("Spacing from bed"), &settings.d_bed,
dbed_min, dbed_max, "%5.2f")) {
settings.d_bed = std::max(dbed_min, settings.d_bed);
m_db->set_distance_from_bed(settings.d_bed);
}
if (m_imgui->checkbox(_L("Enable rotations (slow)"), settings.rotations)) {
m_db->set_rotation_enabled(settings.rotations);
}
if (m_show_xl_combo_predicate() &&
settings.xl_align >= 0 &&
m_imgui->combo(_L("Alignment"),
{_u8L("Center"), _u8L("Rear left"), _u8L("Front left"),
_u8L("Front right"), _u8L("Rear right"),
_u8L("Random")},
settings.xl_align)) {
if (settings.xl_align >= 0 &&
settings.xl_align < ArrangeSettingsView::xlpCount)
m_db->set_xl_alignment(static_cast<ArrangeSettingsView::XLPivots>(
settings.xl_align));
}
if (m_imgui->combo(_L("Geometry handling"),
{_u8L("Fast"), _u8L("Balanced"), _u8L("Full complexity")},
settings.geom_handling)) {
if (settings.geom_handling >= 0 &&
settings.geom_handling < ArrangeSettingsView::ghCount)
m_db->set_geometry_handling(
static_cast<ArrangeSettingsView::GeometryHandling>(
settings.geom_handling));
}
ImGui::Separator();
if (m_imgui->button(_L("Reset defaults"))) {
arr2::ArrangeSettingsDb::Values df = m_db->get_defaults();
m_db->set_distance_from_objects(df.d_obj);
m_db->set_distance_from_bed(df.d_bed);
m_db->set_rotation_enabled(df.rotations);
if (m_show_xl_combo_predicate())
m_db->set_xl_alignment(df.xl_align);
m_db->set_geometry_handling(df.geom_handling);
m_db->set_arrange_strategy(df.arr_strategy);
if (m_on_reset_btn)
m_on_reset_btn();
}
ImGui::SameLine();
if (m_imgui->button(_L("Arrange")) && m_on_arrange_btn) {
m_on_arrange_btn();
}
m_imgui->end();
}
}} // namespace Slic3r::GUI

View File

@ -0,0 +1,53 @@
#ifndef ARRANGESETTINGSDIALOGIMGUI_HPP
#define ARRANGESETTINGSDIALOGIMGUI_HPP
#include "libslic3r/Arrange/ArrangeSettingsView.hpp"
#include "ImGuiWrapper.hpp"
#include "libslic3r/AnyPtr.hpp"
namespace Slic3r {
namespace GUI {
class ArrangeSettingsDialogImgui: public arr2::ArrangeSettingsView {
ImGuiWrapper *m_imgui;
AnyPtr<arr2::ArrangeSettingsDb> m_db;
std::function<void()> m_on_arrange_btn;
std::function<void()> m_on_reset_btn;
std::function<bool()> m_show_xl_combo_predicate = [] { return true; };
public:
ArrangeSettingsDialogImgui(ImGuiWrapper *imgui, AnyPtr<arr2::ArrangeSettingsDb> db);
void render(float pos_x, float pos_y);
void show_xl_align_combo(std::function<bool()> pred)
{
m_show_xl_combo_predicate = pred;
}
void on_arrange_btn(std::function<void()> on_arrangefn)
{
m_on_arrange_btn = on_arrangefn;
}
void on_reset_btn(std::function<void()> on_resetfn)
{
m_on_reset_btn = on_resetfn;
}
// ArrangeSettingsView iface:
float get_distance_from_objects() const override { return m_db->get_distance_from_objects(); }
float get_distance_from_bed() const override { return m_db->get_distance_from_bed(); }
bool is_rotation_enabled() const override { return m_db->is_rotation_enabled(); }
XLPivots get_xl_alignment() const override { return m_db->get_xl_alignment(); }
GeometryHandling get_geometry_handling() const override { return m_db->get_geometry_handling(); }
ArrangeStrategy get_arrange_strategy() const override { return arr2::ArrangeSettingsView::asAuto; }
};
}} // namespace Slic3r::GUI
#endif // ARRANGESETTINGSDIALOGIMGUI_HPP

View File

@ -324,6 +324,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
"wipe_tower_extra_spacing", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) "wipe_tower_extra_spacing", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" })
toggle_field(el, have_wipe_tower); toggle_field(el, have_wipe_tower);
bool have_non_zero_mmu_segmented_region_max_width = config->opt_float("mmu_segmented_region_max_width") > 0.;
toggle_field("mmu_segmented_region_interlocking_depth", have_non_zero_mmu_segmented_region_max_width);
toggle_field("avoid_crossing_curled_overhangs", !config->opt_bool("avoid_crossing_perimeters")); toggle_field("avoid_crossing_curled_overhangs", !config->opt_bool("avoid_crossing_perimeters"));
toggle_field("avoid_crossing_perimeters", !config->opt_bool("avoid_crossing_curled_overhangs")); toggle_field("avoid_crossing_perimeters", !config->opt_bool("avoid_crossing_curled_overhangs"));

View File

@ -707,6 +707,10 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin
list_vendor->SetMinSize(wxSize(13*em, list_h)); list_vendor->SetMinSize(wxSize(13*em, list_h));
list_profile->SetMinSize(wxSize(23*em, list_h)); list_profile->SetMinSize(wxSize(23*em, list_h));
#ifdef __APPLE__
for (wxWindow* win : std::initializer_list<wxWindow*>{ list_printer, list_type, list_vendor, list_profile })
win->SetBackgroundColour(wxGetApp().get_window_default_clr());
#endif
grid = new wxFlexGridSizer(4, em/2, em); grid = new wxFlexGridSizer(4, em/2, em);
@ -817,19 +821,9 @@ void PageMaterials::reload_presets()
void PageMaterials::set_compatible_printers_html_window(const std::vector<std::string>& printer_names, bool all_printers) void PageMaterials::set_compatible_printers_html_window(const std::vector<std::string>& printer_names, bool all_printers)
{ {
const auto bgr_clr =
#if defined(__APPLE__)
html_window->GetParent()->GetBackgroundColour();
#else
#if defined(_WIN32)
wxGetApp().get_window_default_clr();
#else
wxSystemSettings::GetColour(wxSYS_COLOUR_MENU);
#endif
#endif
const auto text_clr = wxGetApp().get_label_clr_default(); const auto text_clr = wxGetApp().get_label_clr_default();
const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
const auto bgr_clr_str = wxGetApp().get_html_bg_color(parent);
wxString text; wxString text;
if (materials->technology == T_FFF && template_shown) { if (materials->technology == T_FFF && template_shown) {
// TRN ConfigWizard: Materials : "%1%" = "Filaments"/"SLA materials" // TRN ConfigWizard: Materials : "%1%" = "Filaments"/"SLA materials"
@ -1468,11 +1462,43 @@ PageDownloader::PageDownloader(ConfigWizard* parent)
box_allow_downloads->SetValue(box_allow_value); box_allow_downloads->SetValue(box_allow_value);
append(box_allow_downloads); append(box_allow_downloads);
// TRN ConfigWizard : Downloader : %1% = "PrusaSlicer" // append info line with link on printables.com
append_text(format_wxstr(_L("If enabled, %1% registers to start on custom URL on www.printables.com." {
" You will be able to use button with %1% logo to open models in this %1%." const int em = parent->em_unit();
" The model will be downloaded into folder you choose bellow." wxHtmlWindow* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(60 * em, 5 * em), wxHW_SCROLLBAR_NEVER);
), SLIC3R_APP_NAME));
html_window->Bind(wxEVT_HTML_LINK_CLICKED, [](wxHtmlLinkEvent& event) {
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref());
event.Skip(false);
});
append(html_window);
const auto text_clr = wxGetApp().get_label_clr_default();
const auto bgr_clr_str = wxGetApp().get_html_bg_color(parent);
const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
const wxString link = format_wxstr("<a href = \"%1%\">%1%</a>", "printables.com");
// TRN ConfigWizard : Downloader : %1% = "printables.com", %2% = "PrusaSlicer"
const wxString main_text = format_wxstr(_L("If enabled, you will be able to open models from the %1% "
"online database with a single click (using a %2% logo button)."
), link, SLIC3R_APP_NAME);
const wxFont& font = this->GetFont();
const int fs = font.GetPointSize();
int size[] = { fs,fs,fs,fs,fs,fs,fs };
html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
html_window->SetPage(format_wxstr(
"<html><body bgcolor=%1% link=%2%>"
"<font color=%2% size=\"3\">%3%</font>"
"</body></html>"
, bgr_clr_str
, text_clr_str
, main_text
));
}
#ifdef __linux__ #ifdef __linux__
append_text(wxString::Format(_L( append_text(wxString::Format(_L(
@ -3351,6 +3377,9 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
: DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, p(new priv(this)) , p(new priv(this))
{ {
#ifdef __APPLE__
this->SetBackgroundColour(wxGetApp().get_window_default_clr());
#endif
wxBusyCursor wait; wxBusyCursor wait;
this->SetFont(wxGetApp().normal_font()); this->SetFont(wxGetApp().normal_font());

View File

@ -1045,94 +1045,6 @@ wxDEFINE_EVENT(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, wxTimerEvent);
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
void GLCanvas3D::load_arrange_settings()
{
std::string dist_fff_str =
wxGetApp().app_config->get("arrange", "min_object_distance_fff");
std::string dist_bed_fff_str =
wxGetApp().app_config->get("arrange", "min_bed_distance_fff");
std::string dist_fff_seq_print_str =
wxGetApp().app_config->get("arrange", "min_object_distance_fff_seq_print");
std::string dist_bed_fff_seq_print_str =
wxGetApp().app_config->get("arrange", "min_bed_distance_fff_seq_print");
std::string dist_sla_str =
wxGetApp().app_config->get("arrange", "min_object_distance_sla");
std::string dist_bed_sla_str =
wxGetApp().app_config->get("arrange", "min_bed_distance_sla");
std::string en_rot_fff_str =
wxGetApp().app_config->get("arrange", "enable_rotation_fff");
std::string en_rot_fff_seqp_str =
wxGetApp().app_config->get("arrange", "enable_rotation_fff_seq_print");
std::string en_rot_sla_str =
wxGetApp().app_config->get("arrange", "enable_rotation_sla");
// std::string alignment_fff_str =
// wxGetApp().app_config->get("arrange", "alignment_fff");
// std::string alignment_fff_seqp_str =
// wxGetApp().app_config->get("arrange", "alignment_fff_seq_pring");
// std::string alignment_sla_str =
// wxGetApp().app_config->get("arrange", "alignment_sla");
// Override default alignment and save save/load it to a temporary slot "alignment_xl"
std::string alignment_xl_str =
wxGetApp().app_config->get("arrange", "alignment_xl");
if (!dist_fff_str.empty())
m_arrange_settings_fff.distance = string_to_float_decimal_point(dist_fff_str);
if (!dist_bed_fff_str.empty())
m_arrange_settings_fff.distance_from_bed = string_to_float_decimal_point(dist_bed_fff_str);
if (!dist_fff_seq_print_str.empty())
m_arrange_settings_fff_seq_print.distance = string_to_float_decimal_point(dist_fff_seq_print_str);
if (!dist_bed_fff_seq_print_str.empty())
m_arrange_settings_fff_seq_print.distance_from_bed = string_to_float_decimal_point(dist_bed_fff_seq_print_str);
if (!dist_sla_str.empty())
m_arrange_settings_sla.distance = string_to_float_decimal_point(dist_sla_str);
if (!dist_bed_sla_str.empty())
m_arrange_settings_sla.distance_from_bed = string_to_float_decimal_point(dist_bed_sla_str);
if (!en_rot_fff_str.empty())
m_arrange_settings_fff.enable_rotation = (en_rot_fff_str == "1" || en_rot_fff_str == "yes");
if (!en_rot_fff_seqp_str.empty())
m_arrange_settings_fff_seq_print.enable_rotation = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes");
if (!en_rot_sla_str.empty())
m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "yes");
// if (!alignment_sla_str.empty())
// m_arrange_settings_sla.alignment = std::stoi(alignment_sla_str);
// if (!alignment_fff_str.empty())
// m_arrange_settings_fff.alignment = std::stoi(alignment_fff_str);
// if (!alignment_fff_seqp_str.empty())
// m_arrange_settings_fff_seq_print.alignment = std::stoi(alignment_fff_seqp_str);
// Override default alignment and save save/load it to a temporary slot "alignment_xl"
int arr_alignment = static_cast<int>(arrangement::Pivots::BottomLeft);
if (!alignment_xl_str.empty())
arr_alignment = std::stoi(alignment_xl_str);
m_arrange_settings_sla.alignment = arr_alignment ;
m_arrange_settings_fff.alignment = arr_alignment ;
m_arrange_settings_fff_seq_print.alignment = arr_alignment ;
}
static std::vector<int> processed_objects_idxs(const Model& model, const SLAPrint& sla_print, const GLVolumePtrs& volumes) static std::vector<int> processed_objects_idxs(const Model& model, const SLAPrint& sla_print, const GLVolumePtrs& volumes)
{ {
std::vector<int> ret; std::vector<int> ret;
@ -1384,46 +1296,50 @@ void GLCanvas3D::SLAView::select_full_instance(const GLVolume::CompositeID& id)
PrinterTechnology GLCanvas3D::current_printer_technology() const PrinterTechnology GLCanvas3D::current_printer_technology() const
{ {
return m_process->current_printer_technology(); return m_process ? m_process->current_printer_technology() : ptFFF;
} }
bool GLCanvas3D::is_arrange_alignment_enabled() const bool GLCanvas3D::is_arrange_alignment_enabled() const
{ {
return m_config ? is_XL_printer(*m_config) : false; return m_config ? is_XL_printer(*m_config) && !this->get_wipe_tower_info() : false;
} }
GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) GLCanvas3D::GLCanvas3D(wxGLCanvas *canvas, Bed3D &bed)
: m_canvas(canvas) : m_canvas(canvas),
, m_context(nullptr) m_context(nullptr),
, m_bed(bed) m_bed(bed)
#if ENABLE_RETINA_GL #if ENABLE_RETINA_GL
, m_retina_helper(nullptr) ,
m_retina_helper(nullptr)
#endif #endif
, m_in_render(false) ,
, m_main_toolbar(GLToolbar::Normal, "Main") m_in_render(false),
, m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo") m_main_toolbar(GLToolbar::Normal, "Main"),
, m_gizmos(*this) m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo"),
, m_use_clipping_planes(false) m_gizmos(*this),
, m_sidebar_field("") m_use_clipping_planes(false),
, m_extra_frame_requested(false) m_sidebar_field(""),
, m_config(nullptr) m_extra_frame_requested(false),
, m_process(nullptr) m_config(nullptr),
, m_model(nullptr) m_process(nullptr),
, m_dirty(true) m_model(nullptr),
, m_initialized(false) m_dirty(true),
, m_apply_zoom_to_volumes_filter(false) m_initialized(false),
, m_picking_enabled(false) m_apply_zoom_to_volumes_filter(false),
, m_moving_enabled(false) m_picking_enabled(false),
, m_dynamic_background_enabled(false) m_moving_enabled(false),
, m_multisample_allowed(false) m_dynamic_background_enabled(false),
, m_moving(false) m_multisample_allowed(false),
, m_tab_down(false) m_moving(false),
, m_cursor_type(Standard) m_tab_down(false),
, m_reload_delayed(false) m_cursor_type(Standard),
, m_render_sla_auxiliaries(true) m_reload_delayed(false),
, m_labels(*this) m_render_sla_auxiliaries(true),
, m_slope(m_volumes) m_labels(*this),
, m_sla_view(*this) m_slope(m_volumes),
m_sla_view(*this),
m_arrange_settings_db{wxGetApp().app_config},
m_arrange_settings_dialog{wxGetApp().imgui(), &m_arrange_settings_db}
{ {
if (m_canvas != nullptr) { if (m_canvas != nullptr) {
m_timer.SetOwner(m_canvas); m_timer.SetOwner(m_canvas);
@ -1433,9 +1349,13 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
#endif // ENABLE_RETINA_GL #endif // ENABLE_RETINA_GL
} }
load_arrange_settings();
m_selection.set_volumes(&m_volumes.volumes); m_selection.set_volumes(&m_volumes.volumes);
m_arrange_settings_dialog.show_xl_align_combo([this](){
return this->is_arrange_alignment_enabled();
});
m_arrange_settings_dialog.on_arrange_btn([]{
wxGetApp().plater()->arrange();
});
} }
GLCanvas3D::~GLCanvas3D() GLCanvas3D::~GLCanvas3D()
@ -1702,6 +1622,30 @@ void GLCanvas3D::set_config(const DynamicPrintConfig* config)
{ {
m_config = config; m_config = config;
m_layers_editing.set_config(config); m_layers_editing.set_config(config);
if (config) {
PrinterTechnology ptech = current_printer_technology();
auto slot = ArrangeSettingsDb_AppCfg::slotFFF;
if (ptech == ptSLA) {
slot = ArrangeSettingsDb_AppCfg::slotSLA;
} else if (ptech == ptFFF) {
auto co_opt = config->option<ConfigOptionBool>("complete_objects");
if (co_opt && co_opt->value)
slot = ArrangeSettingsDb_AppCfg::slotFFFSeqPrint;
else
slot = ArrangeSettingsDb_AppCfg::slotFFF;
}
m_arrange_settings_db.set_active_slot(slot);
double objdst = min_object_distance(*config);
double min_obj_dst = slot == ArrangeSettingsDb_AppCfg::slotFFFSeqPrint ? objdst : 0.;
m_arrange_settings_db.set_distance_from_obj_range(slot, min_obj_dst, 100.);
m_arrange_settings_db.get_defaults(slot).d_obj = objdst;
}
} }
void GLCanvas3D::set_process(BackgroundSlicingProcess *process) void GLCanvas3D::set_process(BackgroundSlicingProcess *process)
@ -4832,104 +4776,9 @@ bool GLCanvas3D::_render_search_list(float pos_x)
bool GLCanvas3D::_render_arrange_menu(float pos_x) bool GLCanvas3D::_render_arrange_menu(float pos_x)
{ {
ImGuiWrapper *imgui = wxGetApp().imgui(); m_arrange_settings_dialog.render(pos_x, m_main_toolbar.get_height());
imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); return true;
imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
ArrangeSettings settings = get_arrange_settings();
ArrangeSettings &settings_out = get_arrange_settings_ref(this);
auto &appcfg = wxGetApp().app_config;
PrinterTechnology ptech = current_printer_technology();
bool settings_changed = false;
float dist_min = 0.f;
float dist_bed_min = 0.f;
std::string dist_key = "min_object_distance";
std::string dist_bed_key = "min_bed_distance";
std::string rot_key = "enable_rotation";
std::string align_key = "alignment";
std::string postfix;
if (ptech == ptSLA) {
postfix = "_sla";
} else if (ptech == ptFFF) {
auto co_opt = m_config->option<ConfigOptionBool>("complete_objects");
if (co_opt && co_opt->value) {
dist_min = float(min_object_distance(*m_config));
postfix = "_fff_seq_print";
} else {
dist_min = 0.f;
postfix = "_fff";
}
}
dist_key += postfix;
dist_bed_key += postfix;
rot_key += postfix;
align_key += postfix;
imgui->text(GUI::format_wxstr(_L("Press %1%left mouse button to enter the exact value"), shortkey_ctrl_prefix()));
if (imgui->slider_float(_L("Spacing"), &settings.distance, dist_min, 100.0f, "%5.2f") || dist_min > settings.distance) {
settings.distance = std::max(dist_min, settings.distance);
settings_out.distance = settings.distance;
appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance));
settings_changed = true;
}
if (imgui->slider_float(_L("Spacing from bed"), &settings.distance_from_bed, dist_bed_min, 100.0f, "%5.2f") || dist_bed_min > settings.distance_from_bed) {
settings.distance_from_bed = std::max(dist_bed_min, settings.distance_from_bed);
settings_out.distance_from_bed = settings.distance_from_bed;
appcfg->set("arrange", dist_bed_key.c_str(), float_to_string_decimal_point(settings_out.distance_from_bed));
settings_changed = true;
}
if (imgui->checkbox(_L("Enable rotations (slow)"), settings.enable_rotation)) {
settings_out.enable_rotation = settings.enable_rotation;
appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0");
settings_changed = true;
}
Points bed = m_config ? get_bed_shape(*m_config) : Points{};
if (arrangement::is_box(bed) && settings.alignment >= 0 &&
imgui->combo(_L("Alignment"), {_u8L("Center"), _u8L("Rear left"), _u8L("Front left"), _u8L("Front right"), _u8L("Rear right"), _u8L("Random") }, settings.alignment)) {
settings_out.alignment = settings.alignment;
appcfg->set("arrange", align_key.c_str(), std::to_string(settings_out.alignment));
settings_changed = true;
}
ImGui::Separator();
if (imgui->button(_L("Reset"))) {
auto alignment = settings_out.alignment;
settings_out = ArrangeSettings{};
settings_out.distance = std::max(dist_min, settings_out.distance);
// Default alignment for XL printers set explicitly:
if (is_arrange_alignment_enabled())
settings_out.alignment = static_cast<int>(arrangement::Pivots::BottomLeft);
else
settings_out.alignment = alignment;
appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance));
appcfg->set("arrange", dist_bed_key.c_str(), float_to_string_decimal_point(settings_out.distance_from_bed));
appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0");
settings_changed = true;
}
ImGui::SameLine();
if (imgui->button(_L("Arrange"))) {
wxGetApp().plater()->arrange();
}
imgui->end();
return settings_changed;
} }
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0 #define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
@ -7774,15 +7623,20 @@ const SLAPrint* GLCanvas3D::sla_print() const
return (m_process == nullptr) ? nullptr : m_process->sla_print(); return (m_process == nullptr) ? nullptr : m_process->sla_print();
} }
void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const void GLCanvas3D::WipeTowerInfo::apply_wipe_tower(Vec2d pos, double rot)
{ {
DynamicPrintConfig cfg; DynamicPrintConfig cfg;
cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = m_pos(X); cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = pos.x();
cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = m_pos(Y); cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = pos.y();
cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = (180./M_PI) * rot;
wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
} }
void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const
{
apply_wipe_tower(m_pos, m_rotation);
}
void GLCanvas3D::RenderTimer::Notify() void GLCanvas3D::RenderTimer::Notify()
{ {
wxPostEvent((wxEvtHandler*)GetOwner(), RenderTimerEvent( EVT_GLCANVAS_RENDER_TIMER, *this)); wxPostEvent((wxEvtHandler*)GetOwner(), RenderTimerEvent( EVT_GLCANVAS_RENDER_TIMER, *this));

View File

@ -19,6 +19,9 @@
#include "SceneRaycaster.hpp" #include "SceneRaycaster.hpp"
#include "GUI_Utils.hpp" #include "GUI_Utils.hpp"
#include "libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp"
#include "ArrangeSettingsDialogImgui.hpp"
#include "libslic3r/Slicing.hpp" #include "libslic3r/Slicing.hpp"
#include <float.h> #include <float.h>
@ -466,6 +469,8 @@ public:
float accuracy = 0.65f; // Unused currently float accuracy = 0.65f; // Unused currently
bool enable_rotation = false; bool enable_rotation = false;
int alignment = 0; int alignment = 0;
int geometry_handling = 0;
int strategy = 0;
}; };
enum class ESLAViewType enum class ESLAViewType
@ -581,44 +586,12 @@ private:
SLAView m_sla_view; SLAView m_sla_view;
bool m_sla_view_type_detection_active{ false }; bool m_sla_view_type_detection_active{ false };
ArrangeSettings m_arrange_settings_fff, m_arrange_settings_sla,
m_arrange_settings_fff_seq_print;
bool is_arrange_alignment_enabled() const; bool is_arrange_alignment_enabled() const;
template<class Self> ArrangeSettingsDb_AppCfg m_arrange_settings_db;
static auto & get_arrange_settings_ref(Self *self) { ArrangeSettingsDialogImgui m_arrange_settings_dialog;
PrinterTechnology ptech = self->current_printer_technology();
auto *ptr = &self->m_arrange_settings_fff;
if (ptech == ptSLA) {
ptr = &self->m_arrange_settings_sla;
} else if (ptech == ptFFF) {
auto co_opt = self->m_config->template option<ConfigOptionBool>("complete_objects");
if (co_opt && co_opt->value)
ptr = &self->m_arrange_settings_fff_seq_print;
else
ptr = &self->m_arrange_settings_fff;
}
return *ptr;
}
public: public:
ArrangeSettings get_arrange_settings() const {
const ArrangeSettings &settings = get_arrange_settings_ref(this);
ArrangeSettings ret = settings;
if (&settings == &m_arrange_settings_fff_seq_print) {
ret.distance = std::max(ret.distance,
float(min_object_distance(*m_config)));
}
if (!is_arrange_alignment_enabled())
ret.alignment = -1;
return ret;
}
struct ContoursList struct ContoursList
{ {
@ -631,7 +604,6 @@ public:
}; };
private: private:
void load_arrange_settings();
class SequentialPrintClearance class SequentialPrintClearance
{ {
@ -754,10 +726,13 @@ public:
void update_instance_printable_state_for_objects(const std::vector<size_t>& object_idxs); void update_instance_printable_state_for_objects(const std::vector<size_t>& object_idxs);
void set_config(const DynamicPrintConfig* config); void set_config(const DynamicPrintConfig* config);
const DynamicPrintConfig *config() const { return m_config; }
void set_process(BackgroundSlicingProcess* process); void set_process(BackgroundSlicingProcess* process);
void set_model(Model* model); void set_model(Model* model);
const Model* get_model() const { return m_model; } const Model* get_model() const { return m_model; }
const arr2::ArrangeSettingsView * get_arrange_settings_view() const { return &m_arrange_settings_dialog; }
const Selection& get_selection() const { return m_selection; } const Selection& get_selection() const { return m_selection; }
Selection& get_selection() { return m_selection; } Selection& get_selection() { return m_selection; }
@ -919,8 +894,11 @@ public:
inline const Vec2d& pos() const { return m_pos; } inline const Vec2d& pos() const { return m_pos; }
inline double rotation() const { return m_rotation; } inline double rotation() const { return m_rotation; }
inline const Vec2d bb_size() const { return m_bb.size(); } inline const Vec2d bb_size() const { return m_bb.size(); }
inline const BoundingBoxf& bounding_box() const { return m_bb; }
void apply_wipe_tower() const; void apply_wipe_tower() const;
static void apply_wipe_tower(Vec2d pos, double rot);
}; };
WipeTowerInfo get_wipe_tower_info() const; WipeTowerInfo get_wipe_tower_info() const;

View File

@ -16,7 +16,9 @@
#import <IOKit/pwr_mgt/IOPMLib.h> #import <IOKit/pwr_mgt/IOPMLib.h>
#elif _WIN32 #elif _WIN32
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#define NOMINMAX #ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h> #include <Windows.h>
#include "boost/nowide/convert.hpp" #include "boost/nowide/convert.hpp"
#endif #endif

View File

@ -1732,6 +1732,24 @@ void GUI_App::set_label_clr_sys(const wxColour& clr)
app_config->set("label_clr_sys", str); app_config->set("label_clr_sys", str);
} }
const std::string GUI_App::get_html_bg_color(wxWindow* html_parent)
{
wxColour bgr_clr = html_parent->GetBackgroundColour();
#ifdef __APPLE__
// On macOS 10.13 and older the background color returned by wxWidgets
// is wrong, which leads to https://github.com/prusa3d/PrusaSlicer/issues/7603
// and https://github.com/prusa3d/PrusaSlicer/issues/3775. wxSYS_COLOUR_WINDOW
// may not match the window background exactly, but it seems to never end up
// as black on black.
if (wxPlatformInfo::Get().GetOSMajorVersion() == 10
&& wxPlatformInfo::Get().GetOSMinorVersion() < 14)
bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
#endif
return encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
}
const std::string& GUI_App::get_mode_btn_color(int mode_id) const std::string& GUI_App::get_mode_btn_color(int mode_id)
{ {
assert(0 <= mode_id && size_t(mode_id) < m_mode_palette.size()); assert(0 <= mode_id && size_t(mode_id) < m_mode_palette.size());

View File

@ -222,6 +222,8 @@ public:
const wxColour& get_label_clr_default() { return m_color_label_default; } const wxColour& get_label_clr_default() { return m_color_label_default; }
const wxColour& get_window_default_clr(){ return m_color_window_default; } const wxColour& get_window_default_clr(){ return m_color_window_default; }
const std::string get_html_bg_color(wxWindow* html_parent);
const std::string& get_mode_btn_color(int mode_id); const std::string& get_mode_btn_color(int mode_id);
std::vector<wxColour> get_mode_palette(); std::vector<wxColour> get_mode_palette();
void set_mode_palette(const std::vector<wxColour> &palette); void set_mode_palette(const std::vector<wxColour> &palette);

View File

@ -403,27 +403,27 @@ arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst,
arrangement::ArrangeParams get_arrange_params(Plater *p) arrangement::ArrangeParams get_arrange_params(Plater *p)
{ {
const GLCanvas3D::ArrangeSettings &settings = const arr2::ArrangeSettingsView *settings =
p->canvas3D()->get_arrange_settings(); p->canvas3D()->get_arrange_settings_view();
arrangement::ArrangeParams params; arrangement::ArrangeParams params;
params.allow_rotations = settings.enable_rotation; params.allow_rotations = settings->is_rotation_enabled();
params.min_obj_distance = scaled(settings.distance); params.min_obj_distance = scaled(settings->get_distance_from_objects());
params.min_bed_distance = scaled(settings.distance_from_bed); params.min_bed_distance = scaled(settings->get_distance_from_bed());
arrangement::Pivots pivot = arrangement::Pivots::Center; arrangement::Pivots pivot = arrangement::Pivots::Center;
int pivot_max = static_cast<int>(arrangement::Pivots::TopRight); int pivot_max = static_cast<int>(arrangement::Pivots::TopRight);
if (settings.alignment < 0) { if (settings->get_xl_alignment() < 0) {
pivot = arrangement::Pivots::Center; pivot = arrangement::Pivots::Center;
} else if (settings.alignment > pivot_max) { } else if (settings->get_xl_alignment() == arr2::ArrangeSettingsView::xlpRandom) {
// means it should be random // means it should be random
std::random_device rd{}; std::random_device rd{};
std::mt19937 rng(rd()); std::mt19937 rng(rd());
std::uniform_int_distribution<std::mt19937::result_type> dist(0, pivot_max); std::uniform_int_distribution<std::mt19937::result_type> dist(0, pivot_max);
pivot = static_cast<arrangement::Pivots>(dist(rng)); pivot = static_cast<arrangement::Pivots>(dist(rng));
} else { } else {
pivot = static_cast<arrangement::Pivots>(settings.alignment); pivot = static_cast<arrangement::Pivots>(settings->get_xl_alignment());
} }
params.alignment = pivot; params.alignment = pivot;

View File

@ -0,0 +1,204 @@
#include "ArrangeJob2.hpp"
#include <numeric>
#include <iterator>
#include <libslic3r/Model.hpp>
#include <libslic3r/TriangleMeshSlicer.hpp>
#include <libslic3r/Geometry/ConvexHull.hpp>
#include <libslic3r/SLAPrint.hpp>
#include <libslic3r/Print.hpp>
#include <slic3r/GUI/Plater.hpp>
#include <slic3r/GUI/GLCanvas3D.hpp>
#include <slic3r/GUI/GUI_App.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace GUI {
class GUISelectionMask: public arr2::SelectionMask {
const Selection *m_sel;
public:
explicit GUISelectionMask(const Selection *sel) : m_sel{sel} {}
bool is_wipe_tower() const override
{
return m_sel->is_wipe_tower();
}
std::vector<bool> selected_objects() const override
{
auto selmap = m_sel->get_object_idxs();
std::vector<bool> ret(m_sel->get_model()->objects.size(), false);
for (auto sel : selmap) {
ret[sel] = true;
}
return ret;
}
std::vector<bool> selected_instances(int obj_id) const override
{
auto objcnt = static_cast<int>(m_sel->get_model()->objects.size());
auto icnt = obj_id < objcnt ?
m_sel->get_model()->objects[obj_id]->instances.size() :
0;
std::vector<bool> ret(icnt, false);
auto selmap = m_sel->get_content();
auto objit = selmap.find(obj_id);
if (objit != selmap.end() && obj_id < objcnt) {
ret = std::vector<bool>(icnt, false);
for (auto sel : objit->second) {
ret[sel] = true;
}
}
return ret;
}
};
static Polygon get_wtpoly(const GLCanvas3D::WipeTowerInfo &wti)
{
auto bb = scaled(wti.bounding_box());
Polygon poly = Polygon({
{bb.min},
{bb.max.x(), bb.min.y()},
{bb.max},
{bb.min.x(), bb.max.y()}
});
poly.rotate(wti.rotation());
poly.translate(scaled(wti.pos()));
return poly;
}
// Wipe tower logic based on GLCanvas3D::WipeTowerInfo implements the Arrangeable
// interface with this class:
class ArrangeableWT: public arr2::ArrangeableWipeTowerBase
{
BoundingBox m_xl_bb;
Vec2d m_orig_tr;
double m_orig_rot;
public:
explicit ArrangeableWT(const ObjectID &oid,
const GLCanvas3D::WipeTowerInfo &wti,
std::function<bool()> sel_pred,
const BoundingBox xl_bb = {})
: arr2::ArrangeableWipeTowerBase{oid, get_wtpoly(wti), std::move(sel_pred)}
, m_orig_tr{wti.pos()}
, m_orig_rot{wti.rotation()}
, m_xl_bb{xl_bb}
{}
// Rotation is disabled for wipe tower in arrangement
void transform(const Vec2d &transl, double /*rot*/) override
{
GLCanvas3D::WipeTowerInfo::apply_wipe_tower(m_orig_tr + transl, m_orig_rot);
}
void imbue_data(arr2::AnyWritable &datastore) const override
{
// For XL printers, there is a requirement that the wipe tower
// needs to be placed right beside the extruders which reside at the
// top edge of the bed.
if (m_xl_bb.defined) {
Vec2crd xl_center = m_xl_bb.center();
datastore.write("sink", Vec2crd{xl_center.x(), 2 * m_xl_bb.max.y()});
}
arr2::ArrangeableWipeTowerBase::imbue_data(datastore);
}
};
// Now the wipe tower handler implementation for GLCanvas3D::WipeTowerInfo
// This is what creates the ArrangeableWT when the arrangement requests it.
// An object of this class is installed into the arrangement Scene.
struct WTH : public arr2::WipeTowerHandler
{
GLCanvas3D::WipeTowerInfo wti;
ObjectID oid;
std::function<bool()> sel_pred;
BoundingBox xl_bb;
WTH(const ObjectID &objid,
const GLCanvas3D::WipeTowerInfo &w,
std::function<bool()> sel_predicate = [] { return false; })
: wti(w), oid{objid}, sel_pred{std::move(sel_predicate)}
{}
template<class Self, class Fn>
static void visit_(Self &&self, Fn &&fn)
{
ArrangeableWT wta{self.oid, self.wti, self.sel_pred, self.xl_bb};
fn(wta);
}
void visit(std::function<void(arr2::Arrangeable &)> fn) override
{
visit_(*this, fn);
}
void visit(std::function<void(const arr2::Arrangeable &)> fn) const override
{
visit_(*this, fn);
}
void set_selection_predicate(std::function<bool()> pred) override
{
sel_pred = std::move(pred);
}
};
arr2::SceneBuilder build_scene(Plater &plater, ArrangeSelectionMode mode)
{
arr2::SceneBuilder builder;
if (mode == ArrangeSelectionMode::SelectionOnly) {
auto sel = std::make_unique<GUISelectionMask>(&plater.get_selection());
builder.set_selection(std::move(sel));
}
builder.set_arrange_settings(plater.canvas3D()->get_arrange_settings_view());
auto wti = plater.canvas3D()->get_wipe_tower_info();
AnyPtr<WTH> wth;
if (wti) {
wth = std::make_unique<WTH>(plater.model().wipe_tower.id(), wti);
}
if (plater.config()) {
builder.set_bed(*plater.config());
if (wth && is_XL_printer(*plater.config())) {
wth->xl_bb = bounding_box(get_bed_shape(*plater.config()));
}
}
builder.set_wipe_tower_handler(std::move(wth));
builder.set_model(plater.model());
if (plater.printer_technology() == ptSLA)
builder.set_sla_print(&plater.sla_print());
else
builder.set_fff_print(&plater.fff_print());
return builder;
}
FillBedJob2::FillBedJob2(arr2::Scene &&scene, const Callbacks &cbs) : Base(std::move(scene), _u8L("Filling bed"), cbs) {}
ArrangeJob2::ArrangeJob2(arr2::Scene &&scene, const Callbacks &cbs) : Base(std::move(scene), _u8L("Arranging"), cbs) {}
}} // namespace Slic3r

View File

@ -0,0 +1,144 @@
#ifndef ARRANGEJOB2_HPP
#define ARRANGEJOB2_HPP
#include <optional>
#include "Job.hpp"
#include "libslic3r/Arrange/Tasks/ArrangeTask.hpp"
#include "libslic3r/Arrange/Tasks/FillBedTask.hpp"
#include "libslic3r/Arrange/Items/ArrangeItem.hpp"
#include "libslic3r/Arrange/SceneBuilder.hpp"
namespace Slic3r {
class Model;
class DynamicPrintConfig;
class ModelInstance;
class Print;
class SLAPrint;
namespace GUI {
class Plater;
enum class ArrangeSelectionMode { SelectionOnly, Full };
arr2::SceneBuilder build_scene(
Plater &plater, ArrangeSelectionMode mode = ArrangeSelectionMode::Full);
struct ArrCtl : public arr2::ArrangeTaskBase::Ctl
{
Job::Ctl &parent_ctl;
int total;
const std::string &msg;
ArrCtl(Job::Ctl &ctl, int cnt, const std::string &m)
: parent_ctl{ctl}, total{cnt}, msg{m}
{}
bool was_canceled() const override
{
return parent_ctl.was_canceled();
}
void update_status(int remaining) override
{
if (remaining > 0)
parent_ctl.update_status((total - remaining) * 100 / total, msg);
}
};
template<class ArrangeTaskT>
class ArrangeJob_ : public Job
{
public:
using ResultType =
typename decltype(std::declval<ArrangeTaskT>().process_native(
std::declval<arr2::ArrangeTaskCtl>()))::element_type;
// All callbacks are called in the main thread.
struct Callbacks {
// Task is prepared but not no processing has been initiated
std::function<void(ArrangeTaskT &)> on_prepared;
// Task has been completed but the result is not yet written (inside finalize)
std::function<void(ArrangeTaskT &)> on_processed;
// Task result has been written
std::function<void(ResultType &)> on_finished;
};
private:
arr2::Scene m_scene;
std::unique_ptr<ArrangeTaskT> m_task;
std::unique_ptr<ResultType> m_result;
Callbacks m_cbs;
std::string m_task_msg;
public:
void process(Ctl &ctl) override
{
ctl.call_on_main_thread([this]{
m_task = ArrangeTaskT::create(m_scene);
m_result.reset();
if (m_task && m_cbs.on_prepared)
m_cbs.on_prepared(*m_task);
}).wait();
if (!m_task)
return;
auto count = m_task->item_count_to_process();
if (count == 0) // Should be taken care of by plater, but doesn't hurt
return;
ctl.update_status(0, m_task_msg);
auto taskctl = ArrCtl{ctl, count, m_task_msg};
m_result = m_task->process_native(taskctl);
ctl.update_status(100, m_task_msg);
}
void finalize(bool canceled, std::exception_ptr &eptr) override
{
if (canceled || eptr || !m_result)
return;
if (m_task && m_cbs.on_processed)
m_cbs.on_processed(*m_task);
m_result->apply_on(m_scene.model());
if (m_task && m_cbs.on_finished)
m_cbs.on_finished(*m_result);
}
explicit ArrangeJob_(arr2::Scene &&scene,
std::string task_msg,
const Callbacks &cbs = {})
: m_scene{std::move(scene)}, m_cbs{cbs}, m_task_msg{std::move(task_msg)}
{}
};
class ArrangeJob2: public ArrangeJob_<arr2::ArrangeTask<arr2::ArrangeItem>>
{
using Base = ArrangeJob_<arr2::ArrangeTask<arr2::ArrangeItem>>;
public:
ArrangeJob2(arr2::Scene &&scene, const Callbacks &cbs = {});
};
class FillBedJob2: public ArrangeJob_<arr2::FillBedTask<arr2::ArrangeItem>>
{
using Base = ArrangeJob_<arr2::FillBedTask<arr2::ArrangeItem>>;
public:
FillBedJob2(arr2::Scene &&scene, const Callbacks &cbs = {});
};
} // namespace GUI
} // namespace Slic3r
#endif // ARRANGEJOB2_HPP

View File

@ -32,6 +32,9 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
, content_sizer(new wxBoxSizer(wxVERTICAL)) , content_sizer(new wxBoxSizer(wxVERTICAL))
, btn_sizer(new wxBoxSizer(wxHORIZONTAL)) , btn_sizer(new wxBoxSizer(wxHORIZONTAL))
{ {
#ifdef __APPLE__
this->SetBackgroundColour(wxGetApp().get_window_default_clr());
#endif
boldfont.SetWeight(wxFONTWEIGHT_BOLD); boldfont.SetWeight(wxFONTWEIGHT_BOLD);
this->SetFont(wxGetApp().normal_font()); this->SetFont(wxGetApp().normal_font());
@ -139,22 +142,8 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
wxFont font = wxGetApp().normal_font();//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); wxFont font = wxGetApp().normal_font();//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
wxFont monospace = wxGetApp().code_font(); wxFont monospace = wxGetApp().code_font();
wxColour text_clr = wxGetApp().get_label_clr_default(); wxColour text_clr = wxGetApp().get_label_clr_default();
wxColour bgr_clr = parent->GetBackgroundColour();
#ifdef __APPLE__
// On macOS 10.13 and older the background color returned by wxWidgets
// is wrong, which leads to https://github.com/prusa3d/PrusaSlicer/issues/7603
// and https://github.com/prusa3d/PrusaSlicer/issues/3775. wxSYS_COLOUR_WINDOW
// may not match the window background exactly, but it seems to never end up
// as black on black.
if (wxPlatformInfo::Get().GetOSMajorVersion() == 10
&& wxPlatformInfo::Get().GetOSMinorVersion() < 14)
bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
#endif
auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); auto bgr_clr_str = wxGetApp().get_html_bg_color(parent);
const int font_size = font.GetPointSize(); const int font_size = font.GetPointSize();
int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size }; int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size };
html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size); html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size);

View File

@ -79,8 +79,10 @@
#include "Camera.hpp" #include "Camera.hpp"
#include "Mouse3DController.hpp" #include "Mouse3DController.hpp"
#include "Tab.hpp" #include "Tab.hpp"
#include "Jobs/ArrangeJob.hpp" //#include "Jobs/ArrangeJob.hpp"
#include "Jobs/FillBedJob.hpp" #include "Jobs/ArrangeJob2.hpp"
//#include "Jobs/FillBedJob.hpp"
#include "Jobs/RotoptimizeJob.hpp" #include "Jobs/RotoptimizeJob.hpp"
#include "Jobs/SLAImportJob.hpp" #include "Jobs/SLAImportJob.hpp"
#include "Jobs/SLAImportDialog.hpp" #include "Jobs/SLAImportDialog.hpp"
@ -6259,8 +6261,50 @@ void Plater::fill_bed_with_instances()
{ {
auto &w = get_ui_job_worker(); auto &w = get_ui_job_worker();
if (w.is_idle()) { if (w.is_idle()) {
p->take_snapshot(_L("Fill bed"));
replace_job(w, std::make_unique<FillBedJob>()); FillBedJob2::Callbacks cbs;
cbs.on_processed = [this](arr2::ArrangeTaskBase &t) {
p->take_snapshot(_L("Fill bed"));
};
auto scene = arr2::Scene{
build_scene(*this, ArrangeSelectionMode::SelectionOnly)};
cbs.on_finished = [this](arr2::FillBedTaskResult &result) {
auto [prototype_mi, pos] = arr2::find_instance_by_id(model(), result.prototype_id);
if (!prototype_mi)
return;
ModelObject *model_object = prototype_mi->get_object();
assert(model_object);
model_object->ensure_on_bed();
size_t inst_cnt = model_object->instances.size();
if (inst_cnt == 0)
return;
int object_idx = pos.obj_idx;
if (object_idx < 0 || object_idx >= int(model().objects.size()))
return;
update(static_cast<unsigned int>(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
if (!result.to_add.empty()) {
auto added_cnt = result.to_add.size();
// FIXME: somebody explain why this is needed for
// increase_object_instances
if (result.arranged_items.size() == 1)
added_cnt++;
sidebar().obj_list()->increase_object_instances(object_idx, added_cnt);
}
};
replace_job(w, std::make_unique<FillBedJob2>(std::move(scene), cbs));
} }
} }
@ -6338,8 +6382,8 @@ void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& ne
selection.add_object((unsigned int)(last_id - i), i == 0); selection.add_object((unsigned int)(last_id - i), i == 0);
UIThreadWorker w; UIThreadWorker w;
replace_job(w, std::make_unique<ArrangeJob>(ArrangeJob::SelectionOnly)); arrange(w, true);
w.process_events(); w.wait_for_idle();
} }
void Plater::export_gcode(bool prefer_removable) void Plater::export_gcode(bool prefer_removable)
@ -7229,19 +7273,70 @@ GLCanvas3D* Plater::get_current_canvas3D()
return p->get_current_canvas3D(); return p->get_current_canvas3D();
} }
static std::string concat_strings(const std::set<std::string> &strings,
const std::string &delim = "\n")
{
return std::accumulate(
strings.begin(), strings.end(), std::string(""),
[delim](const std::string &s, const std::string &name) {
return s + name + delim;
});
}
void Plater::arrange() void Plater::arrange()
{ {
if (p->can_arrange()) { if (p->can_arrange()) {
auto &w = get_ui_job_worker(); auto &w = get_ui_job_worker();
p->take_snapshot(_L("Arrange")); arrange(w, wxGetKeyState(WXK_SHIFT));
auto mode = wxGetKeyState(WXK_SHIFT) ? ArrangeJob::SelectionOnly :
ArrangeJob::Full;
replace_job(w, std::make_unique<ArrangeJob>(mode));
} }
} }
void Plater::arrange(Worker &w, bool selected)
{
ArrangeSelectionMode mode = selected ?
ArrangeSelectionMode::SelectionOnly :
ArrangeSelectionMode::Full;
arr2::Scene arrscene{build_scene(*this, mode)};
ArrangeJob2::Callbacks cbs;
cbs.on_processed = [this](arr2::ArrangeTaskBase &t) {
p->take_snapshot(_L("Arrange"));
};
cbs.on_finished = [this](arr2::ArrangeTaskResult &t) {
std::set<std::string> names;
auto collect_unarranged = [this, &names](const arr2::TrafoOnlyArrangeItem &itm) {
if (!arr2::is_arranged(itm)) {
std::optional<ObjectID> id = arr2::retrieve_id(itm);
if (id) {
auto [mi, pos] = arr2::find_instance_by_id(p->model, *id);
if (mi && mi->get_object()) {
names.insert(mi->get_object()->name);
}
}
}
};
for (const arr2::TrafoOnlyArrangeItem &itm : t.items)
collect_unarranged(itm);
if (!names.empty()) {
get_notification_manager()->push_notification(
GUI::format(_L("Arrangement ignored the following objects which "
"can't fit into a single bed:\n%s"),
concat_strings(names, "\n")));
}
update(static_cast<unsigned int>(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
wxGetApp().obj_manipul()->set_dirty();
};
replace_job(w, std::make_unique<ArrangeJob2>(std::move(arrscene), cbs));
}
void Plater::set_current_canvas_as_dirty() void Plater::set_current_canvas_as_dirty()
{ {
p->set_current_canvas_as_dirty(); p->set_current_canvas_as_dirty();

View File

@ -337,6 +337,7 @@ public:
GLCanvas3D* get_current_canvas3D(); GLCanvas3D* get_current_canvas3D();
void arrange(); void arrange();
void arrange(Worker &w, bool selected);
void set_current_canvas_as_dirty(); void set_current_canvas_as_dirty();
void unbind_canvas_event_handlers(); void unbind_canvas_event_handlers();

Some files were not shown because too many files have changed in this diff Show More