Add ModelArrange.hpp as extension to Model.hpp, use it for duplicating
Refactored Arrange interface: remove the union based BedShapeHint, replace it with proper function overloads WARN: this commit is only intermediate, it does not compile.
This commit is contained in:
parent
44ca0a6c3d
commit
1bffc2b99b
@ -34,6 +34,7 @@
|
|||||||
#include "libslic3r/Config.hpp"
|
#include "libslic3r/Config.hpp"
|
||||||
#include "libslic3r/Geometry.hpp"
|
#include "libslic3r/Geometry.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/ModelArrange.hpp"
|
||||||
#include "libslic3r/Print.hpp"
|
#include "libslic3r/Print.hpp"
|
||||||
#include "libslic3r/SLAPrint.hpp"
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
@ -53,12 +54,6 @@
|
|||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
|
||||||
PrinterTechnology get_printer_technology(const DynamicConfig &config)
|
|
||||||
{
|
|
||||||
const ConfigOptionEnum<PrinterTechnology> *opt = config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology");
|
|
||||||
return (opt == nullptr) ? ptUnknown : opt->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CLI::run(int argc, char **argv)
|
int CLI::run(int argc, char **argv)
|
||||||
{
|
{
|
||||||
// Switch boost::filesystem to utf8.
|
// Switch boost::filesystem to utf8.
|
||||||
@ -87,12 +82,14 @@ int CLI::run(int argc, char **argv)
|
|||||||
m_extra_config.apply(m_config, true);
|
m_extra_config.apply(m_config, true);
|
||||||
m_extra_config.normalize();
|
m_extra_config.normalize();
|
||||||
|
|
||||||
|
PrinterTechnology printer_technology = Slic3r::printer_technology(m_config);
|
||||||
|
|
||||||
bool start_gui = m_actions.empty() &&
|
bool start_gui = m_actions.empty() &&
|
||||||
// cutting transformations are setting an "export" action.
|
// cutting transformations are setting an "export" action.
|
||||||
std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() &&
|
std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() &&
|
||||||
std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() &&
|
std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() &&
|
||||||
std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end();
|
std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end();
|
||||||
PrinterTechnology printer_technology = get_printer_technology(m_extra_config);
|
|
||||||
const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
|
const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
|
||||||
|
|
||||||
// load config files supplied via --load
|
// load config files supplied via --load
|
||||||
@ -113,7 +110,7 @@ int CLI::run(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
config.normalize();
|
config.normalize();
|
||||||
PrinterTechnology other_printer_technology = get_printer_technology(config);
|
PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
|
||||||
if (printer_technology == ptUnknown) {
|
if (printer_technology == ptUnknown) {
|
||||||
printer_technology = other_printer_technology;
|
printer_technology = other_printer_technology;
|
||||||
} else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
|
} else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
|
||||||
@ -134,7 +131,7 @@ int CLI::run(int argc, char **argv)
|
|||||||
// When loading an AMF or 3MF, config is imported as well, including the printer technology.
|
// When loading an AMF or 3MF, config is imported as well, including the printer technology.
|
||||||
DynamicPrintConfig config;
|
DynamicPrintConfig config;
|
||||||
model = Model::read_from_file(file, &config, true);
|
model = Model::read_from_file(file, &config, true);
|
||||||
PrinterTechnology other_printer_technology = get_printer_technology(config);
|
PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
|
||||||
if (printer_technology == ptUnknown) {
|
if (printer_technology == ptUnknown) {
|
||||||
printer_technology = other_printer_technology;
|
printer_technology = other_printer_technology;
|
||||||
} else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
|
} else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
|
||||||
@ -161,9 +158,6 @@ int CLI::run(int argc, char **argv)
|
|||||||
// Normalizing after importing the 3MFs / AMFs
|
// Normalizing after importing the 3MFs / AMFs
|
||||||
m_print_config.normalize();
|
m_print_config.normalize();
|
||||||
|
|
||||||
if (printer_technology == ptUnknown)
|
|
||||||
printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA;
|
|
||||||
|
|
||||||
// Initialize full print configs for both the FFF and SLA technologies.
|
// Initialize full print configs for both the FFF and SLA technologies.
|
||||||
FullPrintConfig fff_print_config;
|
FullPrintConfig fff_print_config;
|
||||||
SLAFullPrintConfig sla_print_config;
|
SLAFullPrintConfig sla_print_config;
|
||||||
@ -187,10 +181,18 @@ int CLI::run(int argc, char **argv)
|
|||||||
m_print_config.apply(sla_print_config, true);
|
m_print_config.apply(sla_print_config, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
double min_obj_dist = min_object_distance(m_print_config);
|
std::string validity = m_print_config.validate();
|
||||||
|
if (!validity.empty()) {
|
||||||
|
boost::nowide::cerr << "error: " << validity << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Loop through transform options.
|
// Loop through transform options.
|
||||||
bool user_center_specified = false;
|
bool user_center_specified = false;
|
||||||
|
Points bed = get_bed_shape(m_print_config);
|
||||||
|
ArrangeParams arrange_cfg;
|
||||||
|
arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config));
|
||||||
|
|
||||||
for (auto const &opt_key : m_transforms) {
|
for (auto const &opt_key : m_transforms) {
|
||||||
if (opt_key == "merge") {
|
if (opt_key == "merge") {
|
||||||
Model m;
|
Model m;
|
||||||
@ -200,29 +202,33 @@ int CLI::run(int argc, char **argv)
|
|||||||
// Rearrange instances unless --dont-arrange is supplied
|
// Rearrange instances unless --dont-arrange is supplied
|
||||||
if (! m_config.opt_bool("dont_arrange")) {
|
if (! m_config.opt_bool("dont_arrange")) {
|
||||||
m.add_default_instances();
|
m.add_default_instances();
|
||||||
const BoundingBoxf &bb = fff_print_config.bed_shape.values;
|
if (this->has_print_action())
|
||||||
m.arrange_objects(
|
arrange_objects(m, bed, arrange_cfg);
|
||||||
min_obj_dist,
|
else
|
||||||
// If we are going to use the merged model for printing, honor
|
arrange_objects(m, InfiniteBed{}, arrange_cfg);
|
||||||
// the configured print bed for arranging, otherwise do it freely.
|
|
||||||
this->has_print_action() ? &bb : nullptr
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
m_models.clear();
|
m_models.clear();
|
||||||
m_models.emplace_back(std::move(m));
|
m_models.emplace_back(std::move(m));
|
||||||
} else if (opt_key == "duplicate") {
|
} else if (opt_key == "duplicate") {
|
||||||
const BoundingBoxf &bb = fff_print_config.bed_shape.values;
|
|
||||||
for (auto &model : m_models) {
|
for (auto &model : m_models) {
|
||||||
const bool all_objects_have_instances = std::none_of(
|
const bool all_objects_have_instances = std::none_of(
|
||||||
model.objects.begin(), model.objects.end(),
|
model.objects.begin(), model.objects.end(),
|
||||||
[](ModelObject* o){ return o->instances.empty(); }
|
[](ModelObject* o){ return o->instances.empty(); }
|
||||||
);
|
);
|
||||||
if (all_objects_have_instances) {
|
|
||||||
// if all input objects have defined position(s) apply duplication to the whole model
|
int dups = m_config.opt_int("duplicate");
|
||||||
model.duplicate(m_config.opt_int("duplicate"), min_obj_dist, &bb);
|
if (!all_objects_have_instances) model.add_default_instances();
|
||||||
} else {
|
|
||||||
model.add_default_instances();
|
try {
|
||||||
model.duplicate_objects(m_config.opt_int("duplicate"), min_obj_dist, &bb);
|
if (dups > 1) {
|
||||||
|
// if all input objects have defined position(s) apply duplication to the whole model
|
||||||
|
duplicate(model, size_t(dups), bed, arrange_cfg);
|
||||||
|
} else {
|
||||||
|
arrange_objects(model, bed, arrange_cfg);
|
||||||
|
}
|
||||||
|
} catch (std::exception &ex) {
|
||||||
|
boost::nowide::cerr << "error: " << ex.what() << std::endl;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (opt_key == "duplicate_grid") {
|
} else if (opt_key == "duplicate_grid") {
|
||||||
@ -426,11 +432,11 @@ int CLI::run(int argc, char **argv)
|
|||||||
|
|
||||||
PrintBase *print = (printer_technology == ptFFF) ? static_cast<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&sla_print);
|
PrintBase *print = (printer_technology == ptFFF) ? static_cast<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&sla_print);
|
||||||
if (! m_config.opt_bool("dont_arrange")) {
|
if (! m_config.opt_bool("dont_arrange")) {
|
||||||
//FIXME make the min_object_distance configurable.
|
if (user_center_specified) {
|
||||||
model.arrange_objects(min_obj_dist);
|
Vec2d c = m_config.option<ConfigOptionPoint>("center")->value;
|
||||||
model.center_instances_around_point((! user_center_specified && m_print_config.has("bed_shape")) ?
|
arrange_objects(model, InfiniteBed{scaled(c)}, arrange_cfg);
|
||||||
BoundingBoxf(m_print_config.opt<ConfigOptionPoints>("bed_shape")->values).center() :
|
} else
|
||||||
m_config.option<ConfigOptionPoint>("center")->value);
|
arrange_objects(model, bed, arrange_cfg);
|
||||||
}
|
}
|
||||||
if (printer_technology == ptFFF) {
|
if (printer_technology == ptFFF) {
|
||||||
for (auto* mo : model.objects)
|
for (auto* mo : model.objects)
|
||||||
@ -613,6 +619,8 @@ bool CLI::setup(int argc, char **argv)
|
|||||||
set_logging_level(opt_loglevel->value);
|
set_logging_level(opt_loglevel->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string validity = m_config.validate();
|
||||||
|
|
||||||
// Initialize with defaults.
|
// Initialize with defaults.
|
||||||
for (const t_optiondef_map *options : { &cli_actions_config_def.options, &cli_transform_config_def.options, &cli_misc_config_def.options })
|
for (const t_optiondef_map *options : { &cli_actions_config_def.options, &cli_transform_config_def.options, &cli_misc_config_def.options })
|
||||||
for (const std::pair<t_config_option_key, ConfigOptionDef> &optdef : *options)
|
for (const std::pair<t_config_option_key, ConfigOptionDef> &optdef : *options)
|
||||||
@ -620,6 +628,11 @@ bool CLI::setup(int argc, char **argv)
|
|||||||
|
|
||||||
set_data_dir(m_config.opt_string("datadir"));
|
set_data_dir(m_config.opt_string("datadir"));
|
||||||
|
|
||||||
|
if (!validity.empty()) {
|
||||||
|
boost::nowide::cerr << "error: " << validity << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#include "Arrange.hpp"
|
#include "Arrange.hpp"
|
||||||
#include "Geometry.hpp"
|
//#include "Geometry.hpp"
|
||||||
#include "SVG.hpp"
|
#include "SVG.hpp"
|
||||||
#include "MTUtils.hpp"
|
|
||||||
|
|
||||||
#include <libnest2d/backends/clipper/geometries.hpp>
|
#include <libnest2d/backends/clipper/geometries.hpp>
|
||||||
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||||
@ -83,7 +82,7 @@ const double BIG_ITEM_TRESHOLD = 0.02;
|
|||||||
// Fill in the placer algorithm configuration with values carefully chosen for
|
// Fill in the placer algorithm configuration with values carefully chosen for
|
||||||
// Slic3r.
|
// Slic3r.
|
||||||
template<class PConf>
|
template<class PConf>
|
||||||
void fillConfig(PConf& pcfg) {
|
void fill_config(PConf& pcfg) {
|
||||||
|
|
||||||
// Align the arranged pile into the center of the bin
|
// Align the arranged pile into the center of the bin
|
||||||
pcfg.alignment = PConf::Alignment::CENTER;
|
pcfg.alignment = PConf::Alignment::CENTER;
|
||||||
@ -105,7 +104,7 @@ void fillConfig(PConf& pcfg) {
|
|||||||
|
|
||||||
// Apply penalty to object function result. This is used only when alignment
|
// Apply penalty to object function result. This is used only when alignment
|
||||||
// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN)
|
// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN)
|
||||||
double fixed_overfit(const std::tuple<double, Box>& result, const Box &binbb)
|
static double fixed_overfit(const std::tuple<double, Box>& result, const Box &binbb)
|
||||||
{
|
{
|
||||||
double score = std::get<0>(result);
|
double score = std::get<0>(result);
|
||||||
Box pilebb = std::get<1>(result);
|
Box pilebb = std::get<1>(result);
|
||||||
@ -312,7 +311,7 @@ public:
|
|||||||
, m_bin_area(sl::area(bin))
|
, m_bin_area(sl::area(bin))
|
||||||
, m_norm(std::sqrt(m_bin_area))
|
, m_norm(std::sqrt(m_bin_area))
|
||||||
{
|
{
|
||||||
fillConfig(m_pconf);
|
fill_config(m_pconf);
|
||||||
|
|
||||||
// Set up a callback that is called just before arranging starts
|
// Set up a callback that is called just before arranging starts
|
||||||
// This functionality is provided by the Nester class (m_pack).
|
// This functionality is provided by the Nester class (m_pack).
|
||||||
@ -363,6 +362,9 @@ public:
|
|||||||
m_item_count = 0;
|
m_item_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PConfig& config() { return m_pconf; }
|
||||||
|
const PConfig& config() const { return m_pconf; }
|
||||||
|
|
||||||
inline void preload(std::vector<Item>& fixeditems) {
|
inline void preload(std::vector<Item>& fixeditems) {
|
||||||
m_pconf.alignment = PConfig::Alignment::DONT_ALIGN;
|
m_pconf.alignment = PConfig::Alignment::DONT_ALIGN;
|
||||||
auto bb = sl::boundingBox(m_bin);
|
auto bb = sl::boundingBox(m_bin);
|
||||||
@ -438,127 +440,6 @@ std::function<double(const Item &)> AutoArranger<clppr::Polygon>::get_objfn()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Circle to_lnCircle(const CircleBed& circ) {
|
|
||||||
return Circle({circ.center()(0), circ.center()(1)}, circ.radius());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the type of bed geometry from a simple vector of points.
|
|
||||||
void BedShapeHint::reset(BedShapes type)
|
|
||||||
{
|
|
||||||
if (m_type != type) {
|
|
||||||
if (m_type == bsIrregular)
|
|
||||||
m_bed.polygon.Slic3r::Polyline::~Polyline();
|
|
||||||
else if (type == bsIrregular)
|
|
||||||
::new (&m_bed.polygon) Polyline();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
BedShapeHint::BedShapeHint(const Polyline &bed) {
|
|
||||||
auto x = [](const Point& p) { return p(X); };
|
|
||||||
auto y = [](const Point& p) { return p(Y); };
|
|
||||||
|
|
||||||
auto width = [x](const BoundingBox& box) {
|
|
||||||
return x(box.max) - x(box.min);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto height = [y](const BoundingBox& box) {
|
|
||||||
return y(box.max) - y(box.min);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto area = [&width, &height](const BoundingBox& box) {
|
|
||||||
double w = width(box);
|
|
||||||
double h = height(box);
|
|
||||||
return w * h;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto poly_area = [](Polyline p) {
|
|
||||||
Polygon pp; pp.points.reserve(p.points.size() + 1);
|
|
||||||
pp.points = std::move(p.points);
|
|
||||||
pp.points.emplace_back(pp.points.front());
|
|
||||||
return std::abs(pp.area());
|
|
||||||
};
|
|
||||||
|
|
||||||
auto distance_to = [x, y](const Point& p1, const Point& p2) {
|
|
||||||
double dx = x(p2) - x(p1);
|
|
||||||
double dy = y(p2) - y(p1);
|
|
||||||
return std::sqrt(dx*dx + dy*dy);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto bb = bed.bounding_box();
|
|
||||||
|
|
||||||
auto isCircle = [bb, distance_to](const Polyline& polygon) {
|
|
||||||
auto center = bb.center();
|
|
||||||
std::vector<double> vertex_distances;
|
|
||||||
double avg_dist = 0;
|
|
||||||
for (auto pt: polygon.points)
|
|
||||||
{
|
|
||||||
double distance = distance_to(center, pt);
|
|
||||||
vertex_distances.push_back(distance);
|
|
||||||
avg_dist += distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
avg_dist /= vertex_distances.size();
|
|
||||||
|
|
||||||
CircleBed ret(center, avg_dist);
|
|
||||||
for(auto el : vertex_distances)
|
|
||||||
{
|
|
||||||
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
|
|
||||||
ret = CircleBed();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto parea = poly_area(bed);
|
|
||||||
|
|
||||||
if( (1.0 - parea/area(bb)) < 1e-3 ) {
|
|
||||||
m_type = BedShapes::bsBox;
|
|
||||||
m_bed.box = bb;
|
|
||||||
}
|
|
||||||
else if(auto c = isCircle(bed)) {
|
|
||||||
m_type = BedShapes::bsCircle;
|
|
||||||
m_bed.circ = c;
|
|
||||||
} else {
|
|
||||||
assert(m_type != BedShapes::bsIrregular);
|
|
||||||
m_type = BedShapes::bsIrregular;
|
|
||||||
::new (&m_bed.polygon) Polyline(bed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BedShapeHint &BedShapeHint::operator=(BedShapeHint &&cpy)
|
|
||||||
{
|
|
||||||
reset(cpy.m_type);
|
|
||||||
|
|
||||||
switch(m_type) {
|
|
||||||
case bsBox: m_bed.box = std::move(cpy.m_bed.box); break;
|
|
||||||
case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break;
|
|
||||||
case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break;
|
|
||||||
case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break;
|
|
||||||
case bsUnknown: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
BedShapeHint &BedShapeHint::operator=(const BedShapeHint &cpy)
|
|
||||||
{
|
|
||||||
reset(cpy.m_type);
|
|
||||||
|
|
||||||
switch(m_type) {
|
|
||||||
case bsBox: m_bed.box = cpy.m_bed.box; break;
|
|
||||||
case bsCircle: m_bed.circ = cpy.m_bed.circ; break;
|
|
||||||
case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break;
|
|
||||||
case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break;
|
|
||||||
case bsUnknown: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Bin> void remove_large_items(std::vector<Item> &items, Bin &&bin)
|
template<class Bin> void remove_large_items(std::vector<Item> &items, Bin &&bin)
|
||||||
{
|
{
|
||||||
auto it = items.begin();
|
auto it = items.begin();
|
||||||
@ -572,12 +453,12 @@ void _arrange(
|
|||||||
std::vector<Item> & shapes,
|
std::vector<Item> & shapes,
|
||||||
std::vector<Item> & excludes,
|
std::vector<Item> & excludes,
|
||||||
const BinT & bin,
|
const BinT & bin,
|
||||||
coord_t minobjd,
|
const ArrangeParams & params,
|
||||||
std::function<void(unsigned)> progressfn,
|
std::function<void(unsigned)> progressfn,
|
||||||
std::function<bool()> stopfn)
|
std::function<bool()> stopfn)
|
||||||
{
|
{
|
||||||
// Integer ceiling the min distance from the bed perimeters
|
// Integer ceiling the min distance from the bed perimeters
|
||||||
coord_t md = minobjd;
|
coord_t md = params.min_obj_distance;
|
||||||
md = (md % 2) ? md / 2 + 1 : md / 2;
|
md = (md % 2) ? md / 2 + 1 : md / 2;
|
||||||
|
|
||||||
auto corrected_bin = bin;
|
auto corrected_bin = bin;
|
||||||
@ -585,7 +466,10 @@ void _arrange(
|
|||||||
|
|
||||||
AutoArranger<BinT> arranger{corrected_bin, progressfn, stopfn};
|
AutoArranger<BinT> arranger{corrected_bin, progressfn, stopfn};
|
||||||
|
|
||||||
auto infl = coord_t(std::ceil(minobjd / 2.0));
|
arranger.config().accuracy = params.accuracy;
|
||||||
|
arranger.config().parallel = params.parallel;
|
||||||
|
|
||||||
|
auto infl = coord_t(std::ceil(params.min_obj_distance / 2.0));
|
||||||
for (Item& itm : shapes) itm.inflate(infl);
|
for (Item& itm : shapes) itm.inflate(infl);
|
||||||
for (Item& itm : excludes) itm.inflate(infl);
|
for (Item& itm : excludes) itm.inflate(infl);
|
||||||
|
|
||||||
@ -603,44 +487,106 @@ void _arrange(
|
|||||||
for (Item &itm : inp) itm.inflate(-infl);
|
for (Item &itm : inp) itm.inflate(-infl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The final client function for arrangement. A progress indicator and
|
inline Box to_nestbin(const BoundingBox &bb) { return Box{{bb.min(X), bb.min(Y)}, {bb.max(X), bb.max(Y)}};}
|
||||||
// a stop predicate can be also be passed to control the process.
|
inline Circle to_nestbin(const CircleBed &c) { return Circle({c.center()(0), c.center()(1)}, c.radius()); }
|
||||||
void arrange(ArrangePolygons & arrangables,
|
inline clppr::Polygon to_nestbin(const Polygon &p) { return sl::create<clppr::Polygon>(Slic3rMultiPoint_to_ClipperPath(p)); }
|
||||||
const ArrangePolygons & excludes,
|
inline Box to_nestbin(const InfiniteBed &bed) { return Box::infinite({bed.center.x(), bed.center.y()}); }
|
||||||
coord_t min_obj_dist,
|
|
||||||
const BedShapeHint & bedhint,
|
inline coord_t width(const BoundingBox& box) { return box.max.x() - box.min.x(); }
|
||||||
std::function<void(unsigned)> progressind,
|
inline coord_t height(const BoundingBox& box) { return box.max.y() - box.min.y(); }
|
||||||
std::function<bool()> stopcondition)
|
inline double area(const BoundingBox& box) { return double(width(box)) * height(box); }
|
||||||
|
inline double poly_area(const Points &pts) { return std::abs(Polygon::area(pts)); }
|
||||||
|
inline double distance_to(const Point& p1, const Point& p2)
|
||||||
|
{
|
||||||
|
double dx = p2.x() - p1.x();
|
||||||
|
double dy = p2.y() - p1.y();
|
||||||
|
return std::sqrt(dx*dx + dy*dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CircleBed to_circle(const Point ¢er, const Points& points) {
|
||||||
|
std::vector<double> vertex_distances;
|
||||||
|
double avg_dist = 0;
|
||||||
|
|
||||||
|
for (auto pt : points)
|
||||||
|
{
|
||||||
|
double distance = distance_to(center, pt);
|
||||||
|
vertex_distances.push_back(distance);
|
||||||
|
avg_dist += distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
avg_dist /= vertex_distances.size();
|
||||||
|
|
||||||
|
CircleBed ret(center, avg_dist);
|
||||||
|
for(auto el : vertex_distances)
|
||||||
|
{
|
||||||
|
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
|
||||||
|
ret = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Item from Arrangeable
|
||||||
|
static void process_arrangeable(const ArrangePolygon &arrpoly,
|
||||||
|
std::vector<Item> & outp)
|
||||||
|
{
|
||||||
|
Polygon p = arrpoly.poly.contour;
|
||||||
|
const Vec2crd &offs = arrpoly.translation;
|
||||||
|
double rotation = arrpoly.rotation;
|
||||||
|
|
||||||
|
if (p.is_counter_clockwise()) p.reverse();
|
||||||
|
|
||||||
|
clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
|
||||||
|
|
||||||
|
if (!clpath.Contour.empty()) {
|
||||||
|
auto firstp = clpath.Contour.front();
|
||||||
|
clpath.Contour.emplace_back(firstp);
|
||||||
|
}
|
||||||
|
|
||||||
|
outp.emplace_back(std::move(clpath));
|
||||||
|
outp.back().rotation(rotation);
|
||||||
|
outp.back().translation({offs.x(), offs.y()});
|
||||||
|
outp.back().binId(arrpoly.bed_idx);
|
||||||
|
outp.back().priority(arrpoly.priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void arrange(ArrangePolygons & items,
|
||||||
|
const ArrangePolygons &excludes,
|
||||||
|
const Points & bed,
|
||||||
|
const ArrangeParams & params)
|
||||||
|
{
|
||||||
|
if (bed.empty())
|
||||||
|
arrange(items, excludes, InfiniteBed{}, params);
|
||||||
|
else if (bed.size() == 1)
|
||||||
|
arrange(items, excludes, InfiniteBed{bed.front()}, params);
|
||||||
|
else {
|
||||||
|
auto bb = BoundingBox(bed);
|
||||||
|
CircleBed circ = to_circle(bb.center(), bed);
|
||||||
|
auto parea = poly_area(bed);
|
||||||
|
|
||||||
|
if ((1.0 - parea / area(bb)) < 1e-3)
|
||||||
|
arrange(items, excludes, bb, params);
|
||||||
|
else if (!std::isnan(circ.radius()))
|
||||||
|
arrange(items, excludes, circ, params);
|
||||||
|
else
|
||||||
|
arrange(items, excludes, Polygon(bed), params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class BedT>
|
||||||
|
void arrange(ArrangePolygons & arrangables,
|
||||||
|
const ArrangePolygons &excludes,
|
||||||
|
const BedT & bed,
|
||||||
|
const ArrangeParams & params)
|
||||||
{
|
{
|
||||||
namespace clppr = ClipperLib;
|
namespace clppr = ClipperLib;
|
||||||
|
|
||||||
std::vector<Item> items, fixeditems;
|
std::vector<Item> items, fixeditems;
|
||||||
items.reserve(arrangables.size());
|
items.reserve(arrangables.size());
|
||||||
|
|
||||||
// Create Item from Arrangeable
|
|
||||||
auto process_arrangeable = [](const ArrangePolygon &arrpoly,
|
|
||||||
std::vector<Item> & outp)
|
|
||||||
{
|
|
||||||
Polygon p = arrpoly.poly.contour;
|
|
||||||
const Vec2crd &offs = arrpoly.translation;
|
|
||||||
double rotation = arrpoly.rotation;
|
|
||||||
|
|
||||||
if (p.is_counter_clockwise()) p.reverse();
|
|
||||||
|
|
||||||
clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
|
|
||||||
|
|
||||||
if (!clpath.Contour.empty()) {
|
|
||||||
auto firstp = clpath.Contour.front();
|
|
||||||
clpath.Contour.emplace_back(firstp);
|
|
||||||
}
|
|
||||||
|
|
||||||
outp.emplace_back(std::move(clpath));
|
|
||||||
outp.back().rotation(rotation);
|
|
||||||
outp.back().translation({offs.x(), offs.y()});
|
|
||||||
outp.back().binId(arrpoly.bed_idx);
|
|
||||||
outp.back().priority(arrpoly.priority);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (ArrangePolygon &arrangeable : arrangables)
|
for (ArrangePolygon &arrangeable : arrangables)
|
||||||
process_arrangeable(arrangeable, items);
|
process_arrangeable(arrangeable, items);
|
||||||
|
|
||||||
@ -649,45 +595,10 @@ void arrange(ArrangePolygons & arrangables,
|
|||||||
|
|
||||||
for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON));
|
for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON));
|
||||||
|
|
||||||
auto &cfn = stopcondition;
|
auto &cfn = params.stopcondition;
|
||||||
auto &pri = progressind;
|
auto &pri = params.progressind;
|
||||||
|
|
||||||
switch (bedhint.get_type()) {
|
_arrange(items, fixeditems, to_nestbin(bed), params, pri, cfn);
|
||||||
case bsBox: {
|
|
||||||
// Create the arranger for the box shaped bed
|
|
||||||
BoundingBox bbb = bedhint.get_box();
|
|
||||||
Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}};
|
|
||||||
|
|
||||||
_arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case bsCircle: {
|
|
||||||
auto cc = to_lnCircle(bedhint.get_circle());
|
|
||||||
|
|
||||||
_arrange(items, fixeditems, cc, min_obj_dist, pri, cfn);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case bsIrregular: {
|
|
||||||
auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular());
|
|
||||||
auto irrbed = sl::create<clppr::Polygon>(std::move(ctour));
|
|
||||||
BoundingBox polybb(bedhint.get_irregular());
|
|
||||||
|
|
||||||
_arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case bsInfinite: {
|
|
||||||
const InfiniteBed& nobin = bedhint.get_infinite();
|
|
||||||
auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()});
|
|
||||||
|
|
||||||
_arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case bsUnknown: {
|
|
||||||
// We know nothing about the bed, let it be infinite and zero centered
|
|
||||||
_arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(size_t i = 0; i < items.size(); ++i) {
|
for(size_t i = 0; i < items.size(); ++i) {
|
||||||
clppr::IntPoint tr = items[i].translation();
|
clppr::IntPoint tr = items[i].translation();
|
||||||
@ -697,15 +608,10 @@ void arrange(ArrangePolygons & arrangables,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arrange, without the fixed items (excludes)
|
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms);
|
||||||
void arrange(ArrangePolygons & inp,
|
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms);
|
||||||
coord_t min_d,
|
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
||||||
const BedShapeHint & bedhint,
|
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
||||||
std::function<void(unsigned)> prfn,
|
|
||||||
std::function<bool()> stopfn)
|
|
||||||
{
|
|
||||||
arrange(inp, {}, min_d, bedhint, prfn, stopfn);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace arr
|
} // namespace arr
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
#ifndef MODELARRANGE_HPP
|
#ifndef ARRANGE_HPP
|
||||||
#define MODELARRANGE_HPP
|
#define ARRANGE_HPP
|
||||||
|
|
||||||
#include "ExPolygon.hpp"
|
#include "ExPolygon.hpp"
|
||||||
#include "BoundingBox.hpp"
|
#include "BoundingBox.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r { namespace arrangement {
|
||||||
|
|
||||||
namespace arrangement {
|
|
||||||
|
|
||||||
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
|
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
|
||||||
class CircleBed {
|
class CircleBed {
|
||||||
@ -15,96 +13,16 @@ class CircleBed {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
inline CircleBed(): center_(0, 0), radius_(std::nan("")) {}
|
inline CircleBed(): center_(0, 0), radius_(std::nan("")) {}
|
||||||
inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
|
explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
|
||||||
|
|
||||||
inline double radius() const { return radius_; }
|
inline double radius() const { return radius_; }
|
||||||
inline const Point& center() const { return center_; }
|
inline const Point& center() const { return center_; }
|
||||||
inline operator bool() { return !std::isnan(radius_); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Representing an unbounded bed.
|
/// Representing an unbounded bed.
|
||||||
struct InfiniteBed { Point center; };
|
struct InfiniteBed {
|
||||||
|
Point center;
|
||||||
/// Types of print bed shapes.
|
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
|
||||||
enum BedShapes {
|
|
||||||
bsBox,
|
|
||||||
bsCircle,
|
|
||||||
bsIrregular,
|
|
||||||
bsInfinite,
|
|
||||||
bsUnknown
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Info about the print bed for the arrange() function. This is a variant
|
|
||||||
/// holding one of the four shapes a bed can be.
|
|
||||||
class BedShapeHint {
|
|
||||||
BedShapes m_type = BedShapes::bsInfinite;
|
|
||||||
|
|
||||||
// The union neither calls constructors nor destructors of its members.
|
|
||||||
// The only member with non-trivial constructor / destructor is the polygon,
|
|
||||||
// a placement new / delete needs to be called over it.
|
|
||||||
union BedShape_u { // TODO: use variant from cpp17?
|
|
||||||
CircleBed circ;
|
|
||||||
BoundingBox box;
|
|
||||||
Polyline polygon;
|
|
||||||
InfiniteBed infbed{};
|
|
||||||
~BedShape_u() {}
|
|
||||||
BedShape_u() {}
|
|
||||||
} m_bed;
|
|
||||||
|
|
||||||
// Reset the type, allocate m_bed properly
|
|
||||||
void reset(BedShapes type);
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
BedShapeHint(){}
|
|
||||||
|
|
||||||
/// Get a bed shape hint for arrange() from a naked Polyline.
|
|
||||||
explicit BedShapeHint(const Polyline &polyl);
|
|
||||||
explicit BedShapeHint(const BoundingBox &bb)
|
|
||||||
{
|
|
||||||
m_type = bsBox; m_bed.box = bb;
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit BedShapeHint(const CircleBed &c)
|
|
||||||
{
|
|
||||||
m_type = bsCircle; m_bed.circ = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit BedShapeHint(const InfiniteBed &ibed)
|
|
||||||
{
|
|
||||||
m_type = bsInfinite; m_bed.infbed = ibed;
|
|
||||||
}
|
|
||||||
|
|
||||||
~BedShapeHint()
|
|
||||||
{
|
|
||||||
if (m_type == BedShapes::bsIrregular)
|
|
||||||
m_bed.polygon.Slic3r::Polyline::~Polyline();
|
|
||||||
}
|
|
||||||
|
|
||||||
BedShapeHint(const BedShapeHint &cpy) { *this = cpy; }
|
|
||||||
BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); }
|
|
||||||
|
|
||||||
BedShapeHint &operator=(const BedShapeHint &cpy);
|
|
||||||
BedShapeHint& operator=(BedShapeHint &&cpy);
|
|
||||||
|
|
||||||
BedShapes get_type() const { return m_type; }
|
|
||||||
|
|
||||||
const BoundingBox &get_box() const
|
|
||||||
{
|
|
||||||
assert(m_type == bsBox); return m_bed.box;
|
|
||||||
}
|
|
||||||
const CircleBed &get_circle() const
|
|
||||||
{
|
|
||||||
assert(m_type == bsCircle); return m_bed.circ;
|
|
||||||
}
|
|
||||||
const Polyline &get_irregular() const
|
|
||||||
{
|
|
||||||
assert(m_type == bsIrregular); return m_bed.polygon;
|
|
||||||
}
|
|
||||||
const InfiniteBed &get_infinite() const
|
|
||||||
{
|
|
||||||
assert(m_type == bsInfinite); return m_bed.infbed;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A logical bed representing an object not being arranged. Either the arrange
|
/// A logical bed representing an object not being arranged. Either the arrange
|
||||||
@ -125,9 +43,14 @@ struct ArrangePolygon {
|
|||||||
ExPolygon poly; /// The 2D silhouette to be arranged
|
ExPolygon poly; /// The 2D silhouette to be arranged
|
||||||
Vec2crd translation{0, 0}; /// The translation of the poly
|
Vec2crd translation{0, 0}; /// The translation of the poly
|
||||||
double rotation{0.0}; /// The rotation of the poly in radians
|
double rotation{0.0}; /// The rotation of the poly in radians
|
||||||
|
coord_t inflation = 0; /// Arrange with inflated polygon
|
||||||
int bed_idx{UNARRANGED}; /// To which logical bed does poly belong...
|
int bed_idx{UNARRANGED}; /// To which logical bed does poly belong...
|
||||||
int priority{0};
|
int priority{0};
|
||||||
|
|
||||||
|
// If empty, any rotation is allowed (currently unsupported)
|
||||||
|
// If only a zero is there, no rotation is allowed
|
||||||
|
std::vector<double> allowed_rotations = {0.};
|
||||||
|
|
||||||
/// Optional setter function which can store arbitrary data in its closure
|
/// Optional setter function which can store arbitrary data in its closure
|
||||||
std::function<void(const ArrangePolygon&)> setter = nullptr;
|
std::function<void(const ArrangePolygon&)> setter = nullptr;
|
||||||
|
|
||||||
@ -140,6 +63,30 @@ struct ArrangePolygon {
|
|||||||
|
|
||||||
using ArrangePolygons = std::vector<ArrangePolygon>;
|
using ArrangePolygons = std::vector<ArrangePolygon>;
|
||||||
|
|
||||||
|
struct ArrangeParams {
|
||||||
|
|
||||||
|
/// The minimum distance which is allowed for any
|
||||||
|
/// pair of items on the print bed in any direction.
|
||||||
|
coord_t min_obj_distance = 0.;
|
||||||
|
|
||||||
|
/// The accuracy of optimization.
|
||||||
|
/// Goes from 0.0 to 1.0 and scales performance as well
|
||||||
|
float accuracy = 0.65f;
|
||||||
|
|
||||||
|
/// Allow parallel execution.
|
||||||
|
bool parallel = true;
|
||||||
|
|
||||||
|
/// Progress indicator callback called when an object gets packed.
|
||||||
|
/// The unsigned argument is the number of items remaining to pack.
|
||||||
|
std::function<void(unsigned)> progressind;
|
||||||
|
|
||||||
|
/// A predicate returning true if abort is needed.
|
||||||
|
std::function<bool(void)> stopcondition;
|
||||||
|
|
||||||
|
ArrangeParams() = default;
|
||||||
|
explicit ArrangeParams(coord_t md) : min_obj_distance(md) {}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Arranges the input polygons.
|
* \brief Arranges the input polygons.
|
||||||
*
|
*
|
||||||
@ -150,33 +97,23 @@ using ArrangePolygons = std::vector<ArrangePolygon>;
|
|||||||
* \param items Input vector of ArrangePolygons. The transformation, rotation
|
* \param items Input vector of ArrangePolygons. The transformation, rotation
|
||||||
* and bin_idx fields will be changed after the call finished and can be used
|
* and bin_idx fields will be changed after the call finished and can be used
|
||||||
* to apply the result on the input polygon.
|
* to apply the result on the input polygon.
|
||||||
*
|
|
||||||
* \param min_obj_distance The minimum distance which is allowed for any
|
|
||||||
* pair of items on the print bed in any direction.
|
|
||||||
*
|
|
||||||
* \param bedhint Info about the shape and type of the bed.
|
|
||||||
*
|
|
||||||
* \param progressind Progress indicator callback called when
|
|
||||||
* an object gets packed. The unsigned argument is the number of items
|
|
||||||
* remaining to pack.
|
|
||||||
*
|
|
||||||
* \param stopcondition A predicate returning true if abort is needed.
|
|
||||||
*/
|
*/
|
||||||
void arrange(ArrangePolygons & items,
|
template<class TBed> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams ¶ms = {});
|
||||||
coord_t min_obj_distance,
|
|
||||||
const BedShapeHint & bedhint,
|
|
||||||
std::function<void(unsigned)> progressind = nullptr,
|
|
||||||
std::function<bool(void)> stopcondition = nullptr);
|
|
||||||
|
|
||||||
/// Same as the previous, only that it takes unmovable items as an
|
// A dispatch function that determines the bed shape from a set of points.
|
||||||
/// additional argument. Those will be considered as already arranged objects.
|
template<> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Points &bed, const ArrangeParams ¶ms);
|
||||||
void arrange(ArrangePolygons & items,
|
|
||||||
const ArrangePolygons & excludes,
|
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms);
|
||||||
coord_t min_obj_distance,
|
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms);
|
||||||
const BedShapeHint & bedhint,
|
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
||||||
std::function<void(unsigned)> progressind = nullptr,
|
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
||||||
std::function<bool(void)> stopcondition = nullptr);
|
|
||||||
|
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||||
|
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||||
|
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||||
|
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||||
|
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||||
|
|
||||||
|
}} // namespace Slic3r::arrangement
|
||||||
|
|
||||||
} // arr
|
|
||||||
} // Slic3r
|
|
||||||
#endif // MODELARRANGE_HPP
|
#endif // MODELARRANGE_HPP
|
||||||
|
@ -120,6 +120,8 @@ add_library(libslic3r STATIC
|
|||||||
Line.hpp
|
Line.hpp
|
||||||
Model.cpp
|
Model.cpp
|
||||||
Model.hpp
|
Model.hpp
|
||||||
|
ModelArrange.hpp
|
||||||
|
ModelArrange.cpp
|
||||||
CustomGCode.cpp
|
CustomGCode.cpp
|
||||||
CustomGCode.hpp
|
CustomGCode.hpp
|
||||||
Arrange.hpp
|
Arrange.hpp
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
|
#include "ModelArrange.hpp"
|
||||||
#include "Geometry.hpp"
|
#include "Geometry.hpp"
|
||||||
#include "MTUtils.hpp"
|
#include "MTUtils.hpp"
|
||||||
|
|
||||||
@ -355,116 +356,6 @@ TriangleMesh Model::mesh() const
|
|||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out)
|
|
||||||
{
|
|
||||||
if (sizes.empty())
|
|
||||||
// return if the list is empty or the following call to BoundingBoxf constructor will lead to a crash
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// we supply unscaled data to arrange()
|
|
||||||
bool result = Slic3r::Geometry::arrange(
|
|
||||||
sizes.size(), // number of parts
|
|
||||||
BoundingBoxf(sizes).max, // width and height of a single cell
|
|
||||||
dist, // distance between cells
|
|
||||||
bb, // bounding box of the area to fill
|
|
||||||
out // output positions
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!result && bb != nullptr) {
|
|
||||||
// Try to arrange again ignoring bb
|
|
||||||
result = Slic3r::Geometry::arrange(
|
|
||||||
sizes.size(), // number of parts
|
|
||||||
BoundingBoxf(sizes).max, // width and height of a single cell
|
|
||||||
dist, // distance between cells
|
|
||||||
nullptr, // bounding box of the area to fill
|
|
||||||
out // output positions
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* arrange objects preserving their instance count
|
|
||||||
but altering their instance positions */
|
|
||||||
bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
|
|
||||||
{
|
|
||||||
size_t count = 0;
|
|
||||||
for (auto obj : objects) count += obj->instances.size();
|
|
||||||
|
|
||||||
arrangement::ArrangePolygons input;
|
|
||||||
ModelInstancePtrs instances;
|
|
||||||
input.reserve(count);
|
|
||||||
instances.reserve(count);
|
|
||||||
for (ModelObject *mo : objects)
|
|
||||||
for (ModelInstance *minst : mo->instances) {
|
|
||||||
input.emplace_back(minst->get_arrange_polygon());
|
|
||||||
instances.emplace_back(minst);
|
|
||||||
}
|
|
||||||
|
|
||||||
arrangement::BedShapeHint bedhint;
|
|
||||||
coord_t bedwidth = 0;
|
|
||||||
|
|
||||||
if (bb) {
|
|
||||||
bedwidth = scaled(bb->size().x());
|
|
||||||
bedhint = arrangement::BedShapeHint(
|
|
||||||
BoundingBox(scaled(bb->min), scaled(bb->max)));
|
|
||||||
}
|
|
||||||
|
|
||||||
arrangement::arrange(input, scaled(dist), bedhint);
|
|
||||||
|
|
||||||
bool ret = true;
|
|
||||||
coord_t stride = bedwidth + bedwidth / 5;
|
|
||||||
|
|
||||||
for(size_t i = 0; i < input.size(); ++i) {
|
|
||||||
if (input[i].bed_idx != 0) ret = false;
|
|
||||||
if (input[i].bed_idx >= 0) {
|
|
||||||
input[i].translation += Vec2crd{input[i].bed_idx * stride, 0};
|
|
||||||
instances[i]->apply_arrange_result(input[i].translation.cast<double>(),
|
|
||||||
input[i].rotation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duplicate the entire model preserving instance relative positions.
|
|
||||||
void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb)
|
|
||||||
{
|
|
||||||
Pointfs model_sizes(copies_num-1, to_2d(this->bounding_box().size()));
|
|
||||||
Pointfs positions;
|
|
||||||
if (! _arrange(model_sizes, dist, bb, positions))
|
|
||||||
throw std::invalid_argument("Cannot duplicate part as the resulting objects would not fit on the print bed.\n");
|
|
||||||
|
|
||||||
// note that this will leave the object count unaltered
|
|
||||||
|
|
||||||
for (ModelObject *o : this->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 (const Vec2d &pos : positions) {
|
|
||||||
ModelInstance *instance = o->add_instance(*i);
|
|
||||||
instance->set_offset(instance->get_offset() + Vec3d(pos(0), pos(1), 0.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o->invalidate_bounding_box();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* this will append more instances to each object
|
|
||||||
and then automatically rearrange everything */
|
|
||||||
void Model::duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb)
|
|
||||||
{
|
|
||||||
for (ModelObject *o : this->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);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->arrange_objects(dist, bb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist)
|
void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist)
|
||||||
{
|
{
|
||||||
if (this->objects.size() > 1) throw "Grid duplication is not supported with multiple objects";
|
if (this->objects.size() > 1) throw "Grid duplication is not supported with multiple objects";
|
||||||
@ -1991,6 +1882,7 @@ void check_model_ids_equal(const Model &model1, const Model &model2)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* NDEBUG */
|
#endif /* NDEBUG */
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -802,11 +802,9 @@ public:
|
|||||||
bool center_instances_around_point(const Vec2d &point);
|
bool center_instances_around_point(const Vec2d &point);
|
||||||
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
|
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
|
||||||
TriangleMesh mesh() const;
|
TriangleMesh mesh() const;
|
||||||
bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL);
|
|
||||||
// Croaks if the duplicated objects do not fit the print bed.
|
// Croaks if the duplicated objects do not fit the print bed.
|
||||||
void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
|
void duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
|
||||||
void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
|
|
||||||
void duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
|
|
||||||
|
|
||||||
bool looks_like_multipart_object() const;
|
bool looks_like_multipart_object() const;
|
||||||
void convert_multipart_object(unsigned int max_extruders);
|
void convert_multipart_object(unsigned int max_extruders);
|
||||||
@ -822,6 +820,7 @@ public:
|
|||||||
std::string propose_export_file_name_and_path(const std::string &new_extension) const;
|
std::string propose_export_file_name_and_path(const std::string &new_extension) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); };
|
explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); };
|
||||||
void assign_new_unique_ids_recursive();
|
void assign_new_unique_ids_recursive();
|
||||||
void update_links_bottom_up_recursive();
|
void update_links_bottom_up_recursive();
|
||||||
@ -831,7 +830,7 @@ private:
|
|||||||
template<class Archive> void serialize(Archive &ar) {
|
template<class Archive> void serialize(Archive &ar) {
|
||||||
Internal::StaticSerializationWrapper<ModelWipeTower> wipe_tower_wrapper(wipe_tower);
|
Internal::StaticSerializationWrapper<ModelWipeTower> wipe_tower_wrapper(wipe_tower);
|
||||||
ar(materials, objects, wipe_tower_wrapper);
|
ar(materials, objects, wipe_tower_wrapper);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
|
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
|
||||||
|
83
src/libslic3r/ModelArrange.cpp
Normal file
83
src/libslic3r/ModelArrange.cpp
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#include "ModelArrange.hpp"
|
||||||
|
#include "MTUtils.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
arrangement::ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
for (auto obj : model.objects) count += obj->instances.size();
|
||||||
|
|
||||||
|
ArrangePolygons input;
|
||||||
|
input.reserve(count);
|
||||||
|
instances.clear(); instances.reserve(count);
|
||||||
|
for (ModelObject *mo : model.objects)
|
||||||
|
for (ModelInstance *minst : mo->instances) {
|
||||||
|
input.emplace_back(minst->get_arrange_polygon());
|
||||||
|
instances.emplace_back(minst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool apply_arrange_polys(ArrangePolygons &input, ModelInstancePtrs &instances, VirtualBedFn vfn)
|
||||||
|
{
|
||||||
|
bool ret = true;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < input.size(); ++i) {
|
||||||
|
if (input[i].bed_idx != 0) { ret = false; if (vfn) vfn(input[i]); }
|
||||||
|
if (input[i].bed_idx >= 0)
|
||||||
|
instances[i]->apply_arrange_result(input[i].translation,
|
||||||
|
input[i].rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Slic3r::arrangement::ArrangePolygon get_arrange_poly(const Model &model)
|
||||||
|
{
|
||||||
|
ArrangePolygon ap;
|
||||||
|
Points &apts = ap.poly.contour.points;
|
||||||
|
for (const ModelObject *mo : model.objects)
|
||||||
|
for (const ModelInstance *minst : mo->instances) {
|
||||||
|
ArrangePolygon obj_ap = minst->get_arrange_polygon();
|
||||||
|
ap.poly.contour.rotate(obj_ap.rotation);
|
||||||
|
ap.poly.contour.translate(obj_ap.translation.x(), obj_ap.translation.y());
|
||||||
|
const Points &pts = obj_ap.poly.contour.points;
|
||||||
|
std::copy(pts.begin(), pts.end(), std::back_inserter(apts));
|
||||||
|
}
|
||||||
|
|
||||||
|
apts = Geometry::convex_hull(apts);
|
||||||
|
return ap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void duplicate(Model &model, Slic3r::arrangement::ArrangePolygons &copies, VirtualBedFn vfn)
|
||||||
|
{
|
||||||
|
for (ModelObject *o : model.objects) {
|
||||||
|
// make a copy of the pointers in order to avoid recursion when appending their copies
|
||||||
|
ModelInstancePtrs instances = o->instances;
|
||||||
|
o->instances.clear();
|
||||||
|
for (const ModelInstance *i : instances) {
|
||||||
|
for (arrangement::ArrangePolygon &ap : copies) {
|
||||||
|
if (ap.bed_idx != 0) vfn(ap);
|
||||||
|
ModelInstance *instance = o->add_instance(*i);
|
||||||
|
Vec2d pos = unscale(ap.translation);
|
||||||
|
instance->set_offset(instance->get_offset() + to_3d(pos, 0.));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o->invalidate_bounding_box();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void duplicate_objects(Model &model, size_t copies_num)
|
||||||
|
{
|
||||||
|
for (ModelObject *o : model.objects) {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
68
src/libslic3r/ModelArrange.hpp
Normal file
68
src/libslic3r/ModelArrange.hpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#ifndef MODELARRANGE_HPP
|
||||||
|
#define MODELARRANGE_HPP
|
||||||
|
|
||||||
|
#include <libslic3r/Model.hpp>
|
||||||
|
#include <libslic3r/Arrange.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
using arrangement::ArrangePolygon;
|
||||||
|
using arrangement::ArrangePolygons;
|
||||||
|
using arrangement::ArrangeParams;
|
||||||
|
using arrangement::InfiniteBed;
|
||||||
|
using arrangement::CircleBed;
|
||||||
|
|
||||||
|
// Do something with ArrangePolygons in virtual beds
|
||||||
|
using VirtualBedFn = std::function<void(arrangement::ArrangePolygon&)>;
|
||||||
|
|
||||||
|
[[noreturn]] inline void throw_if_out_of_bed(arrangement::ArrangePolygon&)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Objects could not fit on the bed");
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances);
|
||||||
|
ArrangePolygon get_arrange_poly(const Model &model);
|
||||||
|
bool apply_arrange_polys(ArrangePolygons &polys, ModelInstancePtrs &instances, VirtualBedFn);
|
||||||
|
|
||||||
|
void duplicate(Model &model, ArrangePolygons &copies, VirtualBedFn);
|
||||||
|
void duplicate_objects(Model &model, size_t copies_num);
|
||||||
|
|
||||||
|
template<class TBed>
|
||||||
|
bool arrange_objects(Model & model,
|
||||||
|
const TBed & bed,
|
||||||
|
const ArrangeParams ¶ms,
|
||||||
|
VirtualBedFn vfn = throw_if_out_of_bed)
|
||||||
|
{
|
||||||
|
ModelInstancePtrs instances;
|
||||||
|
auto&& input = get_arrange_polys(model, instances);
|
||||||
|
arrangement::arrange(input, bed, params);
|
||||||
|
|
||||||
|
return apply_arrange_polys(input, instances, vfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class TBed>
|
||||||
|
void duplicate(Model & model,
|
||||||
|
size_t copies_num,
|
||||||
|
const TBed & bed,
|
||||||
|
const ArrangeParams ¶ms,
|
||||||
|
VirtualBedFn vfn = throw_if_out_of_bed)
|
||||||
|
{
|
||||||
|
ArrangePolygons copies(copies_num, get_arrange_poly(model));
|
||||||
|
arrangement::arrange(copies, bed, params);
|
||||||
|
duplicate(model, copies, vfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class TBed>
|
||||||
|
void duplicate_objects(Model & model,
|
||||||
|
size_t copies_num,
|
||||||
|
const TBed & bed,
|
||||||
|
const ArrangeParams ¶ms,
|
||||||
|
VirtualBedFn vfn = throw_if_out_of_bed)
|
||||||
|
{
|
||||||
|
duplicate_objects(model, copies_num);
|
||||||
|
arrange_objects(model, bed, params, vfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MODELARRANGE_HPP
|
@ -48,7 +48,7 @@ int64_t Polygon::area2x() const
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
double Polygon::area() const
|
double Polygon::area(const Points &points)
|
||||||
{
|
{
|
||||||
size_t n = points.size();
|
size_t n = points.size();
|
||||||
if (n < 3)
|
if (n < 3)
|
||||||
@ -62,6 +62,11 @@ double Polygon::area() const
|
|||||||
return 0.5 * a;
|
return 0.5 * a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double Polygon::area() const
|
||||||
|
{
|
||||||
|
return Polygon::area(points);
|
||||||
|
}
|
||||||
|
|
||||||
bool Polygon::is_counter_clockwise() const
|
bool Polygon::is_counter_clockwise() const
|
||||||
{
|
{
|
||||||
return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this));
|
return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this));
|
||||||
|
@ -22,6 +22,7 @@ public:
|
|||||||
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
|
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
|
||||||
|
|
||||||
Polygon() {}
|
Polygon() {}
|
||||||
|
virtual ~Polygon() = default;
|
||||||
explicit Polygon(const Points &points) : MultiPoint(points) {}
|
explicit Polygon(const Points &points) : MultiPoint(points) {}
|
||||||
Polygon(std::initializer_list<Point> points) : MultiPoint(points) {}
|
Polygon(std::initializer_list<Point> points) : MultiPoint(points) {}
|
||||||
Polygon(const Polygon &other) : MultiPoint(other.points) {}
|
Polygon(const Polygon &other) : MultiPoint(other.points) {}
|
||||||
@ -47,6 +48,7 @@ public:
|
|||||||
Polyline split_at_first_point() const { return this->split_at_index(0); }
|
Polyline split_at_first_point() const { return this->split_at_index(0); }
|
||||||
Points equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); }
|
Points equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); }
|
||||||
|
|
||||||
|
static double area(const Points &pts);
|
||||||
double area() const;
|
double area() const;
|
||||||
bool is_counter_clockwise() const;
|
bool is_counter_clockwise() const;
|
||||||
bool is_clockwise() const;
|
bool is_clockwise() const;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include <boost/nowide/cstdio.hpp>
|
#include <boost/nowide/cstdio.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <libslic3r/ModelArrange.hpp>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@ -167,8 +168,7 @@ void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r
|
|||||||
object->add_volume(std::move(t));
|
object->add_volume(std::move(t));
|
||||||
object->add_instance();
|
object->add_instance();
|
||||||
}
|
}
|
||||||
model.arrange_objects(min_object_distance(config));
|
arrange_objects(model, InfiniteBed{}, ArrangeParams{ scaled(min_object_distance(config))});
|
||||||
model.center_instances_around_point(Slic3r::Vec2d(100, 100));
|
|
||||||
for (ModelObject *mo : model.objects) {
|
for (ModelObject *mo : model.objects) {
|
||||||
mo->ensure_on_bed();
|
mo->ensure_on_bed();
|
||||||
print.auto_assign_extruders(mo);
|
print.auto_assign_extruders(mo);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/ModelArrange.hpp"
|
||||||
|
|
||||||
#include <boost/nowide/cstdio.hpp>
|
#include <boost/nowide/cstdio.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
@ -41,8 +42,7 @@ SCENARIO("Model construction", "[Model]") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
model_object->add_instance();
|
model_object->add_instance();
|
||||||
model.arrange_objects(min_object_distance(config));
|
arrange_objects(model, InfiniteBed{scaled(Vec2d(100, 100))}, ArrangeParams{scaled(min_object_distance(config))});
|
||||||
model.center_instances_around_point(Slic3r::Vec2d(100, 100));
|
|
||||||
model_object->ensure_on_bed();
|
model_object->ensure_on_bed();
|
||||||
print.auto_assign_extruders(model_object);
|
print.auto_assign_extruders(model_object);
|
||||||
THEN("Print works?") {
|
THEN("Print works?") {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
%{
|
%{
|
||||||
#include <xsinit.h>
|
#include <xsinit.h>
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/ModelArrange.hpp"
|
||||||
#include "libslic3r/Print.hpp"
|
#include "libslic3r/Print.hpp"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
#include "libslic3r/Slicing.hpp"
|
#include "libslic3r/Slicing.hpp"
|
||||||
@ -80,9 +81,9 @@
|
|||||||
ModelObjectPtrs* objects()
|
ModelObjectPtrs* objects()
|
||||||
%code%{ RETVAL = &THIS->objects; %};
|
%code%{ RETVAL = &THIS->objects; %};
|
||||||
|
|
||||||
bool arrange_objects(double dist, BoundingBoxf* bb = NULL);
|
bool arrange_objects(double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) arrange_objects(*THIS, scaled(*bb), ap); else arrange_objects(*THIS, InfiniteBed{}, ap); %};
|
||||||
void duplicate(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL);
|
void duplicate(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) duplicate(*THIS, copies_num, scaled(*bb), ap); else duplicate(*THIS, copies_num, InfiniteBed{}, ap); %};
|
||||||
void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL);
|
void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) duplicate_objects(*THIS, copies_num, scaled(*bb), ap); else duplicate_objects(*THIS, copies_num, InfiniteBed{}, ap); %};
|
||||||
void duplicate_objects_grid(unsigned int x, unsigned int y, double dist);
|
void duplicate_objects_grid(unsigned int x, unsigned int y, double dist);
|
||||||
|
|
||||||
bool looks_like_multipart_object() const;
|
bool looks_like_multipart_object() const;
|
||||||
|
Loading…
Reference in New Issue
Block a user