mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-02-03 17:38:43 +03:00
update test
This commit is contained in:
5
src/slic3r-arrange-wrapper/.vscode/settings.json
vendored
Normal file
5
src/slic3r-arrange-wrapper/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"string_view": "cpp"
|
||||
}
|
||||
}
|
||||
35
src/slic3r-arrange-wrapper/CMakeLists.txt
Normal file
35
src/slic3r-arrange-wrapper/CMakeLists.txt
Normal file
@@ -0,0 +1,35 @@
|
||||
project(slic3r-arrange-wrapper)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
add_library(slic3r-arrange-wrapper
|
||||
include/arrange-wrapper/Arrange.hpp
|
||||
include/arrange-wrapper/ArrangeSettingsDb_AppCfg.hpp
|
||||
include/arrange-wrapper/ArrangeSettingsView.hpp
|
||||
include/arrange-wrapper/Items/ArbitraryDataStore.hpp
|
||||
include/arrange-wrapper/Items/ArrangeItem.hpp
|
||||
include/arrange-wrapper/Items/MutableItemTraits.hpp
|
||||
include/arrange-wrapper/Items/SimpleArrangeItem.hpp
|
||||
include/arrange-wrapper/Items/TrafoOnlyArrangeItem.hpp
|
||||
include/arrange-wrapper/Scene.hpp
|
||||
include/arrange-wrapper/SceneBuilder.hpp
|
||||
include/arrange-wrapper/SegmentedRectangleBed.hpp
|
||||
include/arrange-wrapper/Tasks/ArrangeTask.hpp
|
||||
include/arrange-wrapper/Tasks/FillBedTask.hpp
|
||||
include/arrange-wrapper/Tasks/MultiplySelectionTask.hpp
|
||||
include/arrange-wrapper/ModelArrange.hpp
|
||||
|
||||
src/ArrangeImpl.hpp
|
||||
src/ArrangeSettingsDb_AppCfg.cpp
|
||||
src/Items/SimpleArrangeItem.cpp
|
||||
src/SceneBuilder.cpp
|
||||
src/Scene.cpp
|
||||
src/Items/ArrangeItem.cpp
|
||||
src/ModelArrange.cpp
|
||||
src/Tasks/ArrangeTaskImpl.hpp
|
||||
src/Tasks/FillBedTaskImpl.hpp
|
||||
src/Tasks/MultiplySelectionTaskImpl.hpp
|
||||
)
|
||||
|
||||
target_include_directories(slic3r-arrange-wrapper PRIVATE src)
|
||||
target_include_directories(slic3r-arrange-wrapper PUBLIC include)
|
||||
target_link_libraries(slic3r-arrange-wrapper PUBLIC slic3r-arrange)
|
||||
268
src/slic3r-arrange-wrapper/include/arrange-wrapper/Arrange.hpp
Normal file
268
src/slic3r-arrange-wrapper/include/arrange-wrapper/Arrange.hpp
Normal file
@@ -0,0 +1,268 @@
|
||||
#ifndef ARRANGE2_HPP
|
||||
#define ARRANGE2_HPP
|
||||
|
||||
#include <libslic3r/MinAreaBoundingBox.hpp>
|
||||
#include <arrange/NFP/NFPArrangeItemTraits.hpp>
|
||||
|
||||
#include "Scene.hpp"
|
||||
#include "Items/MutableItemTraits.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(ArrangeTaskCtl &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
|
||||
@@ -0,0 +1,96 @@
|
||||
#ifndef ARRANGESETTINGSDB_APPCFG_HPP
|
||||
#define ARRANGESETTINGSDB_APPCFG_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ArrangeSettingsView.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class AppConfig;
|
||||
|
||||
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);
|
||||
|
||||
void sync();
|
||||
|
||||
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
|
||||
@@ -0,0 +1,234 @@
|
||||
#ifndef ARRANGESETTINGSVIEW_HPP
|
||||
#define ARRANGESETTINGSVIEW_HPP
|
||||
|
||||
#include <string_view>
|
||||
#include <array>
|
||||
|
||||
#include "libslic3r/StaticMap.hpp"
|
||||
|
||||
namespace Slic3r { namespace arr2 {
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
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;
|
||||
|
||||
static constexpr std::string_view get_label(GeometryHandling v)
|
||||
{
|
||||
constexpr auto STR = std::array{
|
||||
"0"sv, // convex
|
||||
"1"sv, // balanced
|
||||
"2"sv, // advanced
|
||||
"-1"sv, // undefined
|
||||
};
|
||||
|
||||
return STR[v];
|
||||
}
|
||||
|
||||
static constexpr std::string_view get_label(ArrangeStrategy v)
|
||||
{
|
||||
constexpr auto STR = std::array{
|
||||
"0"sv, // auto
|
||||
"1"sv, // pulltocenter
|
||||
"-1"sv, // undefined
|
||||
};
|
||||
|
||||
return STR[v];
|
||||
}
|
||||
|
||||
static constexpr std::string_view get_label(XLPivots v)
|
||||
{
|
||||
constexpr auto STR = std::array{
|
||||
"0"sv, // center
|
||||
"1"sv, // rearleft
|
||||
"2"sv, // frontleft
|
||||
"3"sv, // frontright
|
||||
"4"sv, // rearright
|
||||
"5"sv, // random
|
||||
"-1"sv, // undefined
|
||||
};
|
||||
|
||||
return STR[v];
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<class EnumType, size_t N>
|
||||
using EnumMap = StaticMap<std::string_view, EnumType, N>;
|
||||
|
||||
template<class EnumType, size_t N>
|
||||
static constexpr std::optional<EnumType> get_enumval(std::string_view str,
|
||||
const EnumMap<EnumType, N> &emap)
|
||||
{
|
||||
std::optional<EnumType> ret;
|
||||
|
||||
if (auto v = query(emap, str); v.has_value()) {
|
||||
ret = *v;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
static constexpr std::optional<GeometryHandling> to_geometry_handling(std::string_view str)
|
||||
{
|
||||
return get_enumval(str, GeometryHandlingLabels);
|
||||
}
|
||||
|
||||
static constexpr std::optional<ArrangeStrategy> to_arrange_strategy(std::string_view str)
|
||||
{
|
||||
return get_enumval(str, ArrangeStrategyLabels);
|
||||
}
|
||||
|
||||
static constexpr std::optional<XLPivots> to_xl_pivots(std::string_view str)
|
||||
{
|
||||
return get_enumval(str, XLPivotsLabels);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static constexpr const auto GeometryHandlingLabels = make_staticmap<std::string_view, GeometryHandling>({
|
||||
{"convex"sv, ghConvex},
|
||||
{"balanced"sv, ghBalanced},
|
||||
{"advanced"sv, ghAdvanced},
|
||||
|
||||
{"0"sv, ghConvex},
|
||||
{"1"sv, ghBalanced},
|
||||
{"2"sv, ghAdvanced},
|
||||
});
|
||||
|
||||
static constexpr const auto ArrangeStrategyLabels = make_staticmap<std::string_view, ArrangeStrategy>({
|
||||
{"auto"sv, asAuto},
|
||||
{"pulltocenter"sv, asPullToCenter},
|
||||
|
||||
{"0"sv, asAuto},
|
||||
{"1"sv, asPullToCenter}
|
||||
});
|
||||
|
||||
static constexpr const auto XLPivotsLabels = make_staticmap<std::string_view, XLPivots>({
|
||||
{"center"sv, xlpCenter },
|
||||
{"rearleft"sv, xlpRearLeft },
|
||||
{"frontleft"sv, xlpFrontLeft },
|
||||
{"frontright"sv, xlpFrontRight },
|
||||
{"rearright"sv, xlpRearRight },
|
||||
{"random"sv, xlpRandom },
|
||||
|
||||
{"0"sv, xlpCenter },
|
||||
{"1"sv, xlpRearLeft },
|
||||
{"2"sv, xlpFrontLeft },
|
||||
{"3"sv, xlpFrontRight },
|
||||
{"4"sv, xlpRearRight },
|
||||
{"5"sv, xlpRandom }
|
||||
});
|
||||
};
|
||||
|
||||
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
|
||||
@@ -0,0 +1,91 @@
|
||||
#ifndef ARBITRARYDATASTORE_HPP
|
||||
#define ARBITRARYDATASTORE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <any>
|
||||
|
||||
#include <arrange/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
|
||||
@@ -0,0 +1,509 @@
|
||||
#ifndef ARRANGEITEM_HPP
|
||||
#define ARRANGEITEM_HPP
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
#include <libslic3r/AnyPtr.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/libslic3r.h>
|
||||
|
||||
#include <arrange/PackingContext.hpp>
|
||||
#include <arrange/NFP/NFPArrangeItemTraits.hpp>
|
||||
#include <arrange/NFP/NFP.hpp>
|
||||
#include <arrange/ArrangeBase.hpp>
|
||||
#include <arrange/ArrangeItemTraits.hpp>
|
||||
#include <arrange/DataStoreTraits.hpp>
|
||||
|
||||
|
||||
#include <arrange-wrapper/Items/MutableItemTraits.hpp>
|
||||
#include <arrange-wrapper/Arrange.hpp>
|
||||
#include <arrange-wrapper/Tasks/ArrangeTask.hpp>
|
||||
#include <arrange-wrapper/Tasks/FillBedTask.hpp>
|
||||
#include <arrange-wrapper/Tasks/MultiplySelectionTask.hpp>
|
||||
#include <arrange-wrapper/Items/ArbitraryDataStore.hpp>
|
||||
|
||||
namespace Slic3r { namespace arr2 {
|
||||
struct InfiniteBed;
|
||||
|
||||
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
|
||||
std::optional<int> m_bed_constraint;
|
||||
|
||||
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; }
|
||||
std::optional<int> bed_constraint() const { return m_bed_constraint; };
|
||||
|
||||
void bed_idx(int v) { m_bed_idx = v; }
|
||||
void priority(int v) { m_priority = v; }
|
||||
void bed_constraint(std::optional<int> v) { m_bed_constraint = 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();
|
||||
}
|
||||
|
||||
static std::optional<int> get_bed_constraint(const ArrangeItem &itm)
|
||||
{
|
||||
return itm.bed_constraint();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
static void set_bed_constraint(ArrangeItem &itm, std::optional<int> v)
|
||||
{
|
||||
itm.bed_constraint(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
|
||||
@@ -0,0 +1,136 @@
|
||||
#ifndef MutableItemTraits_HPP
|
||||
#define MutableItemTraits_HPP
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
#include <arrange/ArrangeItemTraits.hpp>
|
||||
#include <arrange/DataStoreTraits.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
|
||||
@@ -0,0 +1,233 @@
|
||||
#ifndef SIMPLEARRANGEITEM_HPP
|
||||
#define SIMPLEARRANGEITEM_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/Geometry/ConvexHull.hpp>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/ObjectID.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
|
||||
#include <arrange/ArrangeItemTraits.hpp>
|
||||
#include <arrange/PackingContext.hpp>
|
||||
#include <arrange/NFP/NFPArrangeItemTraits.hpp>
|
||||
#include <arrange/NFP/NFP.hpp>
|
||||
|
||||
#include <arrange-wrapper/Arrange.hpp>
|
||||
#include <arrange-wrapper/Tasks/FillBedTask.hpp>
|
||||
#include <arrange-wrapper/Tasks/ArrangeTask.hpp>
|
||||
#include <arrange-wrapper/Items/MutableItemTraits.hpp>
|
||||
|
||||
|
||||
namespace Slic3r { namespace arr2 {
|
||||
struct InfiniteBed;
|
||||
|
||||
class SimpleArrangeItem {
|
||||
Polygon m_shape;
|
||||
|
||||
Vec2crd m_translation = Vec2crd::Zero();
|
||||
double m_rotation = 0.;
|
||||
int m_priority = 0;
|
||||
int m_bed_idx = Unarranged;
|
||||
std::optional<int> m_bed_constraint;
|
||||
|
||||
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; }
|
||||
std::optional<int> get_bed_constraint() const noexcept {
|
||||
return m_bed_constraint;
|
||||
}
|
||||
|
||||
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; }
|
||||
void set_bed_constraint(std::optional<int> v) noexcept { m_bed_constraint = 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
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef TRAFOONLYARRANGEITEM_HPP
|
||||
#define TRAFOONLYARRANGEITEM_HPP
|
||||
|
||||
#include <arrange/ArrangeItemTraits.hpp>
|
||||
|
||||
#include "ArbitraryDataStore.hpp"
|
||||
#include "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.;
|
||||
std::optional<int> m_bed_constraint;
|
||||
|
||||
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)},
|
||||
m_bed_constraint{arr2::get_bed_constraint(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; }
|
||||
std::optional<int> get_bed_constraint() const noexcept { return m_bed_constraint; }
|
||||
|
||||
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
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef MODELARRANGE_HPP
|
||||
#define MODELARRANGE_HPP
|
||||
|
||||
#include <stddef.h>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
|
||||
#include <arrange/Beds.hpp>
|
||||
#include "Scene.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
class ModelInstance;
|
||||
|
||||
namespace arr2 {
|
||||
class ArrangeSettingsView;
|
||||
} // namespace arr2
|
||||
|
||||
using ModelInstancePtrs = std::vector<ModelInstance*>;
|
||||
|
||||
//void duplicate(Model &model, ArrangePolygons &copies, VirtualBedFn);
|
||||
void duplicate_objects(Model &model, size_t copies_num);
|
||||
|
||||
bool arrange_objects(Model &model,
|
||||
const arr2::ArrangeBed &bed,
|
||||
const arr2::ArrangeSettingsView &settings);
|
||||
|
||||
void duplicate_objects(Model & model,
|
||||
size_t copies_num,
|
||||
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
|
||||
440
src/slic3r-arrange-wrapper/include/arrange-wrapper/Scene.hpp
Normal file
440
src/slic3r-arrange-wrapper/include/arrange-wrapper/Scene.hpp
Normal file
@@ -0,0 +1,440 @@
|
||||
#ifndef ARR2_SCENE_HPP
|
||||
#define ARR2_SCENE_HPP
|
||||
|
||||
#include <stddef.h>
|
||||
#include <boost/variant.hpp>
|
||||
#include <boost/variant/variant.hpp>
|
||||
#include <any>
|
||||
#include <string_view>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
|
||||
#include <libslic3r/ObjectID.hpp>
|
||||
#include <libslic3r/AnyPtr.hpp>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/libslic3r.h>
|
||||
|
||||
#include <arrange/Beds.hpp>
|
||||
|
||||
#include "ArrangeSettingsView.hpp"
|
||||
#include "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; }
|
||||
|
||||
virtual std::optional<int> bed_constraint() const { return std::nullopt; }
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
inline Vec2crd bed_gap(const ExtendedBed &bed)
|
||||
{
|
||||
Vec2crd gap;
|
||||
visit_bed([&gap](auto &rawbed) { gap = bed_gap(rawbed); }, bed);
|
||||
|
||||
return gap;
|
||||
}
|
||||
|
||||
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, const Vec2crd &gap)
|
||||
{
|
||||
m_bed = arr2::to_arrange_bed(pts, gap);
|
||||
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:
|
||||
// Scene can only be built from an rvalue SceneBuilder whose content will
|
||||
// potentially be moved to the constructed Scene 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;
|
||||
};
|
||||
|
||||
// Get all the ObjectIDs of Arrangeables which are in selected state
|
||||
std::set<ObjectID> selected_geometry_ids(const Scene &sc);
|
||||
|
||||
// A dummy, empty ArrangeableModel for testing and as placeholder to avoiod using nullptr
|
||||
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>();
|
||||
|
||||
// Apply the bed minimum distance by making the original bed smaller
|
||||
// and arranging on this smaller bed.
|
||||
coord_t inset = std::max(scaled(m_settings->get_distance_from_bed()),
|
||||
m_skirt_offs + m_brims_offs);
|
||||
|
||||
// Objects have also a minimum distance from each other implemented
|
||||
// as inflation applied to object outlines. This object distance
|
||||
// does not apply to the bed, so the bed is inflated by this amount
|
||||
// to compensate.
|
||||
coord_t md = scaled(m_settings->get_distance_from_objects());
|
||||
md = md / 2 - inset;
|
||||
|
||||
// Applying the final bed with the corrected dimensions to account
|
||||
// for safety distances
|
||||
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
|
||||
@@ -0,0 +1,722 @@
|
||||
#ifndef SCENEBUILDER_HPP
|
||||
#define SCENEBUILDER_HPP
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
#include <libslic3r/AnyPtr.hpp>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/ObjectID.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/libslic3r.h>
|
||||
|
||||
#include <arrange/ArrangeItemTraits.hpp>
|
||||
#include <arrange/Beds.hpp>
|
||||
|
||||
#include "Scene.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(int)>;
|
||||
|
||||
// Objects implementing this interface should know how to present the wipe tower
|
||||
// as an Arrangeable. If the wipe tower is not present, the overloads of visit() shouldn't do
|
||||
// anything. (See MissingWipeTowerHandler)
|
||||
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;
|
||||
virtual ObjectID get_id() const = 0;
|
||||
};
|
||||
|
||||
// Something that has a bounding box and can be displaced by arbitrary 2D offset and rotated
|
||||
// by arbitrary rotation. Used as targets to place on virtual beds. Normally this would correspond
|
||||
// to ModelInstances but the same functionality was needed in more contexts.
|
||||
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 VBedPlaceable objects. A VBedPlaceable
|
||||
// may be assigned to a logical bed identified by an integer index value (zero
|
||||
// is the actual physical bed). The VBedPlaceable 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 VBedPlaceable, 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 VBedPlaceable is sitting.
|
||||
virtual int get_bed_index(const VBedPlaceable &obj) const = 0;
|
||||
|
||||
// The returned trafo can be used to displace the VBedPlaceable
|
||||
// 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 VBedPlaceable 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);
|
||||
};
|
||||
|
||||
// Holds the info about which object (ID) is selected/unselected
|
||||
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_selected(int wipe_tower_index) 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_selected(int) const override { return m_wp; }
|
||||
};
|
||||
|
||||
// Common part of any Arrangeable which is a wipe tower
|
||||
struct ArrangeableWipeTowerBase: public Arrangeable
|
||||
{
|
||||
ObjectID oid;
|
||||
|
||||
Polygon poly;
|
||||
SelectionPredicate selection_pred;
|
||||
int bed_index{0};
|
||||
|
||||
ArrangeableWipeTowerBase(
|
||||
const ObjectID &objid,
|
||||
Polygon shape,
|
||||
int bed_index,
|
||||
SelectionPredicate selection_predicate = [](int){ return false; })
|
||||
: oid{objid},
|
||||
poly{std::move(shape)},
|
||||
bed_index{bed_index},
|
||||
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(bed_index);
|
||||
}
|
||||
|
||||
int get_bed_index() const override;
|
||||
bool assign_bed(int /*bed_idx*/) override;
|
||||
|
||||
int priority() const override { return 1; }
|
||||
|
||||
std::optional<int> bed_constraint() const override {
|
||||
return this->bed_index;
|
||||
}
|
||||
|
||||
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; };
|
||||
|
||||
using BedConstraints = std::map<ObjectID, int>;
|
||||
|
||||
// Implementing ArrangeableModel interface for QIDISlicer's Model, ModelObject, ModelInstance data
|
||||
// hierarchy
|
||||
class ArrangeableSlicerModel: public ArrangeableModel
|
||||
{
|
||||
protected:
|
||||
AnyPtr<Model> m_model;
|
||||
std::vector<AnyPtr<WipeTowerHandler>> m_wths; // Determines how wipe tower is handled
|
||||
AnyPtr<VirtualBedHandler> m_vbed_handler; // Determines how virtual beds are handled
|
||||
AnyPtr<const SelectionMask> m_selmask; // Determines which objects are selected/unselected
|
||||
BedConstraints m_bed_constraints;
|
||||
std::optional<std::set<ObjectID>> m_considered_instances;
|
||||
|
||||
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; }
|
||||
};
|
||||
|
||||
// SceneBuilder implementation for QIDISlicer API.
|
||||
class SceneBuilder: public SceneBuilderBase<SceneBuilder>
|
||||
{
|
||||
protected:
|
||||
AnyPtr<Model> m_model;
|
||||
std::vector<AnyPtr<WipeTowerHandler>> m_wipetower_handlers;
|
||||
BedConstraints m_bed_constraints;
|
||||
std::optional<std::set<ObjectID>> m_considered_instances;
|
||||
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, const Vec2crd &gap);
|
||||
SceneBuilder &&set_bed(const Print &print, const Vec2crd &gap);
|
||||
|
||||
SceneBuilder && set_wipe_tower_handlers(std::vector<AnyPtr<WipeTowerHandler>> &&handlers)
|
||||
{
|
||||
m_wipetower_handlers = std::move(handlers);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
SceneBuilder && set_bed_constraints(BedConstraints &&bed_constraints)
|
||||
{
|
||||
m_bed_constraints = std::move(bed_constraints);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
SceneBuilder && set_considered_instances(std::set<ObjectID> &&considered_instances)
|
||||
{
|
||||
m_considered_instances = std::move(considered_instances);
|
||||
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);
|
||||
};
|
||||
|
||||
// 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
|
||||
{
|
||||
XStriderVBedHandler m_xstrider;
|
||||
YStriderVBedHandler m_ystrider;
|
||||
|
||||
public:
|
||||
GridStriderVBedHandler(const BoundingBox &bedbb, const Vec2crd &gap)
|
||||
: m_xstrider{bedbb, gap.x()}
|
||||
, m_ystrider{bedbb, gap.y()}
|
||||
{}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
// Arrangeable interface implementation for ModelInstances
|
||||
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;
|
||||
std::optional<int> m_bed_constraint;
|
||||
|
||||
public:
|
||||
explicit ArrangeableModelInstance(InstPtr *mi,
|
||||
VBedHPtr *vbedh,
|
||||
const SelectionMask *selmask,
|
||||
const InstPos &pos,
|
||||
const std::optional<int> bed_constraint)
|
||||
: m_mi{mi}, m_vbedh{vbedh}, m_selmask{selmask}, m_pos_within_model{pos}, m_bed_constraint(bed_constraint)
|
||||
{
|
||||
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;
|
||||
|
||||
std::optional<int> bed_constraint() const override { return m_bed_constraint; }
|
||||
|
||||
// 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>;
|
||||
|
||||
// Arrangeable implementation for an SLAPrintObject to be able to arrange with the supports and pad
|
||||
class ArrangeableSLAPrintObject : public Arrangeable
|
||||
{
|
||||
const SLAPrintObject *m_po;
|
||||
Arrangeable *m_arrbl;
|
||||
Transform3d m_inst_trafo;
|
||||
std::optional<int> m_bed_constraint;
|
||||
|
||||
public:
|
||||
ArrangeableSLAPrintObject(const SLAPrintObject *po,
|
||||
Arrangeable *arrbl,
|
||||
const std::optional<int> bed_constraint,
|
||||
const Transform3d &inst_tr = Transform3d::Identity())
|
||||
: m_po{po}, m_arrbl{arrbl}, m_inst_trafo{inst_tr}, m_bed_constraint(bed_constraint)
|
||||
{}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
std::optional<int> bed_constraint() const override { return m_bed_constraint; }
|
||||
|
||||
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(); }
|
||||
};
|
||||
|
||||
// Extension of ArrangeableSlicerModel for SLA
|
||||
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>;
|
||||
|
||||
// An implementation of the ArrangeableModel to be used for the full model 'duplicate' feature
|
||||
// accessible from CLI
|
||||
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
|
||||
@@ -0,0 +1,121 @@
|
||||
#ifndef SEGMENTEDRECTANGLEBED_HPP
|
||||
#define SEGMENTEDRECTANGLEBED_HPP
|
||||
|
||||
#include <arrange/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;
|
||||
Vec2crd gap;
|
||||
RectPivots pivot = RectPivots::Center;
|
||||
|
||||
SegmentedRectangleBed() = default;
|
||||
SegmentedRectangleBed(const BoundingBox &bb,
|
||||
size_t segments_x,
|
||||
size_t segments_y,
|
||||
const Vec2crd &gap,
|
||||
const RectPivots pivot = RectPivots::Center)
|
||||
: segments{segments_x, segments_y}, bb{bb}, gap{gap}, 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;
|
||||
Vec2crd gap;
|
||||
RectPivots pivot = RectPivots::Center;
|
||||
|
||||
SegmentedRectangleBed() = default;
|
||||
|
||||
explicit SegmentedRectangleBed(const BoundingBox &b,
|
||||
const Vec2crd &gap,
|
||||
const RectPivots pivot = RectPivots::Center)
|
||||
: bb{b},
|
||||
gap{gap}
|
||||
{}
|
||||
|
||||
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;
|
||||
Vec2crd gap;
|
||||
|
||||
SegmentedRectangleBed() = default;
|
||||
|
||||
explicit SegmentedRectangleBed(const BoundingBox &b, const Vec2crd &gap) : bb{b}, gap{gap} {}
|
||||
|
||||
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 bed_gap(const SegmentedRectangleBed<Args...> &bed)
|
||||
{
|
||||
return bed.gap;
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
|
||||
template<class SegB>
|
||||
struct IsRectangular_<SegB, std::enable_if_t<IsSegmentedBed<SegB>, void>> : public std::true_type
|
||||
{};
|
||||
|
||||
}} // namespace Slic3r::arr2
|
||||
|
||||
#endif // SEGMENTEDRECTANGLEBED_HPP
|
||||
@@ -0,0 +1,81 @@
|
||||
#ifndef ARRANGETASK_HPP
|
||||
#define ARRANGETASK_HPP
|
||||
|
||||
#include <arrange-wrapper/Arrange.hpp>
|
||||
#include <arrange-wrapper/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
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef FILLBEDTASK_HPP
|
||||
#define FILLBEDTASK_HPP
|
||||
|
||||
#include <arrange-wrapper/Arrange.hpp>
|
||||
|
||||
#include "MultiplySelectionTask.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;
|
||||
|
||||
// For workaround regarding "holes" when filling the bed with the same
|
||||
// item's copies
|
||||
std::vector<ArrItem> selected_fillers;
|
||||
|
||||
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
|
||||
@@ -0,0 +1,108 @@
|
||||
#ifndef MULTIPLYSELECTIONTASK_HPP
|
||||
#define MULTIPLYSELECTIONTASK_HPP
|
||||
|
||||
#include <arrange-wrapper/Arrange.hpp>
|
||||
#include <arrange-wrapper/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
|
||||
498
src/slic3r-arrange-wrapper/src/ArrangeImpl.hpp
Normal file
498
src/slic3r-arrange-wrapper/src/ArrangeImpl.hpp
Normal file
@@ -0,0 +1,498 @@
|
||||
#ifndef ARRANGEIMPL_HPP
|
||||
#define ARRANGEIMPL_HPP
|
||||
|
||||
#include <random>
|
||||
#include <map>
|
||||
|
||||
#include <libslic3r/Execution/ExecutionTBB.hpp>
|
||||
#include <libslic3r/Geometry/ConvexHull.hpp>
|
||||
|
||||
#include <arrange/ArrangeBase.hpp>
|
||||
#include <arrange/ArrangeFirstFit.hpp>
|
||||
#include <arrange/NFP/PackStrategyNFP.hpp>
|
||||
#include <arrange/NFP/Kernels/TMArrangeKernel.hpp>
|
||||
#include <arrange/NFP/Kernels/GravityKernel.hpp>
|
||||
#include <arrange/NFP/RectangleOverfitPackingStrategy.hpp>
|
||||
#include <arrange/Beds.hpp>
|
||||
|
||||
#include <arrange-wrapper/Arrange.hpp>
|
||||
#include <arrange-wrapper/Items/MutableItemTraits.hpp>
|
||||
#include <arrange-wrapper/SegmentedRectangleBed.hpp>
|
||||
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include <arrange/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, bed.gap}, 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 of QIDISlicer 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 &&
|
||||
IsRectangular<Bed>) {
|
||||
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());
|
||||
set_bed_constraint(ret, arrbl.bed_constraint());
|
||||
|
||||
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());
|
||||
set_bed_constraint(ret, arrbl.bed_constraint());
|
||||
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
|
||||
190
src/slic3r-arrange-wrapper/src/ArrangeSettingsDb_AppCfg.cpp
Normal file
190
src/slic3r-arrange-wrapper/src/ArrangeSettingsDb_AppCfg.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
#include <arrange-wrapper/ArrangeSettingsDb_AppCfg.hpp>
|
||||
|
||||
#include <LocalesUtils.hpp>
|
||||
#include <libslic3r/AppConfig.hpp>
|
||||
|
||||
#include <arrange-wrapper/ArrangeSettingsView.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ArrangeSettingsDb_AppCfg::ArrangeSettingsDb_AppCfg(AppConfig *appcfg) : m_appcfg{appcfg}
|
||||
{
|
||||
sync();
|
||||
}
|
||||
|
||||
void ArrangeSettingsDb_AppCfg::sync()
|
||||
{
|
||||
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_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);
|
||||
else
|
||||
m_settings_fff.vals.d_obj = m_settings_fff.defaults.d_obj;
|
||||
|
||||
if (!dist_bed_fff_str.empty())
|
||||
m_settings_fff.vals.d_bed = string_to_float_decimal_point(dist_bed_fff_str);
|
||||
else
|
||||
m_settings_fff.vals.d_bed = m_settings_fff.defaults.d_bed;
|
||||
|
||||
if (!dist_fff_seq_print_str.empty())
|
||||
m_settings_fff_seq.vals.d_obj = string_to_float_decimal_point(dist_fff_seq_print_str);
|
||||
else
|
||||
m_settings_fff_seq.vals.d_obj = m_settings_fff_seq.defaults.d_obj;
|
||||
|
||||
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);
|
||||
else
|
||||
m_settings_fff_seq.vals.d_bed = m_settings_fff_seq.defaults.d_bed;
|
||||
|
||||
if (!dist_sla_str.empty())
|
||||
m_settings_sla.vals.d_obj = string_to_float_decimal_point(dist_sla_str);
|
||||
else
|
||||
m_settings_sla.vals.d_obj = m_settings_sla.defaults.d_obj;
|
||||
|
||||
if (!dist_bed_sla_str.empty())
|
||||
m_settings_sla.vals.d_bed = string_to_float_decimal_point(dist_bed_sla_str);
|
||||
else
|
||||
m_settings_sla.vals.d_bed = m_settings_sla.defaults.d_bed;
|
||||
|
||||
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");
|
||||
else
|
||||
m_settings_fff_seq.vals.rotations = m_settings_fff_seq.defaults.rotations;
|
||||
|
||||
if (!en_rot_sla_str.empty())
|
||||
m_settings_sla.vals.rotations = (en_rot_sla_str == "1" || en_rot_sla_str == "yes");
|
||||
else
|
||||
m_settings_sla.vals.rotations = m_settings_sla.defaults.rotations;
|
||||
|
||||
// Override default alignment and save/load it to a temporary slot "alignment_xl"
|
||||
auto arr_alignment = ArrangeSettingsView::to_xl_pivots(alignment_xl_str)
|
||||
.value_or(m_settings_fff.defaults.xl_align);
|
||||
|
||||
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 ;
|
||||
|
||||
auto geom_handl = ArrangeSettingsView::to_geometry_handling(geom_handling_str)
|
||||
.value_or(m_settings_fff.defaults.geom_handling);
|
||||
|
||||
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;
|
||||
|
||||
auto arr_strategy = ArrangeSettingsView::to_arrange_strategy(strategy_str)
|
||||
.value_or(m_settings_fff.defaults.arr_strategy);
|
||||
|
||||
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::string{get_label(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::string{get_label(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::string{get_label(v)});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
207
src/slic3r-arrange-wrapper/src/Items/ArrangeItem.cpp
Normal file
207
src/slic3r-arrange-wrapper/src/Items/ArrangeItem.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
#include <numeric>
|
||||
|
||||
#include <libslic3r/Geometry/ConvexHull.hpp>
|
||||
#include <arrange/NFP/NFPConcave_Tesselate.hpp>
|
||||
|
||||
#include <arrange-wrapper/Items/ArrangeItem.hpp>
|
||||
#include "ArrangeImpl.hpp" // IWYU pragma: keep
|
||||
#include "Tasks/ArrangeTaskImpl.hpp" // IWYU pragma: keep
|
||||
#include "Tasks/FillBedTaskImpl.hpp" // IWYU pragma: keep
|
||||
#include "Tasks/MultiplySelectionTaskImpl.hpp" // IWYU pragma: keep
|
||||
|
||||
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;
|
||||
m_bed_constraint = other.m_bed_constraint;
|
||||
|
||||
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;
|
||||
m_bed_constraint = other.m_bed_constraint;
|
||||
|
||||
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
|
||||
24
src/slic3r-arrange-wrapper/src/Items/SimpleArrangeItem.cpp
Normal file
24
src/slic3r-arrange-wrapper/src/Items/SimpleArrangeItem.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <arrange-wrapper/Items/SimpleArrangeItem.hpp>
|
||||
#include "ArrangeImpl.hpp" // IWYU pragma: keep
|
||||
#include "Tasks/ArrangeTaskImpl.hpp" // IWYU pragma: keep
|
||||
#include "Tasks/FillBedTaskImpl.hpp" // IWYU pragma: keep
|
||||
#include "Tasks/MultiplySelectionTaskImpl.hpp" // IWYU pragma: keep
|
||||
|
||||
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
|
||||
66
src/slic3r-arrange-wrapper/src/ModelArrange.cpp
Normal file
66
src/slic3r-arrange-wrapper/src/ModelArrange.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <utility>
|
||||
|
||||
#include <arrange-wrapper/ModelArrange.hpp>
|
||||
|
||||
#include <arrange-wrapper/Items/ArrangeItem.hpp>
|
||||
#include <arrange-wrapper/Tasks/MultiplySelectionTask.hpp>
|
||||
#include <arrange-wrapper/SceneBuilder.hpp>
|
||||
#include <arrange-wrapper/ArrangeSettingsView.hpp>
|
||||
#include <arrange-wrapper/Scene.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void duplicate_objects(Model &model, size_t copies_num)
|
||||
{
|
||||
for (ModelObject *o : model.objects) {
|
||||
// make a copy of the pointers in order to avoid recursion when appending their copies
|
||||
ModelInstancePtrs instances = o->instances;
|
||||
for (const ModelInstance *i : instances)
|
||||
for (size_t k = 2; k <= copies_num; ++ k)
|
||||
o->add_instance(*i);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
62
src/slic3r-arrange-wrapper/src/Scene.cpp
Normal file
62
src/slic3r-arrange-wrapper/src/Scene.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include <arrange-wrapper/Scene.hpp>
|
||||
#include <arrange-wrapper/Items/ArrangeItem.hpp>
|
||||
#include <arrange-wrapper/Tasks/ArrangeTask.hpp>
|
||||
#include <arrange-wrapper/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
|
||||
986
src/slic3r-arrange-wrapper/src/SceneBuilder.cpp
Normal file
986
src/slic3r-arrange-wrapper/src/SceneBuilder.cpp
Normal file
@@ -0,0 +1,986 @@
|
||||
#ifndef SCENEBUILDER_CPP
|
||||
#define SCENEBUILDER_CPP
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/MultipleBeds.hpp>
|
||||
#include <libslic3r/Print.hpp>
|
||||
#include <libslic3r/SLAPrint.hpp>
|
||||
#include <libslic3r/Geometry/ConvexHull.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
#include <libslic3r/PrintConfig.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/TriangleMeshSlicer.hpp>
|
||||
|
||||
#include <arrange/Beds.hpp>
|
||||
#include <arrange/ArrangeItemTraits.hpp>
|
||||
|
||||
#include <arrange-wrapper/SceneBuilder.hpp>
|
||||
#include <arrange-wrapper/Scene.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, Vec2crd::Zero());
|
||||
} else {
|
||||
set_brim_and_skirt();
|
||||
}
|
||||
}
|
||||
|
||||
// Call the parent class implementation of build_scene to finish constructing of the scene
|
||||
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_fff_print && !m_xl_printer)
|
||||
m_xl_printer = is_XL_printer(m_fff_print->config());
|
||||
|
||||
const bool has_wipe_tower{std::any_of(
|
||||
m_wipetower_handlers.begin(),
|
||||
m_wipetower_handlers.end(),
|
||||
[](const AnyPtr<WipeTowerHandler> &handler){
|
||||
bool is_on_current_bed{false};
|
||||
handler->visit([&](const Arrangeable &arrangeable){
|
||||
is_on_current_bed = arrangeable.get_bed_index() == s_multiple_beds.get_active_bed();
|
||||
});
|
||||
return is_on_current_bed;
|
||||
}
|
||||
)};
|
||||
|
||||
if (m_xl_printer && !has_wipe_tower) {
|
||||
m_bed = XLBed{bounding_box(m_bed), bed_gap(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_wths = std::move(m_wipetower_handlers);
|
||||
amodel.m_bed_constraints = std::move(m_bed_constraints);
|
||||
amodel.m_considered_instances = std::move(m_considered_instances);
|
||||
|
||||
for (auto &wth : amodel.m_wths) {
|
||||
wth->set_selection_predicate(
|
||||
[&amodel](int wipe_tower_index){
|
||||
return amodel.m_selmask->is_wipe_tower_selected(wipe_tower_index);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int GridStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const
|
||||
{
|
||||
Vec2i crd = {m_xstrider.get_bed_index(obj), m_ystrider.get_bed_index(obj)};
|
||||
|
||||
return BedsGrid::grid_coords2index(crd);
|
||||
}
|
||||
|
||||
bool GridStriderVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx)
|
||||
{
|
||||
if (bed_idx < 0) {
|
||||
return false;
|
||||
}
|
||||
Vec2i crd = BedsGrid::index2grid_coords(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 = BedsGrid::index2grid_coords(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, const Vec2crd &gap)
|
||||
{
|
||||
Points bedpts = get_bed_shape(cfg);
|
||||
|
||||
if (is_XL_printer(cfg)) {
|
||||
m_xl_printer = true;
|
||||
}
|
||||
|
||||
m_bed = arr2::to_arrange_bed(bedpts, gap);
|
||||
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
SceneBuilder &&SceneBuilder::set_bed(const Print &print, const Vec2crd &gap)
|
||||
{
|
||||
Points bedpts = get_bed_shape(print.config());
|
||||
|
||||
if (is_XL_printer(print.config())) {
|
||||
m_bed = XLBed{get_extents(bedpts), gap};
|
||||
} else {
|
||||
m_bed = arr2::to_arrange_bed(bedpts, gap);
|
||||
}
|
||||
|
||||
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 this->bed_index;
|
||||
}
|
||||
|
||||
bool ArrangeableWipeTowerBase::assign_bed(int bed_idx)
|
||||
{
|
||||
return bed_idx == this->bed_index;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
for (auto &wth : m_wths) {
|
||||
wth->visit(fn);
|
||||
}
|
||||
}
|
||||
|
||||
void ArrangeableSlicerModel::for_each_arrangeable(
|
||||
std::function<void(const Arrangeable &)> fn) const
|
||||
{
|
||||
for_each_arrangeable_(*this, fn);
|
||||
|
||||
for (auto &wth : m_wths) {
|
||||
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;
|
||||
}
|
||||
|
||||
std::optional<int> get_bed_constraint(
|
||||
const ObjectID &id,
|
||||
const BedConstraints &bed_constraints
|
||||
) {
|
||||
const auto found_constraint{bed_constraints.find(id)};
|
||||
if (found_constraint == bed_constraints.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return found_constraint->second;
|
||||
}
|
||||
|
||||
bool should_include_instance(
|
||||
const ObjectID &instance_id,
|
||||
const std::set<ObjectID> &considered_instances
|
||||
) {
|
||||
if (considered_instances.find(instance_id) == considered_instances.end()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!self.m_considered_instances || should_include_instance(inst->id(), *self.m_considered_instances)) {
|
||||
ArrangeableModelInstance ainst{
|
||||
inst,
|
||||
self.m_vbed_handler.get(),
|
||||
self.m_selmask.get(),
|
||||
pos,
|
||||
get_bed_constraint(inst->id(), self.m_bed_constraints)
|
||||
};
|
||||
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)
|
||||
{
|
||||
for (auto &wth : self.m_wths) {
|
||||
if (id == wth->get_id()) {
|
||||
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,
|
||||
get_bed_constraint(id, self.m_bed_constraints)
|
||||
};
|
||||
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) {
|
||||
if (!self.m_considered_instances || should_include_instance(inst->id(), *self.m_considered_instances)) {
|
||||
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(),
|
||||
self.m_selmask.get(), pos, get_bed_constraint(inst->id(), self.m_bed_constraints)};
|
||||
|
||||
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,
|
||||
get_bed_constraint(inst->id(), self.m_bed_constraints),
|
||||
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);
|
||||
|
||||
for (auto &wth : m_wths) {
|
||||
wth->visit(fn);
|
||||
}
|
||||
}
|
||||
|
||||
void ArrangeableSLAPrint::for_each_arrangeable(
|
||||
std::function<void(const Arrangeable &)> fn) const
|
||||
{
|
||||
for_each_arrangeable_(*this, fn);
|
||||
|
||||
for (auto &wth : m_wths) {
|
||||
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, std::nullopt};
|
||||
|
||||
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,
|
||||
get_bed_constraint(inst->id(), self.m_bed_constraints),
|
||||
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 {
|
||||
Vec2crd gap;
|
||||
visit_bed([&gap](auto &rawbed) { gap = bed_gap(rawbed); }, bed);
|
||||
BoundingBox bedbb;
|
||||
visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed);
|
||||
|
||||
ret = std::make_unique<GridStriderVBedHandler>(bedbb, gap);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::arr2
|
||||
|
||||
#endif // SCENEBUILDER_CPP
|
||||
147
src/slic3r-arrange-wrapper/src/Tasks/ArrangeTaskImpl.hpp
Normal file
147
src/slic3r-arrange-wrapper/src/Tasks/ArrangeTaskImpl.hpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#ifndef ARRANGETASK_IMPL_HPP
|
||||
#define ARRANGETASK_IMPL_HPP
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <libslic3r/SVG.hpp>
|
||||
|
||||
#include <arrange-wrapper/Tasks/ArrangeTask.hpp>
|
||||
#include <arrange-wrapper/Items/ArrangeItem.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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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};
|
||||
|
||||
arranger->arrange(printable.selected, printable.unselected, 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
|
||||
static 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
|
||||
215
src/slic3r-arrange-wrapper/src/Tasks/FillBedTaskImpl.hpp
Normal file
215
src/slic3r-arrange-wrapper/src/Tasks/FillBedTaskImpl.hpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#ifndef FILLBEDTASKIMPL_HPP
|
||||
#define FILLBEDTASKIMPL_HPP
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <arrange/NFP/NFPArrangeItemTraits.hpp>
|
||||
|
||||
#include <arrange-wrapper/Tasks/FillBedTask.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) == get_bed_constraint(prototype_item)) * 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.
|
||||
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);
|
||||
});
|
||||
|
||||
const int bed_constraint{*get_bed_constraint(*task.prototype_item)};
|
||||
if (bed_constraint != get_bed_index(*task.prototype_item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_bed_index(*task.prototype_item, Unarranged);
|
||||
|
||||
auto collect_task_items = [&prototype_geometry_id, &task,
|
||||
&itm_conv, &bed_constraint](const Arrangeable &arrbl) {
|
||||
try {
|
||||
if (arrbl.bed_constraint() == bed_constraint) {
|
||||
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);
|
||||
|
||||
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_fillers), 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) == -1 && get_priority(itm) == 0;
|
||||
}
|
||||
|
||||
} subctl(ctl, *this);
|
||||
|
||||
auto arranger = Arranger<ArrItem>::create(settings);
|
||||
|
||||
arranger->arrange(selected, unselected, bed, subctl);
|
||||
|
||||
auto unsel_cpy = unselected;
|
||||
for (const auto &itm : selected) {
|
||||
unsel_cpy.emplace_back(itm);
|
||||
}
|
||||
|
||||
arranger->arrange(selected_fillers, unsel_cpy, bed, FillBedCtl{ctl, *this});
|
||||
|
||||
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) == get_bed_constraint(itm))
|
||||
result->add_new_item(itm);
|
||||
}
|
||||
|
||||
for (auto &itm : selected_fillers)
|
||||
if (get_bed_index(itm) == get_bed_constraint(itm))
|
||||
result->add_new_item(itm);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace arr2
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // FILLBEDTASKIMPL_HPP
|
||||
@@ -0,0 +1,127 @@
|
||||
#ifndef MULTIPLYSELECTIONTASKIMPL_HPP
|
||||
#define MULTIPLYSELECTIONTASKIMPL_HPP
|
||||
|
||||
#include <arrange-wrapper/Tasks/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
|
||||
Reference in New Issue
Block a user