Merge branch 'lm_multiple_beds' (SPE-2506)

This commit is contained in:
Lukas Matena 2024-11-20 16:04:40 +01:00
commit 02e2c8108c
130 changed files with 2993 additions and 3024 deletions

View File

@ -208,6 +208,8 @@ namespace ImGui
const wchar_t RemoveTick = 0x280F;
const wchar_t RemoveTickHovered = 0x2810;
// icon for multiple beds
const wchar_t SliceAllBtnIcon = 0x2811;
// void MyFunction(const char* name, const MyMatrix44& v);
}

View File

@ -1,23 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
<g id="arrange">
<g>
<path fill="#FFFFFF" d="M120,122.5H8c-1.38,0-2.5-1.12-2.5-2.5V8c0-1.38,1.12-2.5,2.5-2.5h112c1.38,0,2.5,1.12,2.5,2.5v112
C122.5,121.38,121.38,122.5,120,122.5z M10.5,117.5h107v-107h-107V117.5z"/>
<svg
version="1.0"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 128 128"
enable-background="new 0 0 128 128"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs22" />
<g
id="arrange">
<g
id="g4">
<path
fill="#ED6B21"
d="M120,122.5H8c-1.38,0-2.5-1.12-2.5-2.5V8c0-1.38,1.12-2.5,2.5-2.5h112c1.38,0,2.5,1.12,2.5,2.5v112 C122.5,121.38,121.38,122.5,120,122.5z M10.5,117.5h107v-107h-107V117.5z"
id="path2" />
</g>
<g>
<path fill="#ED6B21" d="M104,58.5H24c-1.38,0-2.5-1.12-2.5-2.5V24c0-1.38,1.12-2.5,2.5-2.5h80c1.38,0,2.5,1.12,2.5,2.5v32
C106.5,57.38,105.38,58.5,104,58.5z M26.5,53.5h75v-27h-75V53.5z"/>
<g
id="g8">
<path
fill="#FFFFFF"
d="M104,58.5H24c-1.38,0-2.5-1.12-2.5-2.5V24c0-1.38,1.12-2.5,2.5-2.5h80c1.38,0,2.5,1.12,2.5,2.5v32 C106.5,57.38,105.38,58.5,104,58.5z M26.5,53.5h75v-27h-75V53.5z"
id="path6" />
</g>
<g>
<path fill="#ED6B21" d="M48,106.5H24c-1.38,0-2.5-1.12-2.5-2.5V72c0-1.38,1.12-2.5,2.5-2.5h24c1.38,0,2.5,1.12,2.5,2.5v32
C50.5,105.38,49.38,106.5,48,106.5z M26.5,101.5h19v-27h-19V101.5z"/>
<g
id="g12">
<path
fill="#FFFFFF"
d="M48,106.5H24c-1.38,0-2.5-1.12-2.5-2.5V72c0-1.38,1.12-2.5,2.5-2.5h24c1.38,0,2.5,1.12,2.5,2.5v32 C50.5,105.38,49.38,106.5,48,106.5z M26.5,101.5h19v-27h-19V101.5z"
id="path10" />
</g>
<g>
<path fill="#ED6B21" d="M104,106.5H64c-1.38,0-2.5-1.12-2.5-2.5V72c0-1.38,1.12-2.5,2.5-2.5h40c1.38,0,2.5,1.12,2.5,2.5v32
C106.5,105.38,105.38,106.5,104,106.5z M66.5,101.5h35v-27h-35V101.5z"/>
<g
id="g16">
<path
fill="#FFFFFF"
d="M104,106.5H64c-1.38,0-2.5-1.12-2.5-2.5V72c0-1.38,1.12-2.5,2.5-2.5h40c1.38,0,2.5,1.12,2.5,2.5v32 C106.5,105.38,105.38,106.5,104,106.5z M66.5,101.5h35v-27h-35V101.5z"
id="path14" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" clip-rule="evenodd" image-rendering="optimizeQuality" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" viewBox="0 0 128 128">
<path fill="#ED6B21" fill-rule="nonzero" d="M107.76 57.98H20.24c-1.51 0-2.74-1.22-2.74-2.73V20.24c0-1.51 1.23-2.74 2.74-2.74h87.52c1.51 0 2.74 1.23 2.74 2.74v35.01c0 1.51-1.23 2.73-2.74 2.73zm-84.79-5.47h82.06V22.97H22.97v29.54z"/>
<path fill="#FFFFFF" d="M2.73 0H16v5.47H5.47V16H0V2.73C0 1.23 1.23 0 2.73 0zM112 0h13.27c1.5 0 2.73 1.23 2.73 2.73V16h-5.47V5.47H112V0zm16 112v13.27c0 1.5-1.23 2.73-2.73 2.73H112v-5.47h10.53V112H128zM16 128H2.73A2.74 2.74 0 0 1 0 125.27V112h5.47v10.53H16V128z"/>
<path fill="#FFFFFF" fill-rule="nonzero" d="M46.5 110.5H20.24c-1.51 0-2.74-1.23-2.74-2.74V72.75c0-1.51 1.23-2.73 2.74-2.73H46.5c1.51 0 2.73 1.22 2.73 2.73v35.01c0 1.51-1.22 2.74-2.73 2.74zm-23.53-5.47h20.79V75.49H22.97v29.54zM107.76 110.5H64c-1.51 0-2.74-1.23-2.74-2.74V72.75c0-1.51 1.23-2.73 2.74-2.73h43.76c1.51 0 2.74 1.22 2.74 2.73v35.01c0 1.51-1.23 2.74-2.74 2.74zm-41.02-5.47h38.29V75.49H66.74v29.54z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1 0 16 16">
<path d="M 2 2 L 4 4 L 6 1 M 2 7 L 4 9 L 6 6 M 2 12 L 4 14 L 6 11 M 7 12 L 9 14 L 11 11 M 12 12 L 14 14 L 16 11 M 7 7 L 9 9 L 11 6 M 12 7 L 14 9 L 16 6" stroke="#ED6B21" stroke-width="0.95" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 319 B

View File

@ -20,6 +20,8 @@ endif ()
if (SLIC3R_GUI)
add_subdirectory(libvgcode)
add_subdirectory(slic3r-arrange)
add_subdirectory(slic3r-arrange-wrapper)
if(WIN32)
message(STATUS "WXWIN environment set to: $ENV{WXWIN}")
@ -109,7 +111,7 @@ if (NOT WIN32 AND NOT APPLE)
set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer")
endif ()
target_link_libraries(PrusaSlicer libslic3r libcereal)
target_link_libraries(PrusaSlicer libslic3r libcereal slic3r-arrange-wrapper)
if (APPLE)
# add_compile_options(-stdlib=libc++)

View File

@ -48,7 +48,7 @@
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/CutUtils.hpp"
#include "libslic3r/ModelArrange.hpp"
#include <arrange-wrapper/ModelArrange.hpp>
#include "libslic3r/Platform.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
@ -63,6 +63,7 @@
#include "libslic3r/BlacklistedLibraryCheck.hpp"
#include "libslic3r/ProfilesSharingUtils.hpp"
#include "libslic3r/Utils/DirectoriesUtils.hpp"
#include "libslic3r/MultipleBeds.hpp"
#include "PrusaSlicer.hpp"
@ -388,7 +389,9 @@ int CLI::run(int argc, char **argv)
// Loop through transform options.
bool user_center_specified = false;
arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(m_print_config));
const Vec2crd gap{s_multiple_beds.get_bed_gap()};
arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(m_print_config), gap);
arr2::ArrangeSettings arrange_cfg;
arrange_cfg.set_distance_from_objects(min_object_distance(m_print_config));

View File

@ -1,780 +0,0 @@
///|/ Copyright (c) Prusa Research 2018 - 2023 Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Arrange.hpp"
#include "BoundingBox.hpp"
#include <libnest2d/backends/libslic3r/geometries.hpp>
#include <libnest2d/optimizers/nlopt/subplex.hpp>
#include <libnest2d/placers/nfpplacer.hpp>
#include <libnest2d/selections/firstfit.hpp>
#include <libnest2d/utils/rotcalipers.hpp>
#include <numeric>
#include <ClipperUtils.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <boost/container/small_vector.hpp>
#if defined(_MSC_VER) && defined(__clang__)
#define BOOST_NO_CXX17_HDR_STRING_VIEW
#endif
#include <boost/multiprecision/integer.hpp>
#include <boost/rational.hpp>
namespace libnest2d {
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
using LargeInt = __int128;
#else
using LargeInt = boost::multiprecision::int128_t;
template<> struct _NumTag<LargeInt>
{
using Type = ScalarTag;
};
#endif
template<class T> struct _NumTag<boost::rational<T>>
{
using Type = RationalTag;
};
namespace nfp {
template<class S> struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
{
NfpResult<S> operator()(const S &sh, const S &other)
{
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
}
};
} // namespace nfp
} // namespace libnest2d
namespace Slic3r {
template<class Tout = double, class = FloatingOnly<Tout>, int...EigenArgs>
inline constexpr Eigen::Matrix<Tout, 2, EigenArgs...> unscaled(
const Slic3r::ClipperLib::IntPoint &v) noexcept
{
return Eigen::Matrix<Tout, 2, EigenArgs...>{unscaled<Tout>(v.x()),
unscaled<Tout>(v.y())};
}
namespace arrangement {
using namespace libnest2d;
// Get the libnest2d types for clipper backend
using Item = _Item<ExPolygon>;
using Box = _Box<Point>;
using Circle = _Circle<Point>;
using Segment = _Segment<Point>;
using MultiPolygon = ExPolygons;
// Summon the spatial indexing facilities from boost
namespace bgi = boost::geometry::index;
using SpatElement = std::pair<Box, unsigned>;
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
// A coefficient used in separating bigger items and smaller items.
const double BIG_ITEM_TRESHOLD = 0.02;
// Fill in the placer algorithm configuration with values carefully chosen for
// Slic3r.
template<class PConf>
void fill_config(PConf& pcfg, const ArrangeParams &params) {
// Align the arranged pile into the center of the bin
switch (params.alignment) {
case Pivots::Center: pcfg.alignment = PConf::Alignment::CENTER; break;
case Pivots::BottomLeft: pcfg.alignment = PConf::Alignment::BOTTOM_LEFT; break;
case Pivots::BottomRight: pcfg.alignment = PConf::Alignment::BOTTOM_RIGHT; break;
case Pivots::TopLeft: pcfg.alignment = PConf::Alignment::TOP_LEFT; break;
case Pivots::TopRight: pcfg.alignment = PConf::Alignment::TOP_RIGHT; break;
}
// Start placing the items from the center of the print bed
pcfg.starting_point = PConf::Alignment::CENTER;
// TODO cannot use rotations until multiple objects of same geometry can
// handle different rotations.
if (params.allow_rotations)
pcfg.rotations = {0., PI / 2., PI, 3. * PI / 2. };
else
pcfg.rotations = {0.};
// The accuracy of optimization.
// Goes from 0.0 to 1.0 and scales performance as well
pcfg.accuracy = params.accuracy;
// Allow parallel execution.
pcfg.parallel = params.parallel;
}
// Apply penalty to object function result. This is used only when alignment
// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN)
// Also, this will only work well for Box shaped beds.
static double fixed_overfit(const std::tuple<double, Box>& result, const Box &binbb)
{
double score = std::get<0>(result);
Box pilebb = std::get<1>(result);
Box fullbb = sl::boundingBox(pilebb, binbb);
auto diff = double(fullbb.area()) - binbb.area();
if(diff > 0) score += diff;
return score;
}
// A class encapsulating the libnest2d Nester class and extending it with other
// management and spatial index structures for acceleration.
template<class TBin>
class AutoArranger {
public:
// Useful type shortcuts...
using Placer = typename placers::_NofitPolyPlacer<ExPolygon, TBin>;
using Selector = selections::_FirstFitSelection<ExPolygon>;
using Packer = _Nester<Placer, Selector>;
using PConfig = typename Packer::PlacementConfig;
using Distance = TCoord<PointImpl>;
protected:
Packer m_pck;
PConfig m_pconf; // Placement configuration
TBin m_bin;
double m_bin_area;
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
#pragma warning(disable: 4267)
#endif
SpatIndex m_rtree; // spatial index for the normal (bigger) objects
SpatIndex m_smallsrtree; // spatial index for only the smaller items
#ifdef _MSC_VER
#pragma warning(pop)
#endif
double m_norm; // A coefficient to scale distances
MultiPolygon m_merged_pile; // The already merged pile (vector of items)
Box m_pilebb; // The bounding box of the merged pile.
ItemGroup m_remaining; // Remaining items
ItemGroup m_items; // allready packed items
size_t m_item_count = 0; // Number of all items to be packed
template<class T> ArithmeticOnly<T, double> norm(T val)
{
return double(val) / m_norm;
}
// This is "the" object function which is evaluated many times for each
// vertex (decimated with the accuracy parameter) of each object.
// Therefore it is upmost crucial for this function to be as efficient
// as it possibly can be but at the same time, it has to provide
// reasonable results.
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
objfunc(const Item &item, const Point &bincenter)
{
const double bin_area = m_bin_area;
const SpatIndex& spatindex = m_rtree;
const SpatIndex& smalls_spatindex = m_smallsrtree;
// We will treat big items (compared to the print bed) differently
auto isBig = [bin_area](double a) {
return a/bin_area > BIG_ITEM_TRESHOLD ;
};
// Candidate item bounding box
auto ibb = item.boundingBox();
// Calculate the full bounding box of the pile with the candidate item
auto fullbb = sl::boundingBox(m_pilebb, ibb);
// The bounding box of the big items (they will accumulate in the center
// of the pile
Box bigbb;
if(spatindex.empty()) bigbb = fullbb;
else {
auto boostbb = spatindex.bounds();
boost::geometry::convert(boostbb, bigbb);
}
// Will hold the resulting score
double score = 0;
// Density is the pack density: how big is the arranged pile
double density = 0;
// Distinction of cases for the arrangement scene
enum e_cases {
// This branch is for big items in a mixed (big and small) scene
// OR for all items in a small-only scene.
BIG_ITEM,
// This branch is for the last big item in a mixed scene
LAST_BIG_ITEM,
// For small items in a mixed scene.
SMALL_ITEM
} compute_case;
bool bigitems = isBig(item.area()) || spatindex.empty();
if(bigitems && !m_remaining.empty()) compute_case = BIG_ITEM;
else if (bigitems && m_remaining.empty()) compute_case = LAST_BIG_ITEM;
else compute_case = SMALL_ITEM;
switch (compute_case) {
case BIG_ITEM: {
const Point& minc = ibb.minCorner(); // bottom left corner
const Point& maxc = ibb.maxCorner(); // top right corner
// top left and bottom right corners
Point top_left{getX(minc), getY(maxc)};
Point bottom_right{getX(maxc), getY(minc)};
// Now the distance of the gravity center will be calculated to the
// five anchor points and the smallest will be chosen.
std::array<double, 5> dists;
auto cc = fullbb.center(); // The gravity center
dists[0] = pl::distance(minc, cc);
dists[1] = pl::distance(maxc, cc);
dists[2] = pl::distance(ibb.center(), cc);
dists[3] = pl::distance(top_left, cc);
dists[4] = pl::distance(bottom_right, cc);
// The smalles distance from the arranged pile center:
double dist = norm(*(std::min_element(dists.begin(), dists.end())));
double bindist = norm(pl::distance(ibb.center(), bincenter));
dist = 0.8 * dist + 0.2 * bindist;
// Prepare a variable for the alignment score.
// This will indicate: how well is the candidate item
// aligned with its neighbors. We will check the alignment
// with all neighbors and return the score for the best
// alignment. So it is enough for the candidate to be
// aligned with only one item.
auto alignment_score = 1.0;
auto query = bgi::intersects(ibb);
auto& index = isBig(item.area()) ? spatindex : smalls_spatindex;
// Query the spatial index for the neighbors
boost::container::small_vector<SpatElement, 100> result;
result.reserve(index.size());
index.query(query, std::back_inserter(result));
// now get the score for the best alignment
for(auto& e : result) {
auto idx = e.second;
Item& p = m_items[idx];
auto parea = p.area();
if(std::abs(1.0 - parea/item.area()) < 1e-6) {
auto bb = sl::boundingBox(p.boundingBox(), ibb);
auto bbarea = bb.area();
auto ascore = 1.0 - (item.area() + parea)/bbarea;
if(ascore < alignment_score) alignment_score = ascore;
}
}
density = std::sqrt(norm(fullbb.width()) * norm(fullbb.height()));
double R = double(m_remaining.size()) / m_item_count;
// The final mix of the score is the balance between the
// distance from the full pile center, the pack density and
// the alignment with the neighbors
if (result.empty())
score = 0.50 * dist + 0.50 * density;
else
// Let the density matter more when fewer objects remain
score = 0.50 * dist + (1.0 - R) * 0.20 * density +
0.30 * alignment_score;
break;
}
case LAST_BIG_ITEM: {
score = norm(pl::distance(ibb.center(), m_pilebb.center()));
break;
}
case SMALL_ITEM: {
// Here there are the small items that should be placed around the
// already processed bigger items.
// No need to play around with the anchor points, the center will be
// just fine for small items
score = norm(pl::distance(ibb.center(), bigbb.center()));
break;
}
}
return std::make_tuple(score, fullbb);
}
std::function<double(const Item&)> get_objfn();
public:
AutoArranger(const TBin & bin,
const ArrangeParams &params,
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond)
: m_pck(bin, params.min_obj_distance)
, m_bin(bin)
, m_bin_area(sl::area(bin))
, m_norm(std::sqrt(m_bin_area))
{
fill_config(m_pconf, params);
// Set up a callback that is called just before arranging starts
// This functionality is provided by the Nester class (m_pack).
m_pconf.before_packing =
[this](const MultiPolygon& merged_pile, // merged pile
const ItemGroup& items, // packed items
const ItemGroup& remaining) // future items to be packed
{
m_items = items;
m_merged_pile = merged_pile;
m_remaining = remaining;
m_pilebb = sl::boundingBox(merged_pile);
m_rtree.clear();
m_smallsrtree.clear();
// We will treat big items (compared to the print bed) differently
auto isBig = [this](double a) {
return a / m_bin_area > BIG_ITEM_TRESHOLD ;
};
for(unsigned idx = 0; idx < items.size(); ++idx) {
Item& itm = items[idx];
if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx});
m_smallsrtree.insert({itm.boundingBox(), idx});
}
};
m_pconf.object_function = get_objfn();
m_pconf.on_preload = [this](const ItemGroup &items, PConfig &cfg) {
if (items.empty()) return;
cfg.alignment = PConfig::Alignment::DONT_ALIGN;
auto bb = sl::boundingBox(m_bin);
auto bbcenter = bb.center();
cfg.object_function = [this, bb, bbcenter](const Item &item) {
return fixed_overfit(objfunc(item, bbcenter), bb);
};
};
auto on_packed = params.on_packed;
if (progressind || on_packed)
m_pck.progressIndicator([this, progressind, on_packed](unsigned rem) {
if (progressind)
progressind(rem);
if (on_packed) {
int last_bed = m_pck.lastPackedBinId();
if (last_bed >= 0) {
Item &last_packed = m_pck.lastResult()[last_bed].back();
ArrangePolygon ap;
ap.bed_idx = last_packed.binId();
ap.priority = last_packed.priority();
on_packed(ap);
}
}
});
if (stopcond) m_pck.stopCondition(stopcond);
m_pck.configure(m_pconf);
}
template<class It> inline void operator()(It from, It to) {
m_rtree.clear();
m_item_count += size_t(to - from);
m_pck.execute(from, to);
m_item_count = 0;
}
PConfig& config() { return m_pconf; }
const PConfig& config() const { return m_pconf; }
inline void preload(std::vector<Item>& fixeditems) {
for(unsigned idx = 0; idx < fixeditems.size(); ++idx) {
Item& itm = fixeditems[idx];
itm.markAsFixedInBin(itm.binId());
}
m_item_count += fixeditems.size();
}
};
template<> std::function<double(const Item&)> AutoArranger<Box>::get_objfn()
{
auto bincenter = m_bin.center();
return [this, bincenter](const Item &itm) {
auto result = objfunc(itm, bincenter);
double score = std::get<0>(result);
auto& fullbb = std::get<1>(result);
double miss = Placer::overfit(fullbb, m_bin);
miss = miss > 0? miss : 0;
score += miss * miss;
return score;
};
}
template<> std::function<double(const Item&)> AutoArranger<Circle>::get_objfn()
{
auto bincenter = m_bin.center();
return [this, bincenter](const Item &item) {
auto result = objfunc(item, bincenter);
double score = std::get<0>(result);
return score;
};
}
// Specialization for a generalized polygon.
// Warning: this is unfinished business. It may or may not work.
template<>
std::function<double(const Item &)> AutoArranger<ExPolygon>::get_objfn()
{
auto bincenter = sl::boundingBox(m_bin).center();
return [this, bincenter](const Item &item) {
return std::get<0>(objfunc(item, bincenter));
};
}
template<class Bin> void remove_large_items(std::vector<Item> &items, Bin &&bin)
{
auto it = items.begin();
while (it != items.end())
sl::isInside(it->transformedShape(), bin) ?
++it : it = items.erase(it);
}
template<class S> Radians min_area_boundingbox_rotation(const S &sh)
{
return minAreaBoundingBox<S, TCompute<S>, boost::rational<LargeInt>>(sh)
.angleToX();
}
template<class S>
Radians fit_into_box_rotation(const S &sh, const _Box<TPoint<S>> &box)
{
return fitIntoBoxRotation<S, TCompute<S>, boost::rational<LargeInt>>(sh, box);
}
template<class BinT> // Arrange for arbitrary bin type
void _arrange(
std::vector<Item> & shapes,
std::vector<Item> & excludes,
const BinT & bin,
const ArrangeParams &params,
std::function<void(unsigned)> progressfn,
std::function<bool()> stopfn)
{
// Integer ceiling the min distance from the bed perimeters
coord_t md = params.min_obj_distance;
md = md / 2 - params.min_bed_distance;
auto corrected_bin = bin;
sl::offset(corrected_bin, md);
ArrangeParams mod_params = params;
mod_params.min_obj_distance = 0;
AutoArranger<BinT> arranger{corrected_bin, mod_params, progressfn, stopfn};
auto infl = coord_t(std::ceil(params.min_obj_distance / 2.0));
for (Item& itm : shapes) itm.inflate(infl);
for (Item& itm : excludes) itm.inflate(infl);
remove_large_items(excludes, corrected_bin);
// If there is something on the plate
if (!excludes.empty()) arranger.preload(excludes);
std::vector<std::reference_wrapper<Item>> inp;
inp.reserve(shapes.size() + excludes.size());
for (auto &itm : shapes ) inp.emplace_back(itm);
for (auto &itm : excludes) inp.emplace_back(itm);
// Use the minimum bounding box rotation as a starting point.
// TODO: This only works for convex hull. If we ever switch to concave
// polygon nesting, a convex hull needs to be calculated.
if (params.allow_rotations) {
for (auto &itm : shapes) {
itm.rotation(min_area_boundingbox_rotation(itm.rawShape()));
// If the item is too big, try to find a rotation that makes it fit
if constexpr (std::is_same_v<BinT, Box>) {
auto bb = itm.boundingBox();
if (bb.width() >= bin.width() || bb.height() >= bin.height())
itm.rotate(fit_into_box_rotation(itm.transformedShape(), bin));
}
}
}
if (sl::area(corrected_bin) > 0)
arranger(inp.begin(), inp.end());
else {
for (Item &itm : inp)
itm.binId(BIN_ID_UNSET);
}
for (Item &itm : inp) itm.inflate(-infl);
}
inline Box to_nestbin(const BoundingBox &bb) { return Box{{bb.min(X), bb.min(Y)}, {bb.max(X), bb.max(Y)}};}
inline Circle to_nestbin(const CircleBed &c) { return Circle({c.center()(0), c.center()(1)}, c.radius()); }
inline ExPolygon to_nestbin(const Polygon &p) { return ExPolygon{p}; }
inline Box to_nestbin(const InfiniteBed &bed) { return Box::infinite({bed.center.x(), bed.center.y()}); }
inline coord_t width(const BoundingBox& box) { return box.max.x() - box.min.x(); }
inline coord_t height(const BoundingBox& box) { return box.max.y() - box.min.y(); }
inline double 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 &center, const Points& points) {
std::vector<double> vertex_distances;
double avg_dist = 0;
for (const Point& pt : points)
{
double distance = distance_to(center, pt);
vertex_distances.push_back(distance);
avg_dist += distance;
}
avg_dist /= vertex_distances.size();
CircleBed ret(center, avg_dist);
for(auto el : vertex_distances)
{
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
ret = {};
break;
}
}
return ret;
}
// 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;
outp.emplace_back(std::move(p));
outp.back().rotation(rotation);
outp.back().translation({offs.x(), offs.y()});
outp.back().inflate(arrpoly.inflation);
outp.back().binId(arrpoly.bed_idx);
outp.back().priority(arrpoly.priority);
outp.back().setOnPackedFn([&arrpoly](Item &itm){
itm.inflate(-arrpoly.inflation);
});
}
template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
{
if (bed.empty())
return fn(InfiniteBed{});
else if (bed.size() == 1)
return fn(InfiniteBed{bed.front()});
else {
auto bb = BoundingBox(bed);
CircleBed circ = to_circle(bb.center(), bed);
auto parea = poly_area(bed);
if ((1.0 - parea / area(bb)) < 1e-3)
return fn(RectangleBed{bb});
else if (!std::isnan(circ.radius()))
return fn(circ);
else
return fn(IrregularBed{ExPolygon(bed)});
}
}
bool is_box(const Points &bed)
{
return !bed.empty() &&
((1.0 - poly_area(bed) / area(BoundingBox(bed))) < 1e-3);
}
template<>
void arrange(ArrangePolygons & items,
const ArrangePolygons &excludes,
const Points & bed,
const ArrangeParams & params)
{
arrange(items, excludes, to_arrange_bed(bed), params);
}
template<class BedT>
void arrange(ArrangePolygons & arrangables,
const ArrangePolygons &excludes,
const BedT & bed,
const ArrangeParams & params)
{
namespace clppr = Slic3r::ClipperLib;
std::vector<Item> items, fixeditems;
items.reserve(arrangables.size());
for (ArrangePolygon &arrangeable : arrangables)
process_arrangeable(arrangeable, items);
for (const ArrangePolygon &fixed: excludes)
process_arrangeable(fixed, fixeditems);
for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON));
auto &cfn = params.stopcondition;
auto &pri = params.progressind;
_arrange(items, fixeditems, to_nestbin(bed), params, pri, cfn);
for(size_t i = 0; i < items.size(); ++i) {
Point tr = items[i].translation();
arrangables[i].translation = {coord_t(tr.x()), coord_t(tr.y())};
arrangables[i].rotation = items[i].rotation();
arrangables[i].bed_idx = items[i].binId();
}
}
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams &params);
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams &params);
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams &params);
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params);
ArrangeBed to_arrange_bed(const Points &bedpts)
{
ArrangeBed ret;
call_with_bed(bedpts, [&](const auto &bed) {
ret = bed;
});
return ret;
}
void arrange(ArrangePolygons &items,
const ArrangePolygons &excludes,
const SegmentedRectangleBed &bed,
const ArrangeParams &params)
{
arrange(items, excludes, bed.bb, params);
if (! excludes.empty())
return;
auto it = std::max_element(items.begin(), items.end(),
[](auto &i1, auto &i2) {
return i1.bed_idx < i2.bed_idx;
});
size_t beds = 0;
if (it != items.end())
beds = it->bed_idx + 1;
std::vector<BoundingBox> pilebb(beds);
for (auto &itm : items) {
if (itm.bed_idx >= 0)
pilebb[itm.bed_idx].merge(get_extents(itm.transformed_poly()));
}
auto piecesz = unscaled(bed.bb).size();
piecesz.x() /= bed.segments.x();
piecesz.y() /= bed.segments.y();
for (size_t bedidx = 0; bedidx < beds; ++bedidx) {
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 (params.alignment) {
case Pivots::BottomLeft:
bb.translate(bed.bb.min - bb.min);
break;
case Pivots::TopRight:
bb.translate(bed.bb.max - bb.max);
break;
case Pivots::BottomRight: {
Point bedref{bed.bb.max.x(), bed.bb.min.y()};
Point bbref {bb.max.x(), bb.min.y()};
bb.translate(bedref - bbref);
break;
}
case Pivots::TopLeft: {
Point bedref{bed.bb.min.x(), bed.bb.max.y()};
Point bbref {bb.min.x(), bb.max.y()};
bb.translate(bedref - bbref);
break;
}
case Pivots::Center: {
bb.translate(bed.bb.center() - bb.center());
break;
}
}
Vec2crd d = bb.center() - pilebb[bedidx].center();
auto bedbb = bed.bb;
bedbb.offset(-params.min_bed_distance);
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 (itm.bed_idx == int(bedidx))
itm.translation += d;
}
}
BoundingBox bounding_box(const InfiniteBed &bed)
{
BoundingBox ret;
using C = coord_t;
// It is important for Mx and My to be strictly less than half of the
// range of type C. width(), height() and area() will not overflow this way.
C Mx = C((std::numeric_limits<C>::lowest() + 2 * bed.center.x()) / 4.01);
C My = C((std::numeric_limits<C>::lowest() + 2 * bed.center.y()) / 4.01);
ret.max = bed.center - Point{Mx, My};
ret.min = bed.center + Point{Mx, My};
return ret;
}
} // namespace arr
} // namespace Slic3r

View File

@ -1,220 +0,0 @@
///|/ Copyright (c) Prusa Research 2018 - 2023 Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef ARRANGE_HPP
#define ARRANGE_HPP
#include <boost/variant.hpp>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/BoundingBox.hpp>
namespace Slic3r {
class BoundingBox;
namespace arrangement {
/// Representing an unbounded bed.
struct InfiniteBed {
Point center;
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
};
struct RectangleBed {
BoundingBox bb;
};
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
class CircleBed {
Point center_;
double radius_;
public:
inline CircleBed(): center_(0, 0), radius_(NaNd) {}
explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
inline double radius() const { return radius_; }
inline const Point& center() const { return center_; }
};
struct SegmentedRectangleBed {
Vec<2, size_t> segments;
BoundingBox bb;
SegmentedRectangleBed (const BoundingBox &bb,
size_t segments_x,
size_t segments_y)
: segments{segments_x, segments_y}
, bb{bb}
{}
};
struct IrregularBed {
ExPolygon poly;
};
//enum BedType { Infinite, Rectangle, Circle, SegmentedRectangle, Irregular };
using ArrangeBed = boost::variant<InfiniteBed, RectangleBed, CircleBed, SegmentedRectangleBed, IrregularBed>;
BoundingBox bounding_box(const InfiniteBed &bed);
inline BoundingBox bounding_box(const RectangleBed &b) { return b.bb; }
inline BoundingBox bounding_box(const SegmentedRectangleBed &b) { return b.bb; }
inline BoundingBox bounding_box(const CircleBed &b)
{
auto r = static_cast<coord_t>(std::round(b.radius()));
Point R{r, r};
return {b.center() - R, b.center() + R};
}
inline BoundingBox bounding_box(const ArrangeBed &b)
{
BoundingBox ret;
auto visitor = [&ret](const auto &b) { ret = bounding_box(b); };
boost::apply_visitor(visitor, b);
return ret;
}
ArrangeBed to_arrange_bed(const Points &bedpts);
/// A logical bed representing an object not being arranged. Either the arrange
/// has not yet successfully run on this ArrangePolygon or it could not fit the
/// object due to overly large size or invalid geometry.
static const constexpr int UNARRANGED = -1;
/// Input/Output structure for the arrange() function. The poly field will not
/// be modified during arrangement. Instead, the translation and rotation fields
/// will mark the needed transformation for the polygon to be in the arranged
/// position. These can also be set to an initial offset and rotation.
///
/// The bed_idx field will indicate the logical bed into which the
/// polygon belongs: UNARRANGED means no place for the polygon
/// (also the initial state before arrange), 0..N means the index of the bed.
/// Zero is the physical bed, larger than zero means a virtual bed.
struct ArrangePolygon {
ExPolygon poly; /// The 2D silhouette to be arranged
Vec2crd translation{0, 0}; /// The translation of the poly
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 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
std::function<void(const ArrangePolygon&)> setter = nullptr;
/// Helper function to call the setter with the arrange data arguments
void apply() const { if (setter) setter(*this); }
/// Test if arrange() was called previously and gave a successful result.
bool is_arranged() const { return bed_idx != UNARRANGED; }
inline ExPolygon transformed_poly() const
{
ExPolygon ret = poly;
ret.rotate(rotation);
ret.translate(translation.x(), translation.y());
return ret;
}
};
using ArrangePolygons = std::vector<ArrangePolygon>;
enum class Pivots {
Center, TopLeft, BottomLeft, BottomRight, TopRight
};
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 minimum distance of any object from bed edges
coord_t min_bed_distance = 0;
/// The accuracy of optimization.
/// Goes from 0.0 to 1.0 and scales performance as well
float accuracy = 1.f;
/// Allow parallel execution.
bool parallel = true;
bool allow_rotations = false;
/// Final alignment of the merged pile after arrangement
Pivots alignment = Pivots::Center;
/// Starting position hint for the arrangement
Pivots starting_point = Pivots::Center;
/// 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;
std::function<void(const ArrangePolygon &)> on_packed;
/// 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.
*
* WARNING: Currently, only convex polygons are supported by the libnest2d
* library which is used to do the arrangement. This might change in the future
* this is why the interface contains a general polygon capable to have holes.
*
* \param items Input vector of ArrangePolygons. The transformation, rotation
* and bin_idx fields will be changed after the call finished and can be used
* to apply the result on the input polygon.
*/
template<class TBed> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams &params = {});
// A dispatch function that determines the bed shape from a set of points.
template<> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Points &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params);
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const RectangleBed &bed, const ArrangeParams &params)
{
arrange(items, excludes, bed.bb, params);
}
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const IrregularBed &bed, const ArrangeParams &params)
{
arrange(items, excludes, bed.poly.contour, params);
}
void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const SegmentedRectangleBed &bed, const ArrangeParams &params);
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const ArrangeBed &bed, const ArrangeParams &params)
{
auto call_arrange = [&](const auto &realbed) { arrange(items, excludes, realbed, params); };
boost::apply_visitor(call_arrange, bed);
}
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
bool is_box(const Points &bed);
}} // namespace Slic3r::arrangement
#endif // MODELARRANGE_HPP

View File

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

View File

@ -1,20 +0,0 @@
///|/ Copyright (c) Prusa Research 2023 Tomáš Mészáros @tamasmeszaros
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef NFPCONCAVE_CGAL_HPP
#define NFPCONCAVE_CGAL_HPP
#include <libslic3r/ExPolygon.hpp>
#include "libslic3r/Polygon.hpp"
namespace Slic3r {
Polygons convex_decomposition_cgal(const Polygon &expoly);
Polygons convex_decomposition_cgal(const ExPolygon &expoly);
ExPolygons nfp_concave_concave_cgal(const ExPolygon &fixed, const ExPolygon &movable);
} // namespace Slic3r
#endif // NFPCONCAVE_CGAL_HPP

View File

@ -21,6 +21,8 @@
#include "libslic3r/Geometry/Circle.hpp"
#include "libslic3r/Polygon.hpp"
#include "MultipleBeds.hpp"
namespace Slic3r {
BuildVolume::BuildVolume(const std::vector<Vec2d> &bed_shape, const double max_print_height) : m_bed_shape(bed_shape), m_max_print_height(max_print_height)
@ -284,8 +286,25 @@ BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, con
return inside ? (outside ? BuildVolume::ObjectState::Colliding : BuildVolume::ObjectState::Inside) : BuildVolume::ObjectState::Outside;
}
BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& its, const Transform3f& trafo, bool may_be_below_bed, bool ignore_bottom) const
BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& its, const Transform3f& trafo_orig, bool may_be_below_bed, bool ignore_bottom, int* bed_idx) const
{
ObjectState out = ObjectState::Outside;
if (bed_idx)
*bed_idx = -1;
// When loading an old project with more than the maximum number of beds,
// we still want to move the objects to the respective positions.
// Max beds number is momentarily increased when doing the rearrange, so use it.
const int max_bed = s_multiple_beds.get_loading_project_flag()
? s_multiple_beds.get_number_of_beds() - 1
: std::min(s_multiple_beds.get_number_of_beds(), s_multiple_beds.get_max_beds() - 1);
for (int bed_id = 0; bed_id <= max_bed; ++bed_id) {
Transform3f trafo = trafo_orig;
trafo.pretranslate(-s_multiple_beds.get_bed_translation(bed_id).cast<float>());
switch (m_type) {
case Type::Rectangle:
{
@ -298,28 +317,44 @@ BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& i
// The following test correctly interprets intersection of a non-convex object with a rectangular build volume.
//return rectangle_test(its, trafo, to_2d(build_volume.min), to_2d(build_volume.max), build_volume.max.z());
//FIXME This test does NOT correctly interprets intersection of a non-convex object with a rectangular build volume.
return object_state_templ(its, trafo, may_be_below_bed, [build_volumef](const Vec3f &pt) { return build_volumef.contains(pt); });
out = object_state_templ(its, trafo, may_be_below_bed, [build_volumef](const Vec3f& pt) { return build_volumef.contains(pt); });
break;
}
case Type::Circle:
{
Geometry::Circlef circle { unscaled<float>(m_circle.center), unscaled<float>(m_circle.radius + SceneEpsilon) };
return m_max_print_height == 0.0 ?
object_state_templ(its, trafo, may_be_below_bed, [circle](const Vec3f &pt) { return circle.contains(to_2d(pt)); }) :
object_state_templ(its, trafo, may_be_below_bed, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && circle.contains(to_2d(pt)); });
out = m_max_print_height == 0.0 ?
object_state_templ(its, trafo, may_be_below_bed, [circle](const Vec3f& pt) { return circle.contains(to_2d(pt)); }) :
object_state_templ(its, trafo, may_be_below_bed, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f& pt) { return pt.z() < z && circle.contains(to_2d(pt)); });
break;
}
case Type::Convex:
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
case Type::Custom:
return m_max_print_height == 0.0 ?
object_state_templ(its, trafo, may_be_below_bed, [this](const Vec3f &pt) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); }) :
object_state_templ(its, trafo, may_be_below_bed, [this, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); });
out = m_max_print_height == 0.0 ?
object_state_templ(its, trafo, may_be_below_bed, [this](const Vec3f& pt) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); }) :
object_state_templ(its, trafo, may_be_below_bed, [this, z = m_max_print_height + SceneEpsilon](const Vec3f& pt) { return pt.z() < z && Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); });
break;
case Type::Invalid:
default:
return ObjectState::Inside;
out = ObjectState::Inside;
break;
}
if (out != ObjectState::Outside) {
if (bed_idx)
*bed_idx = bed_id;
break;
}
}
return out;
}
BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom) const
BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3 volume_bbox_orig, bool ignore_bottom, int* bed_idx) const
{
assert(m_type == Type::Rectangle);
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(SceneEpsilon);
@ -327,9 +362,25 @@ BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& vol
build_volume.max.z() = std::numeric_limits<double>::max();
if (ignore_bottom)
build_volume.min.z() = -std::numeric_limits<double>::max();
return build_volume.max.z() <= - SceneEpsilon ? ObjectState::Below :
build_volume.contains(volume_bbox) ? ObjectState::Inside :
build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside;
ObjectState state = ObjectState::Outside;
int obj_bed_id = -1;
for (int bed_id = 0; bed_id <= std::min(s_multiple_beds.get_number_of_beds(), s_multiple_beds.get_max_beds() - 1); ++bed_id) {
BoundingBoxf3 volume_bbox = volume_bbox_orig;
volume_bbox.translate(-s_multiple_beds.get_bed_translation(bed_id));
state = build_volume.max.z() <= -SceneEpsilon ? ObjectState::Below :
build_volume.contains(volume_bbox) ? ObjectState::Inside :
build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside;
if (state != ObjectState::Outside) {
obj_bed_id = bed_id;
break;
}
}
if (bed_idx)
*bed_idx = obj_bed_id;
return state;
}
bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom) const

View File

@ -89,10 +89,10 @@ public:
// Called by Plater to update Inside / Colliding / Outside state of ModelObjects before slicing.
// Called from Model::update_print_volume_state() -> ModelObject::update_instances_print_volume_state()
// Using SceneEpsilon
ObjectState object_state(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed, bool ignore_bottom = true) const;
ObjectState object_state(const indexed_triangle_set &its, const Transform3f& trafo, bool may_be_below_bed, bool ignore_bottom = true, int* bed_idx = nullptr) const;
// Called by GLVolumeCollection::check_outside_state() after an object is manipulated with gizmos for example.
// Called for a rectangular bed:
ObjectState volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom = true) const;
ObjectState volume_state_bbox(BoundingBoxf3 volume_bbox, bool ignore_bottom, int* bed_idx) const;
// 2) Test called on G-code paths.
// Using BedEpsilon for all tests.

View File

@ -262,8 +262,6 @@ set(SLIC3R_SOURCES
CutUtils.hpp
Model.cpp
Model.hpp
ModelArrange.hpp
ModelArrange.cpp
MultiMaterialSegmentation.cpp
MultiMaterialSegmentation.hpp
MeshNormals.hpp
@ -273,55 +271,6 @@ set(SLIC3R_SOURCES
MeasureUtils.hpp
CustomGCode.cpp
CustomGCode.hpp
Arrange/Arrange.hpp
Arrange/ArrangeImpl.hpp
Arrange/Items/ArrangeItem.hpp
Arrange/Items/ArrangeItem.cpp
Arrange/Items/SimpleArrangeItem.hpp
Arrange/Items/SimpleArrangeItem.cpp
Arrange/Items/TrafoOnlyArrangeItem.hpp
Arrange/Items/MutableItemTraits.hpp
Arrange/Items/ArbitraryDataStore.hpp
Arrange/ArrangeSettingsView.hpp
Arrange/ArrangeSettingsDb_AppCfg.hpp
Arrange/ArrangeSettingsDb_AppCfg.cpp
Arrange/Scene.hpp
Arrange/Scene.cpp
Arrange/SceneBuilder.hpp
Arrange/SceneBuilder.cpp
Arrange/Tasks/ArrangeTask.hpp
Arrange/Tasks/ArrangeTaskImpl.hpp
Arrange/Tasks/FillBedTask.hpp
Arrange/Tasks/FillBedTaskImpl.hpp
Arrange/Tasks/MultiplySelectionTask.hpp
Arrange/Tasks/MultiplySelectionTaskImpl.hpp
Arrange/SegmentedRectangleBed.hpp
Arrange/Core/ArrangeItemTraits.hpp
Arrange/Core/DataStoreTraits.hpp
Arrange/Core/ArrangeBase.hpp
Arrange/Core/PackingContext.hpp
Arrange/Core/ArrangeFirstFit.hpp
Arrange/Core/Beds.hpp
Arrange/Core/Beds.cpp
Arrange/Core/NFP/NFP.hpp
Arrange/Core/NFP/NFP.cpp
Arrange/Core/NFP/NFPConcave_CGAL.hpp
Arrange/Core/NFP/NFPConcave_CGAL.cpp
Arrange/Core/NFP/NFPConcave_Tesselate.hpp
Arrange/Core/NFP/NFPConcave_Tesselate.cpp
Arrange/Core/NFP/EdgeCache.hpp
Arrange/Core/NFP/EdgeCache.cpp
Arrange/Core/NFP/CircularEdgeIterator.hpp
Arrange/Core/NFP/NFPArrangeItemTraits.hpp
Arrange/Core/NFP/PackStrategyNFP.hpp
Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp
Arrange/Core/NFP/Kernels/KernelTraits.hpp
Arrange/Core/NFP/Kernels/GravityKernel.hpp
Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp
Arrange/Core/NFP/Kernels/CompactifyKernel.hpp
Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp
Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp
Arrange/Core/NFP/Kernels/KernelUtils.hpp
MultiPoint.cpp
MultiPoint.hpp
MutablePriorityQueue.hpp
@ -445,6 +394,8 @@ set(SLIC3R_SOURCES
miniz_extension.hpp
miniz_extension.cpp
MarchingSquares.hpp
MultipleBeds.cpp
MultipleBeds.hpp
Execution/Execution.hpp
Execution/ExecutionSeq.hpp
Execution/ExecutionTBB.hpp

View File

@ -47,6 +47,8 @@ namespace pt = boost::property_tree;
#include "libslic3r/NSVGUtils.hpp"
#include "libslic3r/MultipleBeds.hpp"
#include <fast_float.h>
// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter,
@ -773,7 +775,7 @@ namespace Slic3r {
}
// Initialize the wipe tower position (see the end of this function):
model.wipe_tower.position.x() = std::numeric_limits<double>::max();
model.get_wipe_tower_vector().front().position.x() = std::numeric_limits<double>::max();
// Read root model file
if (start_part_stat.m_file_index < num_entries) {
@ -850,13 +852,13 @@ namespace Slic3r {
}
if (model.wipe_tower.position.x() == std::numeric_limits<double>::max()) {
if (model.get_wipe_tower_vector().front().position.x() == std::numeric_limits<double>::max()) {
// This is apparently an old project from before PS 2.9.0, which saved wipe tower pos and rotation
// into config, not into Model. Try to load it from the config file.
// First set default in case we do not find it (these were the default values of the config options).
model.wipe_tower.position.x() = 180;
model.wipe_tower.position.y() = 140;
model.wipe_tower.rotation = 0.;
model.get_wipe_tower_vector().front().position.x() = 180;
model.get_wipe_tower_vector().front().position.y() = 140;
model.get_wipe_tower_vector().front().rotation = 0.;
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
@ -1607,46 +1609,61 @@ namespace Slic3r {
if (main_tree.front().first != "custom_gcodes_per_print_z")
return;
pt::ptree code_tree = main_tree.front().second;
m_model->custom_gcode_per_print_z.gcodes.clear();
for (CustomGCode::Info& info : m_model->get_custom_gcode_per_print_z_vector())
info.gcodes.clear();
for (const auto& code : code_tree) {
if (code.first == "mode") {
pt::ptree tree = code.second;
std::string mode = tree.get<std::string>("<xmlattr>.value");
m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle :
CustomGCode::Mode::MultiExtruder;
for (const auto& bed_block : main_tree) {
if (bed_block.first != "custom_gcodes_per_print_z")
continue;
int bed_idx = 0;
try {
bed_block.second.get<int>("<xmlattr>.bed_idx");
} catch (const boost::property_tree::ptree_bad_path&) {
// Probably an old project with no bed_idx info. Imagine that we saw 0.
}
if (code.first != "code")
if (bed_idx >= int(m_model->get_custom_gcode_per_print_z_vector().size()))
continue;
pt::ptree tree = code.second;
double print_z = tree.get<double> ("<xmlattr>.print_z" );
int extruder = tree.get<int> ("<xmlattr>.extruder");
std::string color = tree.get<std::string> ("<xmlattr>.color" );
pt::ptree code_tree = bed_block.second;
CustomGCode::Type type;
std::string extra;
pt::ptree attr_tree = tree.find("<xmlattr>")->second;
if (attr_tree.find("type") == attr_tree.not_found()) {
// It means that data was saved in old version (2.2.0 and older) of PrusaSlicer
// read old data ...
std::string gcode = tree.get<std::string> ("<xmlattr>.gcode");
// ... and interpret them to the new data
type = gcode == "M600" ? CustomGCode::ColorChange :
gcode == "M601" ? CustomGCode::PausePrint :
gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom;
extra = type == CustomGCode::PausePrint ? color :
type == CustomGCode::Custom ? gcode : "";
for (const auto& code : code_tree) {
if (code.first == "mode") {
pt::ptree tree = code.second;
std::string mode = tree.get<std::string>("<xmlattr>.value");
m_model->get_custom_gcode_per_print_z_vector()[bed_idx].mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle :
CustomGCode::Mode::MultiExtruder;
}
if (code.first != "code")
continue;
pt::ptree tree = code.second;
double print_z = tree.get<double> ("<xmlattr>.print_z" );
int extruder = tree.get<int> ("<xmlattr>.extruder");
std::string color = tree.get<std::string> ("<xmlattr>.color" );
CustomGCode::Type type;
std::string extra;
pt::ptree attr_tree = tree.find("<xmlattr>")->second;
if (attr_tree.find("type") == attr_tree.not_found()) {
// It means that data was saved in old version (2.2.0 and older) of PrusaSlicer
// read old data ...
std::string gcode = tree.get<std::string> ("<xmlattr>.gcode");
// ... and interpret them to the new data
type = gcode == "M600" ? CustomGCode::ColorChange :
gcode == "M601" ? CustomGCode::PausePrint :
gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom;
extra = type == CustomGCode::PausePrint ? color :
type == CustomGCode::Custom ? gcode : "";
}
else {
type = static_cast<CustomGCode::Type>(tree.get<int>("<xmlattr>.type"));
extra = tree.get<std::string>("<xmlattr>.extra");
}
m_model->get_custom_gcode_per_print_z_vector()[bed_idx].gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra});
}
else {
type = static_cast<CustomGCode::Type>(tree.get<int>("<xmlattr>.type"));
extra = tree.get<std::string>("<xmlattr>.extra");
}
m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ;
}
}
}
}
@ -1664,17 +1681,29 @@ namespace Slic3r {
pt::ptree main_tree;
pt::read_xml(iss, main_tree);
try {
auto& node = main_tree.get_child("wipe_tower_information");
double pos_x = node.get<double>("<xmlattr>.position_x");
double pos_y = node.get<double>("<xmlattr>.position_y");
double rot_deg = node.get<double>("<xmlattr>.rotation_deg");
model.wipe_tower.position = Vec2d(pos_x, pos_y);
model.wipe_tower.rotation = rot_deg;
} catch (const boost::property_tree::ptree_bad_path&) {
// Handles missing node or attribute.
add_error("Error while reading wipe tower information.");
return;
for (const auto& bed_block : main_tree) {
if (bed_block.first != "wipe_tower_information")
continue;
try {
int bed_idx = 0;
try {
bed_idx = bed_block.second.get<int>("<xmlattr>.bed_idx");
} catch (const boost::property_tree::ptree_bad_path&) {
// Probably an old project with no bed_idx info - pretend that we saw 0.
}
if (bed_idx >= int(m_model->get_wipe_tower_vector().size()))
continue;
double pos_x = bed_block.second.get<double>("<xmlattr>.position_x");
double pos_y = bed_block.second.get<double>("<xmlattr>.position_y");
double rot_deg = bed_block.second.get<double>("<xmlattr>.rotation_deg");
model.get_wipe_tower_vector()[bed_idx].position = Vec2d(pos_x, pos_y);
model.get_wipe_tower_vector()[bed_idx].rotation = rot_deg;
}
catch (const boost::property_tree::ptree_bad_path&) {
// Handles missing node or attribute.
add_error("Error while reading wipe tower information.");
return;
}
}
}
@ -1710,11 +1739,11 @@ namespace Slic3r {
value_ss >> val;
if (! value_ss.fail()) {
if (boost::starts_with(line, "wipe_tower_x"))
model.wipe_tower.position.x() = val;
model.get_wipe_tower_vector().front().position.x() = val;
else if (boost::starts_with(line, "wipe_tower_y"))
model.wipe_tower.position.y() = val;
model.get_wipe_tower_vector().front().position.y() = val;
else
model.wipe_tower.rotation = val;
model.get_wipe_tower_vector().front().rotation = val;
}
}
}
@ -3606,11 +3635,11 @@ namespace Slic3r {
std::string opt_serialized;
if (key == "wipe_tower_x")
opt_serialized = float_to_string_decimal_point(model.wipe_tower.position.x());
opt_serialized = float_to_string_decimal_point(model.get_wipe_tower_vector().front().position.x());
else if (key == "wipe_tower_y")
opt_serialized = float_to_string_decimal_point(model.wipe_tower.position.y());
opt_serialized = float_to_string_decimal_point(model.get_wipe_tower_vector().front().position.y());
else if (key == "wipe_tower_rotation_angle")
opt_serialized = float_to_string_decimal_point(model.wipe_tower.rotation);
opt_serialized = float_to_string_decimal_point(model.get_wipe_tower_vector().front().rotation);
else
opt_serialized = config.opt_serialize(key);
@ -3765,34 +3794,43 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
{
std::string out = "";
if (!model.custom_gcode_per_print_z.gcodes.empty()) {
if (std::any_of(model.get_custom_gcode_per_print_z_vector().begin(), model.get_custom_gcode_per_print_z_vector().end(), [](const auto& cg) { return !cg.gcodes.empty(); })) {
pt::ptree tree;
pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", "");
for (size_t bed_idx=0; bed_idx<model.get_custom_gcode_per_print_z_vector().size(); ++bed_idx) {
if (bed_idx != 0 && model.get_custom_gcode_per_print_z_vector()[bed_idx].gcodes.empty()) {
// Always save the first bed so older slicers are able to tell
// that there are no color changes on it.
continue;
}
for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) {
pt::ptree& code_tree = main_tree.add("code", "");
pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", "");
main_tree.put("<xmlattr>.bed_idx" , bed_idx);
// store data of custom_gcode_per_print_z
code_tree.put("<xmlattr>.print_z" , code.print_z );
code_tree.put("<xmlattr>.type" , static_cast<int>(code.type));
code_tree.put("<xmlattr>.extruder" , code.extruder );
code_tree.put("<xmlattr>.color" , code.color );
code_tree.put("<xmlattr>.extra" , code.extra );
for (const CustomGCode::Item& code : model.get_custom_gcode_per_print_z_vector()[bed_idx].gcodes) {
pt::ptree& code_tree = main_tree.add("code", "");
// add gcode field data for the old version of the PrusaSlicer
std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") :
code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") :
code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
code.type == CustomGCode::ToolChange ? "tool_change" : code.extra;
code_tree.put("<xmlattr>.gcode" , gcode );
// store data of custom_gcode_per_print_z
code_tree.put("<xmlattr>.print_z" , code.print_z );
code_tree.put("<xmlattr>.type" , static_cast<int>(code.type));
code_tree.put("<xmlattr>.extruder" , code.extruder );
code_tree.put("<xmlattr>.color" , code.color );
code_tree.put("<xmlattr>.extra" , code.extra );
// add gcode field data for the old version of the PrusaSlicer
std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") :
code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") :
code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
code.type == CustomGCode::ToolChange ? "tool_change" : code.extra;
code_tree.put("<xmlattr>.gcode" , gcode );
}
pt::ptree& mode_tree = main_tree.add("mode", "");
// store mode of a custom_gcode_per_print_z
mode_tree.put("<xmlattr>.value", model.custom_gcode_per_print_z().mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
model.custom_gcode_per_print_z().mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode :
CustomGCode::MultiExtruderMode);
}
pt::ptree& mode_tree = main_tree.add("mode", "");
// store mode of a custom_gcode_per_print_z
mode_tree.put("<xmlattr>.value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode :
CustomGCode::MultiExtruderMode);
if (!tree.empty()) {
std::ostringstream oss;
boost::property_tree::write_xml(oss, tree);
@ -3818,11 +3856,19 @@ bool _3MF_Exporter::_add_wipe_tower_information_file_to_archive( mz_zip_archive&
std::string out = "";
pt::ptree tree;
pt::ptree& main_tree = tree.add("wipe_tower_information", "");
main_tree.put("<xmlattr>.position_x", model.wipe_tower.position.x());
main_tree.put("<xmlattr>.position_y", model.wipe_tower.position.y());
main_tree.put("<xmlattr>.rotation_deg", model.wipe_tower.rotation);
size_t bed_idx = 0;
for (const ModelWipeTower& wipe_tower : model.get_wipe_tower_vector()) {
pt::ptree& main_tree = tree.add("wipe_tower_information", "");
main_tree.put("<xmlattr>.bed_idx", bed_idx);
main_tree.put("<xmlattr>.position_x", wipe_tower.position.x());
main_tree.put("<xmlattr>.position_y", wipe_tower.position.y());
main_tree.put("<xmlattr>.rotation_deg", wipe_tower.rotation);
++bed_idx;
if (bed_idx >= s_multiple_beds.get_number_of_beds())
break;
}
std::ostringstream oss;
boost::property_tree::write_xml(oss, tree);

View File

@ -700,7 +700,7 @@ void AMFParserContext::endElement(const char * /* name */)
CustomGCode::Type type = static_cast<CustomGCode::Type>(atoi(m_value[3].c_str()));
const std::string& extra= m_value[4];
m_model.custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra});
m_model.custom_gcode_per_print_z().gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra});
for (std::string& val: m_value)
val.clear();
@ -710,9 +710,9 @@ void AMFParserContext::endElement(const char * /* name */)
case NODE_TYPE_CUSTOM_GCODE_MODE: {
const std::string& mode = m_value[0];
m_model.custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle :
CustomGCode::Mode::MultiExtruder;
m_model.custom_gcode_per_print_z().mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle :
CustomGCode::Mode::MultiExtruder;
for (std::string& val: m_value)
val.clear();
break;

View File

@ -546,7 +546,7 @@ namespace DoExport {
}
}
if (ret.size() < MAX_TAGS_COUNT) {
const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z;
const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z();
for (const auto& gcode : custom_gcode_per_print_z.gcodes) {
check(_u8L("Custom G-code"), gcode.extra);
if (ret.size() == MAX_TAGS_COUNT)
@ -1314,7 +1314,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> layers_to_print = collect_layers_to_print(print);
// Prusa Multi-Material wipe tower.
if (has_wipe_tower && ! layers_to_print.empty()) {
m_wipe_tower = std::make_unique<GCode::WipeTowerIntegration>(print.model().wipe_tower.position.cast<float>(), print.model().wipe_tower.rotation, print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get());
m_wipe_tower = std::make_unique<GCode::WipeTowerIntegration>(print.model().wipe_tower().position.cast<float>(), print.model().wipe_tower().rotation, print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get());
// Set position for wipe tower generation.
Vec3d new_position = this->writer().get_position();

View File

@ -151,8 +151,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
// Wipe tower extrusions are saved as if the tower was at the origin with no rotation
// We need to get position and angle of the wipe tower to transform them to actual position.
Transform2d trafo =
Eigen::Translation2d(print.model().wipe_tower.position.x(), print.model().wipe_tower.position.y()) *
Eigen::Rotation2Dd(Geometry::deg2rad(print.model().wipe_tower.rotation));
Eigen::Translation2d(print.model().wipe_tower().position.x(), print.model().wipe_tower().position.y()) *
Eigen::Rotation2Dd(Geometry::deg2rad(print.model().wipe_tower().rotation));
BoundingBoxf bbox;
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {

View File

@ -165,17 +165,17 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
std::vector<std::pair<double, unsigned int>> per_layer_extruder_switches;
auto num_extruders = unsigned(print.config().nozzle_diameter.size());
if (num_extruders > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle
print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiAsSingle) {
print.model().custom_gcode_per_print_z().mode == CustomGCode::MultiAsSingle) {
// Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material).
// There may be custom per-layer tool changes available at the model.
per_layer_extruder_switches = custom_tool_changes(print.model().custom_gcode_per_print_z, num_extruders);
per_layer_extruder_switches = custom_tool_changes(print.model().custom_gcode_per_print_z(), num_extruders);
}
// Color changes for each layer to determine which extruder needs to be picked before color change.
// This is done just for multi-extruder printers without enabled Single Extruder Multi Material (tool changer printers).
std::vector<std::pair<double, unsigned int>> per_layer_color_changes;
if (num_extruders > 1 && print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiExtruder && !print.config().single_extruder_multi_material) {
per_layer_color_changes = custom_color_changes(print.model().custom_gcode_per_print_z, num_extruders);
if (num_extruders > 1 && print.model().custom_gcode_per_print_z().mode == CustomGCode::MultiExtruder && !print.config().single_extruder_multi_material) {
per_layer_color_changes = custom_color_changes(print.model().custom_gcode_per_print_z(), num_extruders);
}
// Collect extruders required to print the layers.
@ -612,7 +612,7 @@ void ToolOrdering::assign_custom_gcodes(const Print &print)
// Only valid for non-sequential print.
assert(! print.config().complete_objects.value);
const CustomGCode::Info &custom_gcode_per_print_z = print.model().custom_gcode_per_print_z;
const CustomGCode::Info &custom_gcode_per_print_z = print.model().custom_gcode_per_print_z();
if (custom_gcode_per_print_z.gcodes.empty())
return;
@ -620,7 +620,7 @@ void ToolOrdering::assign_custom_gcodes(const Print &print)
CustomGCode::Mode mode =
(num_extruders == 1) ? CustomGCode::SingleExtruder :
print.object_extruders().size() == 1 ? CustomGCode::MultiAsSingle : CustomGCode::MultiExtruder;
CustomGCode::Mode model_mode = print.model().custom_gcode_per_print_z.mode;
CustomGCode::Mode model_mode = print.model().custom_gcode_per_print_z().mode;
std::vector<unsigned char> extruder_printing_above(num_extruders, false);
auto custom_gcode_it = custom_gcode_per_print_z.gcodes.rbegin();
// Tool changes and color changes will be ignored, if the model's tool/color changes were entered in mm mode and the print is in non mm mode

View File

@ -16,11 +16,11 @@
#include "BuildVolume.hpp"
#include "Exception.hpp"
#include "Model.hpp"
#include "ModelArrange.hpp"
#include "Geometry/ConvexHull.hpp"
#include "MTUtils.hpp"
#include "TriangleMeshSlicer.hpp"
#include "TriangleSelector.hpp"
#include "MultipleBeds.hpp"
#include "Format/AMF.hpp"
#include "Format/OBJ.hpp"
@ -68,8 +68,9 @@ Model& Model::assign_copy(const Model &rhs)
}
// copy custom code per height
this->custom_gcode_per_print_z = rhs.custom_gcode_per_print_z;
this->wipe_tower = rhs.wipe_tower;
this->custom_gcode_per_print_z_vector = rhs.custom_gcode_per_print_z_vector;
this->wipe_tower_vector = rhs.wipe_tower_vector;
return *this;
}
@ -90,8 +91,9 @@ Model& Model::assign_copy(Model &&rhs)
rhs.objects.clear();
// copy custom code per height
this->custom_gcode_per_print_z = std::move(rhs.custom_gcode_per_print_z);
this->wipe_tower = rhs.wipe_tower;
this->custom_gcode_per_print_z_vector = std::move(rhs.custom_gcode_per_print_z_vector);
this->wipe_tower_vector = rhs.wipe_tower_vector;
return *this;
}
@ -117,6 +119,37 @@ void Model::update_links_bottom_up_recursive()
}
}
ModelWipeTower& Model::wipe_tower()
{
return const_cast<ModelWipeTower&>(const_cast<const Model*>(this)->wipe_tower());
}
const ModelWipeTower& Model::wipe_tower() const
{
return wipe_tower_vector[s_multiple_beds.get_active_bed()];
}
const ModelWipeTower& Model::wipe_tower(const int bed_index) const
{
return wipe_tower_vector[bed_index];
}
ModelWipeTower& Model::wipe_tower(const int bed_index)
{
return wipe_tower_vector[bed_index];
}
CustomGCode::Info& Model::custom_gcode_per_print_z()
{
return const_cast<CustomGCode::Info&>(const_cast<const Model*>(this)->custom_gcode_per_print_z());
}
const CustomGCode::Info& Model::custom_gcode_per_print_z() const
{
return custom_gcode_per_print_z_vector[s_multiple_beds.get_active_bed()];
}
// Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well.
Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
{
@ -153,16 +186,18 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
if (model.objects.empty())
throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty");
if (!boost::ends_with(input_file, ".printRequest"))
for (ModelObject *o : model.objects)
o->input_file = input_file;
if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances();
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
for (CustomGCode::Info& info : model.custom_gcode_per_print_z_vector) {
CustomGCode::update_custom_gcode_per_print_z_from_config(info, config);
CustomGCode::check_mode_for_custom_gcode_per_print_z(info);
}
sort_remove_duplicates(config_substitutions->substitutions);
return model;
@ -200,8 +235,10 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances();
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
for (CustomGCode::Info& info : model.custom_gcode_per_print_z_vector) {
CustomGCode::update_custom_gcode_per_print_z_from_config(info, config);
CustomGCode::check_mode_for_custom_gcode_per_print_z(info);
}
handle_legacy_sla(*config);
@ -374,8 +411,10 @@ double Model::max_z() const
unsigned int Model::update_print_volume_state(const BuildVolume &build_volume)
{
unsigned int num_printable = 0;
s_multiple_beds.clear_inst_map();
for (ModelObject* model_object : this->objects)
num_printable += model_object->update_instances_print_volume_state(build_volume);
s_multiple_beds.inst_map_updated();
return num_printable;
}
@ -1594,11 +1633,15 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume
OUTSIDE = 2
};
for (ModelInstance* model_instance : this->instances) {
int bed_idx = -1;
unsigned int inside_outside = 0;
for (const ModelVolume* vol : this->volumes)
if (vol->is_model_part()) {
const Transform3d matrix = model_instance->get_matrix() * vol->get_matrix();
BuildVolume::ObjectState state = build_volume.object_state(vol->mesh().its, matrix.cast<float>(), true /* may be below print bed */);
int bed = -1;
BuildVolume::ObjectState state = build_volume.object_state(vol->mesh().its, matrix.cast<float>(), true /* may be below print bed */, true /*ignore_bottom*/, &bed);
if (bed_idx == -1) // instance will be assigned to the bed the first volume is assigned to.
bed_idx = bed;
if (state == BuildVolume::ObjectState::Inside)
// Volume is completely inside.
inside_outside |= INSIDE;
@ -1617,6 +1660,8 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume
inside_outside == INSIDE ? ModelInstancePVS_Inside : ModelInstancePVS_Fully_Outside;
if (inside_outside == INSIDE)
++num_printable;
if (bed_idx != -1)
s_multiple_beds.set_instance_bed(model_instance->id(), model_instance->printable, bed_idx);
}
return num_printable;
}

View File

@ -1253,7 +1253,7 @@ private:
// Note: The following class does not have to inherit from ObjectID, it is currently
// only used for arrangement. It might be good to refactor this in future.
class ModelWipeTower final : public ObjectBase
class ModelWipeTower
{
public:
Vec2d position = Vec2d(180., 140.);
@ -1265,25 +1265,9 @@ public:
// Assignment operator does not touch the ID!
ModelWipeTower& operator=(const ModelWipeTower& rhs) { position = rhs.position; rotation = rhs.rotation; return *this; }
private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
friend class Model;
// Constructors to be only called by derived classes.
// Default constructor to assign a unique ID.
explicit ModelWipeTower() {}
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
// by an existing ID copied from elsewhere.
explicit ModelWipeTower(int) : ObjectBase(-1) {}
// Copy constructor copies the ID.
explicit ModelWipeTower(const ModelWipeTower &cfg) = default;
// Disabled methods.
ModelWipeTower(ModelWipeTower &&rhs) = delete;
ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete;
// For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object.
template<typename Archive> void serialize(Archive &ar) { ar(position, rotation); }
};
@ -1301,12 +1285,26 @@ public:
ModelMaterialMap materials;
// Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation).
ModelObjectPtrs objects;
ModelWipeTower& wipe_tower();
const ModelWipeTower& wipe_tower() const;
const ModelWipeTower& wipe_tower(const int bed_index) const;
ModelWipeTower& wipe_tower(const int bed_index);
std::vector<ModelWipeTower>& get_wipe_tower_vector() { return wipe_tower_vector; }
const std::vector<ModelWipeTower>& get_wipe_tower_vector() const { return wipe_tower_vector; }
CustomGCode::Info& custom_gcode_per_print_z();
const CustomGCode::Info& custom_gcode_per_print_z() const;
std::vector<CustomGCode::Info>& get_custom_gcode_per_print_z_vector() { return custom_gcode_per_print_z_vector; }
private:
// Wipe tower object.
ModelWipeTower wipe_tower;
std::vector<ModelWipeTower> wipe_tower_vector = std::vector<ModelWipeTower>(MAX_NUMBER_OF_BEDS);
// Extensions for color print
CustomGCode::Info custom_gcode_per_print_z;
std::vector<CustomGCode::Info> custom_gcode_per_print_z_vector = std::vector<CustomGCode::Info>(MAX_NUMBER_OF_BEDS);
public:
// Default constructor assigns a new ID to the model.
Model() { assert(this->id().valid()); }
~Model() { this->clear_objects(); this->clear_materials(); }
@ -1407,8 +1405,7 @@ private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
template<class Archive> void serialize(Archive &ar) {
Internal::StaticSerializationWrapper<ModelWipeTower> wipe_tower_wrapper(wipe_tower);
ar(materials, objects, wipe_tower_wrapper);
ar(materials, objects, wipe_tower_vector);
}
};

View File

@ -0,0 +1,371 @@
#include "MultipleBeds.hpp"
#include "BuildVolume.hpp"
#include "Model.hpp"
#include "Print.hpp"
#include <cassert>
namespace Slic3r {
MultipleBeds s_multiple_beds;
bool s_reload_preview_after_switching_beds = false;
bool s_beds_just_switched = false;
namespace BedsGrid {
Index grid_coords_abs2index(GridCoords coords) {
coords = {std::abs(coords.x()), std::abs(coords.y())};
const int x{coords.x() + 1};
const int y{coords.y() + 1};
const int a{std::max(x, y)};
if (x == a && y == a) {
return a*a - 1;
} else if (x == a) {
return a*a - 2 * (a - 1) + coords.y() - 1;
} else {
assert(y == a);
return a*a - (a - 1) + coords.x() - 1;
}
}
const int quadrant_offset{std::numeric_limits<int>::max() / 4};
Index grid_coords2index(const GridCoords &coords) {
const int index{grid_coords_abs2index(coords)};
if (index >= quadrant_offset) {
throw std::runtime_error("Object is too far from center!");
}
if (coords.x() >= 0 && coords.y() >= 0) {
return index;
} else if (coords.x() >= 0 && coords.y() < 0) {
return quadrant_offset + index;
} else if (coords.x() < 0 && coords.y() >= 0) {
return 2*quadrant_offset + index;
} else {
return 3*quadrant_offset + index;
}
}
GridCoords index2grid_coords(Index index) {
if (index < 0) {
throw std::runtime_error{"Negative bed index cannot be translated to coords!"};
}
const int quadrant{index / quadrant_offset};
index = index % quadrant_offset;
GridCoords result{GridCoords::Zero()};
if (index == 0) {
return result;
}
int id = index;
++id;
int a = 1;
while ((a+1)*(a+1) < id)
++a;
id = id - a*a;
result.x()=a;
result.y()=a;
if (id <= a)
result.y() = id-1;
else
result.x() = id-a-1;
if (quadrant == 1) {
result.y() = -result.y();
} else if (quadrant == 2) {
result.x() = -result.x();
} else if (quadrant == 3) {
result.y() = -result.y();
result.x() = -result.x();
} else if (quadrant != 0){
throw std::runtime_error{"Impossible bed index > max int!"};
}
return result;
}
}
Vec3d MultipleBeds::get_bed_translation(int id) const
{
if (id == 0)
return Vec3d::Zero();
int x = 0;
int y = 0;
if (m_legacy_layout)
x = id;
else {
BedsGrid::GridCoords coords{BedsGrid::index2grid_coords(id)};
x = coords.x();
y = coords.y();
}
// As for the m_legacy_layout switch, see comments at definition of bed_gap_relative.
Vec2d gap = bed_gap();
double gap_x = (m_legacy_layout ? m_build_volume_bb.size().x() * (2./10.) : gap.x());
return Vec3d(x * (m_build_volume_bb.size().x() + gap_x),
y * (m_build_volume_bb.size().y() + gap.y()), // When using legacy layout, y is zero anyway.
0.);
}
void MultipleBeds::clear_inst_map()
{
m_inst_to_bed.clear();
m_occupied_beds_cache.fill(false);
}
void MultipleBeds::set_instance_bed(ObjectID id, bool printable, int bed_idx)
{
assert(bed_idx < get_max_beds());
m_inst_to_bed[id] = bed_idx;
if (printable)
m_occupied_beds_cache[bed_idx] = true;
}
void MultipleBeds::inst_map_updated()
{
int max_bed_idx = 0;
for (const auto& [obj_id, bed_idx] : m_inst_to_bed)
max_bed_idx = std::max(max_bed_idx, bed_idx);
if (m_number_of_beds != max_bed_idx + 1) {
m_number_of_beds = max_bed_idx + 1;
m_active_bed = m_number_of_beds - 1;
request_next_bed(false);
}
if (m_active_bed >= m_number_of_beds)
m_active_bed = m_number_of_beds - 1;
}
void MultipleBeds::request_next_bed(bool show)
{
m_show_next_bed = (get_number_of_beds() < get_max_beds() ? show : false);
}
void MultipleBeds::set_active_bed(int i)
{
assert(i < get_max_beds());
if (i<m_number_of_beds)
m_active_bed = i;
}
void MultipleBeds::move_active_to_first_bed(Model& model, const BuildVolume& build_volume, bool to_or_from) const
{
static std::vector<std::pair<Vec3d, bool>> old_state;
size_t i = 0;
assert(! to_or_from || old_state.empty());
for (ModelObject* mo : model.objects) {
for (ModelInstance* mi : mo->instances) {
if (to_or_from) {
old_state.resize(i+1);
old_state[i] = std::make_pair(mi->get_offset(), mi->printable);
if (this->is_instance_on_active_bed(mi->id()))
mi->set_offset(mi->get_offset() - get_bed_translation(get_active_bed()));
else
mi->printable = false;
} else {
mi->set_offset(old_state[i].first);
mi->printable = old_state[i].second;
}
++i;
}
}
if (! to_or_from)
old_state.clear();
}
bool MultipleBeds::is_instance_on_active_bed(ObjectID id) const
{
auto it = m_inst_to_bed.find(id);
return (it != m_inst_to_bed.end() && it->second == m_active_bed);
}
bool MultipleBeds::is_glvolume_on_thumbnail_bed(const Model& model, int obj_idx, int instance_idx) const
{
if (obj_idx < 0 || instance_idx < 0 || obj_idx >= int(model.objects.size()) || instance_idx >= int(model.objects[obj_idx]->instances.size()))
return false;
auto it = m_inst_to_bed.find(model.objects[obj_idx]->instances[instance_idx]->id());
if (it == m_inst_to_bed.end())
return false;
return (m_bed_for_thumbnails_generation < 0 || it->second == m_bed_for_thumbnails_generation);
}
void MultipleBeds::update_shown_beds(Model& model, const BuildVolume& build_volume, bool only_remove /*=false*/) {
const int original_number_of_beds = m_number_of_beds;
const int stash_active = get_active_bed();
if (! only_remove)
m_number_of_beds = get_max_beds();
model.update_print_volume_state(build_volume);
const int max_bed{std::accumulate(
this->m_inst_to_bed.begin(), this->m_inst_to_bed.end(), 0,
[](const int max_so_far, const std::pair<ObjectID, int> &value){
return std::max(max_so_far, value.second);
}
)};
m_number_of_beds = std::min(this->get_max_beds(), max_bed + 1);
model.update_print_volume_state(build_volume);
set_active_bed(m_number_of_beds != original_number_of_beds ? 0 : stash_active);
}
bool MultipleBeds::rearrange_after_load(Model& model, const BuildVolume& build_volume, std::function<void()> update_fn)
{
int original_number_of_beds = m_number_of_beds;
int stash_active = get_active_bed();
Slic3r::ScopeGuard guard([&]() {
m_legacy_layout = false;
m_number_of_beds = get_max_beds();
model.update_print_volume_state(build_volume);
int max_bed = 0;
for (const auto& [oid, bed_id] : m_inst_to_bed)
max_bed = std::max(bed_id, max_bed);
m_number_of_beds = std::min(get_max_beds(), max_bed + 1);
model.update_print_volume_state(build_volume);
request_next_bed(false);
set_active_bed(m_number_of_beds != original_number_of_beds ? 0 : stash_active);
update_fn();
});
m_legacy_layout = true;
int abs_max = get_max_beds();
while (true) {
// This is to ensure that even objects on linear bed with higher than
// allowed index will be rearranged.
m_number_of_beds = abs_max;
model.update_print_volume_state(build_volume);
int max_bed = 0;
for (const auto& [oid, bed_id] : m_inst_to_bed)
max_bed = std::max(bed_id, max_bed);
if (max_bed + 1 < abs_max)
break;
abs_max += get_max_beds();
}
m_number_of_beds = 1;
m_legacy_layout = false;
int max_bed = 0;
// Check that no instances are out of any bed.
std::map<ObjectID, std::pair<ModelInstance*, int>> id_to_ptr_and_bed;
for (ModelObject* mo : model.objects) {
for (ModelInstance* mi : mo->instances) {
auto it = m_inst_to_bed.find(mi->id());
if (it == m_inst_to_bed.end()) {
// An instance is outside. Do not rearrange anything,
// that could create collisions.
return false;
}
id_to_ptr_and_bed[mi->id()] = std::make_pair(mi, it->second);
max_bed = std::max(max_bed, it->second);
}
}
// Now do the rearrangement
m_number_of_beds = max_bed + 1;
assert(m_number_of_beds <= get_max_beds());
if (m_number_of_beds == 1)
return false;
// All instances are on some bed, at least two are used.
// Move everything as if its bed was in the first position.
for (auto& [oid, mi_and_bed] : id_to_ptr_and_bed) {
auto& [mi, bed_idx] = mi_and_bed;
m_legacy_layout = true;
mi->set_offset(mi->get_offset() - get_bed_translation(bed_idx));
m_legacy_layout = false;
mi->set_offset(mi->get_offset() + get_bed_translation(bed_idx));
}
return true;
}
Vec2d MultipleBeds::bed_gap() const
{
// This is the only function that defines how far apart should the beds be. Used in scene and arrange.
// Note that the spacing is momentarily switched to legacy value of 2/10 when a project is loaded.
// Slicers before 2.9.0 used this value for arrange, and there are existing projects with objects spaced that way (controlled by the m_legacy_layout flag).
// TOUCHING THIS WILL BREAK LOADING OF EXISTING PROJECTS !!!
double gap = std::min(100., m_build_volume_bb.size().norm() * (3./10.));
return Vec2d::Ones() * gap;
}
bool MultipleBeds::is_bed_occupied(int i) const
{
return m_occupied_beds_cache[i];
}
Vec2crd MultipleBeds::get_bed_gap() const {
return scaled(Vec2d{bed_gap() / 2.0});
};
void MultipleBeds::ensure_wipe_towers_on_beds(Model& model, const std::vector<std::unique_ptr<Print>>& prints)
{
for (size_t bed_idx = 0; bed_idx < get_number_of_beds(); ++bed_idx) {
ModelWipeTower& mwt = model.get_wipe_tower_vector()[bed_idx];
double depth = prints[bed_idx]->wipe_tower_data().depth;
double width = prints[bed_idx]->wipe_tower_data().width;
double brim = prints[bed_idx]->wipe_tower_data().brim_width;
Polygon plg(Points{Point::new_scale(-brim,-brim), Point::new_scale(brim+width, -brim), Point::new_scale(brim+width, brim+depth), Point::new_scale(-brim, brim+depth)});
plg.rotate(Geometry::deg2rad(mwt.rotation));
plg.translate(scaled(mwt.position));
if (std::all_of(plg.points.begin(), plg.points.end(), [this](const Point& pt) { return !m_build_volume_bb.contains(unscale(pt)); }))
mwt.position = 2*brim*Vec2d(1.,1.);
}
}
#ifdef SLIC3R_GUI
void MultipleBeds::start_autoslice(std::function<void(int, bool)> select_bed_fn)
{
if (is_autoslicing())
return;
m_select_bed_fn = select_bed_fn;
m_autoslicing_original_bed = get_active_bed();
m_autoslicing = true;
}
void MultipleBeds::stop_autoslice(bool restore_original)
{
if (! is_autoslicing())
return;
m_autoslicing = false;
if (restore_original)
m_select_bed_fn(m_autoslicing_original_bed, false);
}
void MultipleBeds::autoslice_next_bed()
{
if (! is_autoslicing())
return;
int next_bed = s_multiple_beds.get_active_bed() + 1;
if (next_bed >= s_multiple_beds.get_number_of_beds())
next_bed = 0;
m_select_bed_fn(next_bed, false);
}
#endif // SLIC3R_GUI
}

View File

@ -0,0 +1,96 @@
#ifndef libslic3r_MultipleBeds_hpp_
#define libslic3r_MultipleBeds_hpp_
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/BoundingBox.hpp"
#include <map>
namespace Slic3r {
class Model;
class BuildVolume;
class PrintBase;
class Print;
extern bool s_reload_preview_after_switching_beds;
extern bool s_beds_just_switched;
namespace BedsGrid {
using GridCoords = Vec2crd;
using Index = int;
Index grid_coords2index(const GridCoords &coords);
GridCoords index2grid_coords(Index index);
}
class MultipleBeds {
public:
MultipleBeds() = default;
static constexpr int get_max_beds() { return MAX_NUMBER_OF_BEDS; };
Vec3d get_bed_translation(int id) const;
void clear_inst_map();
void set_instance_bed(ObjectID id, bool printable, int bed_idx);
void inst_map_updated();
const std::map<ObjectID, int> &get_inst_map() const { return m_inst_to_bed; }
bool is_bed_occupied(int bed_idx) const;
int get_number_of_beds() const { return m_number_of_beds; }
bool should_show_next_bed() const { return m_show_next_bed; }
void request_next_bed(bool show);
int get_active_bed() const { return m_active_bed; }
void set_active_bed(int i);
void move_active_to_first_bed(Model& model, const BuildVolume& build_volume, bool to_or_from) const;
void set_thumbnail_bed_idx(int bed_idx) { m_bed_for_thumbnails_generation = bed_idx; }
int get_thumbnail_bed_idx() const { return m_bed_for_thumbnails_generation; }
bool is_glvolume_on_thumbnail_bed(const Model& model, int obj_idx, int instance_idx) const;
void set_last_hovered_bed(int i) { m_last_hovered_bed = i; }
int get_last_hovered_bed() const { return m_last_hovered_bed; }
void update_shown_beds(Model& model, const BuildVolume& build_volume, bool only_remove = false);
bool rearrange_after_load(Model& model, const BuildVolume& build_volume, std::function<void()> update_fn);
void set_loading_project_flag(bool project) { m_loading_project = project; }
bool get_loading_project_flag() const { return m_loading_project; }
void update_build_volume(const BoundingBoxf& build_volume_bb) {
m_build_volume_bb = build_volume_bb;
}
Vec2d bed_gap() const;
Vec2crd get_bed_gap() const;
void ensure_wipe_towers_on_beds(Model& model, const std::vector<std::unique_ptr<Print>>& prints);
void start_autoslice(std::function<void(int,bool)>);
void stop_autoslice(bool restore_original);
bool is_autoslicing() const { return m_autoslicing; }
void autoslice_next_bed();
private:
bool is_instance_on_active_bed(ObjectID id) const;
int m_number_of_beds = 1;
int m_active_bed = 0;
int m_bed_for_thumbnails_generation = -1;
bool m_show_next_bed = false;
std::map<ObjectID, int> m_inst_to_bed;
std::map<PrintBase*, size_t> m_printbase_to_texture;
std::array<int, MAX_NUMBER_OF_BEDS> m_occupied_beds_cache;
int m_last_hovered_bed = -1;
BoundingBoxf m_build_volume_bb;
bool m_legacy_layout = false;
bool m_loading_project = false;
bool m_autoslicing = false;
int m_autoslicing_original_bed = 0;
std::function<void(int, bool)> m_select_bed_fn;
};
extern MultipleBeds s_multiple_beds;
} // namespace Slic3r
#endif // libslic3r_MultipleBeds_hpp_

View File

@ -8,17 +8,19 @@ namespace Slic3r {
size_t ObjectBase::s_last_id = 0;
// Unique object / instance ID for the wipe tower.
ObjectID wipe_tower_object_id()
{
static ObjectBase mine;
return mine.id();
}
struct WipeTowerId : public ObjectBase {
// Need to inherit because ObjectBase
// destructor is protected.
using ObjectBase::ObjectBase;
};
ObjectID wipe_tower_instance_id()
ObjectID wipe_tower_instance_id(size_t bed_idx)
{
static ObjectBase mine;
return mine.id();
static std::vector<WipeTowerId> mine;
if (bed_idx >= mine.size()) {
mine.resize(bed_idx + 1);
}
return mine[bed_idx].id();
}
ObjectWithTimestamp::Timestamp ObjectWithTimestamp::s_last_timestamp = 1;

View File

@ -86,9 +86,6 @@ private:
static inline ObjectID generate_new_id() { return ObjectID(++ s_last_id); }
static size_t s_last_id;
friend ObjectID wipe_tower_object_id();
friend ObjectID wipe_tower_instance_id();
friend class cereal::access;
friend class Slic3r::UndoRedo::StackImpl;
@ -135,8 +132,7 @@ private:
};
// Unique object / instance ID for the wipe tower.
extern ObjectID wipe_tower_object_id();
extern ObjectID wipe_tower_instance_id();
ObjectID wipe_tower_instance_id(size_t bed_idx);
} // namespace Slic3r

View File

@ -1048,8 +1048,8 @@ void Print::process()
if (this->has_wipe_tower()) {
// These values have to be updated here, not during wipe tower generation.
// When the wipe tower is moved/rotated, it is not regenerated.
m_wipe_tower_data.position = model().wipe_tower.position;
m_wipe_tower_data.rotation_angle = model().wipe_tower.rotation;
m_wipe_tower_data.position = model().wipe_tower().position;
m_wipe_tower_data.rotation_angle = model().wipe_tower().rotation;
}
auto conflictRes = ConflictChecker::find_inter_of_lines_in_diff_objs(objects(), m_wipe_tower_data);
@ -1278,8 +1278,8 @@ Points Print::first_layer_wipe_tower_corners() const
pts.emplace_back(center + r*Vec2d(std::cos(alpha)/cone_x_scale, std::sin(alpha)));
for (Vec2d& pt : pts) {
pt = Eigen::Rotation2Dd(Geometry::deg2rad(model().wipe_tower.rotation)) * pt;
pt += model().wipe_tower.position;
pt = Eigen::Rotation2Dd(Geometry::deg2rad(model().wipe_tower().rotation)) * pt;
pt += model().wipe_tower().position;
pts_scaled.emplace_back(Point(scale_(pt.x()), scale_(pt.y())));
}
}
@ -1553,7 +1553,7 @@ void Print::_make_wipe_tower()
this->throw_if_canceled();
// Initialize the wipe tower.
WipeTower wipe_tower(model().wipe_tower.position.cast<float>(), model().wipe_tower.rotation, m_config, m_default_region_config, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder());
WipeTower wipe_tower(model().wipe_tower().position.cast<float>(), model().wipe_tower().rotation, m_config, m_default_region_config, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder());
// Set the extruder & material properties at the wipe tower object.
for (size_t i = 0; i < m_config.nozzle_diameter.size(); ++ i)

View File

@ -1123,9 +1123,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
}
// Check the position and rotation of the wipe tower.
if (model.wipe_tower != m_model.wipe_tower)
if (model.wipe_tower() != m_model.wipe_tower())
update_apply_status(this->invalidate_step(psSkirtBrim));
m_model.wipe_tower = model.wipe_tower;
m_model.wipe_tower() = model.wipe_tower();
ModelObjectStatusDB model_object_status_db;
@ -1147,18 +1147,18 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
for (const ModelObject *model_object : m_model.objects)
model_object_status_db.add(*model_object, ModelObjectStatus::New);
} else {
if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) {
const CustomGCode::Mode current_mode = m_model.custom_gcode_per_print_z.mode;
const CustomGCode::Mode next_mode = model.custom_gcode_per_print_z.mode;
if (m_model.custom_gcode_per_print_z() != model.custom_gcode_per_print_z()) {
const CustomGCode::Mode current_mode = m_model.custom_gcode_per_print_z().mode;
const CustomGCode::Mode next_mode = model.custom_gcode_per_print_z().mode;
const bool multi_extruder_differ = (current_mode == next_mode) && (current_mode == CustomGCode::MultiExtruder || next_mode == CustomGCode::MultiExtruder);
// Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering.
// FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable
// to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same.
const bool tool_change_differ = num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes, CustomGCode::ToolChange);
const bool tool_change_differ = num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z().gcodes, model.custom_gcode_per_print_z().gcodes, CustomGCode::ToolChange);
// For multi-extruder printers, we perform a tool change before a color change.
// So, in that case, we must invalidate tool ordering and wipe tower even if custom color change g-codes differ.
const bool color_change_differ = num_extruders > 1 && (next_mode == CustomGCode::MultiExtruder) && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes, CustomGCode::ColorChange);
const bool color_change_differ = num_extruders > 1 && (next_mode == CustomGCode::MultiExtruder) && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z().gcodes, model.custom_gcode_per_print_z().gcodes, CustomGCode::ColorChange);
update_apply_status(
(num_extruders_changed || tool_change_differ || multi_extruder_differ || color_change_differ) ?
@ -1166,7 +1166,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
this->invalidate_steps({ psWipeTower, psGCodeExport }) :
// There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering.
this->invalidate_step(psGCodeExport));
m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
m_model.custom_gcode_per_print_z() = model.custom_gcode_per_print_z();
}
if (model_object_list_equal(m_model, model)) {
// The object list did not change.

View File

@ -22,6 +22,8 @@
#include <boost/filesystem/path.hpp>
#include <boost/log/trivial.hpp>
#include "libslic3r/MultipleBeds.hpp"
// #define SLAPRINT_DO_BENCHMARK
#ifdef SLAPRINT_DO_BENCHMARK
@ -298,6 +300,16 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con
if (! material_diff.empty())
update_apply_status(this->invalidate_state_by_config_options(material_diff, invalidate_all_model_objects));
// Multiple beds hack: We currently use one SLAPrint for all beds. It must be invalidated
// when beds are switched. If not done explicitly, supports from previously sliced object
// might end up with wrong offset.
static int last_bed_idx = s_multiple_beds.get_active_bed();
int current_bed = s_multiple_beds.get_active_bed();
if (current_bed != last_bed_idx) {
invalidate_all_model_objects = true;
last_bed_idx = current_bed;
}
// Apply variables to placeholder parser. The placeholder parser is currently used
// only to generate the output file name.
if (! placeholder_parser_diff.empty()) {

View File

@ -115,6 +115,8 @@ using deque =
template<typename T, typename Q>
inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); }
constexpr size_t MAX_NUMBER_OF_BEDS = 9;
enum Axis {
X=0,
Y,

View 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)

View File

@ -5,11 +5,11 @@
#ifndef ARRANGE2_HPP
#define ARRANGE2_HPP
#include <libslic3r/MinAreaBoundingBox.hpp>
#include <arrange/NFP/NFPArrangeItemTraits.hpp>
#include "Scene.hpp"
#include "Items/MutableItemTraits.hpp"
#include "Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/MinAreaBoundingBox.hpp"
namespace Slic3r { namespace arr2 {

View File

@ -9,7 +9,7 @@
#include <map>
#include <any>
#include "libslic3r/Arrange/Core/DataStoreTraits.hpp"
#include <arrange/DataStoreTraits.hpp>
namespace Slic3r { namespace arr2 {

View File

@ -20,24 +20,27 @@
#include <cassert>
#include <cstddef>
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/AnyPtr.hpp"
#include "libslic3r/Arrange/Core/PackingContext.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/NFP/NFP.hpp"
#include "libslic3r/Arrange/Items/MutableItemTraits.hpp"
#include "libslic3r/Arrange/Arrange.hpp"
#include "libslic3r/Arrange/Tasks/ArrangeTask.hpp"
#include "libslic3r/Arrange/Tasks/FillBedTask.hpp"
#include "libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp"
#include "libslic3r/Arrange/Items/ArbitraryDataStore.hpp"
#include "libslic3r/Arrange/Core/ArrangeBase.hpp"
#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/DataStoreTraits.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/libslic3r.h"
#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;
@ -154,6 +157,7 @@ private:
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;
@ -180,9 +184,11 @@ public:
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; }
@ -239,6 +245,11 @@ template<> struct ArrangeItemTraits_<ArrangeItem>
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)
@ -255,6 +266,11 @@ template<> struct ArrangeItemTraits_<ArrangeItem>
{
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.

View File

@ -5,10 +5,10 @@
#ifndef MutableItemTraits_HPP
#define MutableItemTraits_HPP
#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/DataStoreTraits.hpp"
#include <libslic3r/ExPolygon.hpp>
#include "libslic3r/ExPolygon.hpp"
#include <arrange/ArrangeItemTraits.hpp>
#include <arrange/DataStoreTraits.hpp>
namespace Slic3r { namespace arr2 {

View File

@ -11,21 +11,23 @@
#include <utility>
#include <vector>
#include "libslic3r/Arrange/Core/PackingContext.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/NFP/NFP.hpp"
#include "libslic3r/Arrange/Arrange.hpp"
#include "libslic3r/Arrange/Tasks/ArrangeTask.hpp"
#include "libslic3r/Arrange/Tasks/FillBedTask.hpp"
#include "libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#include "MutableItemTraits.hpp"
#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/Point.hpp"
#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;
@ -37,6 +39,7 @@ class SimpleArrangeItem {
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;
@ -50,11 +53,15 @@ public:
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;

View File

@ -5,10 +5,10 @@
#ifndef TRAFOONLYARRANGEITEM_HPP
#define TRAFOONLYARRANGEITEM_HPP
#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp"
#include <arrange/ArrangeItemTraits.hpp>
#include "libslic3r/Arrange/Items/ArbitraryDataStore.hpp"
#include "libslic3r/Arrange/Items/MutableItemTraits.hpp"
#include "ArbitraryDataStore.hpp"
#include "MutableItemTraits.hpp"
namespace Slic3r { namespace arr2 {
@ -17,6 +17,7 @@ class TrafoOnlyArrangeItem {
int m_priority = 0;
Vec2crd m_translation = Vec2crd::Zero();
double m_rotation = 0.;
std::optional<int> m_bed_constraint;
ArbitraryDataStore m_datastore;
@ -28,13 +29,15 @@ public:
: 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_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; }

View File

@ -5,12 +5,12 @@
#ifndef MODELARRANGE_HPP
#define MODELARRANGE_HPP
#include <libslic3r/Arrange/Scene.hpp>
#include <stddef.h>
#include <vector>
#include <cstddef>
#include "libslic3r/Arrange/Core/Beds.hpp"
#include <arrange/Beds.hpp>
#include "Scene.hpp"
namespace Slic3r {

View File

@ -19,16 +19,18 @@
#include <vector>
#include <cstddef>
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/AnyPtr.hpp"
#include "libslic3r/Arrange/ArrangeSettingsView.hpp"
#include "libslic3r/Arrange/SegmentedRectangleBed.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/libslic3r.h"
#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 {
@ -99,6 +101,8 @@ public:
// 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
@ -190,6 +194,14 @@ inline BoundingBox bounding_box(const ExtendedBed &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
@ -238,9 +250,9 @@ public:
return std::move(static_cast<Subclass&>(*this));
}
Subclass &&set_bed(const Points &pts)
Subclass &&set_bed(const Points &pts, const Vec2crd &gap)
{
m_bed = arr2::to_arrange_bed(pts);
m_bed = arr2::to_arrange_bed(pts, gap);
return std::move(static_cast<Subclass&>(*this));
}

View File

@ -17,17 +17,19 @@
#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"
#include "Core/ArrangeItemTraits.hpp"
#include "libslic3r/AnyPtr.hpp"
#include "libslic3r/Arrange/Core/Beds.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"
namespace Slic3r {
@ -42,7 +44,7 @@ class DynamicPrintConfig;
namespace arr2 {
using SelectionPredicate = std::function<bool()>;
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
@ -55,6 +57,7 @@ public:
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
@ -110,7 +113,7 @@ public:
virtual std::vector<bool> selected_objects() const = 0;
virtual std::vector<bool> selected_instances(int obj_id) const = 0;
virtual bool is_wipe_tower() const = 0;
virtual bool is_wipe_tower_selected(int wipe_tower_index) const = 0;
};
class FixedSelection : public Slic3r::arr2::SelectionMask
@ -138,7 +141,7 @@ public:
std::vector<bool>{};
}
bool is_wipe_tower() const override { return m_wp; }
bool is_wipe_tower_selected(int) const override { return m_wp; }
};
// Common part of any Arrangeable which is a wipe tower
@ -148,13 +151,16 @@ struct ArrangeableWipeTowerBase: public Arrangeable
Polygon poly;
SelectionPredicate selection_pred;
int bed_index{0};
ArrangeableWipeTowerBase(
const ObjectID &objid,
Polygon shape,
SelectionPredicate selection_predicate = [] { return false; })
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)}
{}
@ -174,7 +180,7 @@ struct ArrangeableWipeTowerBase: public Arrangeable
bool is_selected() const override
{
return selection_pred();
return selection_pred(bed_index);
}
int get_bed_index() const override;
@ -182,6 +188,10 @@ struct ArrangeableWipeTowerBase: public Arrangeable
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
@ -194,15 +204,19 @@ class SceneBuilder;
struct InstPos { size_t obj_idx = 0, inst_idx = 0; };
using BedConstraints = std::map<ObjectID, int>;
// Implementing ArrangeableModel interface for PrusaSlicer's Model, ModelObject, ModelInstance data
// hierarchy
class ArrangeableSlicerModel: public ArrangeableModel
{
protected:
AnyPtr<Model> m_model;
AnyPtr<WipeTowerHandler> m_wth; // Determines how wipe tower is handled
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;
@ -234,7 +248,9 @@ class SceneBuilder: public SceneBuilderBase<SceneBuilder>
{
protected:
AnyPtr<Model> m_model;
AnyPtr<WipeTowerHandler> m_wipetower_handler;
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;
@ -259,18 +275,24 @@ public:
using SceneBuilderBase<SceneBuilder>::set_bed;
SceneBuilder &&set_bed(const DynamicPrintConfig &cfg);
SceneBuilder &&set_bed(const Print &print);
SceneBuilder &&set_bed(const DynamicPrintConfig &cfg, const Vec2crd &gap);
SceneBuilder &&set_bed(const Print &print, const Vec2crd &gap);
SceneBuilder && set_wipe_tower_handler(WipeTowerHandler &wth)
SceneBuilder && set_wipe_tower_handlers(std::vector<AnyPtr<WipeTowerHandler>> &&handlers)
{
m_wipetower_handler = &wth;
m_wipetower_handlers = std::move(handlers);
return std::move(*this);
}
SceneBuilder && set_wipe_tower_handler(AnyPtr<WipeTowerHandler> wth)
SceneBuilder && set_bed_constraints(BedConstraints &&bed_constraints)
{
m_wipetower_handler = std::move(wth);
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);
}
@ -295,13 +317,6 @@ public:
void build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel);
};
struct MissingWipeTowerHandler : public WipeTowerHandler
{
void visit(std::function<void(Arrangeable &)>) override {}
void visit(std::function<void(const Arrangeable &)>) const override {}
void set_selection_predicate(std::function<bool()>) override {}
};
// Only a physical bed, non-zero bed index values are discarded.
class PhysicalOnlyVBedHandler final : public VirtualBedHandler
{
@ -368,29 +383,15 @@ public:
class GridStriderVBedHandler: public VirtualBedHandler
{
// This vbed handler defines a grid of virtual beds with a large number
// of columns so that it behaves as XStrider for regular cases.
// The goal is to handle objects residing at world coordinates
// not representable with scaled coordinates. Combining XStrider with
// YStrider takes care of the X and Y axis to be mapped into the physical
// bed's coordinate region (which is representable in scaled coords)
static const int Cols;
static const int HalfCols;
static const int Offset;
XStriderVBedHandler m_xstrider;
YStriderVBedHandler m_ystrider;
public:
GridStriderVBedHandler(const BoundingBox &bedbb,
coord_t gap)
: m_xstrider{bedbb, gap}
, m_ystrider{bedbb, gap}
GridStriderVBedHandler(const BoundingBox &bedbb, const Vec2crd &gap)
: m_xstrider{bedbb, gap.x()}
, m_ystrider{bedbb, gap.y()}
{}
Vec2i raw2grid(int bedidx) const;
int grid2raw(const Vec2i &crd) const;
int get_bed_index(const VBedPlaceable &obj) const override;
bool assign_bed(VBedPlaceable &inst, int bed_idx) override;
@ -451,13 +452,15 @@ class ArrangeableModelInstance : public Arrangeable, VBedPlaceable
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)
: m_mi{mi}, m_vbedh{vbedh}, m_selmask{selmask}, m_pos_within_model{pos}
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);
}
@ -474,6 +477,8 @@ public:
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
@ -492,12 +497,14 @@ 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_po{po}, m_arrbl{arrbl}, m_inst_trafo{inst_tr}, m_bed_constraint(bed_constraint)
{}
ObjectID id() const override { return m_arrbl->id(); }
@ -519,6 +526,8 @@ public:
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(); }

View File

@ -5,7 +5,7 @@
#ifndef SEGMENTEDRECTANGLEBED_HPP
#define SEGMENTEDRECTANGLEBED_HPP
#include "libslic3r/Arrange/Core/Beds.hpp"
#include <arrange/Beds.hpp>
namespace Slic3r { namespace arr2 {
@ -20,14 +20,16 @@ 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}, pivot{pivot}
: segments{segments_x, segments_y}, bb{bb}, gap{gap}, pivot{pivot}
{}
size_t segments_x() const noexcept { return segments.x(); }
@ -41,13 +43,16 @@ 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}
: bb{b},
gap{gap}
{}
size_t segments_x() const noexcept { return SegX; }
@ -62,10 +67,11 @@ struct SegmentedRectangleBed<std::integral_constant<size_t, SegX>,
std::integral_constant<RectPivots, pivot>>
{
BoundingBox bb;
Vec2crd gap;
SegmentedRectangleBed() = default;
explicit SegmentedRectangleBed(const BoundingBox &b) : bb{b} {}
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; }
@ -92,6 +98,12 @@ 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)
{

View File

@ -5,8 +5,8 @@
#ifndef ARRANGETASK_HPP
#define ARRANGETASK_HPP
#include "libslic3r/Arrange/Arrange.hpp"
#include "libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp"
#include <arrange-wrapper/Arrange.hpp>
#include <arrange-wrapper/Items/TrafoOnlyArrangeItem.hpp>
namespace Slic3r { namespace arr2 {

View File

@ -5,9 +5,9 @@
#ifndef FILLBEDTASK_HPP
#define FILLBEDTASK_HPP
#include "MultiplySelectionTask.hpp"
#include <arrange-wrapper/Arrange.hpp>
#include "libslic3r/Arrange/Arrange.hpp"
#include "MultiplySelectionTask.hpp"
namespace Slic3r { namespace arr2 {

View File

@ -5,8 +5,8 @@
#ifndef MULTIPLYSELECTIONTASK_HPP
#define MULTIPLYSELECTIONTASK_HPP
#include "libslic3r/Arrange/Arrange.hpp"
#include "libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp"
#include <arrange-wrapper/Arrange.hpp>
#include <arrange-wrapper/Items/TrafoOnlyArrangeItem.hpp>
namespace Slic3r { namespace arr2 {

View File

@ -8,25 +8,24 @@
#include <random>
#include <map>
#include "Arrange.hpp"
#include <libslic3r/Execution/ExecutionTBB.hpp>
#include <libslic3r/Geometry/ConvexHull.hpp>
#include "Core/ArrangeBase.hpp"
#include "Core/ArrangeFirstFit.hpp"
#include "Core/NFP/PackStrategyNFP.hpp"
#include "Core/NFP/Kernels/TMArrangeKernel.hpp"
#include "Core/NFP/Kernels/GravityKernel.hpp"
#include "Core/NFP/RectangleOverfitPackingStrategy.hpp"
#include "Core/Beds.hpp"
#include <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 "Items/MutableItemTraits.hpp"
#include <arrange-wrapper/Arrange.hpp>
#include <arrange-wrapper/Items/MutableItemTraits.hpp>
#include <arrange-wrapper/SegmentedRectangleBed.hpp>
#include "SegmentedRectangleBed.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#ifndef NDEBUG
#include "Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp"
#include <arrange/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp>
#endif
namespace Slic3r { namespace arr2 {
@ -46,7 +45,7 @@ void arrange(SelectionStrategy &&selstrategy,
// Dispatch:
arrange(std::forward<SelectionStrategy>(selstrategy),
std::forward<PackStrategy>(packingstrategy), items, fixed,
RectangleBed{bed.bb}, SelStrategyTag<SelectionStrategy>{});
RectangleBed{bed.bb, bed.gap}, SelStrategyTag<SelectionStrategy>{});
std::vector<int> bed_indices = get_bed_indices(items, fixed);
std::map<int, BoundingBox> pilebb;
@ -399,6 +398,7 @@ ArrItem ConvexItemConverter<ArrItem>::convert(const Arrangeable &arrbl,
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>)
@ -416,6 +416,7 @@ ArrItem AdvancedItemConverter<ArrItem>::convert(const Arrangeable &arrbl,
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});

View File

@ -2,11 +2,12 @@
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "ArrangeSettingsDb_AppCfg.hpp"
#include <arrange-wrapper/ArrangeSettingsDb_AppCfg.hpp>
#include "LocalesUtils.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Arrange/ArrangeSettingsView.hpp"
#include <LocalesUtils.hpp>
#include <libslic3r/AppConfig.hpp>
#include <arrange-wrapper/ArrangeSettingsView.hpp>
namespace Slic3r {

View File

@ -2,16 +2,17 @@
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "ArrangeItem.hpp"
#include <numeric>
#include "libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp"
#include "libslic3r/Arrange/ArrangeImpl.hpp" // IWYU pragma: keep
#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp" // IWYU pragma: keep
#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp" // IWYU pragma: keep
#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp" // IWYU pragma: keep
#include "libslic3r/Geometry/ConvexHull.hpp"
#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 {
@ -159,6 +160,7 @@ ArrangeItem &ArrangeItem::operator=(const ArrangeItem &other)
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;
@ -190,6 +192,7 @@ ArrangeItem &ArrangeItem::operator=(ArrangeItem &&other) noexcept
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;

View File

@ -2,11 +2,11 @@
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "SimpleArrangeItem.hpp"
#include "libslic3r/Arrange/ArrangeImpl.hpp" // IWYU pragma: keep
#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp" // IWYU pragma: keep
#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp" // IWYU pragma: keep
#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp" // IWYU pragma: keep
#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 {

View File

@ -2,16 +2,17 @@
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "ModelArrange.hpp"
#include <libslic3r/Arrange/SceneBuilder.hpp>
#include <libslic3r/Arrange/Items/ArrangeItem.hpp>
#include <libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp>
#include <libslic3r/Model.hpp>
#include <utility>
#include "libslic3r/Arrange/ArrangeSettingsView.hpp"
#include "libslic3r/Arrange/Scene.hpp"
#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 {

View File

@ -2,12 +2,10 @@
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Scene.hpp"
#include "Items/ArrangeItem.hpp"
#include "Tasks/ArrangeTask.hpp"
#include "Tasks/FillBedTask.hpp"
#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 {

View File

@ -5,27 +5,29 @@
#ifndef SCENEBUILDER_CPP
#define SCENEBUILDER_CPP
#include "SceneBuilder.hpp"
#include <cmath>
#include <limits>
#include <numeric>
#include <cstdlib>
#include <iterator>
#include "libslic3r/Model.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#include "libslic3r/Arrange/Scene.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 "libslic3r/Arrange/Core/Beds.hpp"
#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 {
@ -189,7 +191,7 @@ void SceneBuilder::build_scene(Scene &sc) &&
if (m_fff_print && !m_sla_print) {
if (is_infinite_bed(m_bed)) {
set_bed(*m_fff_print);
set_bed(*m_fff_print, Vec2crd::Zero());
} else {
set_brim_and_skirt();
}
@ -211,28 +213,29 @@ void SceneBuilder::build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel
m_vbed_handler = VirtualBedHandler::create(m_bed);
}
if (!m_wipetower_handler) {
m_wipetower_handler = std::make_unique<MissingWipeTowerHandler>();
}
if (m_fff_print && !m_xl_printer)
m_xl_printer = is_XL_printer(m_fff_print->config());
bool has_wipe_tower = false;
m_wipetower_handler->visit(
[&has_wipe_tower](const Arrangeable &arrbl) { has_wipe_tower = true; });
const bool has_wipe_tower = !m_wipetower_handlers.empty();
if (m_xl_printer && !has_wipe_tower) {
m_bed = XLBed{bounding_box(m_bed)};
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_wth = std::move(m_wipetower_handler);
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);
amodel.m_wth->set_selection_predicate(
[&amodel] { return amodel.m_selmask->is_wipe_tower(); });
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
@ -331,40 +334,19 @@ Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const
return tr;
}
const int GridStriderVBedHandler::Cols =
2 * static_cast<int>(std::sqrt(std::numeric_limits<int>::max()) / 2);
const int GridStriderVBedHandler::HalfCols = Cols / 2;
const int GridStriderVBedHandler::Offset = HalfCols + Cols * HalfCols;
Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const
{
bed_idx += Offset;
Vec2i ret{bed_idx % Cols - HalfCols, bed_idx / Cols - HalfCols};
return ret;
}
int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const
{
// Overlapping virtual beds will happen if the crd values exceed limits
assert((crd.x() < HalfCols - 1 && crd.x() >= -HalfCols) &&
(crd.y() < HalfCols - 1 && crd.y() >= -HalfCols));
return (crd.x() + HalfCols) + Cols * (crd.y() + HalfCols) - Offset;
}
int GridStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const
{
Vec2i crd = {m_xstrider.get_bed_index(obj), m_ystrider.get_bed_index(obj)};
return grid2raw(crd);
return BedsGrid::grid_coords2index(crd);
}
bool GridStriderVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx)
{
Vec2i crd = raw2grid(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());
@ -374,7 +356,7 @@ bool GridStriderVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx)
Transform3d GridStriderVBedHandler::get_physical_bed_trafo(int bed_idx) const
{
Vec2i crd = raw2grid(bed_idx);
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());
@ -465,7 +447,7 @@ SceneBuilder &&SceneBuilder::set_sla_print(AnyPtr<const SLAPrint> mdl_print)
return std::move(*this);
}
SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg)
SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg, const Vec2crd &gap)
{
Points bedpts = get_bed_shape(cfg);
@ -473,19 +455,19 @@ SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg)
m_xl_printer = true;
}
m_bed = arr2::to_arrange_bed(bedpts);
m_bed = arr2::to_arrange_bed(bedpts, gap);
return std::move(*this);
}
SceneBuilder &&SceneBuilder::set_bed(const Print &print)
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)};
m_bed = XLBed{get_extents(bedpts), gap};
} else {
m_bed = arr2::to_arrange_bed(bedpts);
m_bed = arr2::to_arrange_bed(bedpts, gap);
}
set_brim_and_skirt();
@ -499,11 +481,13 @@ SceneBuilder &&SceneBuilder::set_sla_print(const SLAPrint *slaprint)
return std::move(*this);
}
int ArrangeableWipeTowerBase::get_bed_index() const { return PhysicalBedId; }
int ArrangeableWipeTowerBase::get_bed_index() const {
return this->bed_index;
}
bool ArrangeableWipeTowerBase::assign_bed(int bed_idx)
{
return bed_idx == PhysicalBedId;
return bed_idx == this->bed_index;
}
bool PhysicalOnlyVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx)
@ -523,7 +507,9 @@ void ArrangeableSlicerModel::for_each_arrangeable(
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
for (auto &wth : m_wths) {
wth->visit(fn);
}
}
void ArrangeableSlicerModel::for_each_arrangeable(
@ -531,7 +517,9 @@ void ArrangeableSlicerModel::for_each_arrangeable(
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
for (auto &wth : m_wths) {
wth->visit(fn);
}
}
ObjectID ArrangeableSlicerModel::add_arrangeable(const ObjectID &prototype_id)
@ -549,14 +537,43 @@ ObjectID ArrangeableSlicerModel::add_arrangeable(const ObjectID &prototype_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) {
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), self.m_selmask.get(), pos};
fn(ainst);
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;
@ -567,16 +584,23 @@ void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn)
template<class Self, class Fn>
void ArrangeableSlicerModel::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn)
{
if (id == self.m_model->wipe_tower.id()) {
self.m_wth->visit(fn);
return;
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};
ArrangeableModelInstance ainst{
inst,
self.m_vbed_handler.get(),
self.m_selmask.get(),
pos,
get_bed_constraint(id, self.m_bed_constraints)
};
fn(ainst);
}
}
@ -599,22 +623,28 @@ void ArrangeableSLAPrint::for_each_arrangeable_(Self &&self, Fn &&fn)
InstPos pos;
for (auto *obj : self.m_model->objects) {
for (auto *inst : obj->instances) {
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(),
self.m_selmask.get(), pos};
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);
auto obj_id = inst->get_object()->id();
const SLAPrintObject *po =
self.m_slaprint->get_print_object_by_model_object_id(obj_id);
if (po) {
auto &vbh = self.m_vbed_handler;
auto phtr = vbh->get_physical_bed_trafo(vbh->get_bed_index(VBedPlaceableMI{*inst}));
ArrangeableSLAPrintObject ainst_po{po, &ainst, phtr * inst->get_matrix()};
fn(ainst_po);
} else {
fn(ainst);
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;
@ -627,7 +657,9 @@ void ArrangeableSLAPrint::for_each_arrangeable(
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
for (auto &wth : m_wths) {
wth->visit(fn);
}
}
void ArrangeableSLAPrint::for_each_arrangeable(
@ -635,7 +667,9 @@ void ArrangeableSLAPrint::for_each_arrangeable(
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
for (auto &wth : m_wths) {
wth->visit(fn);
}
}
template<class Self, class Fn>
@ -645,7 +679,7 @@ void ArrangeableSLAPrint::visit_arrangeable_(Self &&self, const ObjectID &id, Fn
if (inst) {
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(),
self.m_selmask.get(), pos};
self.m_selmask.get(), pos, std::nullopt};
auto obj_id = inst->get_object()->id();
const SLAPrintObject *po =
@ -654,7 +688,12 @@ void ArrangeableSLAPrint::visit_arrangeable_(Self &&self, const ObjectID &id, Fn
if (po) {
auto &vbh = self.m_vbed_handler;
auto phtr = vbh->get_physical_bed_trafo(vbh->get_bed_index(VBedPlaceableMI{*inst}));
ArrangeableSLAPrintObject ainst_po{po, &ainst, phtr * inst->get_matrix()};
ArrangeableSLAPrintObject ainst_po{
po,
&ainst,
get_bed_constraint(inst->id(), self.m_bed_constraints),
phtr * inst->get_matrix()
};
fn(ainst_po);
} else {
fn(ainst);
@ -925,16 +964,12 @@ std::unique_ptr<VirtualBedHandler> VirtualBedHandler::create(const ExtendedBed &
if (is_infinite_bed(bed)) {
ret = std::make_unique<PhysicalOnlyVBedHandler>();
} else {
// The gap between logical beds expressed in ratio of
// the current bed width.
constexpr double LogicalBedGap = 1. / 10.;
Vec2crd gap;
visit_bed([&gap](auto &rawbed) { gap = bed_gap(rawbed); }, bed);
BoundingBox bedbb;
visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed);
auto bedwidth = bedbb.size().x();
coord_t xgap = LogicalBedGap * bedwidth;
ret = std::make_unique<GridStriderVBedHandler>(bedbb, xgap);
ret = std::make_unique<GridStriderVBedHandler>(bedbb, gap);
}
return ret;

View File

@ -9,7 +9,10 @@
#include <boost/log/trivial.hpp>
#include "ArrangeTask.hpp"
#include <libslic3r/SVG.hpp>
#include <arrange-wrapper/Tasks/ArrangeTask.hpp>
#include <arrange-wrapper/Items/ArrangeItem.hpp>
namespace Slic3r { namespace arr2 {
@ -42,12 +45,6 @@ void extract_selected(ArrangeTask<ArrItem> &task,
<< "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what();
}
});
// If the selection was empty arrange everything
if (task.printable.selected.empty() && task.unprintable.selected.empty()) {
task.printable.selected.swap(task.printable.unselected);
task.unprintable.selected.swap(task.unprintable.unselected);
}
}
template<class ArrItem>

View File

@ -5,12 +5,12 @@
#ifndef FILLBEDTASKIMPL_HPP
#define FILLBEDTASKIMPL_HPP
#include "FillBedTask.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include <boost/log/trivial.hpp>
#include <arrange/NFP/NFPArrangeItemTraits.hpp>
#include <arrange-wrapper/Tasks/FillBedTask.hpp>
namespace Slic3r { namespace arr2 {
template<class ArrItem>
@ -21,8 +21,8 @@ int calculate_items_needed_to_fill_bed(const ExtendedBed &bed,
{
double poly_area = fixed_area(prototype_item);
auto area_sum_fn = [](double s, const auto &itm) {
return s + (get_bed_index(itm) == 0) * fixed_area(itm);
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(),
@ -82,20 +82,27 @@ void extract(FillBedTask<ArrItem> &task,
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](const Arrangeable &arrbl) {
&itm_conv, &bed_constraint](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));
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));
}
} else {
auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON);
task.unselected.emplace_back(std::move(itm));
}
} catch (const EmptyItemOutlineError &ex) {
BOOST_LOG_TRIVIAL(error)
@ -170,7 +177,7 @@ std::unique_ptr<FillBedTaskResult> FillBedTask<ArrItem>::process_native(
void on_packed(ArrItem &itm) override
{
// Stop at the first filler that is not on the physical bed
do_stop = get_bed_index(itm) > PhysicalBedId && get_priority(itm) == 0;
do_stop = get_bed_index(itm) == -1 && get_priority(itm) == 0;
}
} subctl(ctl, *this);
@ -194,12 +201,13 @@ std::unique_ptr<FillBedTaskResult> FillBedTask<ArrItem>::process_native(
auto to_add_range = Range{selected.begin() + selected_existing_count,
selected.end()};
for (auto &itm : to_add_range)
if (get_bed_index(itm) == PhysicalBedId)
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) == PhysicalBedId)
if (get_bed_index(itm) == get_bed_constraint(itm))
result->add_new_item(itm);
return result;

View File

@ -5,7 +5,7 @@
#ifndef MULTIPLYSELECTIONTASKIMPL_HPP
#define MULTIPLYSELECTIONTASKIMPL_HPP
#include "MultiplySelectionTask.hpp"
#include <arrange-wrapper/Tasks/MultiplySelectionTask.hpp>
#include <boost/log/trivial.hpp>

View File

@ -0,0 +1,34 @@
project(slic3r-arrange)
cmake_minimum_required(VERSION 3.13)
add_library(slic3r-arrange
include/arrange/Beds.hpp
include/arrange/ArrangeItemTraits.hpp
include/arrange/PackingContext.hpp
include/arrange/NFP/NFPArrangeItemTraits.hpp
include/arrange/NFP/NFP.hpp
include/arrange/ArrangeBase.hpp
include/arrange/DataStoreTraits.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/NFP/EdgeCache.hpp
include/arrange/NFP/Kernels/KernelTraits.hpp
include/arrange/NFP/NFPConcave_Tesselate.hpp
include/arrange/NFP/Kernels/KernelUtils.hpp
include/arrange/NFP/Kernels/CompactifyKernel.hpp
include/arrange/NFP/Kernels/RectangleOverfitKernelWrapper.hpp
include/arrange/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp
src/Beds.cpp
src/NFP/NFP.cpp
src/NFP/NFPConcave_Tesselate.cpp
src/NFP/EdgeCache.cpp
src/NFP/CircularEdgeIterator.hpp
)
target_include_directories(slic3r-arrange PRIVATE src)
target_include_directories(slic3r-arrange PUBLIC include)
target_link_libraries(slic3r-arrange PUBLIC libslic3r)

View File

@ -8,10 +8,10 @@
#include <iterator>
#include <type_traits>
#include "ArrangeItemTraits.hpp"
#include "PackingContext.hpp"
#include <libslic3r/Point.hpp>
#include "libslic3r/Point.hpp"
#include <arrange/ArrangeItemTraits.hpp>
#include <arrange/PackingContext.hpp>
namespace Slic3r { namespace arr2 {

View File

@ -8,7 +8,7 @@
#include <iterator>
#include <map>
#include <libslic3r/Arrange/Core/ArrangeBase.hpp>
#include <arrange/ArrangeBase.hpp>
namespace Slic3r { namespace arr2 { namespace firstfit {
@ -139,6 +139,10 @@ void arrange(
int bedidx = 0;
while (!was_packed && !is_cancelled()) {
for (; !was_packed && !is_cancelled(); bedidx++) {
const std::optional<int> bed_constraint{get_bed_constraint(*it)};
if (bed_constraint && bedidx != *bed_constraint) {
continue;
}
set_bed_index(*it, bedidx);
auto remaining = Range{std::next(static_cast<SConstIt>(it)),
@ -157,6 +161,13 @@ void arrange(
sel.on_arranged_fn(*it, bed, packed_range, remaining);
} else {
set_bed_index(*it, Unarranged);
if (bed_constraint && bedidx == *bed_constraint) {
// Leave the item as is as it does not fit on the enforced bed.
auto packed_range = Range{sorted_items.cbegin(),
static_cast<SConstIt>(it)};
was_packed = true;
sel.on_arranged_fn(*it, bed, packed_range, remaining);
}
}
}
}

View File

@ -31,6 +31,11 @@ template<class ArrItem, class En = void> struct ArrangeItemTraits_ {
static int get_bed_index(const ArrItem &ap) { return ap.get_bed_index(); }
static std::optional<int> get_bed_constraint(const ArrItem &ap)
{
return ap.get_bed_constraint();
}
static int get_priority(const ArrItem &ap) { return ap.get_priority(); }
// Setters:
@ -43,6 +48,11 @@ template<class ArrItem, class En = void> struct ArrangeItemTraits_ {
static void set_rotation(ArrItem &ap, double v) { ap.set_rotation(v); }
static void set_bed_index(ArrItem &ap, int v) { ap.set_bed_index(v); }
static void set_bed_constraint(ArrItem &ap, std::optional<int> v)
{
ap.set_bed_constraint(v);
}
};
template<class T> using ArrangeItemTraits = ArrangeItemTraits_<StripCVRef<T>>;
@ -69,6 +79,11 @@ template<class T> int get_priority(const T &itm)
return ArrangeItemTraits<T>::get_priority(itm);
}
template<class T> std::optional<int> get_bed_constraint(const T &itm)
{
return ArrangeItemTraits<T>::get_bed_constraint(itm);
}
// Setters:
template<class T> void set_translation(T &itm, const Vec2crd &v)
@ -86,6 +101,11 @@ template<class T> void set_bed_index(T &itm, int v)
ArrangeItemTraits<T>::set_bed_index(itm, v);
}
template<class T> void set_bed_constraint(T &itm, std::optional<int> v)
{
ArrangeItemTraits<T>::set_bed_constraint(itm, v);
}
// Helper functions for arrange items
template<class ArrItem> bool is_arranged(const ArrItem &ap)
{

View File

@ -35,13 +35,18 @@ struct InfiniteBed {
BoundingBox bounding_box(const InfiniteBed &bed);
inline InfiniteBed offset(const InfiniteBed &bed, coord_t) { return bed; }
inline Vec2crd bed_gap(const InfiniteBed &)
{
return Vec2crd::Zero();
}
struct RectangleBed {
BoundingBox bb;
Vec2crd gap;
explicit RectangleBed(const BoundingBox &bedbb) : bb{bedbb} {}
explicit RectangleBed(coord_t w, coord_t h, Point c = {0, 0}):
bb{{c.x() - w / 2, c.y() - h / 2}, {c.x() + w / 2, c.y() + h / 2}}
explicit RectangleBed(const BoundingBox &bedbb, const Vec2crd &gap) : bb{bedbb}, gap{gap} {}
explicit RectangleBed(coord_t w, coord_t h, const Vec2crd &gap = Vec2crd::Zero(), Point c = {0, 0}):
bb{{c.x() - w / 2, c.y() - h / 2}, {c.x() + w / 2, c.y() + h / 2}}, gap{gap}
{}
coord_t width() const { return bb.size().x(); }
@ -54,6 +59,9 @@ inline RectangleBed offset(RectangleBed bed, coord_t v)
bed.bb.offset(v);
return bed;
}
inline Vec2crd bed_gap(const RectangleBed &bed) {
return bed.gap;
}
Polygon to_rectangle(const BoundingBox &bb);
@ -65,16 +73,19 @@ inline Polygon to_rectangle(const RectangleBed &bed)
class CircleBed {
Point m_center;
double m_radius;
Vec2crd m_gap;
public:
CircleBed(): m_center(0, 0), m_radius(NaNd) {}
explicit CircleBed(const Point& c, double r)
CircleBed(): m_center(0, 0), m_radius(NaNd), m_gap(Vec2crd::Zero()) {}
explicit CircleBed(const Point& c, double r, const Vec2crd &g)
: m_center(c)
, m_radius(r)
, m_gap(g)
{}
double radius() const { return m_radius; }
const Point& center() const { return m_center; }
const Vec2crd &gap() const { return m_gap; }
};
// Function to approximate a circle with a convex polygon
@ -89,10 +100,14 @@ inline BoundingBox bounding_box(const CircleBed &bed)
}
inline CircleBed offset(const CircleBed &bed, coord_t v)
{
return CircleBed{bed.center(), bed.radius() + v};
return CircleBed{bed.center(), bed.radius() + v, bed.gap()};
}
inline Vec2crd bed_gap(const CircleBed &bed)
{
return bed.gap();
}
struct IrregularBed { ExPolygons poly; };
struct IrregularBed { ExPolygons poly; Vec2crd gap; };
inline BoundingBox bounding_box(const IrregularBed &bed)
{
return get_extents(bed.poly);
@ -103,6 +118,10 @@ inline IrregularBed offset(IrregularBed bed, coord_t v)
bed.poly = offset_ex(bed.poly, v);
return bed;
}
inline Vec2crd bed_gap(const IrregularBed &bed)
{
return bed.gap;
}
using ArrangeBed =
boost::variant<InfiniteBed, RectangleBed, CircleBed, IrregularBed>;
@ -124,6 +143,15 @@ inline ArrangeBed offset(ArrangeBed bed, coord_t v)
return bed;
}
inline Vec2crd bed_gap(const ArrangeBed &bed)
{
Vec2crd ret;
auto visitor = [&ret](const auto &b) { ret = bed_gap(b); };
boost::apply_visitor(visitor, bed);
return ret;
}
inline double area(const BoundingBox &bb)
{
auto bbsz = bb.size();
@ -187,7 +215,7 @@ inline ExPolygons to_expolygons(const ArrangeBed &bed)
return ret;
}
ArrangeBed to_arrange_bed(const Points &bedpts);
ArrangeBed to_arrange_bed(const Points &bedpts, const Vec2crd &gap);
template<class Bed, class En = void> struct IsRectangular_ : public std::false_type {};
template<> struct IsRectangular_<RectangleBed>: public std::true_type {};

View File

@ -5,8 +5,8 @@
#ifndef GRAVITYKERNEL_HPP
#define GRAVITYKERNEL_HPP
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include <arrange/NFP/NFPArrangeItemTraits.hpp>
#include <arrange/Beds.hpp>
#include "KernelUtils.hpp"

View File

@ -5,7 +5,7 @@
#ifndef KERNELTRAITS_HPP
#define KERNELTRAITS_HPP
#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp"
#include <arrange/ArrangeItemTraits.hpp>
namespace Slic3r { namespace arr2 {

View File

@ -7,9 +7,9 @@
#include <type_traits>
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include "libslic3r/Arrange/Core/DataStoreTraits.hpp"
#include <arrange/NFP/NFPArrangeItemTraits.hpp>
#include <arrange/Beds.hpp>
#include <arrange/DataStoreTraits.hpp>
namespace Slic3r { namespace arr2 {

View File

@ -7,8 +7,8 @@
#include "KernelTraits.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include <arrange/NFP/NFPArrangeItemTraits.hpp>
#include <arrange/Beds.hpp>
namespace Slic3r { namespace arr2 {
@ -74,7 +74,7 @@ struct RectangleOverfitKernelWrapper {
for (auto &fitm : all_items_range(packing_context))
pilebb.merge(fixed_bounding_box(fitm));
return KernelTraits<Kernel>::on_start_packing(k, itm, RectangleBed{binbb},
return KernelTraits<Kernel>::on_start_packing(k, itm, RectangleBed{binbb, Vec2crd::Zero()},
packing_context,
remaining_items);
}

View File

@ -9,9 +9,9 @@
#include "KernelTraits.hpp"
#include "libslic3r/Arrange/Core/PackingContext.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include "arrange/PackingContext.hpp"
#include "arrange/NFP/NFPArrangeItemTraits.hpp"
#include "arrange/Beds.hpp"
#include <libslic3r/SVG.hpp>

View File

@ -5,10 +5,9 @@
#ifndef TMARRANGEKERNEL_HPP
#define TMARRANGEKERNEL_HPP
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include "KernelUtils.hpp"
#include <arrange/NFP/NFPArrangeItemTraits.hpp>
#include <arrange/Beds.hpp>
#include <arrange/NFP/Kernels/KernelUtils.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <libslic3r/BoostAdapter.hpp>

View File

@ -5,14 +5,15 @@
#ifndef NFP_HPP
#define NFP_HPP
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/Arrange/Core/Beds.hpp>
#include <stdint.h>
#include <boost/variant.hpp>
#include <cinttypes>
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/Point.hpp>
#include <libslic3r/Polygon.hpp>
#include <arrange/Beds.hpp>
namespace Slic3r {

View File

@ -7,10 +7,11 @@
#include <numeric>
#include "libslic3r/Arrange/Core/ArrangeBase.hpp"
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/BoundingBox.hpp>
#include <arrange/ArrangeBase.hpp>
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/BoundingBox.hpp"
namespace Slic3r { namespace arr2 {

View File

@ -5,12 +5,11 @@
#ifndef PACKSTRATEGYNFP_HPP
#define PACKSTRATEGYNFP_HPP
#include "libslic3r/Arrange/Core/ArrangeBase.hpp"
#include <arrange/ArrangeBase.hpp>
#include "EdgeCache.hpp"
#include "Kernels/KernelTraits.hpp"
#include "NFPArrangeItemTraits.hpp"
#include <arrange/NFP/EdgeCache.hpp>
#include <arrange/NFP/Kernels/KernelTraits.hpp>
#include <arrange/NFP/NFPArrangeItemTraits.hpp>
#include "libslic3r/Optimize/NLoptOptimizer.hpp"
#include "libslic3r/Execution/ExecutionSeq.hpp"

View File

@ -5,10 +5,10 @@
#ifndef RECTANGLEOVERFITPACKINGSTRATEGY_HPP
#define RECTANGLEOVERFITPACKINGSTRATEGY_HPP
#include "Kernels/RectangleOverfitKernelWrapper.hpp"
#include <arrange/Beds.hpp>
#include "libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include "Kernels/RectangleOverfitKernelWrapper.hpp"
#include "PackStrategyNFP.hpp"
namespace Slic3r { namespace arr2 {

View File

@ -2,10 +2,11 @@
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Beds.hpp"
#include <cstdlib>
#include <arrange/Beds.hpp>
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Point.hpp"
@ -83,7 +84,7 @@ inline double distance_to(const Point &p1, const Point &p2)
return std::sqrt(dx * dx + dy * dy);
}
static CircleBed to_circle(const Point &center, const Points &points)
static CircleBed to_circle(const Point &center, const Points &points, const Vec2crd &gap)
{
std::vector<double> vertex_distances;
double avg_dist = 0;
@ -96,7 +97,7 @@ static CircleBed to_circle(const Point &center, const Points &points)
avg_dist /= vertex_distances.size();
CircleBed ret(center, avg_dist);
CircleBed ret(center, avg_dist, gap);
for (auto el : vertex_distances) {
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
ret = {};
@ -107,7 +108,7 @@ static CircleBed to_circle(const Point &center, const Points &points)
return ret;
}
template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
template<class Fn> auto call_with_bed(const Points &bed, const Vec2crd &gap, Fn &&fn)
{
if (bed.empty())
return fn(InfiniteBed{});
@ -115,23 +116,23 @@ template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
return fn(InfiniteBed{bed.front()});
else {
auto bb = BoundingBox(bed);
CircleBed circ = to_circle(bb.center(), bed);
CircleBed circ = to_circle(bb.center(), bed, gap);
auto parea = poly_area(bed);
if ((1.0 - parea / area(bb)) < 1e-3) {
return fn(RectangleBed{bb});
return fn(RectangleBed{bb, gap});
} else if (!std::isnan(circ.radius()) && (1.0 - parea / area(circ)) < 1e-2)
return fn(circ);
else
return fn(IrregularBed{{ExPolygon(bed)}});
return fn(IrregularBed{{ExPolygon(bed)}, gap});
}
}
ArrangeBed to_arrange_bed(const Points &bedpts)
ArrangeBed to_arrange_bed(const Points &bedpts, const Vec2crd &gap)
{
ArrangeBed ret;
call_with_bed(bedpts, [&](const auto &bed) { ret = bed; });
call_with_bed(bedpts, gap, [&](const auto &bed) { ret = bed; });
return ret;
}

View File

@ -2,7 +2,7 @@
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "EdgeCache.hpp"
#include <arrange/NFP/EdgeCache.hpp>
#include <iterator>

View File

@ -5,15 +5,16 @@
#ifndef NFP_CPP
#define NFP_CPP
#include "NFP.hpp"
#include <arrange/NFP/NFP.hpp>
#include <arrange/NFP/NFPConcave_Tesselate.hpp>
#include "CircularEdgeIterator.hpp"
#include "NFPConcave_Tesselate.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Line.hpp"
#include "libslic3r/libslic3r.h"
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/Line.hpp>
#include <libslic3r/libslic3r.h>
#include <arrange/Beds.hpp>
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
namespace Slic3r { using LargeInt = __int128; }

View File

@ -2,7 +2,6 @@
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "NFPConcave_Tesselate.hpp"
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Tesselate.hpp>
@ -11,7 +10,9 @@
#include <vector>
#include <cstddef>
#include "NFP.hpp"
#include <arrange//NFP/NFPConcave_Tesselate.hpp>
#include <arrange/NFP/NFP.hpp>
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/libslic3r.h"

View File

@ -433,6 +433,7 @@ target_link_libraries(
libslic3r_gui
PUBLIC
libslic3r
slic3r-arrange-wrapper
avrdude
libcereal
imgui

View File

@ -13,6 +13,7 @@
#include "libslic3r/Geometry/Circle.hpp"
#include "libslic3r/Tesselate.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/MultipleBeds.hpp"
#include "GUI_App.hpp"
#include "GLCanvas3D.hpp"
@ -25,11 +26,14 @@
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include <numeric>
static const float GROUND_Z = -0.02f;
static const Slic3r::ColorRGBA DEFAULT_MODEL_COLOR = Slic3r::ColorRGBA::DARK_GRAY();
static const Slic3r::ColorRGBA PICKING_MODEL_COLOR = Slic3r::ColorRGBA::BLACK();
static const Slic3r::ColorRGBA DEFAULT_SOLID_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 1.0f };
static const Slic3r::ColorRGBA DEFAULT_TRANSPARENT_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 0.6f };
static const Slic3r::ColorRGBA DISABLED_MODEL_COLOR = { 0.6f, 0.6f, 0.6f, 0.75f };
namespace Slic3r {
namespace GUI {
@ -92,41 +96,81 @@ bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, c
m_texture.reset();
m_model.reset();
// unregister from picking
wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Bed);
init_internal_model_from_file();
init_triangles();
s_multiple_beds.update_build_volume(m_build_volume.bounding_volume2d());
m_models_overlap = false;
if (! m_model_filename.empty()) {
// Calculate bb of the bed model and figure out if the models would overlap when rendered next to each other.
const BoundingBoxf3& mdl_bb3 = m_model.model.get_bounding_box();
const BoundingBoxf model_bb(Vec2d(mdl_bb3.min.x(), mdl_bb3.min.y()), Vec2d(mdl_bb3.max.x(), mdl_bb3.max.y()));
BoundingBoxf bed_bb = m_build_volume.bounding_volume2d();
bed_bb.translate(-m_model_offset.x(), -m_model_offset.y());
Vec2d gap = unscale(s_multiple_beds.get_bed_gap());
m_models_overlap = (model_bb.size().x() - bed_bb.size().x() > 2 * gap.x() || model_bb.size().y() - bed_bb.size().y() > 2 * gap.y());
}
// Set the origin and size for rendering the coordinate system axes.
m_axes.set_origin({ 0.0, 0.0, static_cast<double>(GROUND_Z) });
m_axes.set_stem_length(0.1f * static_cast<float>(m_build_volume.bounding_volume().max_size()));
// unregister from picking
wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Bed);
// Let the calee to update the UI.
return true;
}
void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_texture)
{
render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, show_texture, false);
bool is_thumbnail = s_multiple_beds.get_thumbnail_bed_idx() != -1;
bool is_preview = wxGetApp().plater()->is_preview_shown();
int bed_to_highlight = s_multiple_beds.get_active_bed();
static std::vector<int> beds_to_render;
beds_to_render.clear();
if (is_thumbnail)
beds_to_render.push_back(s_multiple_beds.get_thumbnail_bed_idx());
else if (is_preview)
beds_to_render.push_back(s_multiple_beds.get_active_bed());
else {
beds_to_render.resize(s_multiple_beds.get_number_of_beds() + int(s_multiple_beds.should_show_next_bed()));
std::iota(beds_to_render.begin(), beds_to_render.end(), 0);
}
for (int i : beds_to_render) {
Transform3d mat = view_matrix;
mat.translate(s_multiple_beds.get_bed_translation(i));
render_internal(canvas, mat, projection_matrix, bottom, scale_factor, show_texture, false, is_thumbnail || i == bed_to_highlight);
}
}
void Bed3D::render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor)
{
render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, false, true);
render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, false, true, false);
}
void Bed3D::render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor,
bool show_texture, bool picking)
bool show_texture, bool picking, bool active)
{
m_scale_factor = scale_factor;
glsafe(::glEnable(GL_DEPTH_TEST));
m_model.model.set_color(picking ? PICKING_MODEL_COLOR : DEFAULT_MODEL_COLOR);
m_triangles.set_color(picking ? PICKING_MODEL_COLOR : DEFAULT_MODEL_COLOR);
if (!picking && !active) {
m_model.model.set_color(DISABLED_MODEL_COLOR);
m_triangles.set_color(DISABLED_MODEL_COLOR);
}
switch (m_type)
{
case Type::System: { render_system(canvas, view_matrix, projection_matrix, bottom, show_texture); break; }
case Type::System: { render_system(canvas, view_matrix, projection_matrix, bottom, show_texture, active); break; }
default:
case Type::Custom: { render_custom(canvas, view_matrix, projection_matrix, bottom, show_texture, picking); break; }
case Type::Custom: { render_custom(canvas, view_matrix, projection_matrix, bottom, show_texture, picking, active); break; }
}
glsafe(::glDisable(GL_DEPTH_TEST));
@ -203,7 +247,6 @@ void Bed3D::init_triangles()
register_raycasters_for_picking(init_data, Transform3d::Identity());
m_triangles.init_from(std::move(init_data));
m_triangles.set_color(DEFAULT_MODEL_COLOR);
}
void Bed3D::init_gridlines()
@ -309,18 +352,23 @@ void Bed3D::render_axes()
m_axes.render(Transform3d::Identity(), 0.25f);
}
void Bed3D::render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture)
void Bed3D::render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture, bool is_active)
{
if (m_models_overlap && s_multiple_beds.get_number_of_beds() + int(s_multiple_beds.should_show_next_bed()) > 1) {
render_default(bottom, false, show_texture, view_matrix, projection_matrix);
return;
}
if (!bottom)
render_model(view_matrix, projection_matrix);
if (show_texture)
render_texture(bottom, canvas, view_matrix, projection_matrix);
render_texture(bottom, canvas, view_matrix, projection_matrix, is_active);
else if (bottom)
render_contour(view_matrix, projection_matrix);
}
void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix)
void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool is_active)
{
if (m_texture_filename.empty()) {
m_texture.reset();
@ -388,7 +436,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& v
shader->start_using();
shader->set_uniform("view_model_matrix", view_matrix);
shader->set_uniform("projection_matrix", projection_matrix);
shader->set_uniform("transparent_background", bottom);
shader->set_uniform("transparent_background", bottom || ! is_active);
shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg"));
glsafe(::glEnable(GL_DEPTH_TEST));
@ -421,7 +469,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& v
}
}
void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix)
void Bed3D::init_internal_model_from_file()
{
if (m_model_filename.empty())
return;
@ -446,6 +494,14 @@ void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& proj
// update extended bounding box
m_extended_bounding_box = this->calc_extended_bounding_box();
}
}
void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix)
{
if (m_model_filename.empty())
return;
init_internal_model_from_file();
if (!m_model.model.get_filename().empty()) {
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
@ -463,9 +519,10 @@ void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& proj
}
}
void Bed3D::render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture, bool picking)
void Bed3D::render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture, bool picking, bool is_active)
{
if (m_texture_filename.empty() && m_model_filename.empty()) {
if ((m_texture_filename.empty() && m_model_filename.empty())
|| (m_models_overlap && s_multiple_beds.get_number_of_beds() + int(s_multiple_beds.should_show_next_bed()) > 1)) {
render_default(bottom, picking, show_texture, view_matrix, projection_matrix);
return;
}
@ -474,7 +531,7 @@ void Bed3D::render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, co
render_model(view_matrix, projection_matrix);
if (show_texture)
render_texture(bottom, canvas, view_matrix, projection_matrix);
render_texture(bottom, canvas, view_matrix, projection_matrix, is_active);
else if (bottom)
render_contour(view_matrix, projection_matrix);
}

View File

@ -37,6 +37,7 @@ private:
Type m_type{ Type::Custom };
std::string m_texture_filename;
std::string m_model_filename;
bool m_models_overlap;
// Print volume bounding box exteded with axes and model.
BoundingBoxf3 m_extended_bounding_box;
// Print bed polygon
@ -84,13 +85,14 @@ private:
void init_triangles();
void init_gridlines();
void init_contourlines();
void init_internal_model_from_file();
static std::tuple<Type, std::string, std::string> detect_type(const Pointfs& shape);
void render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor,
bool show_texture, bool picking);
void render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture);
void render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix);
bool show_texture, bool picking, bool active);
void render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture, bool is_active);
void render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool is_active);
void render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix);
void render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture, bool picking);
void render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture, bool picking, bool is_active);
void render_default(bool bottom, bool picking, bool show_texture, const Transform3d& view_matrix, const Transform3d& projection_matrix);
void render_contour(const Transform3d& view_matrix, const Transform3d& projection_matrix);

View File

@ -34,6 +34,7 @@
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Tesselate.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/MultipleBeds.hpp"
#include <stdio.h>
#include <stdlib.h>
@ -248,7 +249,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
, is_outside(false)
, hover(HS_None)
, is_modifier(false)
, is_wipe_tower(false)
, is_extrusion_path(false)
, force_native_color(false)
, force_neutral_color(false)
@ -500,13 +500,13 @@ int GLVolumeCollection::load_object_volume(
}
#if SLIC3R_OPENGL_ES
int GLVolumeCollection::load_wipe_tower_preview(
GLVolume* GLVolumeCollection::load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle,
float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh)
float rotation_angle, bool size_unknown, float brim_width, size_t idx, TriangleMesh* out_mesh)
#else
int GLVolumeCollection::load_wipe_tower_preview(
GLVolume* GLVolumeCollection::load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle,
float rotation_angle, bool size_unknown, float brim_width)
float rotation_angle, bool size_unknown, float brim_width, size_t idx)
#endif // SLIC3R_OPENGL_ES
{
if (height == 0.0f)
@ -591,9 +591,8 @@ int GLVolumeCollection::load_wipe_tower_preview(
mesh.merge(cone_mesh);
}
volumes.emplace_back(new GLVolume(color));
GLVolume& v = *volumes.back();
GLVolume* result{new GLVolume(color)};
GLVolume& v = *result;
#if SLIC3R_OPENGL_ES
if (out_mesh != nullptr)
*out_mesh = mesh;
@ -604,12 +603,13 @@ int GLVolumeCollection::load_wipe_tower_preview(
v.set_convex_hull(mesh.convex_hull_3d());
v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0));
v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle));
v.composite_id = GLVolume::CompositeID(INT_MAX, 0, 0);
v.composite_id = GLVolume::CompositeID(INT_MAX - idx, 0, 0);
v.geometry_id.first = 0;
v.geometry_id.second = wipe_tower_instance_id().id;
v.is_wipe_tower = true;
v.geometry_id.second = wipe_tower_instance_id(idx).id;
v.wipe_tower_bed_index = idx;
v.shader_outside_printer_detection_enabled = !size_unknown;
return int(volumes.size() - 1);
return result;
}
// Load SLA auxiliary GLVolumes (for support trees or pad).
@ -657,6 +657,7 @@ void GLVolumeCollection::load_object_auxiliary(
std::shared_ptr<const indexed_triangle_set> preview_mesh_ptr = print_object->get_mesh_to_print();
if (preview_mesh_ptr != nullptr)
backend_mesh = TriangleMesh(*preview_mesh_ptr);
backend_mesh.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()).cast<float>());
if (!backend_mesh.empty()) {
backend_mesh.transform(mesh_trafo_inv);
TriangleMesh convex_hull = backend_mesh.convex_hull_3d();
@ -671,6 +672,7 @@ void GLVolumeCollection::load_object_auxiliary(
// Get the support mesh.
if (milestone == SLAPrintObjectStep::slaposSupportTree) {
TriangleMesh supports_mesh = print_object->support_mesh();
supports_mesh.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()).cast<float>());
if (!supports_mesh.empty()) {
supports_mesh.transform(mesh_trafo_inv);
TriangleMesh convex_hull = supports_mesh.convex_hull_3d();
@ -684,6 +686,7 @@ void GLVolumeCollection::load_object_auxiliary(
// Get the pad mesh.
if (milestone == SLAPrintObjectStep::slaposPad) {
TriangleMesh pad_mesh = print_object->pad_mesh();
pad_mesh.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()).cast<float>());
if (!pad_mesh.empty()) {
pad_mesh.transform(mesh_trafo_inv);
TriangleMesh convex_hull = pad_mesh.convex_hull_3d();
@ -803,7 +806,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
const int obj_idx = volume.first->object_idx();
const int vol_idx = volume.first->volume_idx();
const bool render_as_mmu_painted = is_render_as_mmu_painted_enabled && !volume.first->selected &&
!volume.first->is_outside && volume.first->hover == GLVolume::HS_None && !volume.first->is_wipe_tower && obj_idx >= 0 && vol_idx >= 0 &&
!volume.first->is_outside && volume.first->hover == GLVolume::HS_None && !volume.first->is_wipe_tower() && obj_idx >= 0 && vol_idx >= 0 &&
!model_objects[obj_idx]->volumes[vol_idx]->mm_segmentation_facets.empty() &&
type != GLVolumeCollection::ERenderType::Transparent; // to filter out shells (not very nice)
volume.first->set_render_color(true);
@ -882,7 +885,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
shader->set_uniform("print_volume.xy_data", m_print_volume.data);
shader->set_uniform("print_volume.z_data", m_print_volume.zs);
shader->set_uniform("volume_world_matrix", world_matrix);
shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower);
shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower());
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(world_matrix_inv_transp.cast<float>()));
shader->set_uniform("slope.normal_z", m_slope.normal_z);
@ -1012,7 +1015,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
}
for (GLVolume* volume : volumes) {
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->is_sla_pad() || volume->is_sla_support())
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower() || volume->is_sla_pad() || volume->is_sla_support())
continue;
int extruder_id = volume->extruder_id - 1;

View File

@ -189,8 +189,6 @@ public:
bool is_outside : 1;
// Wheter or not this volume has been generated from a modifier
bool is_modifier : 1;
// Wheter or not this volume has been generated from the wipe tower
bool is_wipe_tower : 1;
// Wheter or not this volume has been generated from an extrusion path
bool is_extrusion_path : 1;
// Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE)
@ -216,6 +214,13 @@ public:
// Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
std::vector<size_t> offsets;
std::optional<int> wipe_tower_bed_index;
// Wheter or not this volume has been generated from the wipe tower
bool is_wipe_tower() const {
return bool{wipe_tower_bed_index};
}
// Bounding box of this volume, in unscaled coordinates.
BoundingBoxf3 bounding_box() const {
return this->model.get_bounding_box();
@ -425,11 +430,11 @@ public:
int instance_idx);
#if SLIC3R_OPENGL_ES
int load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr);
GLVolume* load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, size_t idx, TriangleMesh* out_mesh = nullptr);
#else
int load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width);
GLVolume* load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, size_t idx);
#endif // SLIC3R_OPENGL_ES
// Load SLA auxiliary GLVolumes (for support trees or pad).

View File

@ -34,7 +34,7 @@ ArrangeSettingsDialogImgui::ArrangeSettingsDialogImgui(
: m_imgui{imgui}, m_db{std::move(db)}
{}
void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y)
void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y, bool current_bed)
{
assert(m_imgui && m_db);
@ -131,9 +131,12 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y)
ImGui::SameLine();
if (ImGuiPureWrap::button(_u8L("Arrange")) && m_on_arrange_btn) {
if (!current_bed && ImGuiPureWrap::button(_u8L("Arrange")) && m_on_arrange_btn) {
m_on_arrange_btn();
}
if (current_bed && ImGuiPureWrap::button(_u8L("Arrange bed")) && m_on_arrange_bed_btn) {
m_on_arrange_bed_btn();
}
ImGuiPureWrap::end();
}

View File

@ -5,7 +5,7 @@
#ifndef ARRANGESETTINGSDIALOGIMGUI_HPP
#define ARRANGESETTINGSDIALOGIMGUI_HPP
#include "libslic3r/Arrange/ArrangeSettingsView.hpp"
#include <arrange-wrapper/ArrangeSettingsView.hpp>
#include "ImGuiWrapper.hpp"
#include "libslic3r/AnyPtr.hpp"
@ -17,6 +17,7 @@ class ArrangeSettingsDialogImgui: public arr2::ArrangeSettingsView {
AnyPtr<arr2::ArrangeSettingsDb> m_db;
std::function<void()> m_on_arrange_btn;
std::function<void()> m_on_arrange_bed_btn;
std::function<void()> m_on_reset_btn;
std::function<bool()> m_show_xl_combo_predicate = [] { return true; };
@ -24,7 +25,7 @@ class ArrangeSettingsDialogImgui: public arr2::ArrangeSettingsView {
public:
ArrangeSettingsDialogImgui(ImGuiWrapper *imgui, AnyPtr<arr2::ArrangeSettingsDb> db);
void render(float pos_x, float pos_y);
void render(float pos_x, float pos_y, bool current_bed);
void show_xl_align_combo(std::function<bool()> pred)
{
@ -36,6 +37,11 @@ public:
m_on_arrange_btn = on_arrangefn;
}
void on_arrange_bed_btn(std::function<void()> on_arrangefn)
{
m_on_arrange_bed_btn = on_arrangefn;
}
void on_reset_btn(std::function<void()> on_resetfn)
{
m_on_reset_btn = on_resetfn;

View File

@ -102,10 +102,10 @@ std::pair<std::string, bool> SlicingProcessCompletedEvent::format_error_message(
return std::make_pair(std::move(error), monospace);
}
BackgroundSlicingProcess::BackgroundSlicingProcess()
void BackgroundSlicingProcess::set_temp_output_path(int bed_idx)
{
boost::filesystem::path temp_path(wxStandardPaths::Get().GetTempDir().utf8_str().data());
temp_path /= (boost::format(".%1%.gcode") % get_current_pid()).str();
temp_path /= (boost::format(".%1%_%2%.gcode") % get_current_pid() % bed_idx).str();
m_temp_output_path = temp_path.string();
}
@ -113,7 +113,19 @@ BackgroundSlicingProcess::~BackgroundSlicingProcess()
{
this->stop();
this->join_background_thread();
boost::nowide::remove(m_temp_output_path.c_str());
// Current m_temp_output_path corresponds to the last selected bed. Remove everything
// in the same directory that starts the same (see set_temp_output_path).
const auto temp_dir = boost::filesystem::path(m_temp_output_path).parent_path();
std::string prefix = boost::filesystem::path(m_temp_output_path).filename().string();
prefix = prefix.substr(0, prefix.find('_'));
for (const auto& entry : boost::filesystem::directory_iterator(temp_dir)) {
if (entry.is_regular_file()) {
const std::string filename = entry.path().filename().string();
if (boost::starts_with(filename, prefix) && boost::ends_with(filename, ".gcode"))
boost::filesystem::remove(entry);
}
}
}
bool BackgroundSlicingProcess::select_technology(PrinterTechnology tech)
@ -129,6 +141,8 @@ bool BackgroundSlicingProcess::select_technology(PrinterTechnology tech)
}
changed = true;
}
if (tech == ptFFF)
m_print = m_fff_print;
assert(m_print != nullptr);
return changed;
}

View File

@ -83,12 +83,12 @@ enum BackgroundSlicingProcessStep {
class BackgroundSlicingProcess
{
public:
BackgroundSlicingProcess();
// Stop the background processing and finalize the bacgkround processing thread, remove temp files.
~BackgroundSlicingProcess();
void set_fff_print(Print *print) { m_fff_print = print; }
void set_sla_print(SLAPrint *print) { m_sla_print = print; }
void set_temp_output_path(int bed_idx);
void set_fff_print(Print* print) { if (m_fff_print != print) stop(); m_fff_print = print; }
void set_sla_print(SLAPrint *print) { if (m_sla_print != print) stop(); m_sla_print = print; }
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; }

View File

@ -24,6 +24,8 @@ double Camera::FrustrumMinZRange = 50.0;
double Camera::FrustrumMinNearZ = 100.0;
double Camera::FrustrumZMargin = 10.0;
double Camera::MaxFovDeg = 60.0;
double Camera::SingleBedSceneBoxScaleFactor = 1.5;
double Camera::MultipleBedSceneBoxScaleFactor = 4.0;
std::string Camera::get_type_as_string() const
{
@ -60,7 +62,10 @@ void Camera::set_target(const Vec3d& target)
const Vec3d new_displacement = new_target - m_target;
if (!new_displacement.isApprox(Vec3d::Zero())) {
m_target = new_target;
m_view_matrix.translate(-new_displacement);
Transform3d inv_view_matrix = m_view_matrix.inverse();
inv_view_matrix.translation() = m_target - m_distance * get_dir_forward();
m_view_matrix = inv_view_matrix.inverse();
m_rotation_pivot = m_target;
}
}
@ -284,7 +289,7 @@ void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor)
void Camera::debug_render() const
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
ImGui::Begin(std::string("Camera statistics").c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
std::string type = get_type_as_string();
if (wxGetApp().plater()->get_mouse3d_controller().connected() || (wxGetApp().app_config->get_bool("use_free_camera")))
@ -294,6 +299,7 @@ void Camera::debug_render() const
Vec3f position = get_position().cast<float>();
Vec3f target = m_target.cast<float>();
Vec3f pivot = m_rotation_pivot.cast<float>();
float distance = (float)get_distance();
float zenit = (float)m_zenit;
Vec3f forward = get_dir_forward().cast<float>();
@ -311,6 +317,7 @@ void Camera::debug_render() const
ImGui::Separator();
ImGui::InputFloat3("Position", position.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Target", target.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Pivot", pivot.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Distance", &distance, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("Zenit", &zenit, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
@ -329,7 +336,7 @@ void Camera::debug_render() const
ImGui::InputInt4("Viewport", viewport.data(), ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
imgui.end();
ImGui::End();
}
#endif // ENABLE_CAMERA_STATISTICS
@ -347,11 +354,31 @@ void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad, b
}
}
const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target;
const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_rotation_pivot;
const auto rot_z = Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ());
m_view_rotation *= rot_z * Eigen::AngleAxisd(delta_zenit_rad, rot_z.inverse() * get_dir_right());
m_view_rotation.normalize();
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (- m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.));
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_rotation_pivot) + translation, m_view_rotation, Vec3d(1., 1., 1.));
// adjust target from position
const Vec3d pivot = m_rotation_pivot;
if (!m_target.isApprox(pivot)) {
const Vec3d position = get_position();
const Vec3d forward = get_dir_forward();
const Vec3d new_target = position + m_distance * forward;
if (!is_target_valid()) {
const float forward_dot_unitZ = forward.dot(Vec3d::UnitZ());
if (std::abs(forward_dot_unitZ) > EPSILON) {
const float dist_to_xy = -position.dot(Vec3d::UnitZ()) / forward_dot_unitZ;
set_target(position + dist_to_xy * forward);
}
else
set_target(new_target);
}
else
set_target(new_target);
set_rotation_pivot(pivot);
}
}
// Virtual trackball, rotate around an axis, where the eucledian norm of the axis gives the rotation angle in radians.
@ -363,9 +390,9 @@ void Camera::rotate_local_around_target(const Vec3d& rotation_rad)
const Vec3d axis = m_view_rotation.conjugate() * rotation_rad.normalized();
m_view_rotation *= Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis));
m_view_rotation.normalize();
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.));
update_zenit();
}
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.));
update_zenit();
}
}
std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box)
@ -562,6 +589,21 @@ void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up
update_zenit();
}
bool Camera::is_target_valid() const
{
if (m_target.isApprox(m_rotation_pivot))
return true;
const Vec3d box_size = m_scene_box.size();
BoundingBoxf3 test_box = m_scene_box;
const Vec3d scene_box_center = m_scene_box.center();
test_box.translate(-scene_box_center);
test_box.scale(m_scene_box_scale_factor);
test_box.translate(scene_box_center);
test_box.offset(EPSILON);
return test_box.contains(get_position() + m_distance * get_dir_forward());
}
void Camera::set_default_orientation()
{
m_zenit = 45.0f;
@ -579,13 +621,12 @@ Vec3d Camera::validate_target(const Vec3d& target) const
BoundingBoxf3 test_box = m_scene_box;
test_box.translate(-m_scene_box.center());
// We may let this factor be customizable
static const double ScaleFactor = 1.5;
test_box.scale(ScaleFactor);
test_box.scale(m_scene_box_scale_factor);
test_box.translate(m_scene_box.center());
return { std::clamp(target(0), test_box.min(0), test_box.max(0)),
std::clamp(target(1), test_box.min(1), test_box.max(1)),
std::clamp(target(2), test_box.min(2), test_box.max(2)) };
return { std::clamp(target(0), test_box.min.x(), test_box.max.x()),
std::clamp(target(1), test_box.min.y(), test_box.max.y()),
std::clamp(target(2), test_box.min.z(), test_box.max.z())};
}
void Camera::update_zenit()

View File

@ -17,6 +17,8 @@ struct Camera
static const double DefaultDistance;
static const double DefaultZoomToBoxMarginFactor;
static const double DefaultZoomToVolumesMarginFactor;
static double SingleBedSceneBoxScaleFactor;
static double MultipleBedSceneBoxScaleFactor;
static double FrustrumMinZRange;
static double FrustrumMinNearZ;
static double FrustrumZMargin;
@ -36,6 +38,7 @@ private:
EType m_type{ EType::Perspective };
bool m_update_config_on_type_change_enabled{ false };
Vec3d m_target{ Vec3d::Zero() };
Vec3d m_rotation_pivot{ Vec3d::Zero() };
float m_zenit{ 45.0f };
double m_zoom{ 1.0 };
// Distance between camera position and camera target measured along the camera Z axis
@ -48,6 +51,7 @@ private:
Eigen::Quaterniond m_view_rotation{ 1.0, 0.0, 0.0, 0.0 };
Transform3d m_projection_matrix{ Transform3d::Identity() };
std::pair<double, double> m_frustrum_zs;
double m_scene_box_scale_factor{ SingleBedSceneBoxScaleFactor };
BoundingBoxf3 m_scene_box;
@ -66,6 +70,12 @@ public:
const Vec3d& get_target() const { return m_target; }
void set_target(const Vec3d& target);
void set_scene_box_scale_factor(float factor) { m_scene_box_scale_factor = factor; }
double get_scene_box_scale_factor() const { return m_scene_box_scale_factor; }
const Vec3d& get_rotation_pivot() const { return m_rotation_pivot; }
void set_rotation_pivot(const Vec3d& pivot) { m_rotation_pivot = pivot; }
double get_distance() const { return (get_position() - m_target).norm(); }
double get_gui_scale() const { return m_gui_scale; }
@ -141,6 +151,8 @@ public:
double max_zoom() const { return 250.0; }
double min_zoom() const { return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box); }
bool is_target_valid() const;
private:
// returns tight values for nearZ and farZ plane around the given bounding box
// the camera MUST be outside of the bounding box in eye coordinate of the given box

View File

@ -31,6 +31,8 @@
#include "GUI_ObjectManipulation.hpp"
#include "MsgDialog.hpp"
#include "libslic3r/MultipleBeds.hpp"
#if ENABLE_ACTUAL_SPEED_DEBUG
#define IMGUI_DEFINE_MATH_OPERATORS
#endif // ENABLE_ACTUAL_SPEED_DEBUG
@ -86,7 +88,10 @@ void GCodeViewer::COG::render()
const double inv_zoom = camera.get_inv_zoom();
model_matrix = model_matrix * Geometry::scale_transform(inv_zoom);
}
const Transform3d& view_matrix = camera.get_view_matrix();
Transform3d view_matrix = camera.get_view_matrix();
view_matrix.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()));
shader->set_uniform("view_model_matrix", view_matrix * model_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
@ -233,7 +238,10 @@ void GCodeViewer::SequentialView::Marker::render()
shader->start_using();
shader->set_uniform("emission_factor", 0.0f);
const Camera& camera = wxGetApp().plater()->get_camera();
const Transform3d& view_matrix = camera.get_view_matrix();
Transform3d view_matrix = camera.get_view_matrix();
view_matrix.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()));
float scale_factor = m_scale_factor;
if (m_fixed_screen_size)
scale_factor *= 10.0f * camera.get_inv_zoom();
@ -862,7 +870,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
m_viewer.toggle_top_layer_only_view_range();
// avoid processing if called with the same gcode_result
if (m_last_result_id == gcode_result.id && wxGetApp().is_editor()) {
if (m_last_result_id == gcode_result.id && wxGetApp().is_editor() && ! s_reload_preview_after_switching_beds) {
// collect tool colors
libvgcode::Palette tools_colors;
tools_colors.reserve(str_tool_colors.size());
@ -1690,9 +1698,9 @@ void GCodeViewer::load_wipetower_shell(const Print& print)
const std::vector<std::pair<float, float>> z_and_depth_pairs = print.wipe_tower_data(extruders_count).z_and_depth_pairs;
const float brim_width = wipe_tower_data.brim_width;
if (depth != 0.) {
m_shells.volumes.load_wipe_tower_preview(wxGetApp().plater()->model().wipe_tower.position.x(), wxGetApp().plater()->model().wipe_tower.position.y(), config.wipe_tower_width, depth, z_and_depth_pairs,
max_z, config.wipe_tower_cone_angle, wxGetApp().plater()->model().wipe_tower.rotation, false, brim_width);
GLVolume* volume = m_shells.volumes.volumes.back();
GLVolume* volume{m_shells.volumes.load_wipe_tower_preview(wxGetApp().plater()->model().wipe_tower().position.x(), wxGetApp().plater()->model().wipe_tower().position.y(), config.wipe_tower_width, depth, z_and_depth_pairs,
max_z, config.wipe_tower_cone_angle, wxGetApp().plater()->model().wipe_tower().rotation, false, brim_width, 0)};
m_shells.volumes.volumes.emplace_back(volume);
volume->color.a(0.25f);
volume->force_native_color = true;
volume->set_render_color(true);
@ -1706,7 +1714,12 @@ void GCodeViewer::load_wipetower_shell(const Print& print)
void GCodeViewer::render_toolpaths()
{
const Camera& camera = wxGetApp().plater()->get_camera();
const libvgcode::Mat4x4 converted_view_matrix = libvgcode::convert(static_cast<Matrix4f>(camera.get_view_matrix().matrix().cast<float>()));
Transform3d tr = camera.get_view_matrix();
tr.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()));
Matrix4f m = tr.matrix().cast<float>();
const libvgcode::Mat4x4 converted_view_matrix = libvgcode::convert(m);
const libvgcode::Mat4x4 converted_projetion_matrix = libvgcode::convert(static_cast<Matrix4f>(camera.get_projection_matrix().matrix().cast<float>()));
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
m_viewer.set_cog_marker_scale_factor(m_cog_marker_fixed_screen_size ? 10.0f * m_cog_marker_size * camera.get_inv_zoom() : m_cog_marker_size);
@ -1896,7 +1909,11 @@ void GCodeViewer::render_shells()
shader->start_using();
shader->set_uniform("emission_factor", 0.1f);
const Camera& camera = wxGetApp().plater()->get_camera();
m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, camera.get_view_matrix(), camera.get_projection_matrix());
Transform3d tr = camera.get_view_matrix();
tr.translate(s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()));
m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, tr, camera.get_projection_matrix());
shader->set_uniform("emission_factor", 0.0f);
shader->stop_using();
}

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@
#include "SceneRaycaster.hpp"
#include "GUI_Utils.hpp"
#include "libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp"
#include <arrange-wrapper/ArrangeSettingsDb_AppCfg.hpp>
#include "ArrangeSettingsDialogImgui.hpp"
#include "libslic3r/Slicing.hpp"
@ -158,6 +158,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE_CURRENT_BED, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>); // data: +1 => increase, -1 => decrease
@ -322,6 +323,7 @@ class GLCanvas3D
Point start_position_2D{ Invalid_2D_Point };
Vec3d start_position_3D{ Invalid_3D_Point };
Vec3d camera_start_target{ Invalid_3D_Point };
int move_volume_idx{ -1 };
bool move_requires_threshold{ false };
Point move_start_threshold_position_2D{ Invalid_2D_Point };
@ -335,10 +337,13 @@ class GLCanvas3D
void set_start_position_2D_as_invalid() { drag.start_position_2D = Drag::Invalid_2D_Point; }
void set_start_position_3D_as_invalid() { drag.start_position_3D = Drag::Invalid_3D_Point; }
void set_camera_start_target_as_invalid() { drag.camera_start_target = Drag::Invalid_3D_Point; }
void set_move_start_threshold_position_2D_as_invalid() { drag.move_start_threshold_position_2D = Drag::Invalid_2D_Point; }
bool is_start_position_2D_defined() const { return (drag.start_position_2D != Drag::Invalid_2D_Point); }
bool is_start_position_3D_defined() const { return (drag.start_position_3D != Drag::Invalid_3D_Point); }
bool is_start_position_2D_defined() const { return drag.start_position_2D != Drag::Invalid_2D_Point; }
bool is_start_position_3D_defined() const { return drag.start_position_3D != Drag::Invalid_3D_Point; }
bool is_camera_start_target_defined() { return drag.camera_start_target != Drag::Invalid_3D_Point; }
bool is_move_start_threshold_position_2D_defined() const { return (drag.move_start_threshold_position_2D != Drag::Invalid_2D_Point); }
bool is_move_threshold_met(const Point& mouse_pos) const {
return (std::abs(mouse_pos(0) - drag.move_start_threshold_position_2D(0)) > Drag::MoveThresholdPx)
@ -486,6 +491,7 @@ private:
wxGLContext* m_context;
SceneRaycaster m_scene_raycaster;
Bed3D &m_bed;
int m_last_active_bed_id{ -1 };
#if ENABLE_RETINA_GL
std::unique_ptr<RetinaHelper> m_retina_helper;
#endif
@ -506,11 +512,14 @@ private:
// see request_extra_frame()
bool m_extra_frame_requested;
bool m_event_handlers_bound{ false };
float m_bed_selector_current_height = 0.f;
GLVolumeCollection m_volumes;
#if SLIC3R_OPENGL_ES
TriangleMesh m_wipe_tower_mesh;
std::vector<TriangleMesh> m_wipe_tower_meshes;
#endif // SLIC3R_OPENGL_ES
std::array<std::optional<BoundingBoxf>, MAX_NUMBER_OF_BEDS> m_wipe_tower_bounding_boxes;
GCodeViewer m_gcode_viewer;
RenderTimer m_render_timer;
@ -518,9 +527,13 @@ private:
Selection m_selection;
const DynamicPrintConfig* m_config;
Model* m_model;
public:
BackgroundSlicingProcess *m_process;
private:
bool m_requires_check_outside_state{ false };
void select_bed(int i, bool triggered_by_user);
std::array<unsigned int, 2> m_old_size{ 0, 0 };
// Screen is only refreshed from the OnIdle handler if it is dirty.
@ -620,6 +633,7 @@ private:
std::vector<std::pair<size_t, Transform3d>> m_instances;
bool m_evaluating{ false };
bool m_dragging{ false };
bool m_first_displacement{ true };
std::vector<std::pair<Pointf3s, Transform3d>> m_hulls_2d_cache;
@ -637,7 +651,6 @@ private:
};
SequentialPrintClearance m_sequential_print_clearance;
bool m_sequential_print_clearance_first_displacement{ true };
struct ToolbarHighlighter
{
@ -679,6 +692,8 @@ private:
};
CameraTarget m_camera_target;
CameraTarget m_camera_pivot;
GLModel m_target_validation_box;
#endif // ENABLE_SHOW_CAMERA_TARGET
GLModel m_background;
@ -901,28 +916,30 @@ public:
int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; }
int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); }
void set_selected_extruder(int extruder) { m_selected_extruder = extruder;}
class WipeTowerInfo {
protected:
Vec2d m_pos = {NaNd, NaNd};
double m_rotation = 0.;
BoundingBoxf m_bb;
int m_bed_index{0};
friend class GLCanvas3D;
public:
public:
inline operator bool() const {
return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y());
}
inline const Vec2d& pos() const { return m_pos; }
inline double rotation() const { return m_rotation; }
inline const Vec2d bb_size() const { return m_bb.size(); }
inline const BoundingBoxf& bounding_box() const { return m_bb; }
inline const int bed_index() const { return m_bed_index; }
static void apply_wipe_tower(Vec2d pos, double rot);
static void apply_wipe_tower(Vec2d pos, double rot, int bed_index);
};
WipeTowerInfo get_wipe_tower_info() const;
std::vector<WipeTowerInfo> get_wipe_tower_infos() const;
// Returns the view ray line, in world coordinate, at the given mouse position.
Linef3 mouse_ray(const Point& mouse_pos);
@ -976,7 +993,7 @@ public:
void reset_sequential_print_clearance() {
m_sequential_print_clearance.m_evaluating = false;
if (m_sequential_print_clearance.is_dragging())
m_sequential_print_clearance_first_displacement = true;
m_sequential_print_clearance.m_first_displacement = true;
else
m_sequential_print_clearance.set_contours(ContoursList(), false);
set_as_dirty();
@ -985,6 +1002,8 @@ public:
void set_sequential_print_clearance_contours(const ContoursList& contours, bool generate_fill) {
m_sequential_print_clearance.set_contours(contours, generate_fill);
if (generate_fill)
m_sequential_print_clearance.m_evaluating = false;
set_as_dirty();
request_extra_frame();
}
@ -1056,6 +1075,7 @@ private:
#endif // ENABLE_RENDER_SELECTION_CENTER
void _check_and_update_toolbar_icon_scale();
void _render_overlays();
void _render_bed_selector();
void _render_volumes_for_picking(const Camera& camera) const;
void _render_current_gizmo() const { m_gizmos.render_current_gizmo(); }
void _render_gizmos_overlay();
@ -1065,11 +1085,13 @@ private:
void _render_view_toolbar() const;
#if ENABLE_SHOW_CAMERA_TARGET
void _render_camera_target();
void _render_camera_pivot();
void _render_camera_target_validation_box();
#endif // ENABLE_SHOW_CAMERA_TARGET
void _render_sla_slices();
void _render_selection_sidebar_hints() { m_selection.render_sidebar_hints(m_sidebar_field); }
bool _render_undo_redo_stack(const bool is_undo, float pos_x);
bool _render_arrange_menu(float pos_x);
bool _render_arrange_menu(float pos_x, bool current_bed);
void _render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
// render thumbnail using an off-screen framebuffer
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
@ -1084,7 +1106,7 @@ private:
// Convert the screen space coordinate to an object space coordinate.
// If the Z screen space coordinate is not provided, a depth buffer value is substituted.
Vec3d _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
Vec3d _mouse_to_3d(const Point& mouse_pos, const float* z = nullptr);
// Convert the screen space coordinate to world coordinate on the bed.
Vec3d _mouse_to_bed_3d(const Point& mouse_pos);
@ -1101,6 +1123,7 @@ private:
void _set_warning_notification(EWarning warning, bool state);
std::pair<bool, const GLVolume*> _is_any_volume_outside() const;
bool _is_sequential_print_enabled() const;
// updates the selection from the content of m_hover_volume_idxs
void _update_selection_from_hover();

View File

@ -25,6 +25,7 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE_CURRENT_BED, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);

View File

@ -24,6 +24,7 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE_CURRENT_BED, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);

View File

@ -22,6 +22,8 @@
#include "Gizmos/GLGizmoCut.hpp"
#include "Gizmos/GLGizmoScale.hpp"
#include "libslic3r/MultipleBeds.hpp"
#include "OptionsGroup.hpp"
#include "Tab.hpp"
#include "wxExtensions.hpp"
@ -1835,6 +1837,9 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const std::string &n
new_object->ensure_on_bed();
if (! s_multiple_beds.get_loading_project_flag())
new_object->instances.front()->set_offset(new_object->instances.front()->get_offset() + s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed()));
#ifdef _DEBUG
check_model_ids_validity(model);
#endif /* _DEBUG */

View File

@ -41,6 +41,7 @@
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "NotificationManager.hpp"
#include "libslic3r/MultipleBeds.hpp"
#ifdef _WIN32
#include "BitmapComboBox.hpp"
@ -184,10 +185,10 @@ void View3D::render()
Preview::Preview(
wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config,
BackgroundSlicingProcess* process, GCodeProcessorResult* gcode_result, std::function<void()> schedule_background_process_func)
BackgroundSlicingProcess* process, std::vector<GCodeProcessorResult>* gcode_results, std::function<void()> schedule_background_process_func)
: m_config(config)
, m_process(process)
, m_gcode_result(gcode_result)
, m_gcode_results(gcode_results)
, m_schedule_background_process(schedule_background_process_func)
{
if (init(parent, bed, model))
@ -200,6 +201,11 @@ void Preview::set_layers_slider_values_range(int bottom, int top)
std::max(bottom, m_layers_slider->GetMinPos()));
}
GCodeProcessorResult* Preview::active_gcode_result()
{
return &(*m_gcode_results)[s_multiple_beds.get_active_bed()];
}
bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model)
{
if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */))
@ -342,9 +348,9 @@ float Preview::get_moves_slider_height() const
return 0.0f;
}
float Preview::get_layers_slider_width() const
float Preview::get_layers_slider_width(bool disregard_visibility) const
{
if (m_layers_slider && m_layers_slider->IsShown())
if (m_layers_slider && (m_layers_slider->IsShown() || disregard_visibility))
return m_layers_slider->GetWidth();
return 0.0f;
}
@ -416,7 +422,7 @@ void Preview::create_sliders()
if (wxGetApp().is_editor()) {
m_layers_slider->set_callback_on_ticks_changed([this]() -> void {
Model& model = wxGetApp().plater()->model();
model.custom_gcode_per_print_z = m_layers_slider->GetTicksValues();
model.custom_gcode_per_print_z() = m_layers_slider->GetTicksValues();
m_schedule_background_process();
m_keep_current_preview_type = false;
@ -437,7 +443,7 @@ void Preview::create_sliders()
});
m_layers_slider->set_callback_on_get_print([]() -> const Print& {
return GUI::wxGetApp().plater()->fff_print();
return GUI::wxGetApp().plater()->active_fff_print();
});
m_layers_slider->set_callback_on_get_custom_code([](const std::string& code_in, double height) -> std::string
@ -609,15 +615,15 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
Plater* plater = wxGetApp().plater();
CustomGCode::Info ticks_info_from_model;
if (wxGetApp().is_editor())
ticks_info_from_model = plater->model().custom_gcode_per_print_z;
ticks_info_from_model = plater->model().custom_gcode_per_print_z();
else {
ticks_info_from_model.mode = CustomGCode::Mode::SingleExtruder;
ticks_info_from_model.gcodes = m_gcode_result->custom_gcode_per_print_z;
ticks_info_from_model.gcodes = active_gcode_result()->custom_gcode_per_print_z;
}
check_layers_slider_values(ticks_info_from_model.gcodes, layers_z);
//first of all update extruder colors to avoid crash, when we are switching printer preset from MM to SM
m_layers_slider->SetExtruderColors(plater->get_extruder_color_strings_from_plater_config(wxGetApp().is_editor() ? nullptr : m_gcode_result));
m_layers_slider->SetExtruderColors(plater->get_extruder_color_strings_from_plater_config(wxGetApp().is_editor() ? nullptr : active_gcode_result()));
m_layers_slider->SetSliderValues(layers_z);
m_layers_slider->force_ruler_update();
assert(m_layers_slider->GetMinPos() == 0);
@ -647,9 +653,9 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
bool sequential_print = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects");
m_layers_slider->SetDrawMode(sla_print_technology, sequential_print);
if (sla_print_technology)
m_layers_slider->SetLayersTimes(plater->sla_print().print_statistics().layers_times_running_total);
m_layers_slider->SetLayersTimes(plater->active_sla_print().print_statistics().layers_times_running_total);
else
m_layers_slider->SetLayersTimes(m_canvas->get_gcode_layers_times_cache(), m_gcode_result->print_statistics.modes.front().time);
m_layers_slider->SetLayersTimes(m_canvas->get_gcode_layers_times_cache(), active_gcode_result()->print_statistics.modes.front().time);
m_layers_slider->Thaw();
@ -664,7 +670,7 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
auto get_print_obj_idxs = [plater]() ->std::string {
if (plater->printer_technology() == ptSLA)
return "sla";
const Print& print = GUI::wxGetApp().plater()->fff_print();
const Print& print = GUI::wxGetApp().plater()->active_fff_print();
std::string idxs;
for (auto object : print.objects())
idxs += std::to_string(object->id().id) + "_";
@ -676,7 +682,7 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
wxGetApp().app_config->get_bool("allow_auto_color_change") &&
m_layers_slider->is_new_print(get_print_obj_idxs()))
{
const Print& print = wxGetApp().plater()->fff_print();
const Print& print = wxGetApp().plater()->active_fff_print();
//bool is_possible_auto_color_change = false;
for (auto object : print.objects()) {
@ -841,7 +847,7 @@ void Preview::update_sliders_from_canvas(wxKeyEvent& event)
void Preview::update_moves_slider(std::optional<int> visible_range_min, std::optional<int> visible_range_max)
{
if (m_gcode_result->moves.empty())
if (active_gcode_result()->moves.empty())
return;
const libvgcode::Interval& range = m_canvas->get_gcode_view_enabled_range();
@ -945,15 +951,16 @@ void Preview::load_print_as_fff(bool keep_z_range)
}
libvgcode::EViewType gcode_view_type = m_canvas->get_gcode_view_type();
const bool gcode_preview_data_valid = !m_gcode_result->moves.empty();
const bool gcode_preview_data_valid = !active_gcode_result()->moves.empty();
const bool is_pregcode_preview = !gcode_preview_data_valid && wxGetApp().is_editor();
const std::vector<std::string> tool_colors = wxGetApp().plater()->get_extruder_color_strings_from_plater_config(m_gcode_result);
const std::vector<std::string> tool_colors = wxGetApp().plater()->get_extruder_color_strings_from_plater_config(active_gcode_result());
const std::vector<CustomGCode::Item>& color_print_values = wxGetApp().is_editor() ?
wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_gcode_result->custom_gcode_per_print_z;
wxGetApp().plater()->model().custom_gcode_per_print_z().gcodes : active_gcode_result()->custom_gcode_per_print_z;
std::vector<std::string> color_print_colors;
if (!color_print_values.empty()) {
color_print_colors = wxGetApp().plater()->get_color_strings_for_color_print(m_gcode_result);
color_print_colors = wxGetApp().plater()->get_color_strings_for_color_print(active_gcode_result());
color_print_colors.push_back("#808080"); // gray color for pause print or custom G-code
}
@ -963,7 +970,7 @@ void Preview::load_print_as_fff(bool keep_z_range)
m_canvas->set_selected_extruder(0);
if (gcode_preview_data_valid) {
// Load the real G-code preview.
m_canvas->load_gcode_preview(*m_gcode_result, tool_colors, color_print_colors);
m_canvas->load_gcode_preview(*active_gcode_result(), tool_colors, color_print_colors);
// the view type may have been changed by the call m_canvas->load_gcode_preview()
gcode_view_type = m_canvas->get_gcode_view_type();
zs = m_canvas->get_gcode_layers_zs();

View File

@ -86,7 +86,9 @@ class Preview : public wxPanel
DynamicPrintConfig* m_config;
BackgroundSlicingProcess* m_process;
GCodeProcessorResult* m_gcode_result;
std::vector<GCodeProcessorResult>* m_gcode_results;
GCodeProcessorResult* active_gcode_result();
// Calling this function object forces Plater::schedule_background_process.
std::function<void()> m_schedule_background_process;
@ -117,7 +119,7 @@ public:
};
Preview(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process,
GCodeProcessorResult* gcode_result, std::function<void()> schedule_background_process = []() {});
std::vector<GCodeProcessorResult>* gcode_results, std::function<void()> schedule_background_process = []() {});
virtual ~Preview();
wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; }
@ -136,7 +138,7 @@ public:
void msw_rescale();
void render_sliders(GLCanvas3D& canvas);
float get_layers_slider_width() const;
float get_layers_slider_width(bool disregard_visibility = false) const;
float get_moves_slider_height() const;
bool is_loaded() const { return m_loaded; }

View File

@ -519,7 +519,7 @@ bool GLGizmoFdmSupports::has_backend_supports()
void GLGizmoFdmSupports::auto_generate()
{
std::string err = wxGetApp().plater()->fff_print().validate();
std::string err = wxGetApp().plater()->active_fff_print().validate();
if (!err.empty()) {
MessageDialog dlg(GUI::wxGetApp().plater(), _L("Automatic painting requires valid print setup.") + " \n" + from_u8(err), _L("Warning"), wxOK);
dlg.ShowModal();

View File

@ -81,6 +81,12 @@ void GLGizmoHollow::data_changed(bool is_serializing)
void GLGizmoHollow::on_render()
{
if (! selected_print_object_exists(m_parent, wxEmptyString)) {
wxGetApp().CallAfter([this]() {
// Close current gizmo.
m_parent.get_gizmos_manager().open_gizmo(m_parent.get_gizmos_manager().get_current_type());
});
}
const Selection& selection = m_parent.get_selection();
const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info();
@ -137,7 +143,7 @@ void GLGizmoHollow::render_points(const Selection& selection)
if (!inst)
return;
double shift_z = m_c->selection_info()->print_object()->get_current_elevation();
double shift_z = m_c->selection_info()->print_object() ? m_c->selection_info()->print_object()->get_current_elevation() : 0.;
Transform3d trafo(inst->get_transformation().get_matrix());
trafo.translation()(2) += shift_z;
const Geometry::Transformation transformation{trafo};
@ -849,6 +855,14 @@ void GLGizmoHollow::on_set_state()
if (m_state == m_old_state)
return;
if (m_state == On) {
// Make sure that current object is on current bed. Refuse to turn on otherwise.
if (! selected_print_object_exists(m_parent, _L("Selected object has to be on the active bed."))) {
m_state = Off;
return;
}
}
if (m_state == Off && m_old_state != Off) {
// the gizmo was just turned Off
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));

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