mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-01-30 15:38:43 +03:00
Merge prusa 2.6.1
This commit is contained in:
@@ -26,7 +26,6 @@ endif()
|
||||
option(SLIC3R_STATIC "Compile QIDISlicer with static libraries (Boost, TBB, glew)" ${SLIC3R_STATIC_INITIAL})
|
||||
option(SLIC3R_GUI "Compile QIDISlicer with GUI components (OpenGL, wxWidgets)" 1)
|
||||
option(SLIC3R_FHS "Assume QIDISlicer is to be installed in a FHS directory structure" 0)
|
||||
option(SLIC3R_WX_STABLE "Build against wxWidgets stable (3.0) as oppsed to dev (3.1) on Linux" 0)
|
||||
option(SLIC3R_PCH "Use precompiled headers" 1)
|
||||
option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1)
|
||||
option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1)
|
||||
@@ -60,7 +59,6 @@ if (APPLE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Proposal for C++ unit tests and sandboxes
|
||||
option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF)
|
||||
option(SLIC3R_BUILD_TESTS "Build unit tests" ON)
|
||||
|
||||
|
||||
1214
cmake/modules/FindwxWidgets.cmake
Normal file
1214
cmake/modules/FindwxWidgets.cmake
Normal file
File diff suppressed because it is too large
Load Diff
7
resources/icons/snap.svg
Normal file
7
resources/icons/snap.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path fill="none" stroke="#808080" stroke-linecap="round" stroke-miterlimit="10" d="m1.5,11.5V3.5c0-1.104569435119629.895430564880371-2,2-2h9c1.104569435119629,0,2,.895430564880371,2,2v8" />
|
||||
<line x1="1.5" y1="14.5" x2="14.5" y2="14.5" style="fill:none; stroke:#ed6b21; stroke-linecap:round; stroke-miterlimit:10;"/>
|
||||
<path d="m7,15h-3V5.237499999999272c0-.683452377914364.447715250171314-1.237499999999272,1-1.237499999999272h2v11Z" style="fill:#ed6b21; stroke-width:0px;"/>
|
||||
<path d="m9,15h3V5.237499999999272c0-.683452377914364-.447715250171314-1.237499999999272-1-1.237499999999272h-2v11Z" style="fill:#ed6b21; stroke-width:0px;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 755 B |
@@ -270,7 +270,7 @@ compatible_printers_condition =
|
||||
compatible_prints =
|
||||
compatible_prints_condition =
|
||||
cooling = 1
|
||||
disable_fan_first_layers = 3
|
||||
disable_fan_first_layers = 1
|
||||
enable_advance_pressure = 1
|
||||
enable_auxiliary_fan = 100
|
||||
enable_dynamic_fan_speeds = 0
|
||||
@@ -316,7 +316,7 @@ filament_wipe = nil
|
||||
first_layer_bed_temperature = 55
|
||||
first_layer_temperature = 210
|
||||
first_layer_volume_temperature = 0
|
||||
full_fan_speed_layer = 5
|
||||
full_fan_speed_layer = 0
|
||||
idle_temperature = 100
|
||||
inherits =
|
||||
max_fan_speed = 100
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#add_subdirectory(slasupporttree)
|
||||
#add_subdirectory(openvdb)
|
||||
# add_subdirectory(meshboolean)
|
||||
add_subdirectory(its_neighbor_index)
|
||||
#add_subdirectory(its_neighbor_index)
|
||||
# add_subdirectory(opencsg)
|
||||
#add_subdirectory(aabb-evaluation)
|
||||
add_subdirectory(wx_gl_test)
|
||||
#add_subdirectory(wx_gl_test)
|
||||
add_subdirectory(print_arrange_polys)
|
||||
|
||||
7
sandboxes/print_arrange_polys/CMakeLists.txt
Normal file
7
sandboxes/print_arrange_polys/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
add_executable(print_arrange_polys main.cpp)
|
||||
|
||||
target_link_libraries(print_arrange_polys libslic3r admesh)
|
||||
|
||||
if (WIN32)
|
||||
qidislicer_copy_dlls(print_arrange_polys)
|
||||
endif()
|
||||
103
sandboxes/print_arrange_polys/main.cpp
Normal file
103
sandboxes/print_arrange_polys/main.cpp
Normal 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;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -49,21 +49,12 @@ if (SLIC3R_GUI)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set (wxWidgets_CONFIG_OPTIONS "--toolkit=gtk${SLIC3R_GTK}")
|
||||
if (SLIC3R_WX_STABLE)
|
||||
find_package(wxWidgets 3.0 REQUIRED COMPONENTS base core adv html gl aui net webview)
|
||||
else ()
|
||||
find_package(wxWidgets 3.1 REQUIRED COMPONENTS base core adv html gl aui net webview)
|
||||
|
||||
if (NOT wxWidgets_FOUND)
|
||||
message(FATAL_ERROR "\nCould not find wxWidgets 3.1.\n"
|
||||
"Hint: On Linux you can set -DSLIC3R_WX_STABLE=1 to use wxWidgets 3.0\n")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
include(${wxWidgets_USE_FILE})
|
||||
else ()
|
||||
find_package(wxWidgets 3.2 REQUIRED COMPONENTS html adv gl core base webview aui net)
|
||||
endif ()
|
||||
find_package(wxWidgets 3.2 MODULE REQUIRED COMPONENTS base core adv html gl aui net webview)
|
||||
|
||||
include(${wxWidgets_USE_FILE})
|
||||
|
||||
slic3r_remap_configs(wx::wxhtml wx::wxadv wx::wxgl wx::wxcore wx::wxbase RelWithDebInfo Release)
|
||||
|
||||
if(UNIX)
|
||||
message(STATUS "wx-config path: ${wxWidgets_CONFIG_EXECUTABLE}")
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/GCode/PostProcessor.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/CutUtils.hpp"
|
||||
#include "libslic3r/ModelArrange.hpp"
|
||||
#include "libslic3r/Platform.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
@@ -313,10 +314,10 @@ int CLI::run(int argc, char **argv)
|
||||
|
||||
// Loop through transform options.
|
||||
bool user_center_specified = false;
|
||||
Points bed = get_bed_shape(m_print_config);
|
||||
ArrangeParams arrange_cfg;
|
||||
arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config));
|
||||
|
||||
arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(m_print_config));
|
||||
arr2::ArrangeSettings arrange_cfg;
|
||||
arrange_cfg.set_distance_from_objects(min_object_distance(m_print_config));
|
||||
|
||||
for (auto const &opt_key : m_transforms) {
|
||||
if (opt_key == "merge") {
|
||||
Model m;
|
||||
@@ -329,7 +330,7 @@ int CLI::run(int argc, char **argv)
|
||||
if (this->has_print_action())
|
||||
arrange_objects(m, bed, arrange_cfg);
|
||||
else
|
||||
arrange_objects(m, InfiniteBed{}, arrange_cfg);
|
||||
arrange_objects(m, arr2::InfiniteBed{}, arrange_cfg);
|
||||
}
|
||||
m_models.clear();
|
||||
m_models.emplace_back(std::move(m));
|
||||
@@ -437,8 +438,11 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
#else
|
||||
// model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower);
|
||||
model.objects.front()->cut(0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()),
|
||||
Cut cut(model.objects.front(), 0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()),
|
||||
ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper);
|
||||
auto cut_objects = cut.perform_with_plane();
|
||||
for (ModelObject* obj : cut_objects)
|
||||
model.add_object(*obj);
|
||||
#endif
|
||||
model.delete_object(size_t(0));
|
||||
}
|
||||
@@ -572,7 +576,7 @@ int CLI::run(int argc, char **argv)
|
||||
if (! m_config.opt_bool("dont_arrange")) {
|
||||
if (user_center_specified) {
|
||||
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
|
||||
arrange_objects(model, bed, arrange_cfg);
|
||||
}
|
||||
|
||||
@@ -165,6 +165,7 @@ struct indexed_triangle_set
|
||||
std::vector<stl_vertex> vertices;
|
||||
|
||||
bool empty() const { return indices.empty() || vertices.empty(); }
|
||||
bool operator==(const indexed_triangle_set& other) const { return this->indices == other.indices && this->vertices == other.vertices; }
|
||||
};
|
||||
|
||||
extern bool stl_open(stl_file *stl, const char *file);
|
||||
|
||||
@@ -86,7 +86,13 @@ inline IntPoint IntPoint2d(cInt x, cInt y)
|
||||
|
||||
inline cInt Round(double val)
|
||||
{
|
||||
return static_cast<cInt>((val < 0) ? (val - 0.5) : (val + 0.5));
|
||||
double v = val < 0 ? val - 0.5 : val + 0.5;
|
||||
#if defined(CLIPPERLIB_INT32) && ! defined(NDEBUG)
|
||||
static constexpr const double hi = 65536 * 16383;
|
||||
if (v > hi || -v > hi)
|
||||
throw clipperException("Coordinate outside allowed range");
|
||||
#endif
|
||||
return static_cast<cInt>(v);
|
||||
}
|
||||
|
||||
// Overriding the Eigen operators because we don't want to compare Z coordinate if IntPoint is 3 dimensional.
|
||||
|
||||
@@ -152,6 +152,7 @@ namespace ImGui
|
||||
// const wchar_t MmuSegmentationMarker = 0x1F;
|
||||
const wchar_t PlugMarker = 0x1C;
|
||||
const wchar_t DowelMarker = 0x1D;
|
||||
const wchar_t SnapMarker = 0x1E;
|
||||
// Do not forget use following letters only in wstring
|
||||
const wchar_t DocumentationButton = 0x2600;
|
||||
const wchar_t DocumentationHoverButton = 0x2601;
|
||||
|
||||
@@ -18,11 +18,10 @@ set(LIBNEST2D_SRCFILES
|
||||
include/libnest2d/optimizers/nlopt/simplex.hpp
|
||||
include/libnest2d/optimizers/nlopt/subplex.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_link_libraries(libnest2d PUBLIC 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_include_directories(libnest2d INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
|
||||
target_link_libraries(libnest2d INTERFACE NLopt::nlopt TBB::tbb TBB::tbbmalloc Boost::boost libslic3r)
|
||||
target_compile_definitions(libnest2d INTERFACE LIBNEST2D_THREADING_tbb LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_libslic3r)
|
||||
|
||||
@@ -243,6 +243,12 @@ inline void translate(Slic3r::ExPolygon& sh, const Slic3r::Point& offs)
|
||||
sh.translate(offs);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void translate(Slic3r::Polygon& sh, const Slic3r::Point& offs)
|
||||
{
|
||||
sh.translate(offs);
|
||||
}
|
||||
|
||||
#define DISABLE_BOOST_ROTATE
|
||||
template<>
|
||||
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);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void rotate(Slic3r::Polygon& sh, const Radians& rads)
|
||||
{
|
||||
sh.rotate(rads);
|
||||
}
|
||||
|
||||
} // namespace shapelike
|
||||
|
||||
namespace nfp {
|
||||
|
||||
@@ -15,12 +15,19 @@ namespace Slic3r {
|
||||
// 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
|
||||
// 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>
|
||||
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)
|
||||
{
|
||||
@@ -28,91 +35,119 @@ class AnyPtr {
|
||||
case RawPtr: return boost::get<T *>(s.ptr);
|
||||
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 WkPtr: {
|
||||
auto shptr = boost::get<std::weak_ptr<T>>(s.ptr).lock();
|
||||
return shptr.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
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))}
|
||||
{}
|
||||
template<class TT> friend class AnyPtr;
|
||||
|
||||
~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)} {}
|
||||
|
||||
template<class TT, class = SimilarPtrOnly<TT>>
|
||||
AnyPtr(AnyPtr<TT> &&other) noexcept
|
||||
{
|
||||
this->operator=(std::move(other));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
|
||||
AnyPtr &operator=(TT *p) { ptr = p; return *this; }
|
||||
template<class TT, class = SimilarPtrOnly<TT>>
|
||||
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*>>>
|
||||
AnyPtr &operator=(std::unique_ptr<TT> p) { ptr = std::move(p); return *this; }
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT*, T*>>>
|
||||
AnyPtr &operator=(std::shared_ptr<TT> p) { ptr = p; return *this; }
|
||||
template<class TT, class = SimilarPtrOnly<TT>>
|
||||
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*>>>
|
||||
AnyPtr &operator=(std::weak_ptr<TT> p) { ptr = std::move(p); return *this; }
|
||||
template<class TT, class = SimilarPtrOnly<TT>>
|
||||
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); }
|
||||
T &operator*() { return *get_ptr(*this); }
|
||||
template<class TT, class = SimilarPtrOnly<TT>>
|
||||
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 { return get_ptr(*this); }
|
||||
const T &operator*() const noexcept { return *get_ptr(*this); }
|
||||
T &operator*() noexcept { return *get_ptr(*this); }
|
||||
|
||||
T *get() { return get_ptr(*this); }
|
||||
const T *get() const { return get_ptr(*this); }
|
||||
T *operator->() noexcept { 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()) {
|
||||
case RawPtr: return bool(boost::get<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 WkPtr: {
|
||||
auto shptr = boost::get<std::weak_ptr<T>>(ptr).lock();
|
||||
return bool(shptr);
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
std::shared_ptr<T> get_shared_cpy() const
|
||||
std::shared_ptr<T> get_shared_cpy() const noexcept
|
||||
{
|
||||
std::shared_ptr<T> ret;
|
||||
|
||||
switch (ptr.which()) {
|
||||
case ShPtr: ret = boost::get<std::shared_ptr<T>>(ptr); break;
|
||||
case WkPtr: ret = boost::get<std::weak_ptr<T>>(ptr).lock(); break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
if (ptr.which() == ShPtr)
|
||||
ret = boost::get<std::shared_ptr<T>>(ptr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 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)
|
||||
ptr = std::shared_ptr<T>{std::move(boost::get<std::unique_ptr<T>>(ptr))};
|
||||
@@ -125,6 +160,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // ANYPTR_HPP
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <ClipperUtils.hpp>
|
||||
|
||||
#include <boost/geometry/index/rtree.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#if defined(_MSC_VER) && defined(__clang__)
|
||||
#define BOOST_NO_CXX17_HDR_STRING_VIEW
|
||||
@@ -258,7 +259,7 @@ protected:
|
||||
auto& index = isBig(item.area()) ? spatindex : smalls_spatindex;
|
||||
|
||||
// Query the spatial index for the neighbors
|
||||
std::vector<SpatElement> result;
|
||||
boost::container::small_vector<SpatElement, 100> result;
|
||||
result.reserve(index.size());
|
||||
|
||||
index.query(query, std::back_inserter(result));
|
||||
|
||||
269
src/libslic3r/Arrange/Arrange.hpp
Normal file
269
src/libslic3r/Arrange/Arrange.hpp
Normal file
@@ -0,0 +1,269 @@
|
||||
|
||||
#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});
|
||||
}
|
||||
|
||||
class EmptyItemOutlineError: public std::exception {
|
||||
static constexpr const char *Msg = "No outline can be derived for object";
|
||||
|
||||
public:
|
||||
const char* what() const noexcept override { return Msg; }
|
||||
};
|
||||
|
||||
template<class ArrItem> class ArrangeableToItemConverter
|
||||
{
|
||||
public:
|
||||
virtual ~ArrangeableToItemConverter() = default;
|
||||
|
||||
// May throw EmptyItemOutlineError
|
||||
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
|
||||
498
src/libslic3r/Arrange/ArrangeImpl.hpp
Normal file
498
src/libslic3r/Arrange/ArrangeImpl.hpp
Normal file
@@ -0,0 +1,498 @@
|
||||
|
||||
#ifndef ARRANGEIMPL_HPP
|
||||
#define ARRANGEIMPL_HPP
|
||||
|
||||
#include <random>
|
||||
#include <map>
|
||||
|
||||
#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>{});
|
||||
|
||||
std::vector<int> bed_indices = get_bed_indices(items, fixed);
|
||||
std::map<int, BoundingBox> pilebb;
|
||||
std::map<int, bool> bed_occupied;
|
||||
|
||||
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))
|
||||
bed_occupied[bedidx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &fxitm : fixed) {
|
||||
auto bedidx = get_bed_index(fxitm);
|
||||
if (bedidx >= 0)
|
||||
bed_occupied[bedidx] = true;
|
||||
}
|
||||
|
||||
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 (int bedidx : bed_indices) {
|
||||
if (auto occup_it = bed_occupied.find(bedidx);
|
||||
occup_it != bed_occupied.end() && occup_it->second)
|
||||
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 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 ? area(envelope_convex_hull(itm1)) > area(envelope_convex_hull(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:
|
||||
if constexpr (std::is_convertible_v<Bed, CircleBed>){
|
||||
basekernel = GravityKernel{};
|
||||
} else {
|
||||
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();
|
||||
|
||||
if (outline.empty())
|
||||
throw EmptyItemOutlineError{};
|
||||
|
||||
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();
|
||||
|
||||
if (outline.empty())
|
||||
throw EmptyItemOutlineError{};
|
||||
|
||||
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
|
||||
198
src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp
Normal file
198
src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
|
||||
#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
|
||||
92
src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp
Normal file
92
src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
|
||||
#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
|
||||
119
src/libslic3r/Arrange/ArrangeSettingsView.hpp
Normal file
119
src/libslic3r/Arrange/ArrangeSettingsView.hpp
Normal file
@@ -0,0 +1,119 @@
|
||||
|
||||
#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
|
||||
295
src/libslic3r/Arrange/Core/ArrangeBase.hpp
Normal file
295
src/libslic3r/Arrange/Core/ArrangeBase.hpp
Normal file
@@ -0,0 +1,295 @@
|
||||
|
||||
#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>
|
||||
std::vector<int> get_bed_indices(const Range<It> &items)
|
||||
{
|
||||
auto bed_indices = reserve_vector<int>(items.size());
|
||||
|
||||
for (auto &itm : items)
|
||||
bed_indices.emplace_back(get_bed_index(itm));
|
||||
|
||||
std::sort(bed_indices.begin(), bed_indices.end());
|
||||
auto endit = std::unique(bed_indices.begin(), bed_indices.end());
|
||||
|
||||
bed_indices.erase(endit, bed_indices.end());
|
||||
|
||||
return bed_indices;
|
||||
}
|
||||
|
||||
template<class It, class CIt>
|
||||
std::vector<int> get_bed_indices(const Range<It> &items, const Range<CIt> &fixed)
|
||||
{
|
||||
std::vector<int> ret;
|
||||
|
||||
auto iitems = get_bed_indices(items);
|
||||
auto ifixed = get_bed_indices(fixed);
|
||||
ret.reserve(std::max(iitems.size(), ifixed.size()));
|
||||
std::set_union(iitems.begin(), iitems.end(),
|
||||
ifixed.begin(), ifixed.end(),
|
||||
std::back_inserter(ret));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class It>
|
||||
size_t get_bed_count(const Range<It> &items)
|
||||
{
|
||||
return get_bed_indices(items).size();
|
||||
}
|
||||
|
||||
template<class It> int get_max_bed_index(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);
|
||||
});
|
||||
|
||||
int ret = Unarranged;
|
||||
if (it != items.end())
|
||||
ret = get_bed_index(*it);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct DefaultStopCondition {
|
||||
constexpr bool operator()() const noexcept { return false; }
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::arr2
|
||||
|
||||
#endif // ARRANGEBASE_HPP
|
||||
166
src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp
Normal file
166
src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp
Normal file
@@ -0,0 +1,166 @@
|
||||
|
||||
#ifndef ARRANGEFIRSTFIT_HPP
|
||||
#define ARRANGEFIRSTFIT_HPP
|
||||
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
using Context = PackStrategyContext<PackStrategy, ArrItem>;
|
||||
|
||||
std::map<int, Context> bed_contexts;
|
||||
auto get_or_init_context = [&ps, &bed, &bed_contexts](int bedidx) -> Context& {
|
||||
auto ctx_it = bed_contexts.find(bedidx);
|
||||
if (ctx_it == bed_contexts.end()) {
|
||||
auto res = bed_contexts.emplace(
|
||||
bedidx, create_context<ArrItem>(ps, bed, bedidx));
|
||||
|
||||
assert(res.second);
|
||||
|
||||
ctx_it = res.first;
|
||||
}
|
||||
|
||||
return ctx_it->second;
|
||||
};
|
||||
|
||||
for (auto &itm : fixed) {
|
||||
auto bedidx = get_bed_index(itm);
|
||||
if (bedidx >= 0) {
|
||||
Context &ctx = get_or_init_context(bedidx);
|
||||
add_fixed_item(ctx, 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;
|
||||
int bedidx = 0;
|
||||
while (!was_packed && !is_cancelled()) {
|
||||
for (; !was_packed && !is_cancelled(); bedidx++) {
|
||||
set_bed_index(*it, bedidx);
|
||||
|
||||
auto remaining = Range{std::next(static_cast<SConstIt>(it)),
|
||||
sorted_items.cend()};
|
||||
|
||||
Context &ctx = get_or_init_context(bedidx);
|
||||
|
||||
was_packed = pack(ps, bed, *it, ctx, remaining);
|
||||
|
||||
if(was_packed) {
|
||||
add_packed_item(ctx, *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);
|
||||
}
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::arr2
|
||||
|
||||
#endif // ARRANGEFIRSTFIT_HPP
|
||||
114
src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp
Normal file
114
src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp
Normal file
@@ -0,0 +1,114 @@
|
||||
|
||||
#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
|
||||
130
src/libslic3r/Arrange/Core/Beds.cpp
Normal file
130
src/libslic3r/Arrange/Core/Beds.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
|
||||
#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 ¢er, 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()) && (1.0 - parea / area(circ)) < 1e-2)
|
||||
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
|
||||
192
src/libslic3r/Arrange/Core/Beds.hpp
Normal file
192
src/libslic3r/Arrange/Core/Beds.hpp
Normal file
@@ -0,0 +1,192 @@
|
||||
|
||||
#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
|
||||
79
src/libslic3r/Arrange/Core/DataStoreTraits.hpp
Normal file
79
src/libslic3r/Arrange/Core/DataStoreTraits.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
|
||||
#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
|
||||
111
src/libslic3r/Arrange/Core/NFP/CircularEdgeIterator.hpp
Normal file
111
src/libslic3r/Arrange/Core/NFP/CircularEdgeIterator.hpp
Normal file
@@ -0,0 +1,111 @@
|
||||
|
||||
#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
|
||||
100
src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp
Normal file
100
src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
|
||||
#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);
|
||||
|
||||
if (N == 0 || S == 0)
|
||||
return;
|
||||
|
||||
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);
|
||||
|
||||
if (NH == 0 || SH == 0)
|
||||
continue;
|
||||
|
||||
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
|
||||
72
src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp
Normal file
72
src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
#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)
|
||||
{
|
||||
size_t n = std::max(size_t{1}, N);
|
||||
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
|
||||
62
src/libslic3r/Arrange/Core/NFP/Kernels/CompactifyKernel.hpp
Normal file
62
src/libslic3r/Arrange/Core/NFP/Kernels/CompactifyKernel.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
#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
|
||||
59
src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp
Normal file
59
src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
|
||||
#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
|
||||
58
src/libslic3r/Arrange/Core/NFP/Kernels/KernelTraits.hpp
Normal file
58
src/libslic3r/Arrange/Core/NFP/Kernels/KernelTraits.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
#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
|
||||
76
src/libslic3r/Arrange/Core/NFP/Kernels/KernelUtils.hpp
Normal file
76
src/libslic3r/Arrange/Core/NFP/Kernels/KernelUtils.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,95 @@
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,97 @@
|
||||
|
||||
#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
|
||||
271
src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp
Normal file
271
src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp
Normal file
@@ -0,0 +1,271 @@
|
||||
|
||||
#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
|
||||
419
src/libslic3r/Arrange/Core/NFP/NFP.cpp
Normal file
419
src/libslic3r/Arrange/Core/NFP/NFP.cpp
Normal file
@@ -0,0 +1,419 @@
|
||||
|
||||
#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
|
||||
51
src/libslic3r/Arrange/Core/NFP/NFP.hpp
Normal file
51
src/libslic3r/Arrange/Core/NFP/NFP.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
#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
|
||||
197
src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp
Normal file
197
src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp
Normal file
@@ -0,0 +1,197 @@
|
||||
|
||||
#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
|
||||
112
src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp
Normal file
112
src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
|
||||
#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
|
||||
15
src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp
Normal file
15
src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
#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
|
||||
71
src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp
Normal file
71
src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
#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
|
||||
16
src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp
Normal file
16
src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
#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
|
||||
286
src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp
Normal file
286
src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp
Normal file
@@ -0,0 +1,286 @@
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,142 @@
|
||||
|
||||
#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
|
||||
125
src/libslic3r/Arrange/Core/PackingContext.hpp
Normal file
125
src/libslic3r/Arrange/Core/PackingContext.hpp
Normal file
@@ -0,0 +1,125 @@
|
||||
|
||||
#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
|
||||
92
src/libslic3r/Arrange/Items/ArbitraryDataStore.hpp
Normal file
92
src/libslic3r/Arrange/Items/ArbitraryDataStore.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
|
||||
#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
|
||||
206
src/libslic3r/Arrange/Items/ArrangeItem.cpp
Normal file
206
src/libslic3r/Arrange/Items/ArrangeItem.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
|
||||
#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
|
||||
481
src/libslic3r/Arrange/Items/ArrangeItem.hpp
Normal file
481
src/libslic3r/Arrange/Items/ArrangeItem.hpp
Normal file
@@ -0,0 +1,481 @@
|
||||
|
||||
#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
|
||||
137
src/libslic3r/Arrange/Items/MutableItemTraits.hpp
Normal file
137
src/libslic3r/Arrange/Items/MutableItemTraits.hpp
Normal file
@@ -0,0 +1,137 @@
|
||||
|
||||
#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
|
||||
25
src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp
Normal file
25
src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
#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
|
||||
219
src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp
Normal file
219
src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp
Normal file
@@ -0,0 +1,219 @@
|
||||
|
||||
#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
|
||||
80
src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp
Normal file
80
src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
#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
|
||||
65
src/libslic3r/Arrange/Scene.cpp
Normal file
65
src/libslic3r/Arrange/Scene.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
bool arrange(Scene &scene, ArrangeTaskCtl &ctl)
|
||||
{
|
||||
auto task = ArrangeTaskBase::create(Tasks::Arrange, scene);
|
||||
auto result = task->process(ctl);
|
||||
return result->apply_on(scene.model());
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::arr2
|
||||
402
src/libslic3r/Arrange/Scene.hpp
Normal file
402
src/libslic3r/Arrange/Scene.hpp
Normal file
@@ -0,0 +1,402 @@
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
inline BoundingBox bounding_box(const ExtendedBed &bed)
|
||||
{
|
||||
BoundingBox bedbb;
|
||||
visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed);
|
||||
|
||||
return bedbb;
|
||||
}
|
||||
|
||||
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{});
|
||||
}
|
||||
};
|
||||
|
||||
bool arrange(Scene &scene, ArrangeTaskCtl &ctl);
|
||||
inline bool arrange(Scene &scene, ArrangeTaskCtl &&ctl = DummyCtl{})
|
||||
{
|
||||
return arrange(scene, ctl);
|
||||
}
|
||||
|
||||
inline bool arrange(Scene &&scene, ArrangeTaskCtl &ctl)
|
||||
{
|
||||
return arrange(scene, ctl);
|
||||
}
|
||||
|
||||
inline bool arrange(Scene &&scene, ArrangeTaskCtl &&ctl = DummyCtl{})
|
||||
{
|
||||
return arrange(scene, ctl);
|
||||
}
|
||||
|
||||
template<class Builder, class Ctl = DummyCtl>
|
||||
bool arrange(SceneBuilderBase<Builder> &&builder, Ctl &&ctl = {})
|
||||
{
|
||||
return arrange(Scene{std::move(builder)}, ctl);
|
||||
}
|
||||
|
||||
} // namespace arr2
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // ARR2_SCENE_HPP
|
||||
928
src/libslic3r/Arrange/SceneBuilder.cpp
Normal file
928
src/libslic3r/Arrange/SceneBuilder.cpp
Normal file
@@ -0,0 +1,928 @@
|
||||
|
||||
#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,
|
||||
const Transform3d &tr,
|
||||
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(tr * inst_matrix
|
||||
* v->get_matrix()));
|
||||
}
|
||||
}
|
||||
|
||||
return bb;
|
||||
}
|
||||
|
||||
BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate)
|
||||
{
|
||||
return instance_bounding_box(mi, Transform3d::Identity(), dont_translate);
|
||||
}
|
||||
|
||||
bool check_coord_bounds(const BoundingBoxf &bb)
|
||||
{
|
||||
return std::abs(bb.min.x()) < UnscaledCoordLimit &&
|
||||
std::abs(bb.min.y()) < UnscaledCoordLimit &&
|
||||
std::abs(bb.max.x()) < UnscaledCoordLimit &&
|
||||
std::abs(bb.max.y()) < UnscaledCoordLimit;
|
||||
}
|
||||
|
||||
ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr)
|
||||
{
|
||||
ExPolygons outline;
|
||||
|
||||
if (check_coord_bounds(to_2d(instance_bounding_box(inst, tr)))) {
|
||||
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)
|
||||
{
|
||||
auto bb = to_2d(instance_bounding_box(inst, tr));
|
||||
Polygon ret;
|
||||
|
||||
if (check_coord_bounds(bb)) {
|
||||
ret = inst.get_object()->convex_hull_2d(tr * inst.get_matrix());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
if (m_fff_print && !m_xl_printer)
|
||||
m_xl_printer = is_XL_printer(m_fff_print->config());
|
||||
|
||||
bool has_wipe_tower = false;
|
||||
m_wipetower_handler->visit(
|
||||
[&has_wipe_tower](const Arrangeable &arrbl) { has_wipe_tower = true; });
|
||||
|
||||
if (m_xl_printer && !has_wipe_tower) {
|
||||
m_bed = XLBed{bounding_box(m_bed)};
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
auto bedidx_d = std::floor(reference_pos_x / stride);
|
||||
|
||||
if (bedidx_d < std::numeric_limits<int>::min())
|
||||
bedidx = std::numeric_limits<int>::min();
|
||||
else if (bedidx_d > std::numeric_limits<int>::max())
|
||||
bedidx = std::numeric_limits<int>::max();
|
||||
else
|
||||
bedidx = static_cast<int>(bedidx_d);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
auto bedidx_d = std::floor(reference_pos_y / stride);
|
||||
|
||||
if (bedidx_d < std::numeric_limits<int>::min())
|
||||
bedidx = std::numeric_limits<int>::min();
|
||||
else if (bedidx_d > std::numeric_limits<int>::max())
|
||||
bedidx = std::numeric_limits<int>::max();
|
||||
else
|
||||
bedidx = static_cast<int>(bedidx_d);
|
||||
}
|
||||
|
||||
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::Cols =
|
||||
2 * static_cast<int>(std::sqrt(std::numeric_limits<int>::max()) / 2);
|
||||
|
||||
const int GridStriderVBedHandler::HalfCols = Cols / 2;
|
||||
const int GridStriderVBedHandler::Offset = HalfCols + Cols * HalfCols;
|
||||
|
||||
Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const
|
||||
{
|
||||
bed_idx += Offset;
|
||||
|
||||
Vec2i ret{bed_idx % Cols - HalfCols, bed_idx / Cols - HalfCols};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const
|
||||
{
|
||||
// Overlapping virtual beds will happen if the crd values exceed limits
|
||||
assert((crd.x() < HalfCols - 1 && crd.x() >= -HalfCols) &&
|
||||
(crd.y() < HalfCols - 1 && crd.y() >= -HalfCols));
|
||||
|
||||
return (crd.x() + HalfCols) + Cols * (crd.y() + HalfCols) - Offset;
|
||||
}
|
||||
|
||||
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_xl_printer = true;
|
||||
}
|
||||
|
||||
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
|
||||
678
src/libslic3r/Arrange/SceneBuilder.hpp
Normal file
678
src/libslic3r/Arrange/SceneBuilder.hpp
Normal file
@@ -0,0 +1,678 @@
|
||||
|
||||
#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;
|
||||
bool m_xl_printer = false;
|
||||
|
||||
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 Cols;
|
||||
static const int HalfCols;
|
||||
static const int Offset;
|
||||
|
||||
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);
|
||||
|
||||
BoundingBoxf3 instance_bounding_box(const ModelInstance &mi,
|
||||
const Transform3d &tr,
|
||||
bool dont_translate = false);
|
||||
|
||||
constexpr double UnscaledCoordLimit = 1000.;
|
||||
|
||||
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
|
||||
106
src/libslic3r/Arrange/SegmentedRectangleBed.hpp
Normal file
106
src/libslic3r/Arrange/SegmentedRectangleBed.hpp
Normal file
@@ -0,0 +1,106 @@
|
||||
|
||||
#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
|
||||
82
src/libslic3r/Arrange/Tasks/ArrangeTask.hpp
Normal file
82
src/libslic3r/Arrange/Tasks/ArrangeTask.hpp
Normal file
@@ -0,0 +1,82 @@
|
||||
|
||||
#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
|
||||
159
src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp
Normal file
159
src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp
Normal file
@@ -0,0 +1,159 @@
|
||||
|
||||
#ifndef ARRANGETASK_IMPL_HPP
|
||||
#define ARRANGETASK_IMPL_HPP
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#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();
|
||||
|
||||
try {
|
||||
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));
|
||||
} catch (const EmptyItemOutlineError &ex) {
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what();
|
||||
}
|
||||
});
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
inline int find_first_empty_bed(const std::vector<int>& bed_indices,
|
||||
int starting_from = 0) {
|
||||
int ret = starting_from;
|
||||
|
||||
for (int idx : bed_indices) {
|
||||
if (idx == ret) {
|
||||
ret++;
|
||||
} else if (idx > ret) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
std::vector<int> printable_bed_indices =
|
||||
get_bed_indices(crange(printable.selected), crange(printable.unselected));
|
||||
|
||||
// If there are no printables, leave the physical bed empty
|
||||
constexpr int SearchFrom = 1;
|
||||
|
||||
// Unprintable items should go to the first logical (!) bed not containing
|
||||
// any printable items
|
||||
int first_empty_bed = find_first_empty_bed(printable_bed_indices, SearchFrom);
|
||||
|
||||
prepare_fixed_unselected(unprintable.unselected, first_empty_bed);
|
||||
|
||||
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) + first_empty_bed;
|
||||
arr2::set_bed_index(itm, bedidx);
|
||||
}
|
||||
|
||||
result->add_item(itm);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace arr2
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif //ARRANGETASK_IMPL_HPP
|
||||
54
src/libslic3r/Arrange/Tasks/FillBedTask.hpp
Normal file
54
src/libslic3r/Arrange/Tasks/FillBedTask.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
#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
|
||||
202
src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp
Normal file
202
src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
#ifndef FILLBEDTASKIMPL_HPP
|
||||
#define FILLBEDTASKIMPL_HPP
|
||||
|
||||
#include "FillBedTask.hpp"
|
||||
|
||||
#include "Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
|
||||
|
||||
#include <boost/log/trivial.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) {
|
||||
try {
|
||||
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));
|
||||
}
|
||||
} catch (const EmptyItemOutlineError &ex) {
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what();
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
109
src/libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp
Normal file
109
src/libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp
Normal file
@@ -0,0 +1,109 @@
|
||||
|
||||
#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
|
||||
128
src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp
Normal file
128
src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp
Normal file
@@ -0,0 +1,128 @@
|
||||
|
||||
#ifndef MULTIPLYSELECTIONTASKIMPL_HPP
|
||||
#define MULTIPLYSELECTIONTASKIMPL_HPP
|
||||
|
||||
#include "MultiplySelectionTask.hpp"
|
||||
|
||||
#include <boost/log/trivial.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) {
|
||||
try {
|
||||
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));
|
||||
}
|
||||
} catch (const EmptyItemOutlineError &ex) {
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what();
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <libslic3r/Polyline.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>> {
|
||||
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
|
||||
|
||||
#endif // SLABOOSTADAPTER_HPP
|
||||
|
||||
@@ -54,8 +54,8 @@ public:
|
||||
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());
|
||||
}
|
||||
bool operator==(const BoundingBoxBase<PointType, PointsType> &rhs) { 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->min == rhs.min && this->max == rhs.max; }
|
||||
bool operator!=(const BoundingBoxBase<PointType, PointsType> &rhs) const noexcept { return ! (*this == rhs); }
|
||||
|
||||
private:
|
||||
// to access construct()
|
||||
@@ -192,6 +192,7 @@ public:
|
||||
|
||||
BoundingBox() : BoundingBoxBase<Point, Points>() {}
|
||||
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 inflated(coordf_t delta) const throw() { BoundingBox out(*this); out.offset(delta); return out; }
|
||||
@@ -215,6 +216,7 @@ public:
|
||||
BoundingBoxf() : BoundingBoxBase<Vec2d>() {}
|
||||
BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {}
|
||||
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {}
|
||||
BoundingBoxf(const BoundingBoxBase<Vec2d> &bb): BoundingBoxf{bb.min, bb.max} {}
|
||||
};
|
||||
|
||||
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)}; }
|
||||
|
||||
template<class T = coord_t>
|
||||
BoundingBoxBase<Vec<2, T>> scaled(const BoundingBoxf &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; }
|
||||
template<class T = coord_t, class Tin>
|
||||
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>
|
||||
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>
|
||||
BoundingBoxBase<Vec<2, T>> unscaled(const BoundingBox &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; }
|
||||
|
||||
template<class T = double>
|
||||
BoundingBox3Base<Vec<3, T>> unscaled(const BoundingBox3 &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; }
|
||||
template<class T = double, class Tin>
|
||||
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>
|
||||
auto cast(const BoundingBoxBase<Tin> &b)
|
||||
@@ -298,6 +306,19 @@ inline double bbox_point_distance_squared(const BoundingBox &bbox, const Point &
|
||||
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
|
||||
|
||||
// Serialization through the Cereal library
|
||||
|
||||
@@ -201,6 +201,8 @@ set(SLIC3R_SOURCES
|
||||
BlacklistedLibraryCheck.hpp
|
||||
LocalesUtils.cpp
|
||||
LocalesUtils.hpp
|
||||
CutUtils.cpp
|
||||
CutUtils.hpp
|
||||
Model.cpp
|
||||
Model.hpp
|
||||
ModelArrange.hpp
|
||||
@@ -214,8 +216,55 @@ set(SLIC3R_SOURCES
|
||||
MeasureUtils.hpp
|
||||
CustomGCode.cpp
|
||||
CustomGCode.hpp
|
||||
Arrange.hpp
|
||||
Arrange.cpp
|
||||
Arrange/Arrange.hpp
|
||||
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.hpp
|
||||
MutablePriorityQueue.hpp
|
||||
@@ -431,6 +480,10 @@ set(SLIC3R_SOURCES
|
||||
|
||||
add_library(libslic3r STATIC ${SLIC3R_SOURCES})
|
||||
|
||||
if (WIN32)
|
||||
target_compile_definitions(libslic3r PUBLIC NOMINMAX)
|
||||
endif()
|
||||
|
||||
foreach(_source IN ITEMS ${SLIC3R_SOURCES})
|
||||
get_filename_component(_source_path "${_source}" PATH)
|
||||
string(REPLACE "/" "\\" _group_path "${_source_path}")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#ifndef CSGMESH_HPP
|
||||
#define CSGMESH_HPP
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
#include <libslic3r/AnyPtr.hpp>
|
||||
#include <admesh/stl.h>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
@@ -81,6 +83,35 @@ struct CSGPart {
|
||||
{}
|
||||
};
|
||||
|
||||
template<class Cont> bool is_all_positive(const Cont &csgmesh)
|
||||
{
|
||||
bool is_all_pos =
|
||||
std::all_of(csgmesh.begin(),
|
||||
csgmesh.end(),
|
||||
[](auto &part) {
|
||||
return csg::get_operation(part) == csg::CSGType::Union;
|
||||
});
|
||||
|
||||
return is_all_pos;
|
||||
}
|
||||
|
||||
template<class Cont>
|
||||
indexed_triangle_set csgmesh_merge_positive_parts(const Cont &csgmesh)
|
||||
{
|
||||
indexed_triangle_set m;
|
||||
for (auto &csgpart : csgmesh) {
|
||||
auto op = csg::get_operation(csgpart);
|
||||
const indexed_triangle_set * pmesh = csg::get_mesh(csgpart);
|
||||
if (pmesh && op == csg::CSGType::Union) {
|
||||
indexed_triangle_set mcpy = *pmesh;
|
||||
its_transform(mcpy, csg::get_transform(csgpart), true);
|
||||
its_merge(m, mcpy);
|
||||
}
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // CSGMESH_HPP
|
||||
|
||||
@@ -155,10 +155,10 @@ It check_csgmesh_booleans(const Range<It> &csgrange, Visitor &&vfn)
|
||||
if (!m || MeshBoolean::cgal::empty(*m))
|
||||
return;
|
||||
|
||||
if (!MeshBoolean::cgal::does_bound_a_volume(*m))
|
||||
if (MeshBoolean::cgal::does_self_intersect(*m))
|
||||
return;
|
||||
|
||||
if (MeshBoolean::cgal::does_self_intersect(*m))
|
||||
if (!MeshBoolean::cgal::does_bound_a_volume(*m))
|
||||
return;
|
||||
}
|
||||
catch (...) { return; }
|
||||
|
||||
@@ -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).
|
||||
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); }
|
||||
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)
|
||||
{ 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)
|
||||
{ return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); }
|
||||
|
||||
|
||||
@@ -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);
|
||||
// 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, 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, 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);
|
||||
|
||||
// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed.
|
||||
|
||||
@@ -403,7 +403,7 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s
|
||||
descr += " (";
|
||||
if (!def.sidetext.empty()) {
|
||||
descr += def.sidetext + ", ";
|
||||
} else if (def.enum_def->has_values()) {
|
||||
} else if (def.enum_def && def.enum_def->has_values()) {
|
||||
descr += boost::algorithm::join(def.enum_def->values(), ", ") + "; ";
|
||||
}
|
||||
descr += "default: " + def.default_value->serialize() + ")";
|
||||
@@ -797,6 +797,9 @@ ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, Fo
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
// Do legacy conversion on a completely loaded dictionary.
|
||||
// Perform composite conversions, for example merging multiple keys into one key.
|
||||
this->handle_legacy_composite();
|
||||
return std::move(substitutions_ctxt.substitutions);
|
||||
}
|
||||
|
||||
|
||||
@@ -2160,6 +2160,10 @@ protected:
|
||||
// If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy().
|
||||
// handle_legacy() is called internally by set_deserialize().
|
||||
virtual void handle_legacy(t_config_option_key &/*opt_key*/, std::string &/*value*/) const {}
|
||||
// Called after a config is loaded as a whole.
|
||||
// Perform composite conversions, for example merging multiple keys into one key.
|
||||
// For conversion of single options, the handle_legacy() method above is called.
|
||||
virtual void handle_legacy_composite() {}
|
||||
|
||||
public:
|
||||
using ConfigOptionResolver::option;
|
||||
|
||||
646
src/libslic3r/CutUtils.cpp
Normal file
646
src/libslic3r/CutUtils.cpp
Normal file
@@ -0,0 +1,646 @@
|
||||
|
||||
#include "CutUtils.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "Model.hpp"
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
#include "TriangleSelector.hpp"
|
||||
#include "ObjectID.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
static void apply_tolerance(ModelVolume* vol)
|
||||
{
|
||||
ModelVolume::CutInfo& cut_info = vol->cut_info;
|
||||
|
||||
assert(cut_info.is_connector);
|
||||
if (!cut_info.is_processed)
|
||||
return;
|
||||
|
||||
Vec3d sf = vol->get_scaling_factor();
|
||||
|
||||
// make a "hole" wider
|
||||
sf[X] += double(cut_info.radius_tolerance);
|
||||
sf[Y] += double(cut_info.radius_tolerance);
|
||||
|
||||
// make a "hole" dipper
|
||||
sf[Z] += double(cut_info.height_tolerance);
|
||||
|
||||
vol->set_scaling_factor(sf);
|
||||
|
||||
// correct offset in respect to the new depth
|
||||
Vec3d rot_norm = rotation_transform(vol->get_rotation()) * Vec3d::UnitZ();
|
||||
if (rot_norm.norm() != 0.0)
|
||||
rot_norm.normalize();
|
||||
|
||||
double z_offset = 0.5 * static_cast<double>(cut_info.height_tolerance);
|
||||
if (cut_info.connector_type == CutConnectorType::Plug ||
|
||||
cut_info.connector_type == CutConnectorType::Snap)
|
||||
z_offset -= 0.05; // add small Z offset to better preview
|
||||
|
||||
vol->set_offset(vol->get_offset() + rot_norm * z_offset);
|
||||
}
|
||||
|
||||
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
||||
{
|
||||
if (mesh.empty())
|
||||
return;
|
||||
|
||||
mesh.transform(cut_matrix);
|
||||
ModelVolume* vol = object->add_volume(mesh);
|
||||
vol->set_type(type);
|
||||
|
||||
vol->name = src_volume->name + suffix;
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(src_volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != src_volume->config.id());
|
||||
vol->set_material(src_volume->material_id(), *src_volume->material());
|
||||
vol->cut_info = src_volume->cut_info;
|
||||
}
|
||||
|
||||
static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
|
||||
{
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
const Transformation cut_transformation = Transformation(cut_matrix);
|
||||
const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset());
|
||||
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
// Flip the triangles in case the composite transformation is left handed.
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
|
||||
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
}
|
||||
|
||||
static void process_connector_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
std::vector<ModelObject*>& dowels)
|
||||
{
|
||||
assert(volume->cut_info.is_connector);
|
||||
volume->cut_info.set_processed();
|
||||
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
// ! Don't apply instance transformation for the conntectors.
|
||||
// This transformation is already there
|
||||
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
ModelVolume* vol = nullptr;
|
||||
if (volume->cut_info.connector_type == CutConnectorType::Snap) {
|
||||
TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.));
|
||||
|
||||
vol = upper->add_volume(std::move(mesh));
|
||||
vol->set_transformation(volume->get_transformation());
|
||||
vol->set_type(ModelVolumeType::NEGATIVE_VOLUME);
|
||||
|
||||
vol->cut_info = volume->cut_info;
|
||||
vol->name = volume->name;
|
||||
}
|
||||
else
|
||||
vol = upper->add_volume(*volume);
|
||||
|
||||
vol->set_transformation(volume_matrix);
|
||||
apply_tolerance(vol);
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
ModelVolume* vol = lower->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
|
||||
ModelObject* dowel{ nullptr };
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
volume->get_object()->clone_for_cut(&dowel);
|
||||
|
||||
// add one more solid part same as connector if this connector is a dowel
|
||||
ModelVolume* vol = dowel->add_volume(*volume);
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
|
||||
// But discard rotation and Z-offset for this volume
|
||||
vol->set_rotation(Vec3d::Zero());
|
||||
vol->set_offset(Z, 0.0);
|
||||
|
||||
dowels.push_back(dowel);
|
||||
}
|
||||
|
||||
// Cut the dowel
|
||||
apply_tolerance(volume);
|
||||
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// add small Z offset to better preview
|
||||
upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
|
||||
// Add cut parts to the related objects
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
|
||||
}
|
||||
}
|
||||
|
||||
static void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
|
||||
{
|
||||
const auto volume_matrix = instance_matrix * volume->get_matrix();
|
||||
|
||||
// Modifiers are not cut, but we still need to add the instance transformation
|
||||
// to the modifier volume transformation to preserve their shape properly.
|
||||
volume->set_transformation(Transformation(volume_matrix));
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
|
||||
upper->add_volume(*volume);
|
||||
return;
|
||||
}
|
||||
|
||||
// Some logic for the negative volumes/connectors. Add only needed modifiers
|
||||
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
|
||||
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
|
||||
upper->add_volume(*volume);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
|
||||
lower->add_volume(*volume);
|
||||
}
|
||||
|
||||
static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
|
||||
{
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// Add required cut parts to the objects
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
|
||||
if (!lower_mesh.empty()) {
|
||||
add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
|
||||
upper->volumes.back()->cut_info.is_from_upper = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty())
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
|
||||
}
|
||||
|
||||
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx,
|
||||
const Transform3d& cut_matrix = Transform3d::Identity(),
|
||||
bool place_on_cut = false, bool flip = false)
|
||||
{
|
||||
// Reset instance transformation except offset and Z-rotation
|
||||
|
||||
for (size_t i = 0; i < object->instances.size(); ++i) {
|
||||
auto& obj_instance = object->instances[i];
|
||||
const double rot_z = obj_instance->get_rotation().z();
|
||||
|
||||
Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor());
|
||||
// add respect to mirroring
|
||||
if (obj_instance->is_left_handed())
|
||||
inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1)));
|
||||
|
||||
obj_instance->set_transformation(inst_trafo);
|
||||
|
||||
Vec3d rotation = Vec3d::Zero();
|
||||
if (!flip && !place_on_cut) {
|
||||
if ( i != src_instance_idx)
|
||||
rotation[Z] = rot_z;
|
||||
}
|
||||
else {
|
||||
Transform3d rotation_matrix = Transform3d::Identity();
|
||||
if (flip)
|
||||
rotation_matrix = rotation_transform(PI * Vec3d::UnitX());
|
||||
|
||||
if (place_on_cut)
|
||||
rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse();
|
||||
|
||||
if (i != src_instance_idx)
|
||||
rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
|
||||
|
||||
rotation = Transformation(rotation_matrix).get_rotation();
|
||||
}
|
||||
|
||||
obj_instance->set_rotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Cut::Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes/*= ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts*/)
|
||||
: m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes)
|
||||
{
|
||||
m_model = Model();
|
||||
if (object)
|
||||
m_model.add_object(*object);
|
||||
}
|
||||
|
||||
void Cut::post_process(ModelObject* object, ModelObjectPtrs& cut_object_ptrs, bool keep, bool place_on_cut, bool flip)
|
||||
{
|
||||
if (!object) return;
|
||||
|
||||
if (keep && !object->volumes.empty()) {
|
||||
reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip);
|
||||
cut_object_ptrs.push_back(object);
|
||||
}
|
||||
else
|
||||
m_model.objects.push_back(object); // will be deleted in m_model.clear_objects();
|
||||
}
|
||||
|
||||
void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs& cut_object_ptrs)
|
||||
{
|
||||
post_process(upper, cut_object_ptrs,
|
||||
m_attributes.has(ModelObjectCutAttribute::KeepUpper),
|
||||
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
||||
m_attributes.has(ModelObjectCutAttribute::FlipUpper));
|
||||
|
||||
post_process(lower, cut_object_ptrs,
|
||||
m_attributes.has(ModelObjectCutAttribute::KeepLower),
|
||||
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
|
||||
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower));
|
||||
}
|
||||
|
||||
|
||||
void Cut::finalize(const ModelObjectPtrs& objects)
|
||||
{
|
||||
//clear model from temporarry objects
|
||||
m_model.clear_objects();
|
||||
|
||||
// add to model result objects
|
||||
m_model.objects = objects;
|
||||
}
|
||||
|
||||
|
||||
const ModelObjectPtrs& Cut::perform_with_plane()
|
||||
{
|
||||
if (!m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !m_attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
m_model.clear_objects();
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
ModelObject* mo = m_model.objects.front();
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper{ nullptr };
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
mo->clone_for_cut(&upper);
|
||||
|
||||
ModelObject* lower{ nullptr };
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !m_attributes.has(ModelObjectCutAttribute::KeepAsParts))
|
||||
mo->clone_for_cut(&lower);
|
||||
|
||||
std::vector<ModelObject*> dowels;
|
||||
|
||||
// Because transformations are going to be applied to meshes directly,
|
||||
// we reset transformation of all instances and volumes,
|
||||
// except for translation and Z-rotation on instances, which are preserved
|
||||
// in the transformation matrix and not applied to the mesh transform.
|
||||
|
||||
const auto instance_matrix = mo->instances[m_instance]->get_transformation().get_matrix_no_offset();
|
||||
const Transformation cut_transformation = Transformation(m_cut_matrix);
|
||||
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
|
||||
|
||||
for (ModelVolume* volume : mo->volumes) {
|
||||
volume->reset_extra_facets();
|
||||
|
||||
if (!volume->is_model_part()) {
|
||||
if (volume->cut_info.is_processed)
|
||||
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, m_attributes, upper, lower);
|
||||
else
|
||||
process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels);
|
||||
}
|
||||
else if (!volume->mesh().empty())
|
||||
process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower);
|
||||
}
|
||||
|
||||
// Post-process cut parts
|
||||
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && upper->volumes.empty()) {
|
||||
m_model = Model();
|
||||
m_model.objects.push_back(upper);
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
ModelObjectPtrs cut_object_ptrs;
|
||||
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) {
|
||||
reset_instance_transformation(upper, m_instance, m_cut_matrix);
|
||||
cut_object_ptrs.push_back(upper);
|
||||
}
|
||||
else {
|
||||
// Delete all modifiers which are not intersecting with solid parts bounding box
|
||||
auto delete_extra_modifiers = [this](ModelObject* mo) {
|
||||
if (!mo) return;
|
||||
const BoundingBoxf3 obj_bb = mo->instance_bounding_box(m_instance);
|
||||
const Transform3d inst_matrix = mo->instances[m_instance]->get_transformation().get_matrix();
|
||||
|
||||
for (int i = int(mo->volumes.size()) - 1; i >= 0; --i)
|
||||
if (const ModelVolume* vol = mo->volumes[i];
|
||||
!vol->is_model_part() && !vol->is_cut_connector()) {
|
||||
auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
|
||||
if (!obj_bb.intersects(bb))
|
||||
mo->delete_volume(i);
|
||||
}
|
||||
};
|
||||
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
delete_extra_modifiers(upper);
|
||||
delete_extra_modifiers(lower);
|
||||
|
||||
if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
|
||||
for (auto dowel : dowels) {
|
||||
reset_instance_transformation(dowel, m_instance);
|
||||
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
|
||||
cut_object_ptrs.push_back(dowel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
|
||||
|
||||
finalize(cut_object_ptrs);
|
||||
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
static void distribute_modifiers_from_object(ModelObject* from_obj, const int instance_idx, ModelObject* to_obj1, ModelObject* to_obj2)
|
||||
{
|
||||
auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3();
|
||||
auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3();
|
||||
const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix();
|
||||
|
||||
for (ModelVolume* vol : from_obj->volumes)
|
||||
if (!vol->is_model_part()) {
|
||||
auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
|
||||
// Don't add modifiers which are not intersecting with solid parts
|
||||
if (obj1_bb.intersects(bb))
|
||||
to_obj1->add_volume(*vol);
|
||||
if (obj2_bb.intersects(bb))
|
||||
to_obj2->add_volume(*vol);
|
||||
}
|
||||
}
|
||||
|
||||
static void merge_solid_parts_inside_object(ModelObjectPtrs& objects)
|
||||
{
|
||||
for (ModelObject* mo : objects) {
|
||||
TriangleMesh mesh;
|
||||
// Merge all SolidPart but not Connectors
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
if (mv->is_model_part() && !mv->is_cut_connector()) {
|
||||
TriangleMesh m = mv->mesh();
|
||||
m.transform(mv->get_matrix());
|
||||
mesh.merge(m);
|
||||
}
|
||||
}
|
||||
if (!mesh.empty()) {
|
||||
ModelVolume* new_volume = mo->add_volume(mesh);
|
||||
new_volume->name = mo->name;
|
||||
// Delete all merged SolidPart but not Connectors
|
||||
for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) {
|
||||
const ModelVolume* mv = mo->volumes[i];
|
||||
if (mv->is_model_part() && !mv->is_cut_connector())
|
||||
mo->delete_volume(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const ModelObjectPtrs& Cut::perform_by_contour(std::vector<Part> parts, int dowels_count)
|
||||
{
|
||||
ModelObject* cut_mo = m_model.objects.front();
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper{ nullptr };
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_mo->clone_for_cut(&upper);
|
||||
ModelObject* lower{ nullptr };
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) cut_mo->clone_for_cut(&lower);
|
||||
|
||||
const size_t cut_parts_cnt = parts.size();
|
||||
bool has_modifiers = false;
|
||||
|
||||
// Distribute SolidParts to the Upper/Lower object
|
||||
for (size_t id = 0; id < cut_parts_cnt; ++id) {
|
||||
if (parts[id].is_modifier)
|
||||
has_modifiers = true; // modifiers will be added later to the related parts
|
||||
else if (ModelObject* obj = (parts[id].selected ? upper : lower))
|
||||
obj->add_volume(*(cut_mo->volumes[id]));
|
||||
}
|
||||
|
||||
if (has_modifiers) {
|
||||
// Distribute Modifiers to the Upper/Lower object
|
||||
distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
|
||||
}
|
||||
|
||||
ModelObjectPtrs cut_object_ptrs;
|
||||
|
||||
ModelVolumePtrs& volumes = cut_mo->volumes;
|
||||
if (volumes.size() == cut_parts_cnt) {
|
||||
// Means that object is cut without connectors
|
||||
|
||||
// Just add Upper and Lower objects to cut_object_ptrs
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
}
|
||||
else if (volumes.size() > cut_parts_cnt) {
|
||||
// Means that object is cut with connectors
|
||||
|
||||
// All volumes are distributed to Upper / Lower object,
|
||||
// So we don’t need them anymore
|
||||
for (size_t id = 0; id < cut_parts_cnt; id++)
|
||||
delete* (volumes.begin() + id);
|
||||
volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt);
|
||||
|
||||
// Perform cut just to get connectors
|
||||
Cut cut(cut_mo, m_instance, m_cut_matrix, m_attributes);
|
||||
const ModelObjectPtrs& cut_connectors_obj = cut.perform_with_plane();
|
||||
assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2);
|
||||
|
||||
// Connectors from upper object
|
||||
for (const ModelVolume* volume : cut_connectors_obj[0]->volumes)
|
||||
upper->add_volume(*volume, volume->type());
|
||||
|
||||
// Connectors from lower object
|
||||
for (const ModelVolume* volume : cut_connectors_obj[1]->volumes)
|
||||
lower->add_volume(*volume, volume->type());
|
||||
|
||||
// Add Upper and Lower objects to cut_object_ptrs
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
|
||||
// Add Dowel-connectors as separate objects to cut_object_ptrs
|
||||
if (cut_connectors_obj.size() >= 3)
|
||||
for (size_t id = 2; id < cut_connectors_obj.size(); id++)
|
||||
cut_object_ptrs.push_back(cut_connectors_obj[id]);
|
||||
}
|
||||
|
||||
// Now merge all model parts together:
|
||||
merge_solid_parts_inside_object(cut_object_ptrs);
|
||||
|
||||
finalize(cut_object_ptrs);
|
||||
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
|
||||
const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts/* = false*/)
|
||||
{
|
||||
ModelObject* cut_mo = m_model.objects.front();
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper{ nullptr };
|
||||
cut_mo->clone_for_cut(&upper);
|
||||
ModelObject* lower{ nullptr };
|
||||
cut_mo->clone_for_cut(&lower);
|
||||
|
||||
const double groove_half_depth = 0.5 * double(groove.depth);
|
||||
|
||||
Model tmp_model_for_cut = Model();
|
||||
|
||||
Model tmp_model = Model();
|
||||
tmp_model.add_object(*cut_mo);
|
||||
ModelObject* tmp_object = tmp_model.objects.front();
|
||||
|
||||
auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) {
|
||||
const auto& volumes = tmp_model_for_cut.objects.front()->volumes;
|
||||
for (const ModelVolume* volume : volumes)
|
||||
if (volume->is_model_part()) {
|
||||
if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) ||
|
||||
(attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper())) {
|
||||
ModelVolume* new_vol = object->add_volume(*volume);
|
||||
new_vol->reset_from_upper();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto cut = [this, add_volumes_from_cut]
|
||||
(ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) {
|
||||
Cut cut(object, m_instance, cut_matrix);
|
||||
|
||||
tmp_model_for_cut = Model();
|
||||
tmp_model_for_cut.add_object(*cut.perform_with_plane().front());
|
||||
assert(!tmp_model_for_cut.objects.empty());
|
||||
|
||||
object->clear_volumes();
|
||||
add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut);
|
||||
reset_instance_transformation(object, m_instance);
|
||||
};
|
||||
|
||||
// cut by upper plane
|
||||
|
||||
const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
|
||||
{
|
||||
cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// cut by lower plane
|
||||
|
||||
const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
|
||||
{
|
||||
cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// cut middle part with 2 angles and add parts to related upper/lower objects
|
||||
|
||||
const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle));
|
||||
|
||||
// cut by angle1 plane
|
||||
{
|
||||
const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
|
||||
|
||||
cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// cut by angle2 plane
|
||||
{
|
||||
const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
|
||||
|
||||
cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// apply tolerance to the middle part
|
||||
{
|
||||
const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance;
|
||||
|
||||
const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix;
|
||||
cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
|
||||
const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance);
|
||||
|
||||
const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
|
||||
cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
|
||||
const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
|
||||
cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// this part can be added to the upper object now
|
||||
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
|
||||
ModelObjectPtrs cut_object_ptrs;
|
||||
|
||||
if (keep_as_parts) {
|
||||
// add volumes from lower object to the upper, but mark them as a lower
|
||||
const auto& volumes = lower->volumes;
|
||||
for (const ModelVolume* volume : volumes) {
|
||||
ModelVolume* new_vol = upper->add_volume(*volume);
|
||||
new_vol->cut_info.is_from_upper = false;
|
||||
}
|
||||
|
||||
// add modifiers
|
||||
for (const ModelVolume* volume : cut_mo->volumes)
|
||||
if (!volume->is_model_part())
|
||||
upper->add_volume(*volume);
|
||||
|
||||
cut_object_ptrs.push_back(upper);
|
||||
|
||||
// add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks
|
||||
cut_object_ptrs.push_back(lower);
|
||||
}
|
||||
else {
|
||||
// add modifiers if object has any
|
||||
for (const ModelVolume* volume : cut_mo->volumes)
|
||||
if (!volume->is_model_part()) {
|
||||
distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
|
||||
break;
|
||||
}
|
||||
|
||||
assert(!upper->volumes.empty() && !lower->volumes.empty());
|
||||
|
||||
// Add Upper and Lower parts to cut_object_ptrs
|
||||
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
|
||||
// Now merge all model parts together:
|
||||
merge_solid_parts_inside_object(cut_object_ptrs);
|
||||
}
|
||||
|
||||
finalize(cut_object_ptrs);
|
||||
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
67
src/libslic3r/CutUtils.hpp
Normal file
67
src/libslic3r/CutUtils.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
#ifndef slic3r_CutUtils_hpp_
|
||||
#define slic3r_CutUtils_hpp_
|
||||
|
||||
#include "enum_bitmask.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using ModelObjectPtrs = std::vector<ModelObject*>;
|
||||
|
||||
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo };
|
||||
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
|
||||
|
||||
|
||||
class Cut {
|
||||
|
||||
Model m_model;
|
||||
int m_instance;
|
||||
const Transform3d m_cut_matrix;
|
||||
ModelObjectCutAttributes m_attributes;
|
||||
|
||||
void post_process(ModelObject* object, ModelObjectPtrs& objects, bool keep, bool place_on_cut, bool flip);
|
||||
void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects);
|
||||
void finalize(const ModelObjectPtrs& objects);
|
||||
|
||||
public:
|
||||
|
||||
Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes = ModelObjectCutAttribute::KeepUpper |
|
||||
ModelObjectCutAttribute::KeepLower |
|
||||
ModelObjectCutAttribute::KeepAsParts );
|
||||
~Cut() { m_model.clear_objects(); }
|
||||
|
||||
struct Groove
|
||||
{
|
||||
float depth{ 0.f };
|
||||
float width{ 0.f };
|
||||
float flaps_angle{ 0.f };
|
||||
float angle{ 0.f };
|
||||
float depth_init{ 0.f };
|
||||
float width_init{ 0.f };
|
||||
float flaps_angle_init{ 0.f };
|
||||
float angle_init{ 0.f };
|
||||
float depth_tolerance{ 0.1f };
|
||||
float width_tolerance{ 0.1f };
|
||||
};
|
||||
|
||||
struct Part
|
||||
{
|
||||
bool selected;
|
||||
bool is_modifier;
|
||||
};
|
||||
|
||||
const ModelObjectPtrs& perform_with_plane();
|
||||
const ModelObjectPtrs& perform_by_contour(std::vector<Part> parts, int dowels_count);
|
||||
const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false);
|
||||
|
||||
}; // namespace Cut
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_CutUtils_hpp_ */
|
||||
@@ -19,6 +19,10 @@
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
|
||||
// Experimentaly suggested ration of font ascent by multiple fonts
|
||||
// to get approx center of normal text line
|
||||
const double ASCENT_CENTER = 1/3.; // 0.5 is above small letter
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Emboss;
|
||||
using fontinfo_opt = std::optional<stbtt_fontinfo>;
|
||||
@@ -1207,64 +1211,160 @@ std::optional<Glyph> Emboss::letter2glyph(const FontFile &font,
|
||||
return priv::get_glyph(*font_info_opt, letter, flatness);
|
||||
}
|
||||
|
||||
ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache,
|
||||
const char *text,
|
||||
const FontProp &font_prop,
|
||||
std::function<bool()> was_canceled)
|
||||
const FontFile::Info &Emboss::get_font_info(const FontFile &font, const FontProp &prop)
|
||||
{
|
||||
unsigned int font_index = prop.collection_number.value_or(0);
|
||||
assert(priv::is_valid(font, font_index));
|
||||
return font.infos[font_index];
|
||||
}
|
||||
|
||||
int Emboss::get_line_height(const FontFile &font, const FontProp &prop) {
|
||||
const FontFile::Info &info = get_font_info(font, prop);
|
||||
int line_height = info.ascent - info.descent + info.linegap;
|
||||
line_height += prop.line_gap.value_or(0);
|
||||
return static_cast<int>(line_height / SHAPE_SCALE);
|
||||
}
|
||||
|
||||
namespace {
|
||||
ExPolygons letter2shapes(
|
||||
wchar_t letter, Point &cursor, FontFileWithCache &font_with_cache, const FontProp &font_prop, fontinfo_opt& font_info_cache)
|
||||
{
|
||||
assert(font_with_cache.has_value());
|
||||
fontinfo_opt font_info_opt;
|
||||
Point cursor(0, 0);
|
||||
if (!font_with_cache.has_value())
|
||||
return {};
|
||||
|
||||
Glyphs &cache = *font_with_cache.cache;
|
||||
const FontFile &font = *font_with_cache.font_file;
|
||||
|
||||
if (letter == '\n') {
|
||||
cursor.x() = 0;
|
||||
// 2d shape has opposit direction of y
|
||||
cursor.y() -= get_line_height(font, font_prop);
|
||||
return {};
|
||||
}
|
||||
if (letter == '\t') {
|
||||
// '\t' = 4*space => same as imgui
|
||||
const int count_spaces = 4;
|
||||
const Glyph *space = priv::get_glyph(int(' '), font, font_prop, cache, font_info_cache);
|
||||
if (space == nullptr)
|
||||
return {};
|
||||
cursor.x() += count_spaces * space->advance_width;
|
||||
return {};
|
||||
}
|
||||
if (letter == '\r')
|
||||
return {};
|
||||
|
||||
int unicode = static_cast<int>(letter);
|
||||
auto it = cache.find(unicode);
|
||||
|
||||
// Create glyph from font file and cache it
|
||||
const Glyph *glyph_ptr = (it != cache.end()) ? &it->second : priv::get_glyph(unicode, font, font_prop, cache, font_info_cache);
|
||||
if (glyph_ptr == nullptr)
|
||||
return {};
|
||||
|
||||
// move glyph to cursor position
|
||||
ExPolygons expolygons = glyph_ptr->shape; // copy
|
||||
for (ExPolygon &expolygon : expolygons)
|
||||
expolygon.translate(cursor);
|
||||
|
||||
cursor.x() += glyph_ptr->advance_width;
|
||||
return expolygons;
|
||||
}
|
||||
|
||||
// Check cancel every X letters in text
|
||||
// Lower number - too much checks(slows down)
|
||||
// Higher number - slows down response on cancelation
|
||||
const int CANCEL_CHECK = 10;
|
||||
} // namespace
|
||||
|
||||
ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, const char *text, const FontProp &font_prop, const std::function<bool()>& was_canceled)
|
||||
{
|
||||
std::wstring text_w = boost::nowide::widen(text);
|
||||
std::vector<ExPolygons> vshapes = text2vshapes(font_with_cache, text_w, font_prop, was_canceled);
|
||||
// unify to one expolygon
|
||||
ExPolygons result;
|
||||
const FontFile& font = *font_with_cache.font_file;
|
||||
unsigned int font_index = font_prop.collection_number.has_value()?
|
||||
*font_prop.collection_number : 0;
|
||||
if (!priv::is_valid(font, font_index)) return {};
|
||||
const FontFile::Info& info = font.infos[font_index];
|
||||
Glyphs& cache = *font_with_cache.cache;
|
||||
std::wstring ws = boost::nowide::widen(text);
|
||||
for (wchar_t wc: ws){
|
||||
if (wc == '\n') {
|
||||
int line_height = info.ascent - info.descent + info.linegap;
|
||||
if (font_prop.line_gap.has_value())
|
||||
line_height += *font_prop.line_gap;
|
||||
line_height = static_cast<int>(line_height / SHAPE_SCALE);
|
||||
|
||||
cursor.x() = 0;
|
||||
cursor.y() -= line_height;
|
||||
for (ExPolygons &shapes : vshapes) {
|
||||
if (shapes.empty())
|
||||
continue;
|
||||
}
|
||||
if (wc == '\t') {
|
||||
// '\t' = 4*space => same as imgui
|
||||
const int count_spaces = 4;
|
||||
const Glyph* space = priv::get_glyph(int(' '), font, font_prop, cache, font_info_opt);
|
||||
if (space == nullptr) continue;
|
||||
cursor.x() += count_spaces * space->advance_width;
|
||||
continue;
|
||||
}
|
||||
if (wc == '\r') continue;
|
||||
|
||||
int unicode = static_cast<int>(wc);
|
||||
// check cancelation only before unknown symbol - loading of symbol could be timeconsuming on slow computer and dificult fonts
|
||||
auto it = cache.find(unicode);
|
||||
if (it == cache.end() && was_canceled != nullptr && was_canceled()) return {};
|
||||
const Glyph *glyph_ptr = (it != cache.end())? &it->second :
|
||||
priv::get_glyph(unicode, font, font_prop, cache, font_info_opt);
|
||||
if (glyph_ptr == nullptr) continue;
|
||||
|
||||
// move glyph to cursor position
|
||||
ExPolygons expolygons = glyph_ptr->shape; // copy
|
||||
for (ExPolygon &expolygon : expolygons)
|
||||
expolygon.translate(cursor);
|
||||
|
||||
cursor.x() += glyph_ptr->advance_width;
|
||||
expolygons_append(result, std::move(expolygons));
|
||||
expolygons_append(result, std::move(shapes));
|
||||
}
|
||||
result = Slic3r::union_ex(result);
|
||||
heal_shape(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// <summary>
|
||||
/// Align shape against pivot
|
||||
/// </summary>
|
||||
/// <param name="shapes">Shapes to align
|
||||
/// Prerequisities: shapes are aligned left top</param>
|
||||
/// <param name="text">To detect end of lines - to be able horizontal center the line</param>
|
||||
/// <param name="prop">Containe Horizontal and vertical alignment</param>
|
||||
/// <param name="font">Needed for scale and font size</param>
|
||||
void align_shape(std::vector<ExPolygons> &shapes, const std::wstring &text, const FontProp &prop, const FontFile &font);
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> Emboss::text2vshapes(FontFileWithCache &font_with_cache, const std::wstring& text, const FontProp &font_prop, const std::function<bool()>& was_canceled){
|
||||
assert(font_with_cache.has_value());
|
||||
const FontFile &font = *font_with_cache.font_file;
|
||||
unsigned int font_index = font_prop.collection_number.value_or(0);
|
||||
if (!priv::is_valid(font, font_index))
|
||||
return {};
|
||||
|
||||
unsigned counter = 0;
|
||||
Point cursor(0, 0);
|
||||
|
||||
fontinfo_opt font_info_cache;
|
||||
std::vector<ExPolygons> result;
|
||||
result.reserve(text.size());
|
||||
for (wchar_t letter : text) {
|
||||
if (++counter == CANCEL_CHECK) {
|
||||
counter = 0;
|
||||
if (was_canceled())
|
||||
return {};
|
||||
}
|
||||
result.emplace_back(letter2shapes(letter, cursor, font_with_cache, font_prop, font_info_cache));
|
||||
}
|
||||
|
||||
align_shape(result, text, font_prop, font);
|
||||
return result;
|
||||
}
|
||||
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
unsigned Emboss::get_count_lines(const std::wstring& ws)
|
||||
{
|
||||
if (ws.empty())
|
||||
return 0;
|
||||
|
||||
unsigned count = 1;
|
||||
for (wchar_t wc : ws)
|
||||
if (wc == '\n')
|
||||
++count;
|
||||
return count;
|
||||
|
||||
// unsigned prev_count = 0;
|
||||
// for (wchar_t wc : ws)
|
||||
// if (wc == '\n')
|
||||
// ++prev_count;
|
||||
// else
|
||||
// break;
|
||||
//
|
||||
// unsigned post_count = 0;
|
||||
// for (wchar_t wc : boost::adaptors::reverse(ws))
|
||||
// if (wc == '\n')
|
||||
// ++post_count;
|
||||
// else
|
||||
// break;
|
||||
//return count - prev_count - post_count;
|
||||
}
|
||||
|
||||
unsigned Emboss::get_count_lines(const std::string &text)
|
||||
{
|
||||
std::wstring ws = boost::nowide::widen(text.c_str());
|
||||
return get_count_lines(ws);
|
||||
}
|
||||
|
||||
void Emboss::apply_transformation(const FontProp &font_prop, Transform3d &transformation){
|
||||
apply_transformation(font_prop.angle, font_prop.distance, transformation);
|
||||
}
|
||||
@@ -1361,8 +1461,7 @@ std::string Emboss::create_range_text(const std::string &text,
|
||||
|
||||
double Emboss::get_shape_scale(const FontProp &fp, const FontFile &ff)
|
||||
{
|
||||
size_t font_index = fp.collection_number.value_or(0);
|
||||
const FontFile::Info &info = ff.infos[font_index];
|
||||
const FontFile::Info &info = get_font_info(ff, fp);
|
||||
double scale = fp.size_in_mm / (double) info.unit_per_em;
|
||||
// Shape is scaled for store point coordinate as integer
|
||||
return scale * SHAPE_SCALE;
|
||||
@@ -1657,6 +1756,266 @@ std::optional<Vec2d> Emboss::OrthoProject::unproject(const Vec3d &p, double *dep
|
||||
return Vec2d(pp.x(), pp.y());
|
||||
}
|
||||
|
||||
// sample slice
|
||||
namespace {
|
||||
|
||||
// using coor2 = int64_t;
|
||||
using Coord2 = double;
|
||||
using P2 = Eigen::Matrix<Coord2, 2, 1, Eigen::DontAlign>;
|
||||
|
||||
bool point_in_distance(const Coord2 &distance_sq, PolygonPoint &polygon_point, const size_t &i, const Slic3r::Polygon &polygon, bool is_first, bool is_reverse = false)
|
||||
{
|
||||
size_t s = polygon.size();
|
||||
size_t ii = (i + polygon_point.index) % s;
|
||||
|
||||
// second point of line
|
||||
const Point &p = polygon[ii];
|
||||
Point p_d = p - polygon_point.point;
|
||||
|
||||
P2 p_d2 = p_d.cast<Coord2>();
|
||||
Coord2 p_distance_sq = p_d2.squaredNorm();
|
||||
if (p_distance_sq < distance_sq)
|
||||
return false;
|
||||
|
||||
// found line
|
||||
if (is_first) {
|
||||
// on same line
|
||||
// center also lay on line
|
||||
// new point is distance moved from point by direction
|
||||
polygon_point.point += p_d * sqrt(distance_sq / p_distance_sq);
|
||||
return true;
|
||||
}
|
||||
|
||||
// line cross circle
|
||||
|
||||
// start point of line
|
||||
size_t ii2 = (is_reverse) ? (ii + 1) % s : (ii + s - 1) % s;
|
||||
polygon_point.index = (is_reverse) ? ii : ii2;
|
||||
const Point &p2 = polygon[ii2];
|
||||
|
||||
Point line_dir = p2 - p;
|
||||
P2 line_dir2 = line_dir.cast<Coord2>();
|
||||
|
||||
Coord2 a = line_dir2.dot(line_dir2);
|
||||
Coord2 b = 2 * p_d2.dot(line_dir2);
|
||||
Coord2 c = p_d2.dot(p_d2) - distance_sq;
|
||||
|
||||
double discriminant = b * b - 4 * a * c;
|
||||
if (discriminant < 0) {
|
||||
assert(false);
|
||||
// no intersection
|
||||
polygon_point.point = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ray didn't totally miss sphere,
|
||||
// so there is a solution to
|
||||
// the equation.
|
||||
discriminant = sqrt(discriminant);
|
||||
|
||||
// either solution may be on or off the ray so need to test both
|
||||
// t1 is always the smaller value, because BOTH discriminant and
|
||||
// a are nonnegative.
|
||||
double t1 = (-b - discriminant) / (2 * a);
|
||||
double t2 = (-b + discriminant) / (2 * a);
|
||||
|
||||
double t = std::min(t1, t2);
|
||||
if (t < 0. || t > 1.) {
|
||||
// Bad intersection
|
||||
assert(false);
|
||||
polygon_point.point = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
polygon_point.point = p + (t * line_dir2).cast<Point::coord_type>();
|
||||
return true;
|
||||
}
|
||||
|
||||
void point_in_distance(int32_t distance, PolygonPoint &p, const Slic3r::Polygon &polygon)
|
||||
{
|
||||
Coord2 distance_sq = static_cast<Coord2>(distance) * distance;
|
||||
bool is_first = true;
|
||||
for (size_t i = 1; i < polygon.size(); ++i) {
|
||||
if (point_in_distance(distance_sq, p, i, polygon, is_first))
|
||||
return;
|
||||
is_first = false;
|
||||
}
|
||||
// There is not point on polygon with this distance
|
||||
}
|
||||
|
||||
void point_in_reverse_distance(int32_t distance, PolygonPoint &p, const Slic3r::Polygon &polygon)
|
||||
{
|
||||
Coord2 distance_sq = static_cast<Coord2>(distance) * distance;
|
||||
bool is_first = true;
|
||||
bool is_reverse = true;
|
||||
for (size_t i = polygon.size(); i > 0; --i) {
|
||||
if (point_in_distance(distance_sq, p, i, polygon, is_first, is_reverse))
|
||||
return;
|
||||
is_first = false;
|
||||
}
|
||||
// There is not point on polygon with this distance
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// calculate rotation, need copy of polygon point
|
||||
double Emboss::calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon)
|
||||
{
|
||||
PolygonPoint polygon_point2 = polygon_point; // copy
|
||||
point_in_distance(distance, polygon_point, polygon);
|
||||
point_in_reverse_distance(distance, polygon_point2, polygon);
|
||||
|
||||
Point surface_dir = polygon_point2.point - polygon_point.point;
|
||||
Point norm(-surface_dir.y(), surface_dir.x());
|
||||
Vec2d norm_d = norm.cast<double>();
|
||||
//norm_d.normalize();
|
||||
return std::atan2(norm_d.y(), norm_d.x());
|
||||
}
|
||||
|
||||
std::vector<double> Emboss::calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon)
|
||||
{
|
||||
std::vector<double> result;
|
||||
result.reserve(polygon_points.size());
|
||||
for(const PolygonPoint& pp: polygon_points)
|
||||
result.emplace_back(calculate_angle(distance, pp, polygon));
|
||||
return result;
|
||||
}
|
||||
|
||||
PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale)
|
||||
{
|
||||
// find BB in center of line
|
||||
size_t first_right_index = 0;
|
||||
for (const BoundingBox &bb : bbs)
|
||||
if (!bb.defined) // white char do not have bb
|
||||
continue;
|
||||
else if (bb.min.x() < 0)
|
||||
++first_right_index;
|
||||
else
|
||||
break;
|
||||
|
||||
PolygonPoints samples(bbs.size());
|
||||
int32_t shapes_x_cursor = 0;
|
||||
|
||||
PolygonPoint cursor = slice.start; //copy
|
||||
|
||||
auto create_sample = [&] //polygon_cursor, &polygon_line_index, &line_bbs, &shapes_x_cursor, &shape_scale, &em_2_polygon, &line, &offsets]
|
||||
(const BoundingBox &bb, bool is_reverse) {
|
||||
if (!bb.defined)
|
||||
return cursor;
|
||||
Point letter_center = bb.center();
|
||||
int32_t shape_distance = shapes_x_cursor - letter_center.x();
|
||||
shapes_x_cursor = letter_center.x();
|
||||
double distance_mm = shape_distance * scale;
|
||||
int32_t distance_polygon = static_cast<int32_t>(std::round(scale_(distance_mm)));
|
||||
if (is_reverse)
|
||||
point_in_distance(distance_polygon, cursor, slice.polygon);
|
||||
else
|
||||
point_in_reverse_distance(distance_polygon, cursor, slice.polygon);
|
||||
return cursor;
|
||||
};
|
||||
|
||||
// calc transformation for letters on the Right side from center
|
||||
bool is_reverse = true;
|
||||
for (size_t index = first_right_index; index < bbs.size(); ++index)
|
||||
samples[index] = create_sample(bbs[index], is_reverse);
|
||||
|
||||
// calc transformation for letters on the Left side from center
|
||||
if (first_right_index < bbs.size()) {
|
||||
shapes_x_cursor = bbs[first_right_index].center().x();
|
||||
cursor = samples[first_right_index];
|
||||
}else{
|
||||
// only left side exists
|
||||
shapes_x_cursor = 0;
|
||||
cursor = slice.start; // copy
|
||||
}
|
||||
is_reverse = false;
|
||||
for (size_t index_plus_one = first_right_index; index_plus_one > 0; --index_plus_one) {
|
||||
size_t index = index_plus_one - 1;
|
||||
samples[index] = create_sample(bbs[index], is_reverse);
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
namespace {
|
||||
float get_align_y_offset(FontProp::VerticalAlign align, unsigned count_lines, const FontFile &ff, const FontProp &fp)
|
||||
{
|
||||
assert(count_lines != 0);
|
||||
int line_height = get_line_height(ff, fp);
|
||||
int ascent = get_font_info(ff, fp).ascent / SHAPE_SCALE;
|
||||
float line_center = static_cast<float>(std::round(ascent * ASCENT_CENTER));
|
||||
|
||||
// direction of Y in 2d is from top to bottom
|
||||
// zero is on base line of first line
|
||||
switch (align) {
|
||||
case FontProp::VerticalAlign::bottom: return line_height * (count_lines - 1);
|
||||
case FontProp::VerticalAlign::top: return -ascent;
|
||||
case FontProp::VerticalAlign::center:
|
||||
default:
|
||||
return -line_center + line_height * (count_lines - 1) / 2.;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t get_align_x_offset(FontProp::HorizontalAlign align, const BoundingBox &shape_bb, const BoundingBox &line_bb)
|
||||
{
|
||||
switch (align) {
|
||||
case FontProp::HorizontalAlign::right: return -shape_bb.max.x() + (shape_bb.size().x() - line_bb.size().x());
|
||||
case FontProp::HorizontalAlign::center: return -shape_bb.center().x() + (shape_bb.size().x() - line_bb.size().x()) / 2;
|
||||
case FontProp::HorizontalAlign::left: // no change
|
||||
default: break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void align_shape(std::vector<ExPolygons> &shapes, const std::wstring &text, const FontProp &prop, const FontFile &font)
|
||||
{
|
||||
// Shapes have to match letters in text
|
||||
assert(shapes.size() == text.length());
|
||||
|
||||
unsigned count_lines = get_count_lines(text);
|
||||
int y_offset = get_align_y_offset(prop.align.second, count_lines, font, prop);
|
||||
|
||||
// Speed up for left aligned text
|
||||
//if (prop.align.first == FontProp::HorizontalAlign::left){
|
||||
// // already horizontaly aligned
|
||||
// for (ExPolygons shape : shapes)
|
||||
// for (ExPolygon &s : shape)
|
||||
// s.translate(Point(0, y_offset));
|
||||
// return;
|
||||
//}
|
||||
|
||||
BoundingBox shape_bb;
|
||||
for (const ExPolygons& shape: shapes)
|
||||
shape_bb.merge(get_extents(shape));
|
||||
|
||||
auto get_line_bb = [&](size_t j) {
|
||||
BoundingBox line_bb;
|
||||
for (; j < text.length() && text[j] != '\n'; ++j)
|
||||
line_bb.merge(get_extents(shapes[j]));
|
||||
return line_bb;
|
||||
};
|
||||
|
||||
// Align x line by line
|
||||
Point offset(
|
||||
get_align_x_offset(prop.align.first, shape_bb, get_line_bb(0)),
|
||||
y_offset);
|
||||
for (size_t i = 0; i < shapes.size(); ++i) {
|
||||
wchar_t letter = text[i];
|
||||
if (letter == '\n'){
|
||||
offset.x() = get_align_x_offset(prop.align.first, shape_bb, get_line_bb(i + 1));
|
||||
continue;
|
||||
}
|
||||
ExPolygons &shape = shapes[i];
|
||||
for (ExPolygon &s : shape)
|
||||
s.translate(offset);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
double Emboss::get_align_y_offset_in_mm(FontProp::VerticalAlign align, unsigned count_lines, const FontFile &ff, const FontProp &fp){
|
||||
float offset_in_font_point = get_align_y_offset(align, count_lines, ff, fp);
|
||||
double scale = get_shape_scale(fp, ff);
|
||||
return scale * offset_in_font_point;
|
||||
}
|
||||
|
||||
#ifdef REMOVE_SPIKES
|
||||
#include <Geometry.hpp>
|
||||
void priv::remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <admesh/stl.h> // indexed_triangle_set
|
||||
#include "Polygon.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "TextConfiguration.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -112,7 +113,7 @@ namespace Emboss
|
||||
std::shared_ptr<Emboss::Glyphs> cache;
|
||||
|
||||
FontFileWithCache() : font_file(nullptr), cache(nullptr) {}
|
||||
FontFileWithCache(std::unique_ptr<FontFile> font_file)
|
||||
explicit FontFileWithCache(std::unique_ptr<FontFile> font_file)
|
||||
: font_file(std::move(font_file))
|
||||
, cache(std::make_shared<Emboss::Glyphs>())
|
||||
{}
|
||||
@@ -151,7 +152,12 @@ namespace Emboss
|
||||
/// <param name="font_prop">User defined property of the font</param>
|
||||
/// <param name="was_canceled">Way to interupt processing</param>
|
||||
/// <returns>Inner polygon cw(outer ccw)</returns>
|
||||
ExPolygons text2shapes(FontFileWithCache &font, const char *text, const FontProp &font_prop, std::function<bool()> was_canceled = nullptr);
|
||||
ExPolygons text2shapes (FontFileWithCache &font, const char *text, const FontProp &font_prop, const std::function<bool()> &was_canceled = []() {return false;});
|
||||
std::vector<ExPolygons> text2vshapes(FontFileWithCache &font, const std::wstring& text, const FontProp &font_prop, const std::function<bool()>& was_canceled = []() {return false;});
|
||||
|
||||
/// Sum of character '\n'
|
||||
unsigned get_count_lines(const std::wstring &ws);
|
||||
unsigned get_count_lines(const std::string &text);
|
||||
|
||||
/// <summary>
|
||||
/// Fix duplicit points and self intersections in polygons.
|
||||
@@ -222,6 +228,30 @@ namespace Emboss
|
||||
/// <returns>Conversion to mm</returns>
|
||||
double get_shape_scale(const FontProp &fp, const FontFile &ff);
|
||||
|
||||
/// <summary>
|
||||
/// getter of font info by collection defined in prop
|
||||
/// </summary>
|
||||
/// <param name="font">Contain infos about all fonts(collections) in file</param>
|
||||
/// <param name="prop">Index of collection</param>
|
||||
/// <returns>Ascent, descent, line gap</returns>
|
||||
const FontFile::Info &get_font_info(const FontFile &font, const FontProp &prop);
|
||||
|
||||
/// <summary>
|
||||
/// Read from font file and properties height of line with spacing
|
||||
/// </summary>
|
||||
/// <param name="font">Infos for collections</param>
|
||||
/// <param name="prop">Collection index + Additional line gap</param>
|
||||
/// <returns>Line height with spacing in ExPolygon size</returns>
|
||||
int get_line_height(const FontFile &font, const FontProp &prop);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate Vertical align
|
||||
/// </summary>
|
||||
/// <param name="align">Top | Center | Bottom</param>
|
||||
/// <param name="count_lines"></param>
|
||||
/// <returns>Return align Y offset in mm</returns>
|
||||
double get_align_y_offset_in_mm(FontProp::VerticalAlign align, unsigned count_lines, const FontFile &ff, const FontProp &fp);
|
||||
|
||||
/// <summary>
|
||||
/// Project spatial point
|
||||
/// </summary>
|
||||
@@ -337,6 +367,36 @@ namespace Emboss
|
||||
}
|
||||
};
|
||||
|
||||
class ProjectTransform : public IProjection
|
||||
{
|
||||
std::unique_ptr<IProjection> m_core;
|
||||
Transform3d m_tr;
|
||||
Transform3d m_tr_inv;
|
||||
double z_scale;
|
||||
public:
|
||||
ProjectTransform(std::unique_ptr<IProjection> core, const Transform3d &tr) : m_core(std::move(core)), m_tr(tr)
|
||||
{
|
||||
m_tr_inv = m_tr.inverse();
|
||||
z_scale = (m_tr.linear() * Vec3d::UnitZ()).norm();
|
||||
}
|
||||
|
||||
// Inherited via IProject
|
||||
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override
|
||||
{
|
||||
auto [front, back] = m_core->create_front_back(p);
|
||||
return std::make_pair(m_tr * front, m_tr * back);
|
||||
}
|
||||
Vec3d project(const Vec3d &point) const override{
|
||||
return m_core->project(point);
|
||||
}
|
||||
std::optional<Vec2d> unproject(const Vec3d &p, double *depth = nullptr) const override {
|
||||
auto res = m_core->unproject(m_tr_inv * p, depth);
|
||||
if (depth != nullptr)
|
||||
*depth *= z_scale;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
class OrthoProject3d : public Emboss::IProject3d
|
||||
{
|
||||
// size and direction of emboss for ortho projection
|
||||
@@ -360,7 +420,43 @@ namespace Emboss
|
||||
Vec3d project(const Vec3d &point) const override;
|
||||
std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const override;
|
||||
};
|
||||
} // namespace Emboss
|
||||
|
||||
/// <summary>
|
||||
/// Define polygon for draw letters
|
||||
/// </summary>
|
||||
struct TextLine
|
||||
{
|
||||
// slice of object
|
||||
Polygon polygon;
|
||||
|
||||
// point laying on polygon closest to zero
|
||||
PolygonPoint start;
|
||||
|
||||
// offset of text line in volume mm
|
||||
float y;
|
||||
};
|
||||
using TextLines = std::vector<TextLine>;
|
||||
|
||||
/// <summary>
|
||||
/// Sample slice polygon by bounding boxes centers
|
||||
/// slice start point has shape_center_x coor
|
||||
/// </summary>
|
||||
/// <param name="slice">Polygon and start point[Slic3r scaled milimeters]</param>
|
||||
/// <param name="bbs">Bounding boxes of letter on one line[in font scales]</param>
|
||||
/// <param name="scale">Scale for bbs (after multiply bb is in milimeters)</param>
|
||||
/// <returns>Sampled polygon by bounding boxes</returns>
|
||||
PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate angle for polygon point
|
||||
/// </summary>
|
||||
/// <param name="distance">Distance for found normal in point</param>
|
||||
/// <param name="polygon_point">Select point on polygon</param>
|
||||
/// <param name="polygon">Polygon know neighbor of point</param>
|
||||
/// <returns>angle(atan2) of normal in polygon point</returns>
|
||||
double calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon);
|
||||
std::vector<double> calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon);
|
||||
|
||||
} // namespace Emboss
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_Emboss_hpp_
|
||||
|
||||
@@ -239,9 +239,13 @@ namespace Slic3r {
|
||||
|
||||
const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id);
|
||||
const bool will_go_down = ! is_approx(z, current_z);
|
||||
if (tcr.force_travel || ! needs_toolchange || (gcodegen.config().single_extruder_multi_material && ! tcr.priming)) {
|
||||
// Move over the wipe tower. If this is not single-extruder MM, the first wipe tower move following the
|
||||
// toolchange will travel there anyway (if there is a toolchange).
|
||||
const bool is_ramming = (gcodegen.config().single_extruder_multi_material)
|
||||
|| (! gcodegen.config().single_extruder_multi_material && gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool));
|
||||
const bool should_travel_to_tower = ! tcr.priming
|
||||
&& (tcr.force_travel // wipe tower says so
|
||||
|| ! needs_toolchange // this is just finishing the tower with no toolchange
|
||||
|| is_ramming);
|
||||
if (should_travel_to_tower) {
|
||||
// FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges,
|
||||
// then we could simplify the condition and make it more readable.
|
||||
gcode += gcodegen.retract();
|
||||
@@ -251,6 +255,9 @@ namespace Slic3r {
|
||||
ExtrusionRole::Mixed,
|
||||
"Travel to a Wipe Tower");
|
||||
gcode += gcodegen.unretract();
|
||||
} else {
|
||||
// When this is multiextruder printer without any ramming, we can just change
|
||||
// the tool without travelling to the tower.
|
||||
}
|
||||
|
||||
if (will_go_down) {
|
||||
@@ -262,7 +269,7 @@ namespace Slic3r {
|
||||
std::string toolchange_gcode_str;
|
||||
std::string deretraction_str;
|
||||
if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
|
||||
if (gcodegen.config().single_extruder_multi_material)
|
||||
if (is_ramming)
|
||||
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
|
||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
|
||||
if (gcodegen.config().wipe_tower)
|
||||
@@ -3310,7 +3317,7 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
|
||||
|
||||
bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
|
||||
{
|
||||
if (travel.length() < scale_(EXTRUDER_CONFIG(retract_before_travel))) {
|
||||
if (! m_writer.extruder() || travel.length() < scale_(EXTRUDER_CONFIG(retract_before_travel))) {
|
||||
// skip retraction if the move is shorter than the configured threshold
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -792,8 +792,8 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
if (int(layer_id) >= disable_fan_first_layers && int(layer_id) + 1 < full_fan_speed_layer) {
|
||||
// Ramp up the fan speed from disable_fan_first_layers to full_fan_speed_layer.
|
||||
float factor = float(int(layer_id + 1) - disable_fan_first_layers) / float(full_fan_speed_layer - disable_fan_first_layers);
|
||||
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
|
||||
bridge_fan_speed = std::clamp(int(float(bridge_fan_speed) * factor + 0.5f), 0, 255);
|
||||
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 100);
|
||||
bridge_fan_speed = std::clamp(int(float(bridge_fan_speed) * factor + 0.5f), 0, 100);
|
||||
custom_fan_speed_limits.second = fan_speed_new;
|
||||
}
|
||||
#undef EXTRUDER_CONFIG
|
||||
|
||||
@@ -146,67 +146,68 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
std::vector<float> angles_for_curvature(points.size());
|
||||
std::vector<float> distances_for_curvature(points.size());
|
||||
|
||||
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
|
||||
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) {
|
||||
ExtendedPoint &a = points[point_idx];
|
||||
ExtendedPoint &prev = points[point_idx > 0 ? point_idx - 1 : point_idx];
|
||||
size_t prev = prev_idx_modulo(point_idx, points.size());
|
||||
size_t next = next_idx_modulo(point_idx, points.size());
|
||||
|
||||
int prev_point_idx = point_idx;
|
||||
while (prev_point_idx > 0) {
|
||||
prev_point_idx--;
|
||||
if ((a.position - points[prev_point_idx].position).squaredNorm() > EPSILON) {
|
||||
break;
|
||||
}
|
||||
int iter_limit = points.size();
|
||||
while ((a.position - points[prev].position).squaredNorm() < 1 && iter_limit > 0) {
|
||||
prev = prev_idx_modulo(prev, points.size());
|
||||
iter_limit--;
|
||||
}
|
||||
|
||||
int next_point_index = point_idx;
|
||||
while (next_point_index < int(points.size()) - 1) {
|
||||
next_point_index++;
|
||||
if ((a.position - points[next_point_index].position).squaredNorm() > EPSILON) {
|
||||
break;
|
||||
}
|
||||
while ((a.position - points[next].position).squaredNorm() < 1 && iter_limit > 0) {
|
||||
next = next_idx_modulo(next, points.size());
|
||||
iter_limit--;
|
||||
}
|
||||
|
||||
distances_for_curvature[point_idx] = (prev.position - a.position).norm();
|
||||
if (prev_point_idx != point_idx && next_point_index != point_idx) {
|
||||
float alfa = angle(a.position - points[prev_point_idx].position, points[next_point_index].position - a.position);
|
||||
angles_for_curvature[point_idx] = alfa;
|
||||
} // else keep zero
|
||||
distances_for_curvature[point_idx] = (points[prev].position - a.position).norm();
|
||||
float alfa = angle(a.position - points[prev].position, points[next].position - a.position);
|
||||
angles_for_curvature[point_idx] = alfa;
|
||||
}
|
||||
|
||||
for (float window_size : {3.0f, 9.0f, 16.0f}) {
|
||||
size_t tail_point = 0;
|
||||
float tail_window_acc = 0;
|
||||
float tail_angle_acc = 0;
|
||||
if (std::accumulate(distances_for_curvature.begin(), distances_for_curvature.end(), 0) > EPSILON)
|
||||
for (float window_size : {3.0f, 9.0f, 16.0f}) {
|
||||
size_t tail_point = 0;
|
||||
float tail_window_acc = 0;
|
||||
float tail_angle_acc = 0;
|
||||
|
||||
size_t head_point = 0;
|
||||
float head_window_acc = 0;
|
||||
float head_angle_acc = 0;
|
||||
size_t head_point = 0;
|
||||
float head_window_acc = 0;
|
||||
float head_angle_acc = 0;
|
||||
|
||||
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
|
||||
if (point_idx > 0) {
|
||||
tail_window_acc += distances_for_curvature[point_idx - 1];
|
||||
tail_angle_acc += angles_for_curvature[point_idx - 1];
|
||||
head_window_acc -= distances_for_curvature[point_idx - 1];
|
||||
head_angle_acc -= angles_for_curvature[point_idx - 1];
|
||||
}
|
||||
while (tail_window_acc > window_size * 0.5 && int(tail_point) < point_idx) {
|
||||
tail_window_acc -= distances_for_curvature[tail_point];
|
||||
tail_angle_acc -= angles_for_curvature[tail_point];
|
||||
tail_point++;
|
||||
}
|
||||
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) {
|
||||
if (point_idx == 0) {
|
||||
while (tail_window_acc < window_size * 0.5) {
|
||||
tail_window_acc += distances_for_curvature[tail_point];
|
||||
tail_angle_acc += angles_for_curvature[tail_point];
|
||||
tail_point = prev_idx_modulo(tail_point, points.size());
|
||||
}
|
||||
}
|
||||
while (tail_window_acc - distances_for_curvature[next_idx_modulo(tail_point, points.size())] > window_size * 0.5) {
|
||||
tail_point = next_idx_modulo(tail_point, points.size());
|
||||
tail_window_acc -= distances_for_curvature[tail_point];
|
||||
tail_angle_acc -= angles_for_curvature[tail_point];
|
||||
}
|
||||
|
||||
while (head_window_acc < window_size * 0.5 && int(head_point) < int(points.size()) - 1) {
|
||||
head_window_acc += distances_for_curvature[head_point];
|
||||
head_angle_acc += angles_for_curvature[head_point];
|
||||
head_point++;
|
||||
}
|
||||
while (head_window_acc < window_size * 0.5) {
|
||||
head_point = next_idx_modulo(head_point, points.size());
|
||||
head_window_acc += distances_for_curvature[head_point];
|
||||
head_angle_acc += angles_for_curvature[head_point];
|
||||
}
|
||||
|
||||
float curvature = (tail_angle_acc + head_angle_acc) / (tail_window_acc + head_window_acc);
|
||||
if (std::abs(curvature) > std::abs(points[point_idx].curvature)) {
|
||||
points[point_idx].curvature = curvature;
|
||||
float curvature = (tail_angle_acc + head_angle_acc) / window_size;
|
||||
if (std::abs(curvature) > std::abs(points[point_idx].curvature)) {
|
||||
points[point_idx].curvature = curvature;
|
||||
}
|
||||
|
||||
tail_window_acc += distances_for_curvature[point_idx];
|
||||
tail_angle_acc += angles_for_curvature[point_idx];
|
||||
head_window_acc -= distances_for_curvature[point_idx];
|
||||
head_angle_acc -= angles_for_curvature[point_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
@@ -3610,7 +3610,8 @@ void GCodeProcessor::post_process()
|
||||
while (rev_it != m_lines.rend() && rev_it->time > time_threshold_i && curr_cmd != cmd && curr_cmd != "G28" && curr_cmd != "G29") {
|
||||
rev_it->line = line_replacer(rev_it->line);
|
||||
++rev_it;
|
||||
curr_cmd = GCodeReader::GCodeLine::extract_cmd(rev_it->line);
|
||||
if (rev_it != m_lines.rend())
|
||||
curr_cmd = GCodeReader::GCodeLine::extract_cmd(rev_it->line);
|
||||
}
|
||||
|
||||
// we met the previous evenience of cmd, or a G28/G29 command. stop inserting lines
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
// The standard Windows includes.
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
|
||||
@@ -169,6 +169,11 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
|
||||
this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height);
|
||||
|
||||
if (this->insert_wipe_tower_extruder()) {
|
||||
// Now convert the 0-based list to 1-based again, because that is what reorder_extruder expects.
|
||||
for (LayerTools& lt : m_layer_tools) {
|
||||
for (auto& extruder : lt.extruders)
|
||||
++extruder;
|
||||
}
|
||||
this->reorder_extruders(first_extruder);
|
||||
this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height);
|
||||
}
|
||||
@@ -478,12 +483,7 @@ bool ToolOrdering::insert_wipe_tower_extruder()
|
||||
sort_remove_duplicates(lt.extruders);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
// Now convert the 0-based list to 1-based again.
|
||||
for (LayerTools& lt : m_layer_tools) {
|
||||
for (auto& extruder : lt.extruders)
|
||||
++extruder;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -516,7 +516,7 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer,
|
||||
|
||||
|
||||
|
||||
WipeTower::WipeTower(const PrintConfig& config, const std::vector<std::vector<float>>& wiping_matrix, size_t initial_tool) :
|
||||
WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default_region_config, const std::vector<std::vector<float>>& wiping_matrix, size_t initial_tool) :
|
||||
m_semm(config.single_extruder_multi_material.value),
|
||||
m_wipe_tower_pos(config.wipe_tower_x, config.wipe_tower_y),
|
||||
m_wipe_tower_width(float(config.wipe_tower_width)),
|
||||
@@ -530,6 +530,8 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector<std::vector<fl
|
||||
m_no_sparse_layers(config.wipe_tower_no_sparse_layers),
|
||||
m_gcode_flavor(config.gcode_flavor),
|
||||
m_travel_speed(config.travel_speed),
|
||||
m_infill_speed(default_region_config.infill_speed),
|
||||
m_perimeter_speed(default_region_config.perimeter_speed),
|
||||
m_current_tool(initial_tool),
|
||||
wipe_volumes(wiping_matrix)
|
||||
{
|
||||
@@ -541,6 +543,13 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector<std::vector<fl
|
||||
if (m_first_layer_speed == 0.f) // just to make sure autospeed doesn't break it.
|
||||
m_first_layer_speed = default_speed / 2.f;
|
||||
|
||||
// Autospeed may be used...
|
||||
if (m_infill_speed == 0.f)
|
||||
m_infill_speed = 80.f;
|
||||
if (m_perimeter_speed == 0.f)
|
||||
m_perimeter_speed = 80.f;
|
||||
|
||||
|
||||
// If this is a single extruder MM printer, we will use all the SE-specific config values.
|
||||
// Otherwise, the defaults will be used to turn off the SE stuff.
|
||||
if (m_semm) {
|
||||
@@ -550,6 +559,7 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector<std::vector<fl
|
||||
m_extra_loading_move = float(config.extra_loading_move);
|
||||
m_set_extruder_trimpot = config.high_current_on_filament_swap;
|
||||
}
|
||||
|
||||
// Calculate where the priming lines should be - very naive test not detecting parallelograms etc.
|
||||
const std::vector<Vec2d>& bed_points = config.bed_shape.values;
|
||||
BoundingBoxf bb(bed_points);
|
||||
@@ -616,6 +626,24 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
m_filpar[idx].ramming_step_multiplicator /= 100;
|
||||
while (stream >> speed)
|
||||
m_filpar[idx].ramming_speed.push_back(speed);
|
||||
// ramming_speed now contains speeds to be used for every 0.25s piece of the ramming line.
|
||||
// This allows to have the ramming flow variable. The 0.25s value is how it is saved in config
|
||||
// and the same time step has to be used when the ramming is performed.
|
||||
} else {
|
||||
// We will use the same variables internally, but the correspondence to the configuration options will be different.
|
||||
float vol = config.filament_multitool_ramming_volume.get_at(idx);
|
||||
float flow = config.filament_multitool_ramming_flow.get_at(idx);
|
||||
m_filpar[idx].multitool_ramming = config.filament_multitool_ramming.get_at(idx);
|
||||
m_filpar[idx].ramming_line_width_multiplicator = 2.;
|
||||
m_filpar[idx].ramming_step_multiplicator = 1.;
|
||||
|
||||
// Now the ramming speed vector. In this case it contains just one value (flow).
|
||||
// The time is calculated and saved separately. This is here so that the MM ramming
|
||||
// is not limited by the 0.25s granularity - it is not possible to create a SEMM-style
|
||||
// ramming_speed vector that would respect both the volume and flow (because of
|
||||
// rounding issues with small volumes and high flow).
|
||||
m_filpar[idx].ramming_speed.push_back(flow);
|
||||
m_filpar[idx].multitool_ramming_time = vol/flow;
|
||||
}
|
||||
|
||||
m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later
|
||||
@@ -830,12 +858,17 @@ void WipeTower::toolchange_Unload(
|
||||
float remaining = xr - xl ; // keeps track of distance to the next turnaround
|
||||
float e_done = 0; // measures E move done from each segment
|
||||
|
||||
if (m_semm)
|
||||
const bool do_ramming = m_semm || m_filpar[m_current_tool].multitool_ramming;
|
||||
|
||||
if (do_ramming) {
|
||||
writer.travel(ramming_start_pos); // move to starting position
|
||||
writer.disable_linear_advance();
|
||||
}
|
||||
else
|
||||
writer.set_position(ramming_start_pos);
|
||||
|
||||
// if the ending point of the ram would end up in mid air, align it with the end of the wipe tower:
|
||||
if (m_semm && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) {
|
||||
if (do_ramming && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) {
|
||||
|
||||
// this is y of the center of previous sparse infill border
|
||||
float sparse_beginning_y = 0.f;
|
||||
@@ -863,16 +896,18 @@ void WipeTower::toolchange_Unload(
|
||||
sum_of_depths += tch.required_depth;
|
||||
}
|
||||
}
|
||||
|
||||
writer.disable_linear_advance();
|
||||
|
||||
|
||||
// now the ramming itself:
|
||||
while (m_semm && i < m_filpar[m_current_tool].ramming_speed.size())
|
||||
while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size())
|
||||
{
|
||||
const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height);
|
||||
const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move;
|
||||
const float dist = std::min(x - e_done, remaining); // distance to travel for either the next 0.25s, or to the next turnaround
|
||||
const float actual_time = dist/x * 0.25f;
|
||||
// The time step is different for SEMM ramming and the MM ramming. See comments in set_extruder() for details.
|
||||
const float time_step = m_semm ? 0.25f : m_filpar[m_current_tool].multitool_ramming_time;
|
||||
|
||||
const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * time_step, line_width, m_layer_height);
|
||||
const float e = m_filpar[m_current_tool].ramming_speed[i] * time_step / filament_area(); // transform volume per sec to E move;
|
||||
const float dist = std::min(x - e_done, remaining); // distance to travel for either the next time_step, or to the next turnaround
|
||||
const float actual_time = dist/x * time_step;
|
||||
writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0.f, 0.f, e * (dist / x), dist / (actual_time / 60.f));
|
||||
remaining -= dist;
|
||||
|
||||
@@ -944,7 +979,7 @@ void WipeTower::toolchange_Unload(
|
||||
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
|
||||
// the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
|
||||
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width);
|
||||
if (m_semm)
|
||||
if (do_ramming)
|
||||
writer.travel(pos, 2400.f);
|
||||
else
|
||||
writer.set_position(pos);
|
||||
@@ -1031,10 +1066,11 @@ void WipeTower::toolchange_Wipe(
|
||||
// the ordered volume, even if it means violating the box. This can later be removed and simply
|
||||
// wipe until the end of the assigned area.
|
||||
|
||||
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height);
|
||||
float dy = m_extra_spacing*m_perimeter_width;
|
||||
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) * (is_first_layer() ? m_extra_spacing : 1.f);
|
||||
float dy = (is_first_layer() ? 1.f : m_extra_spacing) * m_perimeter_width; // Don't use the extra spacing for the first layer.
|
||||
// All the calculations in all other places take the spacing into account for all the layers.
|
||||
|
||||
const float target_speed = is_first_layer() ? m_first_layer_speed * 60.f : 4800.f;
|
||||
const float target_speed = is_first_layer() ? m_first_layer_speed * 60.f : m_infill_speed * 60.f;
|
||||
float wipe_speed = 0.33f * target_speed;
|
||||
|
||||
// if there is less than 2.5*m_perimeter_width to the edge, advance straightaway (there is likely a blob anyway)
|
||||
@@ -1103,7 +1139,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer()
|
||||
|
||||
// Slow down on the 1st layer.
|
||||
bool first_layer = is_first_layer();
|
||||
float feedrate = first_layer ? m_first_layer_speed * 60.f : 2900.f;
|
||||
float feedrate = first_layer ? m_first_layer_speed * 60.f : m_infill_speed * 60.f;
|
||||
float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth();
|
||||
box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)),
|
||||
m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width);
|
||||
@@ -1203,7 +1239,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer()
|
||||
// First generate vector of annotated point which form the boundary.
|
||||
std::vector<std::pair<Vec2f, Type>> pts = {{wt_box.ru, Corner}};
|
||||
if (double alpha_start = std::asin((0.5*w)/r); ! std::isnan(alpha_start) && r > 0.5*w+0.01) {
|
||||
for (double alpha = alpha_start; alpha < M_PI-alpha_start+0.001; alpha+=(M_PI-2*alpha_start) / 20.)
|
||||
for (double alpha = alpha_start; alpha < M_PI-alpha_start+0.001; alpha+=(M_PI-2*alpha_start) / 40.)
|
||||
pts.emplace_back(Vec2f(center.x() + r*std::cos(alpha)/support_scale, center.y() + r*std::sin(alpha)), alpha == alpha_start ? ArcStart : Arc);
|
||||
pts.back().second = ArcEnd;
|
||||
}
|
||||
@@ -1285,6 +1321,8 @@ WipeTower::ToolChangeResult WipeTower::finish_layer()
|
||||
return poly;
|
||||
};
|
||||
|
||||
feedrate = first_layer ? m_first_layer_speed * 60.f : m_perimeter_speed * 60.f;
|
||||
|
||||
// outer contour (always)
|
||||
bool infill_cone = first_layer && m_wipe_tower_width > 2*spacing && m_wipe_tower_depth > 2*spacing;
|
||||
Polygon poly = supported_rectangle(wt_box, feedrate, infill_cone);
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Slic3r
|
||||
|
||||
class WipeTowerWriter;
|
||||
class PrintConfig;
|
||||
class PrintRegionConfig;
|
||||
enum GCodeFlavor : unsigned char;
|
||||
|
||||
|
||||
@@ -129,7 +130,10 @@ public:
|
||||
// y -- y coordinates of wipe tower in mm ( left bottom corner )
|
||||
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
|
||||
// wipe_area -- space available for one toolchange in mm
|
||||
WipeTower(const PrintConfig& config, const std::vector<std::vector<float>>& wiping_matrix, size_t initial_tool);
|
||||
WipeTower(const PrintConfig& config,
|
||||
const PrintRegionConfig& default_region_config,
|
||||
const std::vector<std::vector<float>>& wiping_matrix,
|
||||
size_t initial_tool);
|
||||
|
||||
|
||||
// Set the extruder properties.
|
||||
@@ -237,6 +241,8 @@ public:
|
||||
std::vector<float> ramming_speed;
|
||||
float nozzle_diameter;
|
||||
float filament_area;
|
||||
bool multitool_ramming;
|
||||
float multitool_ramming_time = 0.f;
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -269,6 +275,8 @@ private:
|
||||
size_t m_max_color_changes = 0; // Maximum number of color changes per layer.
|
||||
int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary)
|
||||
float m_travel_speed = 0.f;
|
||||
float m_infill_speed = 0.f;
|
||||
float m_perimeter_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
size_t m_first_layer_idx = size_t(-1);
|
||||
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../Polygon.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
|
||||
namespace Geometry {
|
||||
@@ -16,7 +15,9 @@ Pointf3s convex_hull(Pointf3s points);
|
||||
Polygon convex_hull(Points points);
|
||||
Polygon convex_hull(const Polygons &polygons);
|
||||
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
|
||||
// is not an empty set.
|
||||
|
||||
@@ -450,6 +450,19 @@ MedialAxis::MedialAxis(double min_width, double max_width, const ExPolygon &expo
|
||||
|
||||
void MedialAxis::build(ThickPolylines* polylines)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
// Verify the scaling of the coordinates of input line segments.
|
||||
for (const Line& l : m_lines) {
|
||||
auto test = [](int32_t v) {
|
||||
static constexpr const int32_t hi = 65536 * 16383;
|
||||
assert(v <= hi && -v < hi);
|
||||
};
|
||||
test(l.a.x());
|
||||
test(l.a.y());
|
||||
test(l.b.x());
|
||||
test(l.b.y());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd);
|
||||
Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines);
|
||||
// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
|
||||
|
||||
@@ -81,7 +81,8 @@ void Layer::make_slices()
|
||||
// Top / bottom surfaces must overlap more than 2um to be chained into a Z graph.
|
||||
// Also a larger offset will likely be more robust on non-manifold input polygons.
|
||||
static constexpr const float delta = scaled<float>(0.001);
|
||||
co.MiterLimit = scaled<double>(3.);
|
||||
// Don't scale the miter limit, it is a factor, not an absolute length!
|
||||
co.MiterLimit = 3.;
|
||||
// Use the default zero edge merging distance. For this kind of safety offset the accuracy of normal direction is not important.
|
||||
// co.ShortestEdgeLength = delta * ClipperOffsetShortestEdgeFactor;
|
||||
// static constexpr const double accept_area_threshold_ccw = sqr(scaled<double>(0.1 * delta));
|
||||
|
||||
@@ -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_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.
|
||||
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)
|
||||
@@ -162,7 +188,7 @@ public:
|
||||
void translate(double x, double y) { this->translate(Point(x, y)); }
|
||||
void rotate(double angle, const Point ¢er) { this->a.rotate(angle, center); this->b.rotate(angle, center); }
|
||||
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; }
|
||||
bool intersection_infinite(const Line &other, Point* point) const;
|
||||
bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; }
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
|
||||
#include "libslic3r/Geometry/Circle.hpp"
|
||||
#include "libslic3r/SurfaceMesh.hpp"
|
||||
#include <numeric>
|
||||
|
||||
|
||||
#include <numeric>
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#define DEBUG_EXTRACT_ALL_FEATURES_AT_ONCE 0
|
||||
|
||||
@@ -72,7 +73,7 @@ public:
|
||||
int get_num_of_planes() const;
|
||||
const std::vector<int>& get_plane_triangle_indices(int idx) const;
|
||||
const std::vector<SurfaceFeature>& get_plane_features(unsigned int plane_id);
|
||||
const TriangleMesh& get_mesh() const;
|
||||
const indexed_triangle_set& get_its() const;
|
||||
|
||||
private:
|
||||
void update_planes();
|
||||
@@ -80,7 +81,7 @@ private:
|
||||
|
||||
std::vector<PlaneData> m_planes;
|
||||
std::vector<size_t> m_face_to_plane;
|
||||
TriangleMesh m_mesh;
|
||||
indexed_triangle_set m_its;
|
||||
};
|
||||
|
||||
|
||||
@@ -89,7 +90,7 @@ private:
|
||||
|
||||
|
||||
MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its)
|
||||
: m_mesh(its)
|
||||
: m_its(its)
|
||||
{
|
||||
update_planes();
|
||||
|
||||
@@ -104,14 +105,12 @@ MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its)
|
||||
|
||||
void MeasuringImpl::update_planes()
|
||||
{
|
||||
m_planes.clear();
|
||||
|
||||
// Now we'll go through all the facets and append Points of facets sharing the same normal.
|
||||
// This part is still performed in mesh coordinate system.
|
||||
const size_t num_of_facets = m_mesh.its.indices.size();
|
||||
const size_t num_of_facets = m_its.indices.size();
|
||||
m_face_to_plane.resize(num_of_facets, size_t(-1));
|
||||
const std::vector<Vec3f> face_normals = its_face_normals(m_mesh.its);
|
||||
const std::vector<Vec3i> face_neighbors = its_face_neighbors(m_mesh.its);
|
||||
const std::vector<Vec3f> face_normals = its_face_normals(m_its);
|
||||
const std::vector<Vec3i> face_neighbors = its_face_neighbors(m_its);
|
||||
std::vector<int> facet_queue(num_of_facets, 0);
|
||||
int facet_queue_cnt = 0;
|
||||
const stl_normal* normal_ptr = nullptr;
|
||||
@@ -121,6 +120,10 @@ void MeasuringImpl::update_planes()
|
||||
return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001);
|
||||
};
|
||||
|
||||
m_planes.clear();
|
||||
m_planes.reserve(num_of_facets / 5); // empty plane data object is quite lightweight, let's save the initial reallocations
|
||||
|
||||
|
||||
// First go through all the triangles and fill in m_planes vector. For each "plane"
|
||||
// detected on the model, it will contain list of facets that are part of it.
|
||||
// We will also fill in m_face_to_plane, which contains index into m_planes
|
||||
@@ -132,7 +135,7 @@ void MeasuringImpl::update_planes()
|
||||
facet_queue[facet_queue_cnt ++] = seed_facet_idx;
|
||||
normal_ptr = &face_normals[seed_facet_idx];
|
||||
m_face_to_plane[seed_facet_idx] = m_planes.size();
|
||||
m_planes.emplace_back();
|
||||
m_planes.emplace_back();
|
||||
break;
|
||||
}
|
||||
if (seed_facet_idx == num_of_facets)
|
||||
@@ -160,16 +163,21 @@ void MeasuringImpl::update_planes()
|
||||
assert(std::none_of(m_face_to_plane.begin(), m_face_to_plane.end(), [](size_t val) { return val == size_t(-1); }));
|
||||
|
||||
// Now we will walk around each of the planes and save vertices which form the border.
|
||||
SurfaceMesh sm(m_mesh.its);
|
||||
for (int plane_id=0; plane_id < int(m_planes.size()); ++plane_id) {
|
||||
const auto& facets = m_planes[plane_id].facets;
|
||||
m_planes[plane_id].borders.clear();
|
||||
const SurfaceMesh sm(m_its);
|
||||
|
||||
const auto& face_to_plane = m_face_to_plane;
|
||||
auto& planes = m_planes;
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_planes.size()),
|
||||
[&planes, &face_to_plane, &face_neighbors, &sm](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t plane_id = range.begin(); plane_id != range.end(); ++plane_id) {
|
||||
|
||||
const auto& facets = planes[plane_id].facets;
|
||||
planes[plane_id].borders.clear();
|
||||
std::vector<std::array<bool, 3>> visited(facets.size(), {false, false, false});
|
||||
|
||||
|
||||
|
||||
for (int face_id=0; face_id<int(facets.size()); ++face_id) {
|
||||
assert(m_face_to_plane[facets[face_id]] == plane_id);
|
||||
assert(face_to_plane[facets[face_id]] == plane_id);
|
||||
|
||||
for (int edge_id=0; edge_id<3; ++edge_id) {
|
||||
// Every facet's edge which has a neighbor from a different plane is
|
||||
@@ -177,7 +185,7 @@ void MeasuringImpl::update_planes()
|
||||
int neighbor_idx = face_neighbors[facets[face_id]][edge_id];
|
||||
if (neighbor_idx == -1)
|
||||
goto PLANE_FAILURE;
|
||||
if (visited[face_id][edge_id] || (int)m_face_to_plane[neighbor_idx] == plane_id) {
|
||||
if (visited[face_id][edge_id] || (int)face_to_plane[neighbor_idx] == plane_id) {
|
||||
visited[face_id][edge_id] = true;
|
||||
continue;
|
||||
}
|
||||
@@ -188,8 +196,9 @@ void MeasuringImpl::update_planes()
|
||||
|
||||
// he is the first halfedge on the border. Now walk around and append the points.
|
||||
//const Halfedge_index he_orig = he;
|
||||
m_planes[plane_id].borders.emplace_back();
|
||||
std::vector<Vec3d>& last_border = m_planes[plane_id].borders.back();
|
||||
planes[plane_id].borders.emplace_back();
|
||||
std::vector<Vec3d>& last_border = planes[plane_id].borders.back();
|
||||
last_border.reserve(4);
|
||||
last_border.emplace_back(sm.point(sm.source(he)).cast<double>());
|
||||
//Vertex_index target = sm.target(he);
|
||||
const Halfedge_index he_start = he;
|
||||
@@ -210,7 +219,7 @@ void MeasuringImpl::update_planes()
|
||||
// Remember all halfedges we saw to break out of such infinite loops.
|
||||
boost::container::small_vector<Halfedge_index, 10> he_seen;
|
||||
|
||||
while ( (int)m_face_to_plane[sm.face(he)] == plane_id && he != he_orig) {
|
||||
while ( (int)face_to_plane[sm.face(he)] == plane_id && he != he_orig) {
|
||||
he_seen.emplace_back(he);
|
||||
he = sm.next_around_target(he);
|
||||
if (he.is_invalid() || std::find(he_seen.begin(), he_seen.end(), he) != he_seen.end())
|
||||
@@ -241,7 +250,7 @@ void MeasuringImpl::update_planes()
|
||||
} while (he != he_start);
|
||||
|
||||
if (last_border.size() == 1)
|
||||
m_planes[plane_id].borders.pop_back();
|
||||
planes[plane_id].borders.pop_back();
|
||||
else {
|
||||
assert(last_border.front() == last_border.back());
|
||||
last_border.pop_back();
|
||||
@@ -251,8 +260,9 @@ void MeasuringImpl::update_planes()
|
||||
continue; // There was no failure.
|
||||
|
||||
PLANE_FAILURE:
|
||||
m_planes[plane_id].borders.clear();
|
||||
}
|
||||
planes[plane_id].borders.clear();
|
||||
}});
|
||||
m_planes.shrink_to_fit();
|
||||
}
|
||||
|
||||
|
||||
@@ -581,9 +591,9 @@ const std::vector<SurfaceFeature>& MeasuringImpl::get_plane_features(unsigned in
|
||||
return m_planes[plane_id].surface_features;
|
||||
}
|
||||
|
||||
const TriangleMesh& MeasuringImpl::get_mesh() const
|
||||
const indexed_triangle_set& MeasuringImpl::get_its() const
|
||||
{
|
||||
return this->m_mesh;
|
||||
return this->m_its;
|
||||
}
|
||||
|
||||
|
||||
@@ -626,9 +636,9 @@ const std::vector<SurfaceFeature>& Measuring::get_plane_features(unsigned int pl
|
||||
return priv->get_plane_features(plane_id);
|
||||
}
|
||||
|
||||
const TriangleMesh& Measuring::get_mesh() const
|
||||
const indexed_triangle_set& Measuring::get_its() const
|
||||
{
|
||||
return priv->get_mesh();
|
||||
return priv->get_its();
|
||||
}
|
||||
|
||||
const AngleAndEdges AngleAndEdges::Dummy = { 0.0, Vec3d::Zero(), { Vec3d::Zero(), Vec3d::Zero() }, { Vec3d::Zero(), Vec3d::Zero() }, 0.0, true };
|
||||
|
||||
@@ -109,7 +109,7 @@ public:
|
||||
const std::vector<SurfaceFeature>& get_plane_features(unsigned int plane_id) const;
|
||||
|
||||
// Returns the mesh used for measuring
|
||||
const TriangleMesh& get_mesh() const;
|
||||
const indexed_triangle_set& get_its() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<MeasuringImpl> priv;
|
||||
|
||||
@@ -148,12 +148,12 @@ indexed_triangle_set cgal_to_indexed_triangle_set(const _Mesh &cgalmesh)
|
||||
const auto &vertices = cgalmesh.vertices();
|
||||
int vsize = int(vertices.size());
|
||||
|
||||
for (auto &vi : vertices) {
|
||||
for (const auto &vi : vertices) {
|
||||
auto &v = cgalmesh.point(vi); // Don't ask...
|
||||
its.vertices.emplace_back(to_vec3f(v));
|
||||
}
|
||||
|
||||
for (auto &face : faces) {
|
||||
for (const auto &face : faces) {
|
||||
auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face));
|
||||
|
||||
int i = 0;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "MinAreaBoundingBox.hpp"
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <BoundingBox.hpp>
|
||||
|
||||
#if defined(_MSC_VER) && defined(__clang__)
|
||||
#define BOOST_NO_CXX17_HDR_STRING_VIEW
|
||||
@@ -103,4 +104,16 @@ void remove_collinear_points(ExPolygon &p)
|
||||
{
|
||||
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
|
||||
|
||||
@@ -50,6 +50,8 @@ public:
|
||||
const Point& axis() const { return m_axis; }
|
||||
};
|
||||
|
||||
}
|
||||
double fit_into_box_rotation(const Polygon &shape, const BoundingBox &box);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // MINAREABOUNDINGBOX_HPP
|
||||
|
||||
@@ -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) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++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));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1280,64 +1281,6 @@ bool ModelObject::has_connectors() const
|
||||
return false;
|
||||
}
|
||||
|
||||
indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes)
|
||||
{
|
||||
indexed_triangle_set connector_mesh;
|
||||
|
||||
int sectorCount {1};
|
||||
switch (CutConnectorShape(connector_attributes.shape)) {
|
||||
case CutConnectorShape::Triangle:
|
||||
sectorCount = 3;
|
||||
break;
|
||||
case CutConnectorShape::Square:
|
||||
sectorCount = 4;
|
||||
break;
|
||||
case CutConnectorShape::Circle:
|
||||
sectorCount = 360;
|
||||
break;
|
||||
case CutConnectorShape::Hexagon:
|
||||
sectorCount = 6;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (connector_attributes.style == CutConnectorStyle::Prism)
|
||||
connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
|
||||
else if (connector_attributes.type == CutConnectorType::Plug)
|
||||
connector_mesh = its_make_frustum(1.0, 1.0, (2 * PI / sectorCount));
|
||||
else
|
||||
connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount);
|
||||
|
||||
return connector_mesh;
|
||||
}
|
||||
|
||||
void ModelObject::apply_cut_connectors(const std::string& new_name)
|
||||
{
|
||||
if (cut_connectors.empty())
|
||||
return;
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
size_t connector_id = cut_id.connectors_cnt();
|
||||
for (const CutConnector& connector : cut_connectors) {
|
||||
TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs));
|
||||
// Mesh will be centered when loading.
|
||||
ModelVolume* new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME);
|
||||
|
||||
// Transform the new modifier to be aligned inside the instance
|
||||
new_volume->set_transformation(translation_transform(connector.pos) * connector.rotation_m *
|
||||
scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast<double>()));
|
||||
|
||||
new_volume->cut_info = { connector.attribs.type, connector.radius_tolerance, connector.height_tolerance };
|
||||
new_volume->name = new_name + "-" + std::to_string(++connector_id);
|
||||
}
|
||||
cut_id.increase_connectors_cnt(cut_connectors.size());
|
||||
|
||||
// delete all connectors
|
||||
cut_connectors.clear();
|
||||
}
|
||||
|
||||
void ModelObject::invalidate_cut()
|
||||
{
|
||||
this->cut_id.invalidate();
|
||||
@@ -1390,293 +1333,6 @@ void ModelVolume::reset_extra_facets()
|
||||
this->mmu_segmentation_facets.reset();
|
||||
}
|
||||
|
||||
void ModelVolume::apply_tolerance()
|
||||
{
|
||||
assert(cut_info.is_connector);
|
||||
if (!cut_info.is_processed)
|
||||
return;
|
||||
|
||||
Vec3d sf = get_scaling_factor();
|
||||
|
||||
// make a "hole" wider
|
||||
sf[X] += double(cut_info.radius_tolerance);
|
||||
sf[Y] += double(cut_info.radius_tolerance);
|
||||
|
||||
// make a "hole" dipper
|
||||
sf[Z] += double(cut_info.height_tolerance);
|
||||
|
||||
set_scaling_factor(sf);
|
||||
|
||||
// correct offset in respect to the new depth
|
||||
Vec3d rot_norm = Geometry::rotation_transform(get_rotation()) * Vec3d::UnitZ();
|
||||
if (rot_norm.norm() != 0.0)
|
||||
rot_norm.normalize();
|
||||
|
||||
double z_offset = 0.5 * static_cast<double>(cut_info.height_tolerance);
|
||||
if (cut_info.connector_type == CutConnectorType::Plug)
|
||||
z_offset -= 0.05; // add small Z offset to better preview
|
||||
|
||||
set_offset(get_offset() + rot_norm * z_offset);
|
||||
}
|
||||
|
||||
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
||||
{
|
||||
if (mesh.empty())
|
||||
return;
|
||||
|
||||
mesh.transform(cut_matrix);
|
||||
ModelVolume* vol = object->add_volume(mesh);
|
||||
vol->set_type(type);
|
||||
|
||||
vol->name = src_volume->name + suffix;
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(src_volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != src_volume->config.id());
|
||||
vol->set_material(src_volume->material_id(), *src_volume->material());
|
||||
vol->cut_info = src_volume->cut_info;
|
||||
}
|
||||
|
||||
void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
std::vector<ModelObject*>& dowels)
|
||||
{
|
||||
assert(volume->cut_info.is_connector);
|
||||
volume->cut_info.set_processed();
|
||||
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
// ! Don't apply instance transformation for the conntectors.
|
||||
// This transformation is already there
|
||||
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
ModelVolume* vol = upper->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
vol->apply_tolerance();
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
ModelVolume* vol = lower->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
|
||||
ModelObject* dowel{ nullptr };
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
clone_for_cut(&dowel);
|
||||
|
||||
// add one more solid part same as connector if this connector is a dowel
|
||||
ModelVolume* vol = dowel->add_volume(*volume);
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
|
||||
// But discard rotation and Z-offset for this volume
|
||||
vol->set_rotation(Vec3d::Zero());
|
||||
vol->set_offset(Z, 0.0);
|
||||
|
||||
dowels.push_back(dowel);
|
||||
}
|
||||
|
||||
// Cut the dowel
|
||||
volume->apply_tolerance();
|
||||
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// add small Z offset to better preview
|
||||
upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
|
||||
// Add cut parts to the related objects
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
|
||||
}
|
||||
}
|
||||
|
||||
void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
|
||||
{
|
||||
const auto volume_matrix = instance_matrix * volume->get_matrix();
|
||||
|
||||
// Modifiers are not cut, but we still need to add the instance transformation
|
||||
// to the modifier volume transformation to preserve their shape properly.
|
||||
volume->set_transformation(Geometry::Transformation(volume_matrix));
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
|
||||
upper->add_volume(*volume);
|
||||
return;
|
||||
}
|
||||
|
||||
// Some logic for the negative volumes/connectors. Add only needed modifiers
|
||||
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
|
||||
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
|
||||
upper->add_volume(*volume);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
|
||||
lower->add_volume(*volume);
|
||||
}
|
||||
|
||||
void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
|
||||
{
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
const Transformation cut_transformation = Transformation(cut_matrix);
|
||||
const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset());
|
||||
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
// Flip the triangles in case the composite transformation is left handed.
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
|
||||
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
}
|
||||
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
|
||||
{
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// Add required cut parts to the objects
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
|
||||
add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
|
||||
return;
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty())
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
|
||||
}
|
||||
|
||||
void ModelObject::reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
|
||||
bool place_on_cut/* = false*/, bool flip/* = false*/)
|
||||
{
|
||||
using namespace Geometry;
|
||||
|
||||
// Reset instance transformation except offset and Z-rotation
|
||||
|
||||
for (size_t i = 0; i < object->instances.size(); ++i) {
|
||||
auto& obj_instance = object->instances[i];
|
||||
const double rot_z = obj_instance->get_rotation().z();
|
||||
|
||||
obj_instance->set_transformation(Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor()));
|
||||
|
||||
Vec3d rotation = Vec3d::Zero();
|
||||
if (!flip && !place_on_cut) {
|
||||
if ( i != src_instance_idx)
|
||||
rotation[Z] = rot_z;
|
||||
}
|
||||
else {
|
||||
Transform3d rotation_matrix = Transform3d::Identity();
|
||||
if (flip)
|
||||
rotation_matrix = rotation_transform(PI * Vec3d::UnitX());
|
||||
|
||||
if (place_on_cut)
|
||||
rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse();
|
||||
|
||||
if (i != src_instance_idx)
|
||||
rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
|
||||
|
||||
rotation = Transformation(rotation_matrix).get_rotation();
|
||||
}
|
||||
|
||||
obj_instance->set_rotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes)
|
||||
{
|
||||
if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
return {};
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper{ nullptr };
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
clone_for_cut(&upper);
|
||||
|
||||
ModelObject* lower{ nullptr };
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !attributes.has(ModelObjectCutAttribute::KeepAsParts))
|
||||
clone_for_cut(&lower);
|
||||
|
||||
std::vector<ModelObject*> dowels;
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
// Because transformations are going to be applied to meshes directly,
|
||||
// we reset transformation of all instances and volumes,
|
||||
// except for translation and Z-rotation on instances, which are preserved
|
||||
// in the transformation matrix and not applied to the mesh transform.
|
||||
|
||||
// const auto instance_matrix = instances[instance]->get_matrix(true);
|
||||
const auto instance_matrix = instances[instance]->get_transformation().get_matrix_no_offset();
|
||||
const Transformation cut_transformation = Transformation(cut_matrix);
|
||||
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
|
||||
|
||||
for (ModelVolume* volume : volumes) {
|
||||
volume->reset_extra_facets();
|
||||
|
||||
if (!volume->is_model_part()) {
|
||||
if (volume->cut_info.is_processed)
|
||||
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
|
||||
else
|
||||
process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels);
|
||||
}
|
||||
else if (!volume->mesh().empty())
|
||||
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower);
|
||||
}
|
||||
|
||||
// Post-process cut parts
|
||||
|
||||
ModelObjectPtrs res;
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) {
|
||||
reset_instance_transformation(upper, instance, cut_matrix);
|
||||
res.push_back(upper);
|
||||
}
|
||||
else {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) {
|
||||
reset_instance_transformation(upper, instance, cut_matrix,
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
||||
attributes.has(ModelObjectCutAttribute::FlipUpper));
|
||||
res.push_back(upper);
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) {
|
||||
reset_instance_transformation(lower, instance, cut_matrix,
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || attributes.has(ModelObjectCutAttribute::FlipLower));
|
||||
res.push_back(lower);
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
|
||||
for (auto dowel : dowels) {
|
||||
reset_instance_transformation(dowel, instance, Transform3d::Identity());
|
||||
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
|
||||
res.push_back(dowel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare TriangleMeshes by Bounding boxes (mainly for sort)
|
||||
@@ -2342,38 +1998,6 @@ void ModelInstance::transform_polygon(Polygon* polygon) const
|
||||
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
|
||||
{
|
||||
TriangleSelector selector(mv.mesh());
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "SLA/SupportPoint.hpp"
|
||||
#include "SLA/Hollowing.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "Arrange.hpp"
|
||||
#include "CustomGCode.hpp"
|
||||
#include "enum_bitmask.hpp"
|
||||
#include "TextConfiguration.hpp"
|
||||
@@ -224,6 +223,7 @@ private:
|
||||
enum class CutConnectorType : int {
|
||||
Plug
|
||||
, Dowel
|
||||
, Snap
|
||||
, Undef
|
||||
};
|
||||
|
||||
@@ -316,10 +316,6 @@ enum class ModelVolumeType : int {
|
||||
SUPPORT_ENFORCER,
|
||||
};
|
||||
|
||||
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo };
|
||||
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
|
||||
|
||||
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
|
||||
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
|
||||
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
|
||||
@@ -461,29 +457,12 @@ public:
|
||||
size_t materials_count() const;
|
||||
size_t facets_count() const;
|
||||
size_t parts_count() const;
|
||||
static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes);
|
||||
void apply_cut_connectors(const std::string& name);
|
||||
// invalidate cut state for this object and its connectors/volumes
|
||||
void invalidate_cut();
|
||||
// delete volumes which are marked as connector for this object
|
||||
void delete_connectors();
|
||||
void clone_for_cut(ModelObject **obj);
|
||||
|
||||
private:
|
||||
void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
std::vector<ModelObject*>& dowels);
|
||||
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
|
||||
void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh);
|
||||
void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
|
||||
public:
|
||||
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
|
||||
bool place_on_cut = false, bool flip = false);
|
||||
|
||||
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
|
||||
void split(ModelObjectPtrs*new_objects);
|
||||
void merge();
|
||||
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||
@@ -777,6 +756,7 @@ public:
|
||||
// It contains information about connetors
|
||||
struct CutInfo
|
||||
{
|
||||
bool is_from_upper{ true };
|
||||
bool is_connector{ false };
|
||||
bool is_processed{ true };
|
||||
CutConnectorType connector_type{ CutConnectorType::Plug };
|
||||
@@ -794,6 +774,7 @@ public:
|
||||
|
||||
void set_processed() { is_processed = true; }
|
||||
void invalidate() { is_connector = false; }
|
||||
void reset_from_upper() { is_from_upper = true; }
|
||||
|
||||
template<class Archive> inline void serialize(Archive& ar) {
|
||||
ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance);
|
||||
@@ -801,6 +782,9 @@ public:
|
||||
};
|
||||
CutInfo cut_info;
|
||||
|
||||
bool is_from_upper() const { return cut_info.is_from_upper; }
|
||||
void reset_from_upper() { cut_info.reset_from_upper(); }
|
||||
|
||||
bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; }
|
||||
void invalidate_cut_info() { cut_info.invalidate(); }
|
||||
|
||||
@@ -846,7 +830,6 @@ public:
|
||||
bool is_the_only_one_part() const; // behave like an object
|
||||
t_model_material_id material_id() const { return m_material_id; }
|
||||
void reset_extra_facets();
|
||||
void apply_tolerance();
|
||||
void set_material_id(t_model_material_id material_id);
|
||||
ModelMaterial* material() const;
|
||||
void set_material(t_model_material_id material_id, const ModelMaterial &material);
|
||||
@@ -1171,11 +1154,7 @@ public:
|
||||
|
||||
bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); }
|
||||
|
||||
// Getting the input polygon for arrange
|
||||
arrangement::ArrangePolygon get_arrange_polygon() const;
|
||||
|
||||
// Apply the arrange result on the ModelInstance
|
||||
void apply_arrange_result(const Vec2d& offs, double rotation);
|
||||
void invalidate_object_bounding_box() { object->invalidate_bounding_box(); }
|
||||
|
||||
protected:
|
||||
friend class Print;
|
||||
|
||||
@@ -1,77 +1,14 @@
|
||||
#include "ModelArrange.hpp"
|
||||
|
||||
#include <libslic3r/Arrange/SceneBuilder.hpp>
|
||||
#include <libslic3r/Arrange/Items/ArrangeItem.hpp>
|
||||
#include <libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp>
|
||||
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/Geometry/ConvexHull.hpp>
|
||||
#include "MTUtils.hpp"
|
||||
|
||||
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)
|
||||
{
|
||||
for (ModelObject *o : model.objects) {
|
||||
@@ -83,4 +20,45 @@ void duplicate_objects(Model &model, size_t copies_num)
|
||||
}
|
||||
}
|
||||
|
||||
bool arrange_objects(Model &model,
|
||||
const arr2::ArrangeBed &bed,
|
||||
const arr2::ArrangeSettingsView &settings)
|
||||
{
|
||||
return arrange(arr2::SceneBuilder{}
|
||||
.set_bed(bed)
|
||||
.set_arrange_settings(settings)
|
||||
.set_model(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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef MODELARRANGE_HPP
|
||||
#define MODELARRANGE_HPP
|
||||
|
||||
#include <libslic3r/Arrange.hpp>
|
||||
#include <libslic3r/Arrange/Scene.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -9,63 +9,23 @@ class Model;
|
||||
class ModelInstance;
|
||||
using ModelInstancePtrs = std::vector<ModelInstance*>;
|
||||
|
||||
using arrangement::ArrangePolygon;
|
||||
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(Model &model, ArrangePolygons &copies, VirtualBedFn);
|
||||
void duplicate_objects(Model &model, size_t copies_num);
|
||||
|
||||
template<class TBed>
|
||||
bool arrange_objects(Model & model,
|
||||
const TBed & bed,
|
||||
const ArrangeParams ¶ms,
|
||||
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);
|
||||
}
|
||||
bool arrange_objects(Model &model,
|
||||
const arr2::ArrangeBed &bed,
|
||||
const arr2::ArrangeSettingsView &settings);
|
||||
|
||||
template<class TBed>
|
||||
void duplicate(Model & model,
|
||||
size_t copies_num,
|
||||
const TBed & bed,
|
||||
const ArrangeParams ¶ms,
|
||||
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,
|
||||
size_t copies_num,
|
||||
const TBed & bed,
|
||||
const ArrangeParams ¶ms,
|
||||
VirtualBedFn vfn = throw_if_out_of_bed)
|
||||
{
|
||||
duplicate_objects(model, copies_num);
|
||||
arrange_objects(model, bed, params, vfn);
|
||||
}
|
||||
const arr2::ArrangeBed &bed,
|
||||
const arr2::ArrangeSettingsView &settings);
|
||||
|
||||
}
|
||||
void duplicate(Model & model,
|
||||
size_t copies_num,
|
||||
const arr2::ArrangeBed &bed,
|
||||
const arr2::ArrangeSettingsView &settings);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // MODELARRANGE_HPP
|
||||
|
||||
@@ -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,
|
||||
std::vector<std::vector<ExPolygons>> &segmented_regions,
|
||||
const float cut_width,
|
||||
const float interlocking_depth,
|
||||
const std::function<void()> &throw_on_cancel_callback)
|
||||
{
|
||||
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) {
|
||||
throw_on_cancel_callback();
|
||||
const size_t num_extruders_plus_one = segmented_regions[layer_idx].size();
|
||||
std::vector<ExPolygons> segmented_regions_cuts(num_extruders_plus_one); // Indexed by extruder_id
|
||||
for (size_t extruder_idx = 0; extruder_idx < num_extruders_plus_one; ++extruder_idx)
|
||||
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], cut_width));
|
||||
segmented_regions[layer_idx] = std::move(segmented_regions_cuts);
|
||||
const float region_cut_width = (layer_idx % 2 == 0 && interlocking_cut_width > 0.f) ? interlocking_cut_width : cut_width;
|
||||
const size_t num_extruders_plus_one = segmented_regions[layer_idx].size();
|
||||
if (region_cut_width > 0.f) {
|
||||
std::vector<ExPolygons> segmented_regions_cuts(num_extruders_plus_one); // Indexed by extruder_id
|
||||
for (size_t extruder_idx = 0; extruder_idx < num_extruders_plus_one; ++extruder_idx)
|
||||
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
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end";
|
||||
@@ -1470,7 +1475,8 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
||||
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
|
||||
if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) {
|
||||
// Clean up thin projections. They are not printable anyways.
|
||||
top_ex = opening_ex(top_ex, stat.small_region_threshold);
|
||||
if (stat.small_region_threshold > 0)
|
||||
top_ex = opening_ex(top_ex, stat.small_region_threshold);
|
||||
if (! top_ex.empty()) {
|
||||
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
|
||||
float offset = 0.f;
|
||||
@@ -1478,7 +1484,9 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
||||
for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - stat.top_solid_layers), int(0)); --last_idx) {
|
||||
offset -= stat.extrusion_width;
|
||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
||||
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
||||
ExPolygons last = intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset));
|
||||
if (stat.small_region_threshold > 0)
|
||||
last = opening_ex(last, stat.small_region_threshold);
|
||||
if (last.empty())
|
||||
break;
|
||||
append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
@@ -1488,7 +1496,8 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
||||
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
|
||||
if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) {
|
||||
// Clean up thin projections. They are not printable anyways.
|
||||
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
|
||||
if (stat.small_region_threshold > 0)
|
||||
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
|
||||
if (! bottom_ex.empty()) {
|
||||
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
|
||||
float offset = 0.f;
|
||||
@@ -1496,7 +1505,9 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
||||
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_solid_layers, num_layers); ++last_idx) {
|
||||
offset -= stat.extrusion_width;
|
||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
||||
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
||||
ExPolygons last = intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset));
|
||||
if (stat.small_region_threshold > 0)
|
||||
last = opening_ex(last, stat.small_region_threshold);
|
||||
if (last.empty())
|
||||
break;
|
||||
append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
@@ -1891,8 +1902,8 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - end";
|
||||
throw_on_cancel_callback();
|
||||
|
||||
if (auto w = print_object.config().mmu_segmented_region_max_width; w > 0.f) {
|
||||
cut_segmented_layers(input_expolygons, segmented_regions, float(-scale_(w)), throw_on_cancel_callback);
|
||||
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_(max_width)), float(scale_(interlocking_depth)), throw_on_cancel_callback);
|
||||
throw_on_cancel_callback();
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user