Merge remote-tracking branch 'origin/master' into ys_selection
This commit is contained in:
commit
d806e8e5e1
@ -64,6 +64,7 @@ endif()
|
||||
target_include_directories(ClipperBackend INTERFACE ${Boost_INCLUDE_DIRS} )
|
||||
target_sources(ClipperBackend INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/clipper_polygon.hpp
|
||||
${SRC_DIR}/libnest2d/utils/boost_alg.hpp )
|
||||
|
||||
target_compile_definitions(ClipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER)
|
||||
|
@ -0,0 +1,72 @@
|
||||
#ifndef CLIPPER_POLYGON_HPP
|
||||
#define CLIPPER_POLYGON_HPP
|
||||
|
||||
#include <clipper.hpp>
|
||||
|
||||
namespace ClipperLib {
|
||||
|
||||
struct Polygon {
|
||||
Path Contour;
|
||||
Paths Holes;
|
||||
|
||||
inline Polygon() = default;
|
||||
|
||||
inline explicit Polygon(const Path& cont): Contour(cont) {}
|
||||
inline explicit Polygon(const Paths& holes):
|
||||
Holes(holes) {}
|
||||
inline Polygon(const Path& cont, const Paths& holes):
|
||||
Contour(cont), Holes(holes) {}
|
||||
|
||||
inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {}
|
||||
inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {}
|
||||
inline Polygon(Path&& cont, Paths&& holes):
|
||||
Contour(std::move(cont)), Holes(std::move(holes)) {}
|
||||
};
|
||||
|
||||
inline IntPoint& operator +=(IntPoint& p, const IntPoint& pa ) {
|
||||
// This could be done with SIMD
|
||||
p.X += pa.X;
|
||||
p.Y += pa.Y;
|
||||
return p;
|
||||
}
|
||||
|
||||
inline IntPoint operator+(const IntPoint& p1, const IntPoint& p2) {
|
||||
IntPoint ret = p1;
|
||||
ret += p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) {
|
||||
p.X -= pa.X;
|
||||
p.Y -= pa.Y;
|
||||
return p;
|
||||
}
|
||||
|
||||
inline IntPoint operator -(IntPoint& p ) {
|
||||
IntPoint ret = p;
|
||||
ret.X = -ret.X;
|
||||
ret.Y = -ret.Y;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline IntPoint operator-(const IntPoint& p1, const IntPoint& p2) {
|
||||
IntPoint ret = p1;
|
||||
ret -= p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline IntPoint& operator *=(IntPoint& p, const IntPoint& pa ) {
|
||||
p.X *= pa.X;
|
||||
p.Y *= pa.Y;
|
||||
return p;
|
||||
}
|
||||
|
||||
inline IntPoint operator*(const IntPoint& p1, const IntPoint& p2) {
|
||||
IntPoint ret = p1;
|
||||
ret *= p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // CLIPPER_POLYGON_HPP
|
@ -10,84 +10,15 @@
|
||||
#include <libnest2d/geometry_traits.hpp>
|
||||
#include <libnest2d/geometry_traits_nfp.hpp>
|
||||
|
||||
#include <clipper.hpp>
|
||||
|
||||
namespace ClipperLib {
|
||||
using PointImpl = IntPoint;
|
||||
using PathImpl = Path;
|
||||
using HoleStore = std::vector<PathImpl>;
|
||||
|
||||
struct PolygonImpl {
|
||||
PathImpl Contour;
|
||||
HoleStore Holes;
|
||||
|
||||
inline PolygonImpl() = default;
|
||||
|
||||
inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {}
|
||||
inline explicit PolygonImpl(const HoleStore& holes):
|
||||
Holes(holes) {}
|
||||
inline PolygonImpl(const Path& cont, const HoleStore& holes):
|
||||
Contour(cont), Holes(holes) {}
|
||||
|
||||
inline explicit PolygonImpl(PathImpl&& cont): Contour(std::move(cont)) {}
|
||||
inline explicit PolygonImpl(HoleStore&& holes): Holes(std::move(holes)) {}
|
||||
inline PolygonImpl(Path&& cont, HoleStore&& holes):
|
||||
Contour(std::move(cont)), Holes(std::move(holes)) {}
|
||||
};
|
||||
|
||||
inline PointImpl& operator +=(PointImpl& p, const PointImpl& pa ) {
|
||||
// This could be done with SIMD
|
||||
p.X += pa.X;
|
||||
p.Y += pa.Y;
|
||||
return p;
|
||||
}
|
||||
|
||||
inline PointImpl operator+(const PointImpl& p1, const PointImpl& p2) {
|
||||
PointImpl ret = p1;
|
||||
ret += p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline PointImpl& operator -=(PointImpl& p, const PointImpl& pa ) {
|
||||
p.X -= pa.X;
|
||||
p.Y -= pa.Y;
|
||||
return p;
|
||||
}
|
||||
|
||||
inline PointImpl operator -(PointImpl& p ) {
|
||||
PointImpl ret = p;
|
||||
ret.X = -ret.X;
|
||||
ret.Y = -ret.Y;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline PointImpl operator-(const PointImpl& p1, const PointImpl& p2) {
|
||||
PointImpl ret = p1;
|
||||
ret -= p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline PointImpl& operator *=(PointImpl& p, const PointImpl& pa ) {
|
||||
p.X *= pa.X;
|
||||
p.Y *= pa.Y;
|
||||
return p;
|
||||
}
|
||||
|
||||
inline PointImpl operator*(const PointImpl& p1, const PointImpl& p2) {
|
||||
PointImpl ret = p1;
|
||||
ret *= p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
#include "clipper_polygon.hpp"
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
// Aliases for convinience
|
||||
using ClipperLib::PointImpl;
|
||||
using ClipperLib::PathImpl;
|
||||
using ClipperLib::PolygonImpl;
|
||||
using ClipperLib::HoleStore;
|
||||
using PointImpl = ClipperLib::IntPoint;
|
||||
using PathImpl = ClipperLib::Path;
|
||||
using HoleStore = ClipperLib::Paths;
|
||||
using PolygonImpl = ClipperLib::Polygon;
|
||||
|
||||
// Type of coordinate units used by Clipper
|
||||
template<> struct CoordType<PointImpl> {
|
||||
@ -158,33 +89,24 @@ template<> inline TCoord<PointImpl>& y(PointImpl& p)
|
||||
#define DISABLE_BOOST_AREA
|
||||
|
||||
namespace _smartarea {
|
||||
|
||||
template<Orientation o>
|
||||
inline double area(const PolygonImpl& /*sh*/) {
|
||||
return std::nan("");
|
||||
}
|
||||
|
||||
template<>
|
||||
inline double area<Orientation::CLOCKWISE>(const PolygonImpl& sh) {
|
||||
double a = 0;
|
||||
|
||||
std::for_each(sh.Holes.begin(), sh.Holes.end(), [&a](const PathImpl& h)
|
||||
{
|
||||
a -= ClipperLib::Area(h);
|
||||
inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
|
||||
return std::accumulate(sh.Holes.begin(), sh.Holes.end(),
|
||||
ClipperLib::Area(sh.Contour),
|
||||
[](double a, const ClipperLib::Path& pt){
|
||||
return a + ClipperLib::Area(pt);
|
||||
});
|
||||
|
||||
return -ClipperLib::Area(sh.Contour) + a;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
|
||||
double a = 0;
|
||||
|
||||
std::for_each(sh.Holes.begin(), sh.Holes.end(), [&a](const PathImpl& h)
|
||||
{
|
||||
a += ClipperLib::Area(h);
|
||||
});
|
||||
|
||||
return ClipperLib::Area(sh.Contour) + a;
|
||||
inline double area<Orientation::CLOCKWISE>(const PolygonImpl& sh) {
|
||||
return -area<Orientation::COUNTER_CLOCKWISE>(sh);
|
||||
}
|
||||
|
||||
}
|
||||
@ -228,9 +150,10 @@ template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
|
||||
// but throwing would be an overkill. Instead, we should warn the
|
||||
// caller about the inability to create correct geometries
|
||||
if(!found_the_contour) {
|
||||
sh.Contour = r;
|
||||
sh.Contour = std::move(r);
|
||||
ClipperLib::ReversePath(sh.Contour);
|
||||
sh.Contour.push_back(sh.Contour.front());
|
||||
auto front_p = sh.Contour.front();
|
||||
sh.Contour.emplace_back(std::move(front_p));
|
||||
found_the_contour = true;
|
||||
} else {
|
||||
dout() << "Warning: offsetting result is invalid!";
|
||||
@ -240,9 +163,10 @@ template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
|
||||
// TODO If there are multiple contours we can't be sure which hole
|
||||
// belongs to the first contour. (But in this case the situation is
|
||||
// bad enough to let it go...)
|
||||
sh.Holes.push_back(r);
|
||||
sh.Holes.emplace_back(std::move(r));
|
||||
ClipperLib::ReversePath(sh.Holes.back());
|
||||
sh.Holes.back().push_back(sh.Holes.back().front());
|
||||
auto front_p = sh.Holes.back().front();
|
||||
sh.Holes.back().emplace_back(std::move(front_p));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -390,34 +314,53 @@ inline void rotate(PolygonImpl& sh, const Radians& rads)
|
||||
} // namespace shapelike
|
||||
|
||||
#define DISABLE_BOOST_NFP_MERGE
|
||||
inline std::vector<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
|
||||
inline std::vector<PolygonImpl> clipper_execute(
|
||||
ClipperLib::Clipper& clipper,
|
||||
ClipperLib::ClipType clipType,
|
||||
ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd,
|
||||
ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd)
|
||||
{
|
||||
shapelike::Shapes<PolygonImpl> retv;
|
||||
|
||||
ClipperLib::PolyTree result;
|
||||
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative);
|
||||
clipper.Execute(clipType, result, subjFillType, clipFillType);
|
||||
|
||||
retv.reserve(static_cast<size_t>(result.Total()));
|
||||
|
||||
std::function<void(ClipperLib::PolyNode*, PolygonImpl&)> processHole;
|
||||
|
||||
auto processPoly = [&retv, &processHole](ClipperLib::PolyNode *pptr) {
|
||||
PolygonImpl poly(pptr->Contour);
|
||||
poly.Contour.push_back(poly.Contour.front());
|
||||
PolygonImpl poly;
|
||||
poly.Contour.swap(pptr->Contour);
|
||||
|
||||
assert(!pptr->IsHole());
|
||||
|
||||
if(pptr->IsOpen()) {
|
||||
auto front_p = poly.Contour.front();
|
||||
poly.Contour.emplace_back(front_p);
|
||||
}
|
||||
|
||||
for(auto h : pptr->Childs) { processHole(h, poly); }
|
||||
retv.push_back(poly);
|
||||
};
|
||||
|
||||
processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly)
|
||||
{
|
||||
poly.Holes.push_back(pptr->Contour);
|
||||
poly.Holes.back().push_back(poly.Holes.back().front());
|
||||
poly.Holes.emplace_back(std::move(pptr->Contour));
|
||||
|
||||
assert(pptr->IsHole());
|
||||
|
||||
if(pptr->IsOpen()) {
|
||||
auto front_p = poly.Holes.back().front();
|
||||
poly.Holes.back().emplace_back(front_p);
|
||||
}
|
||||
|
||||
for(auto c : pptr->Childs) processPoly(c);
|
||||
};
|
||||
|
||||
auto traverse = [&processPoly] (ClipperLib::PolyNode *node)
|
||||
{
|
||||
for(auto ch : node->Childs) {
|
||||
processPoly(ch);
|
||||
}
|
||||
for(auto ch : node->Childs) processPoly(ch);
|
||||
};
|
||||
|
||||
traverse(&result);
|
||||
@ -438,14 +381,13 @@ merge(const std::vector<PolygonImpl>& shapes)
|
||||
for(auto& path : shapes) {
|
||||
valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||
|
||||
for(auto& hole : path.Holes) {
|
||||
valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
for(auto& h : path.Holes)
|
||||
valid &= clipper.AddPath(h, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
|
||||
if(!valid) throw GeometryException(GeomErr::MERGE);
|
||||
|
||||
return _merge(clipper);
|
||||
return clipper_execute(clipper, ClipperLib::ctUnion, ClipperLib::pftNegative);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -966,7 +966,7 @@ private:
|
||||
|
||||
for(size_t i = 0; i < pckgrp.size(); i++) {
|
||||
auto items = pckgrp[i];
|
||||
pg.push_back({});
|
||||
pg.emplace_back();
|
||||
pg[i].reserve(items.size());
|
||||
|
||||
for(Item& itemA : items) {
|
||||
|
@ -261,7 +261,7 @@ template<class RawShape> class EdgeCache {
|
||||
while(next != endit) {
|
||||
contour_.emap.emplace_back(*(first++), *(next++));
|
||||
contour_.full_distance += contour_.emap.back().length();
|
||||
contour_.distances.push_back(contour_.full_distance);
|
||||
contour_.distances.emplace_back(contour_.full_distance);
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,10 +276,10 @@ template<class RawShape> class EdgeCache {
|
||||
while(next != endit) {
|
||||
hc.emap.emplace_back(*(first++), *(next++));
|
||||
hc.full_distance += hc.emap.back().length();
|
||||
hc.distances.push_back(hc.full_distance);
|
||||
hc.distances.emplace_back(hc.full_distance);
|
||||
}
|
||||
|
||||
holes_.push_back(hc);
|
||||
holes_.emplace_back(std::move(hc));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ public:
|
||||
bool pack(Item& item, const Range& rem = Range()) {
|
||||
auto&& r = static_cast<Subclass*>(this)->trypack(item, rem);
|
||||
if(r) {
|
||||
items_.push_back(*(r.item_ptr_));
|
||||
items_.emplace_back(*(r.item_ptr_));
|
||||
farea_valid_ = false;
|
||||
}
|
||||
return r;
|
||||
@ -78,7 +78,7 @@ public:
|
||||
if(r) {
|
||||
r.item_ptr_->translation(r.move_);
|
||||
r.item_ptr_->rotation(r.rot_);
|
||||
items_.push_back(*(r.item_ptr_));
|
||||
items_.emplace_back(*(r.item_ptr_));
|
||||
farea_valid_ = false;
|
||||
}
|
||||
}
|
||||
|
@ -667,7 +667,7 @@ public:
|
||||
addBin();
|
||||
ItemList& not_packed = not_packeds[b];
|
||||
for(unsigned idx = b; idx < store_.size(); idx+=bincount_guess) {
|
||||
not_packed.push_back(store_[idx]);
|
||||
not_packed.emplace_back(store_[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -463,7 +463,7 @@ template<> inline std::string serialize<libnest2d::Formats::SVG>(
|
||||
auto& v = *it;
|
||||
hf.emplace_back(getX(v)*scale, getY(v)*scale);
|
||||
};
|
||||
holes.push_back(hf);
|
||||
holes.emplace_back(std::move(hf));
|
||||
}
|
||||
|
||||
Polygonf poly;
|
||||
|
@ -2,36 +2,10 @@
|
||||
#define PRINTER_PARTS_H
|
||||
|
||||
#include <vector>
|
||||
#include <clipper.hpp>
|
||||
|
||||
#ifndef CLIPPER_BACKEND_HPP
|
||||
namespace ClipperLib {
|
||||
using PointImpl = IntPoint;
|
||||
using PathImpl = Path;
|
||||
using HoleStore = std::vector<PathImpl>;
|
||||
|
||||
struct PolygonImpl {
|
||||
PathImpl Contour;
|
||||
HoleStore Holes;
|
||||
|
||||
inline PolygonImpl() {}
|
||||
|
||||
inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {}
|
||||
inline explicit PolygonImpl(const HoleStore& holes):
|
||||
Holes(holes) {}
|
||||
inline PolygonImpl(const Path& cont, const HoleStore& holes):
|
||||
Contour(cont), Holes(holes) {}
|
||||
|
||||
inline explicit PolygonImpl(PathImpl&& cont): Contour(std::move(cont)) {}
|
||||
inline explicit PolygonImpl(HoleStore&& holes): Holes(std::move(holes)) {}
|
||||
inline PolygonImpl(Path&& cont, HoleStore&& holes):
|
||||
Contour(std::move(cont)), Holes(std::move(holes)) {}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
|
||||
using TestData = std::vector<ClipperLib::Path>;
|
||||
using TestDataEx = std::vector<ClipperLib::PolygonImpl>;
|
||||
using TestDataEx = std::vector<ClipperLib::Polygon>;
|
||||
|
||||
extern const TestData PRINTER_PART_POLYGONS;
|
||||
extern const TestData STEGOSAUR_POLYGONS;
|
||||
|
@ -246,6 +246,7 @@ public:
|
||||
|
||||
const Vec3d& get_mirror() const { return m_mirror; }
|
||||
double get_mirror(Axis axis) const { return m_mirror(axis); }
|
||||
bool is_left_handed() const { return m_mirror.x() * m_mirror.y() * m_mirror.z() < 0.; }
|
||||
|
||||
void set_mirror(const Vec3d& mirror);
|
||||
void set_mirror(Axis axis, double mirror);
|
||||
|
@ -258,12 +258,17 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
||||
#ifdef SLIC3R_DEBUG
|
||||
printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id());
|
||||
#endif
|
||||
if (bd.detect_angle(Geometry::deg2rad(this->region()->config().bridge_angle.value))) {
|
||||
double custom_angle = Geometry::deg2rad(this->region()->config().bridge_angle.value);
|
||||
if (bd.detect_angle(custom_angle)) {
|
||||
bridges[idx_last].bridge_angle = bd.angle;
|
||||
if (this->layer()->object()->config().support_material) {
|
||||
polygons_append(this->bridged, bd.coverage());
|
||||
this->unsupported_bridge_edges.append(bd.unsupported_edges());
|
||||
}
|
||||
} else if (custom_angle > 0) {
|
||||
// Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in
|
||||
// using a bridging flow, therefore it makes sense to respect the custom bridging direction.
|
||||
bridges[idx_last].bridge_angle = custom_angle;
|
||||
}
|
||||
// without safety offset, artifacts are generated (GH #2494)
|
||||
surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]);
|
||||
|
@ -1467,9 +1467,9 @@ int ModelVolume::extruder_id() const
|
||||
|
||||
bool ModelVolume::is_splittable() const
|
||||
{
|
||||
// the call mesh.has_multiple_patches() is expensive, so cache the value to calculate it only once
|
||||
// the call mesh.is_splittable() is expensive, so cache the value to calculate it only once
|
||||
if (m_is_splittable == -1)
|
||||
m_is_splittable = (int)mesh.has_multiple_patches();
|
||||
m_is_splittable = (int)mesh.is_splittable();
|
||||
|
||||
return m_is_splittable == 1;
|
||||
}
|
||||
@ -1609,6 +1609,7 @@ void ModelVolume::rotate(double angle, Axis axis)
|
||||
case X: { rotate(angle, Vec3d::UnitX()); break; }
|
||||
case Y: { rotate(angle, Vec3d::UnitY()); break; }
|
||||
case Z: { rotate(angle, Vec3d::UnitZ()); break; }
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1625,6 +1626,7 @@ void ModelVolume::mirror(Axis axis)
|
||||
case X: { mirror(0) *= -1.0; break; }
|
||||
case Y: { mirror(1) *= -1.0; break; }
|
||||
case Z: { mirror(2) *= -1.0; break; }
|
||||
default: break;
|
||||
}
|
||||
set_mirror(mirror);
|
||||
}
|
||||
@ -1711,7 +1713,6 @@ bool model_object_list_extended(const Model &model_old, const Model &model_new)
|
||||
|
||||
bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type)
|
||||
{
|
||||
bool modifiers_differ = false;
|
||||
size_t i_old, i_new;
|
||||
for (i_old = 0, i_new = 0; i_old < model_object_old.volumes.size() && i_new < model_object_new.volumes.size();) {
|
||||
const ModelVolume &mv_old = *model_object_old.volumes[i_old];
|
||||
|
@ -394,6 +394,7 @@ public:
|
||||
|
||||
const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
|
||||
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
|
||||
bool is_left_handed() const { return m_transformation.is_left_handed(); }
|
||||
|
||||
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
|
||||
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
|
||||
@ -498,6 +499,7 @@ public:
|
||||
|
||||
const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
|
||||
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
|
||||
bool is_left_handed() const { return m_transformation.is_left_handed(); }
|
||||
|
||||
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
|
||||
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
|
||||
|
@ -574,7 +574,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
|
||||
|
||||
for(ModelInstance* objinst : objptr->instances) {
|
||||
if(objinst) {
|
||||
ClipperLib::PolygonImpl pn;
|
||||
ClipperLib::Polygon pn;
|
||||
pn.Contour = clpath;
|
||||
|
||||
// Efficient conversion to item.
|
||||
|
@ -42,8 +42,9 @@ template<FilePrinterFormat format>
|
||||
class FilePrinter {
|
||||
public:
|
||||
|
||||
// Draw an ExPolygon which is a polygon inside a slice on the specified layer.
|
||||
// Draw a polygon which is a polygon inside a slice on the specified layer.
|
||||
void draw_polygon(const ExPolygon& p, unsigned lyr);
|
||||
void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr);
|
||||
|
||||
// Tell the printer how many layers should it consider.
|
||||
void layers(unsigned layernum);
|
||||
@ -221,6 +222,11 @@ public:
|
||||
m_layers_rst[lyr].raster.draw(p);
|
||||
}
|
||||
|
||||
inline void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr) {
|
||||
assert(lyr < m_layers_rst.size());
|
||||
m_layers_rst[lyr].raster.draw(p);
|
||||
}
|
||||
|
||||
inline void begin_layer(unsigned lyr) {
|
||||
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
|
||||
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_o);
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "Rasterizer.hpp"
|
||||
#include <ExPolygon.hpp>
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
|
||||
// For rasterizing
|
||||
#include <agg/agg_basics.h>
|
||||
@ -89,6 +90,25 @@ public:
|
||||
agg::render_scanlines(ras, scanlines, m_renderer);
|
||||
}
|
||||
|
||||
void draw(const ClipperLib::Polygon &poly) {
|
||||
agg::rasterizer_scanline_aa<> ras;
|
||||
agg::scanline_p8 scanlines;
|
||||
|
||||
auto&& path = to_path(poly.Contour);
|
||||
|
||||
if(m_o == Origin::TOP_LEFT) flipy(path);
|
||||
|
||||
ras.add_path(path);
|
||||
|
||||
for(auto h : poly.Holes) {
|
||||
auto&& holepath = to_path(h);
|
||||
if(m_o == Origin::TOP_LEFT) flipy(holepath);
|
||||
ras.add_path(holepath);
|
||||
}
|
||||
|
||||
agg::render_scanlines(ras, scanlines, m_renderer);
|
||||
}
|
||||
|
||||
inline void clear() {
|
||||
m_raw_renderer.clear(ColorBlack);
|
||||
}
|
||||
@ -108,14 +128,36 @@ private:
|
||||
return p(1) * SCALING_FACTOR/m_pxdim.h_mm;
|
||||
}
|
||||
|
||||
agg::path_storage to_path(const Polygon& poly) {
|
||||
agg::path_storage to_path(const Polygon& poly)
|
||||
{
|
||||
agg::path_storage path;
|
||||
|
||||
auto it = poly.points.begin();
|
||||
path.move_to(getPx(*it), getPy(*it));
|
||||
while(++it != poly.points.end())
|
||||
while(++it != poly.points.end()) path.line_to(getPx(*it), getPy(*it));
|
||||
path.line_to(getPx(poly.points.front()), getPy(poly.points.front()));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
double getPx(const ClipperLib::IntPoint& p) {
|
||||
return p.X * SCALING_FACTOR/m_pxdim.w_mm;
|
||||
}
|
||||
|
||||
double getPy(const ClipperLib::IntPoint& p) {
|
||||
return p.Y * SCALING_FACTOR/m_pxdim.h_mm;
|
||||
}
|
||||
|
||||
agg::path_storage to_path(const ClipperLib::Path& poly)
|
||||
{
|
||||
agg::path_storage path;
|
||||
auto it = poly.begin();
|
||||
path.move_to(getPx(*it), getPy(*it));
|
||||
while(++it != poly.end())
|
||||
path.line_to(getPx(*it), getPy(*it));
|
||||
|
||||
path.line_to(getPx(poly.points.front()), getPy(poly.points.front()));
|
||||
path.line_to(getPx(poly.front()), getPy(poly.front()));
|
||||
return path;
|
||||
}
|
||||
|
||||
@ -167,9 +209,13 @@ void Raster::clear()
|
||||
m_impl->clear();
|
||||
}
|
||||
|
||||
void Raster::draw(const ExPolygon &poly)
|
||||
void Raster::draw(const ExPolygon &expoly)
|
||||
{
|
||||
m_impl->draw(expoly);
|
||||
}
|
||||
|
||||
void Raster::draw(const ClipperLib::Polygon &poly)
|
||||
{
|
||||
assert(m_impl);
|
||||
m_impl->draw(poly);
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ClipperLib { struct Polygon; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
@ -123,6 +125,7 @@ public:
|
||||
|
||||
/// Draw a polygon with holes.
|
||||
void draw(const ExPolygon& poly);
|
||||
void draw(const ClipperLib::Polygon& poly);
|
||||
|
||||
/// Save the raster on the specified stream.
|
||||
void save(std::ostream& stream, Compression comp = Compression::RAW);
|
||||
|
@ -49,8 +49,8 @@ float SLAAutoSupports::distance_limit(float angle) const
|
||||
}*/
|
||||
|
||||
SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
|
||||
const Config& config, std::function<void(void)> throw_on_cancel)
|
||||
: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel)
|
||||
const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn)
|
||||
: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel), m_statusfn(statusfn)
|
||||
{
|
||||
process(slices, heights);
|
||||
project_onto_mesh(m_output);
|
||||
@ -197,6 +197,9 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
||||
PointGrid3D point_grid;
|
||||
point_grid.cell_size = Vec3f(10.f, 10.f, 10.f);
|
||||
|
||||
double increment = 100.0 / layers.size();
|
||||
double status = 0;
|
||||
|
||||
for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) {
|
||||
SLAAutoSupports::MyLayer *layer_top = &layers[layer_id];
|
||||
SLAAutoSupports::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
|
||||
@ -252,6 +255,9 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
||||
|
||||
m_throw_on_cancel();
|
||||
|
||||
status += increment;
|
||||
m_statusfn(int(std::round(status)));
|
||||
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
/*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i);
|
||||
output_expolygons(expolys_top, "top" + layer_num_str + ".svg");
|
||||
|
@ -24,7 +24,7 @@ public:
|
||||
};
|
||||
|
||||
SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
||||
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel);
|
||||
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||
const std::vector<sla::SupportPoint>& output() { return m_output; }
|
||||
|
||||
struct MyLayer;
|
||||
@ -196,8 +196,9 @@ private:
|
||||
static void output_structures(const std::vector<Structure> &structures);
|
||||
#endif // SLA_AUTOSUPPORTS_DEBUG
|
||||
|
||||
std::function<void(void)> m_throw_on_cancel;
|
||||
const sla::EigenMesh3D& m_emesh;
|
||||
std::function<void(void)> m_throw_on_cancel;
|
||||
std::function<void(int)> m_statusfn;
|
||||
};
|
||||
|
||||
|
||||
|
@ -12,6 +12,9 @@
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
// For geometry algorithms with native Clipper types (no copies and conversions)
|
||||
#include <libnest2d/backends/clipper/geometries.hpp>
|
||||
|
||||
//#include <tbb/spin_mutex.h>//#include "tbb/mutex.h"
|
||||
|
||||
#include "I18N.hpp"
|
||||
@ -39,12 +42,11 @@ namespace {
|
||||
// should add up to 100 (%)
|
||||
const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS =
|
||||
{
|
||||
10, // slaposObjectSlice,
|
||||
30, // slaposSupportPoints,
|
||||
25, // slaposSupportTree,
|
||||
25, // slaposBasePool,
|
||||
5, // slaposSliceSupports,
|
||||
5 // slaposIndexSlices
|
||||
30, // slaposObjectSlice,
|
||||
20, // slaposSupportPoints,
|
||||
10, // slaposSupportTree,
|
||||
10, // slaposBasePool,
|
||||
30, // slaposSliceSupports,
|
||||
};
|
||||
|
||||
const std::array<std::string, slaposCount> OBJ_STEP_LABELS =
|
||||
@ -54,22 +56,19 @@ const std::array<std::string, slaposCount> OBJ_STEP_LABELS =
|
||||
L("Generating support tree"), // slaposSupportTree,
|
||||
L("Generating pad"), // slaposBasePool,
|
||||
L("Slicing supports"), // slaposSliceSupports,
|
||||
L("Slicing supports") // slaposIndexSlices,
|
||||
};
|
||||
|
||||
// Should also add up to 100 (%)
|
||||
const std::array<unsigned, slapsCount> PRINT_STEP_LEVELS =
|
||||
{
|
||||
5, // slapsStats
|
||||
94, // slapsRasterize
|
||||
1, // slapsValidate
|
||||
10, // slapsMergeSlicesAndEval
|
||||
90, // slapsRasterize
|
||||
};
|
||||
|
||||
const std::array<std::string, slapsCount> PRINT_STEP_LABELS =
|
||||
{
|
||||
L("Calculating statistics"), // slapsStats
|
||||
L("Merging slices and calculating statistics"), // slapsStats
|
||||
L("Rasterizing layers"), // slapsRasterize
|
||||
L("Validating"), // slapsValidate
|
||||
};
|
||||
|
||||
}
|
||||
@ -94,7 +93,10 @@ static Transform3d sla_trafo(const ModelObject &model_object)
|
||||
offset(0) = 0.;
|
||||
offset(1) = 0.;
|
||||
rotation(2) = 0.;
|
||||
return Geometry::assemble_transform(offset, rotation, model_instance.get_scaling_factor(), model_instance.get_mirror());
|
||||
Transform3d trafo = Geometry::assemble_transform(offset, rotation, model_instance.get_scaling_factor(), model_instance.get_mirror());
|
||||
if (model_instance.is_left_handed())
|
||||
trafo = Eigen::Scaling(Vec3d(-1., 1., 1.)) * trafo;
|
||||
return trafo;
|
||||
}
|
||||
|
||||
// List of instances, where the ModelInstance transformation is a composite of sla_trafo and the transformation defined by SLAPrintObject::Instance.
|
||||
@ -103,10 +105,10 @@ static std::vector<SLAPrintObject::Instance> sla_instances(const ModelObject &mo
|
||||
std::vector<SLAPrintObject::Instance> instances;
|
||||
for (ModelInstance *model_instance : model_object.instances)
|
||||
if (model_instance->is_printable()) {
|
||||
instances.emplace_back(SLAPrintObject::Instance(
|
||||
instances.emplace_back(
|
||||
model_instance->id(),
|
||||
Point::new_scale(model_instance->get_offset(X), model_instance->get_offset(Y)),
|
||||
float(model_instance->get_rotation(Z))));
|
||||
float(model_instance->get_rotation(Z)));
|
||||
}
|
||||
return instances;
|
||||
}
|
||||
@ -206,7 +208,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
|
||||
model_object_status.emplace(model_object->id(), ModelObjectStatus::Old);
|
||||
} else if (model_object_list_extended(m_model, model)) {
|
||||
// Add new objects. Their volumes and configs will be synchronized later.
|
||||
update_apply_status(this->invalidate_step(slapsRasterize));
|
||||
update_apply_status(this->invalidate_step(slapsMergeSlicesAndEval));
|
||||
for (const ModelObject *model_object : m_model.objects)
|
||||
model_object_status.emplace(model_object->id(), ModelObjectStatus::Old);
|
||||
for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) {
|
||||
@ -218,7 +220,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
|
||||
// Reorder the objects, add new objects.
|
||||
// First stop background processing before shuffling or deleting the PrintObjects in the object list.
|
||||
this->call_cancel_callback();
|
||||
update_apply_status(this->invalidate_step(slapsRasterize));
|
||||
update_apply_status(this->invalidate_step(slapsMergeSlicesAndEval));
|
||||
// Second create a new list of objects.
|
||||
std::vector<ModelObject*> model_objects_old(std::move(m_model.objects));
|
||||
m_model.objects.clear();
|
||||
@ -315,8 +317,11 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
|
||||
it_print_object_status = print_object_status.end();
|
||||
// Check whether a model part volume was added or removed, their transformations or order changed.
|
||||
bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART);
|
||||
bool sla_trafo_differs = model_object.instances.empty() != model_object_new.instances.empty() ||
|
||||
(! model_object.instances.empty() && ! sla_trafo(model_object).isApprox(sla_trafo(model_object_new)));
|
||||
bool sla_trafo_differs =
|
||||
model_object.instances.empty() != model_object_new.instances.empty() ||
|
||||
(! model_object.instances.empty() &&
|
||||
(! sla_trafo(model_object).isApprox(sla_trafo(model_object_new)) ||
|
||||
model_object.instances.front()->is_left_handed() != model_object_new.instances.front()->is_left_handed()));
|
||||
if (model_parts_differ || sla_trafo_differs) {
|
||||
// The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects.
|
||||
if (it_print_object_status != print_object_status.end()) {
|
||||
@ -390,7 +395,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
|
||||
if (new_instances != it_print_object_status->print_object->instances()) {
|
||||
// Instances changed.
|
||||
it_print_object_status->print_object->set_instances(new_instances);
|
||||
update_apply_status(this->invalidate_step(slapsRasterize));
|
||||
update_apply_status(this->invalidate_step(slapsMergeSlicesAndEval));
|
||||
}
|
||||
print_objects_new.emplace_back(it_print_object_status->print_object);
|
||||
const_cast<PrintObjectStatus&>(*it_print_object_status).status = PrintObjectStatus::Reused;
|
||||
@ -400,9 +405,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
|
||||
|
||||
// FIXME: this invalidates the transformed mesh in SLAPrintObject
|
||||
// which is expensive to calculate (especially the raw_mesh() call)
|
||||
print_object->set_trafo(sla_trafo(model_object));
|
||||
print_object->set_trafo(sla_trafo(model_object), model_object.instances.front()->is_left_handed());
|
||||
|
||||
print_object->set_instances(new_instances);
|
||||
print_object->set_instances(std::move(new_instances));
|
||||
print_object->config_apply(config, true);
|
||||
print_objects_new.emplace_back(print_object);
|
||||
new_objects = true;
|
||||
@ -579,11 +584,6 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) {
|
||||
|
||||
return pcfg;
|
||||
}
|
||||
|
||||
void swapXY(ExPolygon& expoly) {
|
||||
for(auto& p : expoly.contour.points) std::swap(p(X), p(Y));
|
||||
for(auto& h : expoly.holes) for(auto& p : h.points) std::swap(p(X), p(Y));
|
||||
}
|
||||
}
|
||||
|
||||
std::string SLAPrint::validate() const
|
||||
@ -613,13 +613,17 @@ std::string SLAPrint::validate() const
|
||||
return "";
|
||||
}
|
||||
|
||||
template<class...Args>
|
||||
void report_status(SLAPrint& p, int st, const std::string& msg, Args&&...args)
|
||||
bool SLAPrint::invalidate_step(SLAPrintStep step)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << st << "% " << msg;
|
||||
p.set_status(st, msg, std::forward<Args>(args)...);
|
||||
bool invalidated = Inherited::invalidate_step(step);
|
||||
|
||||
// propagate to dependent steps
|
||||
if (step == slapsMergeSlicesAndEval) {
|
||||
invalidated |= this->invalidate_all_steps();
|
||||
}
|
||||
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
void SLAPrint::process()
|
||||
{
|
||||
@ -639,7 +643,7 @@ void SLAPrint::process()
|
||||
const size_t objcount = m_objects.size();
|
||||
|
||||
const unsigned min_objstatus = 0; // where the per object operations start
|
||||
const unsigned max_objstatus = PRINT_STEP_LEVELS[slapsRasterize]; // where the per object operations end
|
||||
const unsigned max_objstatus = 50; // where the per object operations end
|
||||
|
||||
// the coefficient that multiplies the per object status values which
|
||||
// are set up for <0, 100>. They need to be scaled into the whole process
|
||||
@ -714,7 +718,7 @@ void SLAPrint::process()
|
||||
|
||||
// In this step we check the slices, identify island and cover them with
|
||||
// support points. Then we sprinkle the rest of the mesh.
|
||||
auto support_points = [this](SLAPrintObject& po) {
|
||||
auto support_points = [this, ostepd](SLAPrintObject& po) {
|
||||
const ModelObject& mo = *po.m_model_object;
|
||||
po.m_supportdata.reset(
|
||||
new SLAPrintObject::SupportData(po.transformed_mesh()) );
|
||||
@ -748,6 +752,19 @@ void SLAPrint::process()
|
||||
config.minimal_distance = float(cfg.support_points_minimal_distance);
|
||||
config.head_diameter = float(cfg.support_head_front_diameter);
|
||||
|
||||
// scaling for the sub operations
|
||||
double d = ostepd * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0;
|
||||
double init = m_report_status.status();
|
||||
|
||||
auto statuscb = [this, d, init](unsigned st)
|
||||
{
|
||||
double current = init + st * d;
|
||||
if(std::round(m_report_status.status()) < std::round(current))
|
||||
m_report_status(*this, current,
|
||||
OBJ_STEP_LABELS[slaposSupportPoints]);
|
||||
|
||||
};
|
||||
|
||||
// Construction of this object does the calculation.
|
||||
this->throw_if_canceled();
|
||||
SLAAutoSupports auto_supports(po.transformed_mesh(),
|
||||
@ -755,7 +772,8 @@ void SLAPrint::process()
|
||||
po.get_model_slices(),
|
||||
heights,
|
||||
config,
|
||||
[this]() { throw_if_canceled(); });
|
||||
[this]() { throw_if_canceled(); },
|
||||
statuscb);
|
||||
|
||||
// Now let's extract the result.
|
||||
const std::vector<sla::SupportPoint>& points = auto_supports.output();
|
||||
@ -766,7 +784,7 @@ void SLAPrint::process()
|
||||
<< po.m_supportdata->support_points.size();
|
||||
|
||||
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports
|
||||
report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
|
||||
m_report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
|
||||
}
|
||||
else {
|
||||
// There are either some points on the front-end, or the user removed them on purpose. No calculation will be done.
|
||||
@ -775,7 +793,8 @@ void SLAPrint::process()
|
||||
};
|
||||
|
||||
// In this step we create the supports
|
||||
auto support_tree = [this, objcount, ostepd](SLAPrintObject& po) {
|
||||
auto support_tree = [this, ostepd](SLAPrintObject& po)
|
||||
{
|
||||
if(!po.m_supportdata) return;
|
||||
|
||||
if(!po.m_config.supports_enable.getBool()) {
|
||||
@ -787,22 +806,17 @@ void SLAPrint::process()
|
||||
sla::SupportConfig scfg = make_support_cfg(po.m_config);
|
||||
sla::Controller ctl;
|
||||
|
||||
// some magic to scale the status values coming from the support
|
||||
// tree creation into the whole print process
|
||||
auto stfirst = OBJ_STEP_LEVELS.begin();
|
||||
auto stthis = stfirst + slaposSupportTree;
|
||||
// we need to add up the status portions until this operation
|
||||
int init = std::accumulate(stfirst, stthis, 0);
|
||||
init = int(init * ostepd); // scale the init portion
|
||||
|
||||
// scaling for the sub operations
|
||||
double d = *stthis / (objcount * 100.0);
|
||||
double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
|
||||
double init = m_report_status.status();
|
||||
|
||||
ctl.statuscb = [this, init, d](unsigned st, const std::string& msg)
|
||||
ctl.statuscb = [this, d, init](unsigned st, const std::string&)
|
||||
{
|
||||
//FIXME this status line scaling does not seem to be correct.
|
||||
// How does it account for an increasing object index?
|
||||
report_status(*this, int(init + st*d), msg);
|
||||
double current = init + st * d;
|
||||
if(std::round(m_report_status.status()) < std::round(current))
|
||||
m_report_status(*this, current,
|
||||
OBJ_STEP_LABELS[slaposSupportTree]);
|
||||
|
||||
};
|
||||
|
||||
ctl.stopcondition = [this](){ return canceled(); };
|
||||
@ -818,7 +832,7 @@ void SLAPrint::process()
|
||||
auto rc = SlicingStatus::RELOAD_SCENE;
|
||||
|
||||
// This is to prevent "Done." being displayed during merged_mesh()
|
||||
report_status(*this, -1, L("Visualizing supports"));
|
||||
m_report_status(*this, -1, L("Visualizing supports"));
|
||||
po.m_supportdata->support_tree_ptr->merged_mesh();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
|
||||
@ -828,8 +842,7 @@ void SLAPrint::process()
|
||||
if(po.support_mesh().empty())
|
||||
BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty";
|
||||
|
||||
report_status(*this, -1, L("Visualizing supports"), rc);
|
||||
|
||||
m_report_status(*this, -1, L("Visualizing supports"), rc);
|
||||
};
|
||||
|
||||
// This step generates the sla base pad
|
||||
@ -877,13 +890,13 @@ void SLAPrint::process()
|
||||
|
||||
po.throw_if_canceled();
|
||||
auto rc = SlicingStatus::RELOAD_SCENE;
|
||||
report_status(*this, -1, L("Visualizing supports"), rc);
|
||||
m_report_status(*this, -1, L("Visualizing supports"), rc);
|
||||
};
|
||||
|
||||
// Slicing the support geometries similarly to the model slicing procedure.
|
||||
// If the pad had been added previously (see step "base_pool" than it will
|
||||
// be part of the slices)
|
||||
auto slice_supports = [](SLAPrintObject& po) {
|
||||
auto slice_supports = [this](SLAPrintObject& po) {
|
||||
auto& sd = po.m_supportdata;
|
||||
|
||||
if(sd) sd->support_slices.clear();
|
||||
@ -906,28 +919,14 @@ void SLAPrint::process()
|
||||
{
|
||||
po.m_slice_index[i].set_support_slice_idx(po, i);
|
||||
}
|
||||
};
|
||||
|
||||
// We have the layer polygon collection but we need to unite them into
|
||||
// an index where the key is the height level in discrete levels (clipper)
|
||||
auto index_slices = [this/*, ilhd*/](SLAPrintObject& /*po*/) {
|
||||
// Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update status to the 3D preview to load the SLA slices.
|
||||
report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||
m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||
};
|
||||
|
||||
auto fillstats = [this]() {
|
||||
|
||||
m_print_statistics.clear();
|
||||
|
||||
// Fill statistics
|
||||
fill_statistics();
|
||||
|
||||
report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||
};
|
||||
|
||||
// Rasterizing the model objects, and their supports
|
||||
auto rasterize = [this, max_objstatus, ilhs]() {
|
||||
if(canceled()) return;
|
||||
// Merging the slices from all the print objects into one slice grid and
|
||||
// calculating print statistics from the merge result.
|
||||
auto merge_slices_and_eval_stats = [this, ilhs]() {
|
||||
|
||||
// clear the rasterizer input
|
||||
m_printer_input.clear();
|
||||
@ -962,6 +961,284 @@ void SLAPrint::process()
|
||||
}
|
||||
}
|
||||
|
||||
m_print_statistics.clear();
|
||||
|
||||
using ClipperPoint = ClipperLib::IntPoint;
|
||||
using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d
|
||||
using ClipperPolygons = std::vector<ClipperPolygon>;
|
||||
namespace sl = libnest2d::shapelike; // For algorithms
|
||||
|
||||
// If the raster has vertical orientation, we will flip the coordinates
|
||||
bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait;
|
||||
|
||||
// Set up custom union and diff functions for clipper polygons
|
||||
auto polyunion = [] (const ClipperPolygons& subjects)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
bool closed = true;
|
||||
|
||||
for(auto& path : subjects) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
|
||||
auto mode = ClipperLib::pftPositive;
|
||||
|
||||
return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode);
|
||||
};
|
||||
|
||||
auto polydiff = [](const ClipperPolygons& subjects, const ClipperPolygons& clips)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
bool closed = true;
|
||||
|
||||
for(auto& path : subjects) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
|
||||
for(auto& path : clips) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptClip, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed);
|
||||
}
|
||||
|
||||
auto mode = ClipperLib::pftPositive;
|
||||
|
||||
return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode);
|
||||
};
|
||||
|
||||
// libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
|
||||
auto areafn = [](const ClipperPolygon& poly) { return - sl::area(poly); };
|
||||
|
||||
const double area_fill = m_printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
|
||||
const double fast_tilt = m_printer_config.fast_tilt_time.getFloat();// 5.0;
|
||||
const double slow_tilt = m_printer_config.slow_tilt_time.getFloat();// 8.0;
|
||||
|
||||
const double init_exp_time = m_material_config.initial_exposure_time.getFloat();
|
||||
const double exp_time = m_material_config.exposure_time.getFloat();
|
||||
|
||||
const int fade_layers_cnt = m_default_object_config.faded_layers.getInt();// 10 // [3;20]
|
||||
|
||||
const double width = m_printer_config.display_width.getFloat() / SCALING_FACTOR;
|
||||
const double height = m_printer_config.display_height.getFloat() / SCALING_FACTOR;
|
||||
const double display_area = width*height;
|
||||
|
||||
// get polygons for all instances in the object
|
||||
auto get_all_polygons =
|
||||
[flpXY](const ExPolygons& input_polygons,
|
||||
const std::vector<SLAPrintObject::Instance>& instances,
|
||||
bool is_lefthanded)
|
||||
{
|
||||
ClipperPolygons polygons;
|
||||
polygons.reserve(input_polygons.size() * instances.size());
|
||||
|
||||
for (const ExPolygon& polygon : input_polygons) {
|
||||
if(polygon.contour.empty()) continue;
|
||||
|
||||
for (size_t i = 0; i < instances.size(); ++i)
|
||||
{
|
||||
ClipperPolygon poly;
|
||||
|
||||
// should be a move
|
||||
poly.Contour.reserve(polygon.contour.size() + 1);
|
||||
|
||||
for(auto& p : polygon.contour.points)
|
||||
poly.Contour.emplace_back(p.x(), p.y());
|
||||
|
||||
auto pfirst = poly.Contour.front();
|
||||
poly.Contour.emplace_back(pfirst);
|
||||
|
||||
for(auto& h : polygon.holes) {
|
||||
poly.Holes.emplace_back();
|
||||
auto& hole = poly.Holes.back();
|
||||
hole.reserve(h.points.size() + 1);
|
||||
|
||||
for(auto& p : h.points) hole.emplace_back(p.x(), p.y());
|
||||
auto pfirst = hole.front(); hole.emplace_back(pfirst);
|
||||
}
|
||||
|
||||
if(is_lefthanded) {
|
||||
for(auto& p : poly.Contour) p.X = -p.X;
|
||||
std::reverse(poly.Contour.begin(), poly.Contour.end());
|
||||
for(auto& h : poly.Holes) {
|
||||
for(auto& p : h) p.X = -p.X;
|
||||
std::reverse(h.begin(), h.end());
|
||||
}
|
||||
}
|
||||
|
||||
sl::rotate(poly, double(instances[i].rotation));
|
||||
sl::translate(poly, ClipperPoint{instances[i].shift(X),
|
||||
instances[i].shift(Y)});
|
||||
|
||||
if (flpXY) {
|
||||
for(auto& p : poly.Contour) std::swap(p.X, p.Y);
|
||||
std::reverse(poly.Contour.begin(), poly.Contour.end());
|
||||
|
||||
for(auto& h : poly.Holes) {
|
||||
for(auto& p : h) std::swap(p.X, p.Y);
|
||||
std::reverse(h.begin(), h.end());
|
||||
}
|
||||
}
|
||||
|
||||
polygons.emplace_back(std::move(poly));
|
||||
}
|
||||
}
|
||||
return polygons;
|
||||
};
|
||||
|
||||
double supports_volume(0.0);
|
||||
double models_volume(0.0);
|
||||
|
||||
double estim_time(0.0);
|
||||
|
||||
size_t slow_layers = 0;
|
||||
size_t fast_layers = 0;
|
||||
|
||||
const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
|
||||
double fade_layer_time = init_exp_time;
|
||||
|
||||
SpinMutex mutex;
|
||||
using Lock = std::lock_guard<SpinMutex>;
|
||||
|
||||
// Going to parallel:
|
||||
auto printlayerfn = [this,
|
||||
// functions and read only vars
|
||||
get_all_polygons, polyunion, polydiff, areafn,
|
||||
area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time,
|
||||
|
||||
// write vars
|
||||
&mutex, &models_volume, &supports_volume, &estim_time, &slow_layers,
|
||||
&fast_layers, &fade_layer_time](size_t sliced_layer_cnt)
|
||||
{
|
||||
PrintLayer& layer = m_printer_input[sliced_layer_cnt];
|
||||
|
||||
// vector of slice record references
|
||||
auto& slicerecord_references = layer.slices();
|
||||
|
||||
if(slicerecord_references.empty()) return;
|
||||
|
||||
// Layer height should match for all object slices for a given level.
|
||||
const auto l_height = double(slicerecord_references.front().get().layer_height());
|
||||
|
||||
// Calculation of the consumed material
|
||||
|
||||
ClipperPolygons model_polygons;
|
||||
ClipperPolygons supports_polygons;
|
||||
|
||||
size_t c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) {
|
||||
return a + sr.get_slice(soModel).size();
|
||||
});
|
||||
|
||||
model_polygons.reserve(c);
|
||||
|
||||
c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) {
|
||||
return a + sr.get_slice(soModel).size();
|
||||
});
|
||||
|
||||
supports_polygons.reserve(c);
|
||||
|
||||
for(const SliceRecord& record : layer.slices()) {
|
||||
const SLAPrintObject *po = record.print_obj();
|
||||
|
||||
const ExPolygons &modelslices = record.get_slice(soModel);
|
||||
bool is_lefth = record.print_obj()->is_left_handed();
|
||||
if (!modelslices.empty()) {
|
||||
ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth);
|
||||
for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp));
|
||||
}
|
||||
|
||||
const ExPolygons &supportslices = record.get_slice(soSupport);
|
||||
if (!supportslices.empty()) {
|
||||
ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth);
|
||||
for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp));
|
||||
}
|
||||
}
|
||||
|
||||
model_polygons = polyunion(model_polygons);
|
||||
double layer_model_area = 0;
|
||||
for (const ClipperPolygon& polygon : model_polygons)
|
||||
layer_model_area += areafn(polygon);
|
||||
|
||||
if (layer_model_area < 0 || layer_model_area > 0) {
|
||||
Lock lck(mutex); models_volume += layer_model_area * l_height;
|
||||
}
|
||||
|
||||
if(!supports_polygons.empty()) {
|
||||
if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons);
|
||||
else supports_polygons = polydiff(supports_polygons, model_polygons);
|
||||
// allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
|
||||
}
|
||||
|
||||
double layer_support_area = 0;
|
||||
for (const ClipperPolygon& polygon : supports_polygons)
|
||||
layer_support_area += areafn(polygon);
|
||||
|
||||
if (layer_support_area < 0 || layer_support_area > 0) {
|
||||
Lock lck(mutex); supports_volume += layer_support_area * l_height;
|
||||
}
|
||||
|
||||
// Here we can save the expensively calculated polygons for printing
|
||||
ClipperPolygons trslices;
|
||||
trslices.reserve(model_polygons.size() + supports_polygons.size());
|
||||
for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly));
|
||||
for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly));
|
||||
|
||||
layer.transformed_slices(polyunion(trslices));
|
||||
|
||||
// Calculation of the slow and fast layers to the future controlling those values on FW
|
||||
|
||||
const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill;
|
||||
const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt;
|
||||
|
||||
{ Lock lck(mutex);
|
||||
if (is_fast_layer)
|
||||
fast_layers++;
|
||||
else
|
||||
slow_layers++;
|
||||
|
||||
|
||||
// Calculation of the printing time
|
||||
|
||||
if (sliced_layer_cnt < 3)
|
||||
estim_time += init_exp_time;
|
||||
else if (fade_layer_time > exp_time)
|
||||
{
|
||||
fade_layer_time -= delta_fade_time;
|
||||
estim_time += fade_layer_time;
|
||||
}
|
||||
else
|
||||
estim_time += exp_time;
|
||||
|
||||
estim_time += tilt_time;
|
||||
}
|
||||
};
|
||||
|
||||
// sequential version for debugging:
|
||||
// for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
|
||||
tbb::parallel_for<size_t, decltype(printlayerfn)>(0, m_printer_input.size(), printlayerfn);
|
||||
|
||||
m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR;
|
||||
m_print_statistics.objects_used_material = models_volume * SCALING_FACTOR * SCALING_FACTOR;
|
||||
|
||||
// Estimated printing time
|
||||
// A layers count o the highest object
|
||||
if (m_printer_input.size() == 0)
|
||||
m_print_statistics.estimated_print_time = "N/A";
|
||||
else
|
||||
m_print_statistics.estimated_print_time = get_time_dhms(float(estim_time));
|
||||
|
||||
m_print_statistics.fast_layers_count = fast_layers;
|
||||
m_print_statistics.slow_layers_count = slow_layers;
|
||||
|
||||
m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||
};
|
||||
|
||||
// Rasterizing the model objects, and their supports
|
||||
auto rasterize = [this, max_objstatus]() {
|
||||
if(canceled()) return;
|
||||
|
||||
// collect all the keys
|
||||
|
||||
// If the raster has vertical orientation, we will flip the coordinates
|
||||
@ -994,18 +1271,24 @@ void SLAPrint::process()
|
||||
auto lvlcnt = unsigned(m_printer_input.size());
|
||||
printer.layers(lvlcnt);
|
||||
|
||||
// slot is the portion of 100% that is realted to rasterization
|
||||
unsigned slot = PRINT_STEP_LEVELS[slapsRasterize];
|
||||
// ist: initial state; pst: previous state
|
||||
unsigned ist = max_objstatus, pst = ist;
|
||||
// coefficient to map the rasterization state (0-99) to the allocated
|
||||
// portion (slot) of the process state
|
||||
double sd = (100 - ist) / 100.0;
|
||||
double sd = (100 - max_objstatus) / 100.0;
|
||||
|
||||
// slot is the portion of 100% that is realted to rasterization
|
||||
unsigned slot = PRINT_STEP_LEVELS[slapsRasterize];
|
||||
|
||||
// pst: previous state
|
||||
double pst = m_report_status.status();
|
||||
|
||||
double increment = (slot * sd) / m_printer_input.size();
|
||||
double dstatus = m_report_status.status();
|
||||
|
||||
SpinMutex slck;
|
||||
|
||||
// procedure to process one height level. This will run in parallel
|
||||
auto lvlfn =
|
||||
[this, &slck, &printer, slot, sd, ist, &pst, flpXY]
|
||||
[this, &slck, &printer, increment, &dstatus, &pst]
|
||||
(unsigned level_id)
|
||||
{
|
||||
if(canceled()) return;
|
||||
@ -1015,38 +1298,19 @@ void SLAPrint::process()
|
||||
// Switch to the appropriate layer in the printer
|
||||
printer.begin_layer(level_id);
|
||||
|
||||
using Instance = SLAPrintObject::Instance;
|
||||
|
||||
auto draw =
|
||||
[&printer, flpXY, level_id](ExPolygon& poly, const Instance& tr)
|
||||
{
|
||||
poly.rotate(double(tr.rotation));
|
||||
poly.translate(tr.shift(X), tr.shift(Y));
|
||||
if(flpXY) swapXY(poly);
|
||||
for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
|
||||
printer.draw_polygon(poly, level_id);
|
||||
};
|
||||
|
||||
for(const SliceRecord& sr : printlayer.slices()) {
|
||||
if(! sr.print_obj()) continue;
|
||||
|
||||
for(const Instance& inst : sr.print_obj()->instances()) {
|
||||
ExPolygons objsl = sr.get_slice(soModel);
|
||||
for(ExPolygon& poly : objsl) draw(poly, inst);
|
||||
|
||||
ExPolygons supsl = sr.get_slice(soSupport);
|
||||
for(ExPolygon& poly : supsl) draw(poly, inst);
|
||||
}
|
||||
}
|
||||
|
||||
// Finish the layer for later saving it.
|
||||
printer.finish_layer(level_id);
|
||||
|
||||
// Status indication guarded with the spinlock
|
||||
auto st = ist + unsigned(sd*level_id*slot/m_printer_input.size());
|
||||
{
|
||||
std::lock_guard<SpinMutex> lck(slck);
|
||||
dstatus += increment;
|
||||
double st = std::round(dstatus);
|
||||
if(st > pst) {
|
||||
report_status(*this, int(st),
|
||||
m_report_status(*this, st,
|
||||
PRINT_STEP_LABELS[slapsRasterize]);
|
||||
pst = st;
|
||||
}
|
||||
@ -1079,18 +1343,16 @@ void SLAPrint::process()
|
||||
support_points,
|
||||
support_tree,
|
||||
base_pool,
|
||||
slice_supports,
|
||||
index_slices
|
||||
slice_supports
|
||||
};
|
||||
|
||||
std::array<slapsFn, slapsCount> print_program =
|
||||
{
|
||||
fillstats,
|
||||
rasterize,
|
||||
[](){} // validate
|
||||
merge_slices_and_eval_stats,
|
||||
rasterize
|
||||
};
|
||||
|
||||
unsigned st = min_objstatus;
|
||||
double st = min_objstatus;
|
||||
unsigned incr = 0;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Start slicing process.";
|
||||
@ -1104,18 +1366,18 @@ void SLAPrint::process()
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name;
|
||||
|
||||
for (int s = (int)step_ranges[idx_range]; s < (int)step_ranges[idx_range + 1]; ++s) {
|
||||
auto currentstep = (SLAPrintObjectStep)s;
|
||||
for (int s = int(step_ranges[idx_range]); s < int(step_ranges[idx_range + 1]); ++s) {
|
||||
auto currentstep = static_cast<SLAPrintObjectStep>(s);
|
||||
|
||||
// Cancellation checking. Each step will check for cancellation
|
||||
// on its own and return earlier gracefully. Just after it returns
|
||||
// execution gets to this point and throws the canceled signal.
|
||||
throw_if_canceled();
|
||||
|
||||
st += unsigned(incr * ostepd);
|
||||
st += incr * ostepd;
|
||||
|
||||
if(po->m_stepmask[currentstep] && po->set_started(currentstep)) {
|
||||
report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]);
|
||||
m_report_status(*this, st, OBJ_STEP_LABELS[currentstep]);
|
||||
pobj_program[currentstep](*po);
|
||||
throw_if_canceled();
|
||||
po->set_done(currentstep);
|
||||
@ -1127,7 +1389,7 @@ void SLAPrint::process()
|
||||
}
|
||||
|
||||
std::array<SLAPrintStep, slapsCount> printsteps = {
|
||||
slapsStats, slapsRasterize, slapsValidate
|
||||
slapsMergeSlicesAndEval, slapsRasterize
|
||||
};
|
||||
|
||||
// this would disable the rasterization step
|
||||
@ -1142,17 +1404,17 @@ void SLAPrint::process()
|
||||
|
||||
if(m_stepmask[currentstep] && set_started(currentstep))
|
||||
{
|
||||
report_status(*this, int(st), PRINT_STEP_LABELS[currentstep]);
|
||||
m_report_status(*this, st, PRINT_STEP_LABELS[currentstep]);
|
||||
print_program[currentstep]();
|
||||
throw_if_canceled();
|
||||
set_done(currentstep);
|
||||
}
|
||||
|
||||
st += unsigned(PRINT_STEP_LEVELS[currentstep] * pstd);
|
||||
st += PRINT_STEP_LEVELS[currentstep] * pstd;
|
||||
}
|
||||
|
||||
// If everything vent well
|
||||
report_status(*this, 100, L("Slicing done"));
|
||||
m_report_status(*this, 100, L("Slicing done"));
|
||||
}
|
||||
|
||||
bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys)
|
||||
@ -1193,11 +1455,11 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
|
||||
if (steps_rasterize.find(opt_key) != steps_rasterize.end()) {
|
||||
// These options only affect the final rasterization, or they are just notes without influence on the output,
|
||||
// so there is nothing to invalidate.
|
||||
steps.emplace_back(slapsRasterize);
|
||||
steps.emplace_back(slapsMergeSlicesAndEval);
|
||||
} else if (steps_ignore.find(opt_key) != steps_ignore.end()) {
|
||||
// These steps have no influence on the output. Just ignore them.
|
||||
} else if (opt_key == "initial_layer_height") {
|
||||
steps.emplace_back(slapsRasterize);
|
||||
steps.emplace_back(slapsMergeSlicesAndEval);
|
||||
osteps.emplace_back(slaposObjectSlice);
|
||||
} else {
|
||||
// All values should be covered.
|
||||
@ -1215,165 +1477,6 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
void SLAPrint::fill_statistics()
|
||||
{
|
||||
const double init_layer_height = m_material_config.initial_layer_height.getFloat();
|
||||
const double layer_height = m_default_object_config.layer_height.getFloat();
|
||||
|
||||
const double area_fill = m_printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
|
||||
const double fast_tilt = m_printer_config.fast_tilt_time.getFloat();// 5.0;
|
||||
const double slow_tilt = m_printer_config.slow_tilt_time.getFloat();// 8.0;
|
||||
|
||||
const double init_exp_time = m_material_config.initial_exposure_time.getFloat();
|
||||
const double exp_time = m_material_config.exposure_time.getFloat();
|
||||
|
||||
const int fade_layers_cnt = m_default_object_config.faded_layers.getInt();// 10 // [3;20]
|
||||
|
||||
const double width = m_printer_config.display_width.getFloat() / SCALING_FACTOR;
|
||||
const double height = m_printer_config.display_height.getFloat() / SCALING_FACTOR;
|
||||
const double display_area = width*height;
|
||||
|
||||
// get polygons for all instances in the object
|
||||
auto get_all_polygons = [](const ExPolygons& input_polygons, const std::vector<SLAPrintObject::Instance>& instances) {
|
||||
const size_t inst_cnt = instances.size();
|
||||
|
||||
size_t polygon_cnt = 0;
|
||||
for (const ExPolygon& polygon : input_polygons)
|
||||
polygon_cnt += polygon.holes.size() + 1;
|
||||
|
||||
Polygons polygons;
|
||||
polygons.reserve(polygon_cnt * inst_cnt);
|
||||
for (const ExPolygon& polygon : input_polygons) {
|
||||
for (size_t i = 0; i < inst_cnt; ++i)
|
||||
{
|
||||
ExPolygon tmp = polygon;
|
||||
tmp.rotate(double(instances[i].rotation));
|
||||
tmp.translate(instances[i].shift.x(), instances[i].shift.y());
|
||||
polygons_append(polygons, to_polygons(std::move(tmp)));
|
||||
}
|
||||
}
|
||||
return polygons;
|
||||
};
|
||||
|
||||
double supports_volume = 0.0;
|
||||
double models_volume = 0.0;
|
||||
|
||||
double estim_time = 0.0;
|
||||
|
||||
size_t slow_layers = 0;
|
||||
size_t fast_layers = 0;
|
||||
|
||||
// find highest object
|
||||
// Which is a better bet? To compare by max_z or by number of layers in the index?
|
||||
// float max_z = 0.;
|
||||
size_t max_layers_cnt = 0;
|
||||
size_t highest_obj_idx = 0;
|
||||
for (SLAPrintObject *&po : m_objects) {
|
||||
auto& slice_index = po->get_slice_index();
|
||||
if (! slice_index.empty()) {
|
||||
// float z = (-- slice_index.end())->slice_level();
|
||||
size_t cnt = slice_index.size();
|
||||
//if (z > max_z) {
|
||||
if (cnt > max_layers_cnt) {
|
||||
max_layers_cnt = cnt;
|
||||
// max_z = z;
|
||||
highest_obj_idx = &po - &m_objects.front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SLAPrintObject * highest_obj = m_objects[highest_obj_idx];
|
||||
auto& highest_obj_slice_index = highest_obj->get_slice_index();
|
||||
|
||||
const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
|
||||
double fade_layer_time = init_exp_time;
|
||||
|
||||
int sliced_layer_cnt = 0;
|
||||
for (const SliceRecord& layer : highest_obj_slice_index)
|
||||
{
|
||||
const auto l_height = double(layer.layer_height());
|
||||
|
||||
// Calculation of the consumed material
|
||||
|
||||
Polygons model_polygons;
|
||||
Polygons supports_polygons;
|
||||
|
||||
for (SLAPrintObject * po : m_objects)
|
||||
{
|
||||
const SliceRecord *record = nullptr;
|
||||
{
|
||||
const SliceRecord& slr = po->closest_slice_to_slice_level(layer.slice_level(), float(EPSILON));
|
||||
if (!slr.is_valid()) continue;
|
||||
record = &slr;
|
||||
}
|
||||
|
||||
const ExPolygons &modelslices = record->get_slice(soModel);
|
||||
if (!modelslices.empty())
|
||||
append(model_polygons, get_all_polygons(modelslices, po->instances()));
|
||||
|
||||
const ExPolygons &supportslices = record->get_slice(soSupport);
|
||||
if (!supportslices.empty())
|
||||
append(supports_polygons, get_all_polygons(supportslices, po->instances()));
|
||||
}
|
||||
|
||||
model_polygons = union_(model_polygons);
|
||||
double layer_model_area = 0;
|
||||
for (const Polygon& polygon : model_polygons)
|
||||
layer_model_area += polygon.area();
|
||||
|
||||
if (layer_model_area != 0)
|
||||
models_volume += layer_model_area * l_height;
|
||||
|
||||
if (!supports_polygons.empty() && !model_polygons.empty())
|
||||
supports_polygons = diff(supports_polygons, model_polygons);
|
||||
double layer_support_area = 0;
|
||||
for (const Polygon& polygon : supports_polygons)
|
||||
layer_support_area += polygon.area();
|
||||
|
||||
if (layer_support_area != 0)
|
||||
supports_volume += layer_support_area * l_height;
|
||||
|
||||
// Calculation of the slow and fast layers to the future controlling those values on FW
|
||||
|
||||
const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill;
|
||||
const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt;
|
||||
if (is_fast_layer)
|
||||
fast_layers++;
|
||||
else
|
||||
slow_layers++;
|
||||
|
||||
|
||||
// Calculation of the printing time
|
||||
|
||||
if (sliced_layer_cnt < 3)
|
||||
estim_time += init_exp_time;
|
||||
else if (fade_layer_time > exp_time)
|
||||
{
|
||||
fade_layer_time -= delta_fade_time;
|
||||
estim_time += fade_layer_time;
|
||||
}
|
||||
else
|
||||
estim_time += exp_time;
|
||||
|
||||
estim_time += tilt_time;
|
||||
|
||||
sliced_layer_cnt++;
|
||||
}
|
||||
|
||||
m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR;
|
||||
m_print_statistics.objects_used_material = models_volume * SCALING_FACTOR * SCALING_FACTOR;
|
||||
|
||||
// Estimated printing time
|
||||
// A layers count o the highest object
|
||||
if (max_layers_cnt == 0)
|
||||
m_print_statistics.estimated_print_time = "N/A";
|
||||
else
|
||||
m_print_statistics.estimated_print_time = get_time_dhms(float(estim_time));
|
||||
|
||||
m_print_statistics.fast_layers_count = fast_layers;
|
||||
m_print_statistics.slow_layers_count = slow_layers;
|
||||
}
|
||||
|
||||
// Returns true if an object step is done on all objects and there's at least one object.
|
||||
bool SLAPrint::is_step_done(SLAPrintObjectStep step) const
|
||||
{
|
||||
@ -1459,19 +1562,16 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step)
|
||||
if (step == slaposObjectSlice) {
|
||||
invalidated |= this->invalidate_all_steps();
|
||||
} else if (step == slaposSupportPoints) {
|
||||
invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports, slaposIndexSlices });
|
||||
invalidated |= m_print->invalidate_step(slapsRasterize);
|
||||
invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports });
|
||||
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
||||
} else if (step == slaposSupportTree) {
|
||||
invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports, slaposIndexSlices });
|
||||
invalidated |= m_print->invalidate_step(slapsRasterize);
|
||||
invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports });
|
||||
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
||||
} else if (step == slaposBasePool) {
|
||||
invalidated |= this->invalidate_steps({slaposSliceSupports, slaposIndexSlices});
|
||||
invalidated |= m_print->invalidate_step(slapsRasterize);
|
||||
invalidated |= this->invalidate_steps({slaposSliceSupports});
|
||||
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
||||
} else if (step == slaposSliceSupports) {
|
||||
invalidated |= this->invalidate_step(slaposIndexSlices);
|
||||
invalidated |= m_print->invalidate_step(slapsRasterize);
|
||||
} else if(step == slaposIndexSlices) {
|
||||
invalidated |= m_print->invalidate_step(slapsRasterize);
|
||||
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
||||
}
|
||||
return invalidated;
|
||||
}
|
||||
@ -1547,18 +1647,6 @@ const ExPolygons &SliceRecord::get_slice(SliceOrigin o) const
|
||||
return idx >= v.size() ? EMPTY_SLICE : v[idx];
|
||||
}
|
||||
|
||||
const std::vector<SliceRecord> & SLAPrintObject::get_slice_index() const
|
||||
{
|
||||
// assert(is_step_done(slaposIndexSlices));
|
||||
return m_slice_index;
|
||||
}
|
||||
|
||||
const std::vector<ExPolygons> &SLAPrintObject::get_model_slices() const
|
||||
{
|
||||
// assert(is_step_done(slaposObjectSlice));
|
||||
return m_model_slices;
|
||||
}
|
||||
|
||||
bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const
|
||||
{
|
||||
switch (step) {
|
||||
@ -1650,6 +1738,7 @@ DynamicConfig SLAPrintStatistics::placeholders()
|
||||
"print_time", "total_cost", "total_weight",
|
||||
"objects_used_material", "support_used_material" })
|
||||
config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}"));
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@ -1670,4 +1759,12 @@ std::string SLAPrintStatistics::finalize_output_path(const std::string &path_in)
|
||||
return final_path;
|
||||
}
|
||||
|
||||
void SLAPrint::StatusReporter::operator()(
|
||||
SLAPrint &p, double st, const std::string &msg, unsigned flags)
|
||||
{
|
||||
m_st = st;
|
||||
BOOST_LOG_TRIVIAL(info) << st << "% " << msg;
|
||||
p.set_status(int(std::round(st)), msg, flags);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -6,14 +6,14 @@
|
||||
#include "PrintExport.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
#include "Zipper.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum SLAPrintStep : unsigned int {
|
||||
slapsStats,
|
||||
slapsMergeSlicesAndEval,
|
||||
slapsRasterize,
|
||||
slapsValidate,
|
||||
slapsCount
|
||||
};
|
||||
|
||||
@ -23,7 +23,6 @@ enum SLAPrintObjectStep : unsigned int {
|
||||
slaposSupportTree,
|
||||
slaposBasePool,
|
||||
slaposSliceSupports,
|
||||
slaposIndexSlices,
|
||||
slaposCount
|
||||
};
|
||||
|
||||
@ -52,6 +51,7 @@ public:
|
||||
|
||||
const SLAPrintObjectConfig& config() const { return m_config; }
|
||||
const Transform3d& trafo() const { return m_trafo; }
|
||||
bool is_left_handed() const { return m_left_handed; }
|
||||
|
||||
struct Instance {
|
||||
Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {}
|
||||
@ -127,7 +127,7 @@ public:
|
||||
|
||||
bool is_valid() const { return ! std::isnan(m_slice_z); }
|
||||
|
||||
const SLAPrintObject* print_obj() const { return m_po; }
|
||||
const SLAPrintObject* print_obj() const { assert(m_po); return m_po; }
|
||||
|
||||
// Methods for setting the indices into the slice vectors.
|
||||
void set_model_slice_idx(const SLAPrintObject &po, size_t id) {
|
||||
@ -190,7 +190,7 @@ private:
|
||||
return it;
|
||||
}
|
||||
|
||||
const std::vector<ExPolygons>& get_model_slices() const;
|
||||
const std::vector<ExPolygons>& get_model_slices() const { return m_model_slices; }
|
||||
const std::vector<ExPolygons>& get_support_slices() const;
|
||||
|
||||
public:
|
||||
@ -205,7 +205,9 @@ public:
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Retrieve the slice index.
|
||||
const std::vector<SliceRecord>& get_slice_index() const;
|
||||
const std::vector<SliceRecord>& get_slice_index() const {
|
||||
return m_slice_index;
|
||||
}
|
||||
|
||||
// Search slice index for the closest slice to given print_level.
|
||||
// max_epsilon gives the allowable deviation of the returned slice record's
|
||||
@ -240,11 +242,12 @@ protected:
|
||||
void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false)
|
||||
{ this->m_config.apply_only(other, keys, ignore_nonexistent); }
|
||||
|
||||
void set_trafo(const Transform3d& trafo) {
|
||||
m_transformed_rmesh.invalidate([this, &trafo](){ m_trafo = trafo; });
|
||||
void set_trafo(const Transform3d& trafo, bool left_handed) {
|
||||
m_transformed_rmesh.invalidate([this, &trafo, left_handed](){ m_trafo = trafo; m_left_handed = left_handed; });
|
||||
}
|
||||
|
||||
void set_instances(const std::vector<Instance> &instances) { m_instances = instances; }
|
||||
template<class InstVec> inline void set_instances(InstVec&& instances) { m_instances = std::forward<InstVec>(instances); }
|
||||
|
||||
// Invalidates the step, and its depending steps in SLAPrintObject and SLAPrint.
|
||||
bool invalidate_step(SLAPrintObjectStep step);
|
||||
bool invalidate_all_steps();
|
||||
@ -261,6 +264,8 @@ private:
|
||||
|
||||
// Translation in Z + Rotation by Y and Z + Scaling / Mirroring.
|
||||
Transform3d m_trafo = Transform3d::Identity();
|
||||
// m_trafo is left handed -> 3x3 affine transformation has negative determinant.
|
||||
bool m_left_handed = false;
|
||||
|
||||
std::vector<Instance> m_instances;
|
||||
|
||||
@ -367,31 +372,6 @@ private: // Prevents erroneous use by other classes.
|
||||
|
||||
public:
|
||||
|
||||
// An aggregation of SliceRecord-s from all the print objects for each
|
||||
// occupied layer. Slice record levels dont have to match exactly.
|
||||
// They are unified if the level difference is within +/- SCALED_EPSILON
|
||||
class PrintLayer {
|
||||
coord_t m_level;
|
||||
|
||||
// The collection of slice records for the current level.
|
||||
std::vector<std::reference_wrapper<const SliceRecord>> m_slices;
|
||||
|
||||
public:
|
||||
|
||||
explicit PrintLayer(coord_t lvl) : m_level(lvl) {}
|
||||
|
||||
// for being sorted in their container (see m_printer_input)
|
||||
bool operator<(const PrintLayer& other) const {
|
||||
return m_level < other.m_level;
|
||||
}
|
||||
|
||||
void add(const SliceRecord& sr) { m_slices.emplace_back(sr); }
|
||||
|
||||
coord_t level() const { return m_level; }
|
||||
|
||||
auto slices() const -> const decltype (m_slices)& { return m_slices; }
|
||||
};
|
||||
|
||||
SLAPrint(): m_stepmask(slapsCount, true) {}
|
||||
|
||||
virtual ~SLAPrint() override { this->clear(); }
|
||||
@ -407,7 +387,7 @@ public:
|
||||
// Returns true if an object step is done on all objects and there's at least one object.
|
||||
bool is_step_done(SLAPrintObjectStep step) const;
|
||||
// Returns true if the last step was finished with success.
|
||||
bool finished() const override { return this->is_step_done(slaposIndexSlices) && this->Inherited::is_step_done(slapsRasterize); }
|
||||
bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); }
|
||||
|
||||
template<class Fmt = SLAminzZipper>
|
||||
void export_raster(const std::string& fname) {
|
||||
@ -427,6 +407,43 @@ public:
|
||||
|
||||
std::string validate() const override;
|
||||
|
||||
// An aggregation of SliceRecord-s from all the print objects for each
|
||||
// occupied layer. Slice record levels dont have to match exactly.
|
||||
// They are unified if the level difference is within +/- SCALED_EPSILON
|
||||
class PrintLayer {
|
||||
coord_t m_level;
|
||||
|
||||
// The collection of slice records for the current level.
|
||||
std::vector<std::reference_wrapper<const SliceRecord>> m_slices;
|
||||
|
||||
std::vector<ClipperLib::Polygon> m_transformed_slices;
|
||||
|
||||
template<class Container> void transformed_slices(Container&& c) {
|
||||
m_transformed_slices = std::forward<Container>(c);
|
||||
}
|
||||
|
||||
friend void SLAPrint::process();
|
||||
|
||||
public:
|
||||
|
||||
explicit PrintLayer(coord_t lvl) : m_level(lvl) {}
|
||||
|
||||
// for being sorted in their container (see m_printer_input)
|
||||
bool operator<(const PrintLayer& other) const {
|
||||
return m_level < other.m_level;
|
||||
}
|
||||
|
||||
void add(const SliceRecord& sr) { m_slices.emplace_back(sr); }
|
||||
|
||||
coord_t level() const { return m_level; }
|
||||
|
||||
auto slices() const -> const decltype (m_slices)& { return m_slices; }
|
||||
|
||||
const std::vector<ClipperLib::Polygon> & transformed_slices() const {
|
||||
return m_transformed_slices;
|
||||
}
|
||||
};
|
||||
|
||||
// The aggregated and leveled print records from various objects.
|
||||
// TODO: use this structure for the preview in the future.
|
||||
const std::vector<PrintLayer>& print_layers() const { return m_printer_input; }
|
||||
@ -435,11 +452,12 @@ private:
|
||||
using SLAPrinter = FilePrinter<FilePrinterFormat::SLA_PNGZIP>;
|
||||
using SLAPrinterPtr = std::unique_ptr<SLAPrinter>;
|
||||
|
||||
// Implement same logic as in SLAPrintObject
|
||||
bool invalidate_step(SLAPrintStep st);
|
||||
|
||||
// Invalidate steps based on a set of parameters changed.
|
||||
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
|
||||
|
||||
void fill_statistics();
|
||||
|
||||
SLAPrintConfig m_print_config;
|
||||
SLAPrinterConfig m_printer_config;
|
||||
SLAMaterialConfig m_material_config;
|
||||
@ -457,6 +475,15 @@ private:
|
||||
// Estimated print time, material consumed.
|
||||
SLAPrintStatistics m_print_statistics;
|
||||
|
||||
class StatusReporter {
|
||||
double m_st = 0;
|
||||
public:
|
||||
void operator() (SLAPrint& p, double st, const std::string& msg,
|
||||
unsigned flags = SlicingStatus::DEFAULT);
|
||||
double status() const { return m_st; }
|
||||
} m_report_status;
|
||||
|
||||
|
||||
friend SLAPrintObject;
|
||||
};
|
||||
|
||||
|
@ -55,7 +55,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& f
|
||||
stl.stats.original_num_facets = stl.stats.number_of_facets;
|
||||
stl_allocate(&stl);
|
||||
|
||||
for (int i = 0; i < stl.stats.number_of_facets; i++) {
|
||||
for (uint32_t i = 0; i < stl.stats.number_of_facets; i++) {
|
||||
stl_facet facet;
|
||||
facet.vertex[0] = points[facets[i](0)].cast<float>();
|
||||
facet.vertex[1] = points[facets[i](1)].cast<float>();
|
||||
@ -125,9 +125,9 @@ void TriangleMesh::repair()
|
||||
float tolerance = stl.stats.shortest_edge;
|
||||
float increment = stl.stats.bounding_diameter / 10000.0;
|
||||
int iterations = 2;
|
||||
if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
|
||||
if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) {
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
|
||||
if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) {
|
||||
//printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations);
|
||||
#ifdef SLIC3R_TRACE_REPAIR
|
||||
BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_nearby";
|
||||
@ -143,7 +143,7 @@ void TriangleMesh::repair()
|
||||
}
|
||||
|
||||
// remove_unconnected
|
||||
if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
|
||||
if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) {
|
||||
#ifdef SLIC3R_TRACE_REPAIR
|
||||
BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets";
|
||||
#endif /* SLIC3R_TRACE_REPAIR */
|
||||
@ -212,9 +212,9 @@ void TriangleMesh::check_topology()
|
||||
float tolerance = stl.stats.shortest_edge;
|
||||
float increment = stl.stats.bounding_diameter / 10000.0;
|
||||
int iterations = 2;
|
||||
if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
|
||||
if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) {
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
|
||||
if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) {
|
||||
//printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations);
|
||||
stl_check_facets_nearby(&stl, tolerance);
|
||||
//printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed);
|
||||
@ -338,113 +338,79 @@ void TriangleMesh::rotate(double angle, Point* center)
|
||||
this->translate(c(0), c(1), 0);
|
||||
}
|
||||
|
||||
bool TriangleMesh::has_multiple_patches() const
|
||||
/**
|
||||
* Calculates whether or not the mesh is splittable.
|
||||
*/
|
||||
bool TriangleMesh::is_splittable() const
|
||||
{
|
||||
// we need neighbors
|
||||
if (!this->repaired)
|
||||
throw std::runtime_error("split() requires repair()");
|
||||
std::vector<unsigned char> visited;
|
||||
find_unvisited_neighbors(visited);
|
||||
|
||||
if (this->stl.stats.number_of_facets == 0)
|
||||
return false;
|
||||
|
||||
std::vector<int> facet_queue(this->stl.stats.number_of_facets, 0);
|
||||
std::vector<char> facet_visited(this->stl.stats.number_of_facets, false);
|
||||
int facet_queue_cnt = 1;
|
||||
facet_queue[0] = 0;
|
||||
facet_visited[0] = true;
|
||||
while (facet_queue_cnt > 0) {
|
||||
int facet_idx = facet_queue[-- facet_queue_cnt];
|
||||
facet_visited[facet_idx] = true;
|
||||
for (int j = 0; j < 3; ++ j) {
|
||||
int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j];
|
||||
if (neighbor_idx != -1 && ! facet_visited[neighbor_idx])
|
||||
facet_queue[facet_queue_cnt ++] = neighbor_idx;
|
||||
}
|
||||
// Try finding an unvisited facet. If there are none, the mesh is not splittable.
|
||||
auto it = std::find(visited.begin(), visited.end(), false);
|
||||
return it != visited.end();
|
||||
}
|
||||
|
||||
// If any of the face was not visited at the first time, return "multiple bodies".
|
||||
for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; ++ facet_idx)
|
||||
if (! facet_visited[facet_idx])
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t TriangleMesh::number_of_patches() const
|
||||
/**
|
||||
* Visit all unvisited neighboring facets that are reachable from the first unvisited facet,
|
||||
* and return them.
|
||||
*
|
||||
* @param facet_visited A reference to a vector of booleans. Contains whether or not a
|
||||
* facet with the same index has been visited.
|
||||
* @return A deque with all newly visited facets.
|
||||
*/
|
||||
std::deque<uint32_t> TriangleMesh::find_unvisited_neighbors(std::vector<unsigned char> &facet_visited) const
|
||||
{
|
||||
// we need neighbors
|
||||
// Make sure we're not operating on a broken mesh.
|
||||
if (!this->repaired)
|
||||
throw std::runtime_error("split() requires repair()");
|
||||
throw std::runtime_error("find_unvisited_neighbors() requires repair()");
|
||||
|
||||
if (this->stl.stats.number_of_facets == 0)
|
||||
return false;
|
||||
// If the visited list is empty, populate it with false for every facet.
|
||||
if (facet_visited.empty())
|
||||
facet_visited = std::vector<unsigned char>(this->stl.stats.number_of_facets, false);
|
||||
|
||||
std::vector<int> facet_queue(this->stl.stats.number_of_facets, 0);
|
||||
std::vector<char> facet_visited(this->stl.stats.number_of_facets, false);
|
||||
int facet_queue_cnt = 0;
|
||||
size_t num_bodies = 0;
|
||||
for (;;) {
|
||||
// Find a seeding triangle for a new body.
|
||||
int facet_idx = 0;
|
||||
for (; facet_idx < this->stl.stats.number_of_facets; ++ facet_idx)
|
||||
if (! facet_visited[facet_idx]) {
|
||||
// A seed triangle was found.
|
||||
facet_queue[facet_queue_cnt ++] = facet_idx;
|
||||
facet_visited[facet_idx] = true;
|
||||
break;
|
||||
}
|
||||
if (facet_idx == this->stl.stats.number_of_facets)
|
||||
// No seed found.
|
||||
break;
|
||||
++ num_bodies;
|
||||
while (facet_queue_cnt > 0) {
|
||||
int facet_idx = facet_queue[-- facet_queue_cnt];
|
||||
facet_visited[facet_idx] = true;
|
||||
for (int j = 0; j < 3; ++ j) {
|
||||
int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j];
|
||||
if (neighbor_idx != -1 && ! facet_visited[neighbor_idx])
|
||||
facet_queue[facet_queue_cnt ++] = neighbor_idx;
|
||||
// Find the first unvisited facet.
|
||||
std::queue<uint32_t> facet_queue;
|
||||
std::deque<uint32_t> facets;
|
||||
auto facet = std::find(facet_visited.begin(), facet_visited.end(), false);
|
||||
if (facet != facet_visited.end()) {
|
||||
uint32_t idx = uint32_t(facet - facet_visited.begin());
|
||||
facet_queue.push(idx);
|
||||
facet_visited[idx] = true;
|
||||
facets.emplace_back(idx);
|
||||
}
|
||||
|
||||
// Traverse all reachable neighbors and mark them as visited.
|
||||
while (! facet_queue.empty()) {
|
||||
uint32_t facet_idx = facet_queue.front();
|
||||
facet_queue.pop();
|
||||
for (int neighbor_idx : this->stl.neighbors_start[facet_idx].neighbor)
|
||||
if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) {
|
||||
facet_queue.push(uint32_t(neighbor_idx));
|
||||
facet_visited[neighbor_idx] = true;
|
||||
facets.emplace_back(uint32_t(neighbor_idx));
|
||||
}
|
||||
}
|
||||
|
||||
return num_bodies;
|
||||
return facets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a mesh into multiple meshes when possible.
|
||||
*
|
||||
* @return A TriangleMeshPtrs with the newly created meshes.
|
||||
*/
|
||||
TriangleMeshPtrs TriangleMesh::split() const
|
||||
{
|
||||
// Loop while we have remaining facets.
|
||||
std::vector<unsigned char> facet_visited;
|
||||
TriangleMeshPtrs meshes;
|
||||
std::vector<unsigned char> facet_visited(this->stl.stats.number_of_facets, false);
|
||||
|
||||
// we need neighbors
|
||||
if (!this->repaired)
|
||||
throw std::runtime_error("split() requires repair()");
|
||||
|
||||
// loop while we have remaining facets
|
||||
for (;;) {
|
||||
// get the first facet
|
||||
std::queue<int> facet_queue;
|
||||
std::deque<int> facets;
|
||||
for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; ++ facet_idx) {
|
||||
if (! facet_visited[facet_idx]) {
|
||||
// if facet was not seen put it into queue and start searching
|
||||
facet_queue.push(facet_idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (facet_queue.empty())
|
||||
std::deque<uint32_t> facets = find_unvisited_neighbors(facet_visited);
|
||||
if (facets.empty())
|
||||
break;
|
||||
|
||||
while (! facet_queue.empty()) {
|
||||
int facet_idx = facet_queue.front();
|
||||
facet_queue.pop();
|
||||
if (! facet_visited[facet_idx]) {
|
||||
facets.emplace_back(facet_idx);
|
||||
for (int j = 0; j < 3; ++ j)
|
||||
facet_queue.push(this->stl.neighbors_start[facet_idx].neighbor[j]);
|
||||
facet_visited[facet_idx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new mesh for the part that was just split off.
|
||||
TriangleMesh* mesh = new TriangleMesh;
|
||||
meshes.emplace_back(mesh);
|
||||
mesh->stl.stats.type = inmemory;
|
||||
@ -453,8 +419,9 @@ TriangleMeshPtrs TriangleMesh::split() const
|
||||
stl_clear_error(&mesh->stl);
|
||||
stl_allocate(&mesh->stl);
|
||||
|
||||
// Assign the facets to the new mesh.
|
||||
bool first = true;
|
||||
for (std::deque<int>::const_iterator facet = facets.begin(); facet != facets.end(); ++ facet) {
|
||||
for (auto facet = facets.begin(); facet != facets.end(); ++ facet) {
|
||||
mesh->stl.facet_start[facet - facets.begin()] = this->stl.facet_start[*facet];
|
||||
stl_facet_stats(&mesh->stl, this->stl.facet_start[*facet], first);
|
||||
}
|
||||
@ -476,7 +443,7 @@ void TriangleMesh::merge(const TriangleMesh &mesh)
|
||||
stl_reallocate(&this->stl);
|
||||
|
||||
// copy facets
|
||||
for (int i = 0; i < mesh.stl.stats.number_of_facets; ++ i)
|
||||
for (uint32_t i = 0; i < mesh.stl.stats.number_of_facets; ++ i)
|
||||
this->stl.facet_start[number_of_facets + i] = mesh.stl.facet_start[i];
|
||||
|
||||
// update size
|
||||
@ -489,7 +456,7 @@ ExPolygons TriangleMesh::horizontal_projection() const
|
||||
{
|
||||
Polygons pp;
|
||||
pp.reserve(this->stl.stats.number_of_facets);
|
||||
for (int i = 0; i < this->stl.stats.number_of_facets; ++ i) {
|
||||
for (uint32_t i = 0; i < this->stl.stats.number_of_facets; ++ i) {
|
||||
stl_facet* facet = &this->stl.facet_start[i];
|
||||
Polygon p;
|
||||
p.points.resize(3);
|
||||
@ -531,7 +498,7 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) c
|
||||
BoundingBoxf3 bbox;
|
||||
if (stl.v_shared == nullptr) {
|
||||
// Using the STL faces.
|
||||
for (int i = 0; i < this->facets_count(); ++ i) {
|
||||
for (size_t i = 0; i < this->facets_count(); ++ i) {
|
||||
const stl_facet &facet = this->stl.facet_start[i];
|
||||
for (size_t j = 0; j < 3; ++ j)
|
||||
bbox.merge(trafo * facet.vertex[j].cast<double>());
|
||||
@ -656,7 +623,7 @@ void TriangleMeshSlicer::init(TriangleMesh *_mesh, throw_on_cancel_callback_type
|
||||
};
|
||||
std::vector<EdgeToFace> edges_map;
|
||||
edges_map.assign(this->mesh->stl.stats.number_of_facets * 3, EdgeToFace());
|
||||
for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx)
|
||||
for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx)
|
||||
for (int i = 0; i < 3; ++ i) {
|
||||
EdgeToFace &e2f = edges_map[facet_idx*3+i];
|
||||
e2f.vertex_low = this->mesh->stl.v_indices[facet_idx].vertex[i];
|
||||
@ -905,7 +872,6 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
|
||||
const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal;
|
||||
// We may ignore this edge for slicing purposes, but we may still use it for object cutting.
|
||||
FacetSliceType result = Slicing;
|
||||
const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx];
|
||||
if (min_z == max_z) {
|
||||
// All three vertices are aligned with slice_z.
|
||||
line_out->edge_type = feHorizontal;
|
||||
@ -917,8 +883,6 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
|
||||
}
|
||||
} else {
|
||||
// Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane.
|
||||
int nbr_idx = j % 3;
|
||||
int nbr_face = nbr.neighbor[nbr_idx];
|
||||
// Is the third vertex below the cutting plane?
|
||||
bool third_below = v0.z() < slice_z || v1.z() < slice_z || v2.z() < slice_z;
|
||||
// Two vertices on the cutting plane, the third vertex is below the plane. Consider the edge to be part of the slice
|
||||
@ -1697,7 +1661,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object";
|
||||
float scaled_z = scale_(z);
|
||||
for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) {
|
||||
for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) {
|
||||
stl_facet* facet = &this->mesh->stl.facet_start[facet_idx];
|
||||
|
||||
// find facet extents
|
||||
|
@ -68,18 +68,14 @@ public:
|
||||
size_t facets_count() const { return this->stl.stats.number_of_facets; }
|
||||
bool empty() const { return this->facets_count() == 0; }
|
||||
|
||||
// Returns true, if there are two and more connected patches in the mesh.
|
||||
// Returns false, if one or zero connected patch is in the mesh.
|
||||
bool has_multiple_patches() const;
|
||||
|
||||
// Count disconnected triangle patches.
|
||||
size_t number_of_patches() const;
|
||||
bool is_splittable() const;
|
||||
|
||||
stl_file stl;
|
||||
bool repaired;
|
||||
|
||||
private:
|
||||
void require_shared_vertices();
|
||||
std::deque<uint32_t> find_unvisited_neighbors(std::vector<unsigned char> &facet_visited) const;
|
||||
friend class TriangleMeshSlicer;
|
||||
};
|
||||
|
||||
|
@ -208,7 +208,7 @@ public:
|
||||
|
||||
// Shorten the dhms time by removing the seconds, rounding the dhm to full minutes
|
||||
// and removing spaces.
|
||||
static std::string short_time(const std::string &time)
|
||||
inline std::string short_time(const std::string &time)
|
||||
{
|
||||
// Parse the dhms time format.
|
||||
int days = 0;
|
||||
@ -247,7 +247,7 @@ static std::string short_time(const std::string &time)
|
||||
}
|
||||
|
||||
// Returns the given time is seconds in format DDd HHh MMm SSs
|
||||
static std::string get_time_dhms(float time_in_secs)
|
||||
inline std::string get_time_dhms(float time_in_secs)
|
||||
{
|
||||
int days = (int)(time_in_secs / 86400.0f);
|
||||
time_in_secs -= (float)days * 86400.0f;
|
||||
|
@ -361,6 +361,14 @@ int CLI::run(int argc, char **argv)
|
||||
std::string outfile = m_config.opt_string("output");
|
||||
Print fff_print;
|
||||
SLAPrint sla_print;
|
||||
|
||||
sla_print.set_status_callback(
|
||||
[](const PrintBase::SlicingStatus& s)
|
||||
{
|
||||
if(s.percent >= 0) // FIXME: is this sufficient?
|
||||
printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str());
|
||||
});
|
||||
|
||||
PrintBase *print = (printer_technology == ptFFF) ? static_cast<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&sla_print);
|
||||
if (! m_config.opt_bool("dont_arrange")) {
|
||||
//FIXME make the min_object_distance configurable.
|
||||
|
@ -333,6 +333,13 @@ Transform3d GLVolume::world_matrix() const
|
||||
return m;
|
||||
}
|
||||
|
||||
bool GLVolume::is_left_handed() const
|
||||
{
|
||||
const Vec3d &m1 = m_instance_transformation.get_mirror();
|
||||
const Vec3d &m2 = m_volume_transformation.get_mirror();
|
||||
return m1.x() * m1.y() * m1.z() * m2.x() * m2.y() * m2.z() < 0.;
|
||||
}
|
||||
|
||||
const BoundingBoxf3& GLVolume::transformed_bounding_box() const
|
||||
{
|
||||
assert(bounding_box.defined || bounding_box.min(0) >= bounding_box.max(0) || bounding_box.min(1) >= bounding_box.max(1) || bounding_box.min(2) >= bounding_box.max(2));
|
||||
@ -401,6 +408,8 @@ void GLVolume::render() const
|
||||
if (!is_active)
|
||||
return;
|
||||
|
||||
if (this->is_left_handed())
|
||||
glFrontFace(GL_CW);
|
||||
glsafe(::glCullFace(GL_BACK));
|
||||
glsafe(::glPushMatrix());
|
||||
|
||||
@ -410,6 +419,8 @@ void GLVolume::render() const
|
||||
else
|
||||
this->indexed_vertex_array.render();
|
||||
glsafe(::glPopMatrix());
|
||||
if (this->is_left_handed())
|
||||
glFrontFace(GL_CCW);
|
||||
}
|
||||
|
||||
void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) const
|
||||
@ -420,6 +431,9 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c
|
||||
if (!indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)
|
||||
return;
|
||||
|
||||
if (this->is_left_handed())
|
||||
glFrontFace(GL_CW);
|
||||
|
||||
GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first));
|
||||
GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first));
|
||||
if (n_triangles + n_quads == 0)
|
||||
@ -481,6 +495,9 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c
|
||||
}
|
||||
|
||||
glsafe(::glPopMatrix());
|
||||
|
||||
if (this->is_left_handed())
|
||||
glFrontFace(GL_CCW);
|
||||
}
|
||||
|
||||
void GLVolume::render_legacy() const
|
||||
@ -489,6 +506,9 @@ void GLVolume::render_legacy() const
|
||||
if (!is_active)
|
||||
return;
|
||||
|
||||
if (this->is_left_handed())
|
||||
glFrontFace(GL_CW);
|
||||
|
||||
GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first));
|
||||
GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first));
|
||||
if (n_triangles + n_quads == 0)
|
||||
@ -520,6 +540,9 @@ void GLVolume::render_legacy() const
|
||||
glsafe(::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, indexed_vertex_array.quad_indices.data() + qverts_range.first));
|
||||
|
||||
glsafe(::glPopMatrix());
|
||||
|
||||
if (this->is_left_handed())
|
||||
glFrontFace(GL_CCW);
|
||||
}
|
||||
|
||||
std::vector<int> GLVolumeCollection::load_object(
|
||||
|
@ -388,6 +388,7 @@ public:
|
||||
int instance_idx() const { return this->composite_id.instance_id; }
|
||||
|
||||
Transform3d world_matrix() const;
|
||||
bool is_left_handed() const;
|
||||
|
||||
const BoundingBoxf3& transformed_bounding_box() const;
|
||||
const BoundingBoxf3& transformed_convex_hull_bounding_box() const;
|
||||
|
@ -59,6 +59,8 @@ enum {
|
||||
USB_PID_MK3 = 2,
|
||||
USB_PID_MMU_BOOT = 3,
|
||||
USB_PID_MMU_APP = 4,
|
||||
USB_PID_CW1_BOOT = 7,
|
||||
USB_PID_CW1_APP = 8,
|
||||
};
|
||||
|
||||
// This enum discriminates the kind of information in EVT_AVRDUDE,
|
||||
@ -77,6 +79,13 @@ wxDEFINE_EVENT(EVT_AVRDUDE, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent);
|
||||
|
||||
struct Avr109Pid
|
||||
{
|
||||
unsigned boot;
|
||||
unsigned app;
|
||||
|
||||
Avr109Pid(unsigned boot, unsigned app) : boot(boot), app(app) {}
|
||||
};
|
||||
|
||||
// Private
|
||||
|
||||
@ -146,24 +155,40 @@ struct FirmwareDialog::priv
|
||||
void flashing_done(AvrDudeComplete complete);
|
||||
void enable_port_picker(bool enable);
|
||||
void load_hex_file(const wxString &path);
|
||||
void queue_status(wxString message);
|
||||
void queue_error(const wxString &message);
|
||||
void queue_event(AvrdudeEvent aevt, wxString message);
|
||||
|
||||
bool ask_model_id_mismatch(const std::string &printer_model);
|
||||
bool check_model_id();
|
||||
void wait_for_mmu_bootloader(unsigned retries);
|
||||
void mmu_reboot(const SerialPortInfo &port);
|
||||
void lookup_port_mmu();
|
||||
void avr109_wait_for_bootloader(Avr109Pid usb_pid, unsigned retries);
|
||||
void avr109_reboot(const SerialPortInfo &port);
|
||||
void avr109_lookup_port(Avr109Pid usb_pid);
|
||||
void prepare_common();
|
||||
void prepare_mk2();
|
||||
void prepare_mk3();
|
||||
void prepare_mm_control();
|
||||
void prepare_avr109(Avr109Pid usb_pid);
|
||||
void perform_upload();
|
||||
|
||||
void user_cancel();
|
||||
void on_avrdude(const wxCommandEvent &evt);
|
||||
void on_async_dialog(const wxCommandEvent &evt);
|
||||
void ensure_joined();
|
||||
|
||||
void queue_status(wxString message) { queue_event(AE_STATUS, std::move(message)); }
|
||||
|
||||
template<class ...Args> void queue_message(const wxString &format, Args... args) {
|
||||
auto message = wxString::Format(format, args...);
|
||||
BOOST_LOG_TRIVIAL(info) << message;
|
||||
message.Append('\n');
|
||||
queue_event(AE_MESSAGE, std::move(message));
|
||||
}
|
||||
|
||||
template<class ...Args> void queue_error(const wxString &format, Args... args) {
|
||||
queue_message(format, args...);
|
||||
queue_event(AE_STATUS, _(L("Flashing failed: ")) + wxString::Format(format, args...));
|
||||
avrdude->cancel();
|
||||
}
|
||||
|
||||
static const char* avr109_dev_name(Avr109Pid usb_pid);
|
||||
};
|
||||
|
||||
void FirmwareDialog::priv::find_serial_ports()
|
||||
@ -259,26 +284,18 @@ void FirmwareDialog::priv::enable_port_picker(bool enable)
|
||||
void FirmwareDialog::priv::load_hex_file(const wxString &path)
|
||||
{
|
||||
hex_file = HexFile(path.wx_str());
|
||||
enable_port_picker(hex_file.device != HexFile::DEV_MM_CONTROL);
|
||||
const bool auto_lookup = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1;
|
||||
enable_port_picker(! auto_lookup);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::queue_status(wxString message)
|
||||
void FirmwareDialog::priv::queue_event(AvrdudeEvent aevt, wxString message)
|
||||
{
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
|
||||
evt->SetExtraLong(AE_STATUS);
|
||||
evt->SetExtraLong(aevt);
|
||||
evt->SetString(std::move(message));
|
||||
wxQueueEvent(this->q, evt);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::queue_error(const wxString &message)
|
||||
{
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
|
||||
evt->SetExtraLong(AE_STATUS);
|
||||
evt->SetString(wxString::Format(_(L("Flashing failed: %s")), message));
|
||||
|
||||
wxQueueEvent(this->q, evt); avrdude->cancel();
|
||||
}
|
||||
|
||||
bool FirmwareDialog::priv::ask_model_id_mismatch(const std::string &printer_model)
|
||||
{
|
||||
// model_id in the hex file doesn't match what the printer repoted.
|
||||
@ -356,7 +373,7 @@ bool FirmwareDialog::priv::check_model_id()
|
||||
// return false;
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries)
|
||||
void FirmwareDialog::priv::avr109_wait_for_bootloader(Avr109Pid usb_pid, unsigned retries)
|
||||
{
|
||||
enum {
|
||||
SLEEP_MS = 500,
|
||||
@ -367,61 +384,63 @@ void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries)
|
||||
|
||||
auto ports = Utils::scan_serial_ports_extended();
|
||||
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
|
||||
return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT;
|
||||
return port.id_vendor != USB_VID_PRUSA || port.id_product != usb_pid.boot;
|
||||
}), ports.end());
|
||||
|
||||
if (ports.size() == 1) {
|
||||
port = ports[0];
|
||||
return;
|
||||
} else if (ports.size() > 1) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
|
||||
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
|
||||
queue_message("Several VID/PID 0x2c99/%u devices found", usb_pid.boot);
|
||||
queue_error(_(L("Multiple %s devices found. Please only connect one at a time for flashing.")), avr109_dev_name(usb_pid));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port)
|
||||
void FirmwareDialog::priv::avr109_reboot(const SerialPortInfo &port)
|
||||
{
|
||||
asio::io_service io;
|
||||
Serial serial(io, port.port, 1200);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::lookup_port_mmu()
|
||||
void FirmwareDialog::priv::avr109_lookup_port(Avr109Pid usb_pid)
|
||||
{
|
||||
static const auto msg_not_found =
|
||||
"The Multi Material Control device was not found.\n"
|
||||
"If the device is connected, please press the Reset button next to the USB connector ...";
|
||||
const char *dev_name = avr109_dev_name(usb_pid);
|
||||
const wxString msg_not_found = wxString::Format(
|
||||
_(L("The %s device was not found.\n"
|
||||
"If the device is connected, please press the Reset button next to the USB connector ...")),
|
||||
dev_name);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ...";
|
||||
queue_message("Flashing %s, looking for VID/PID 0x2c99/%u or 0x2c99/%u ...", dev_name, usb_pid.boot, usb_pid.app);
|
||||
|
||||
auto ports = Utils::scan_serial_ports_extended();
|
||||
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
|
||||
return port.id_vendor != USB_VID_PRUSA ||
|
||||
port.id_product != USB_PID_MMU_BOOT &&
|
||||
port.id_product != USB_PID_MMU_APP;
|
||||
port.id_product != usb_pid.boot &&
|
||||
port.id_product != usb_pid.app;
|
||||
}), ports.end());
|
||||
|
||||
if (ports.size() == 0) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ...";
|
||||
queue_status(_(L(msg_not_found)));
|
||||
wait_for_mmu_bootloader(30);
|
||||
queue_message("The %s device was not found.", dev_name);
|
||||
queue_status(msg_not_found);
|
||||
avr109_wait_for_bootloader(usb_pid, 30);
|
||||
} else if (ports.size() > 1) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
|
||||
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
|
||||
queue_message("Several VID/PID 0x2c99/%u devices found", usb_pid.boot);
|
||||
queue_error(_(L("Multiple %s devices found. Please only connect one at a time for flashing.")), dev_name);
|
||||
} else {
|
||||
if (ports[0].id_product == USB_PID_MMU_APP) {
|
||||
if (ports[0].id_product == usb_pid.app) {
|
||||
// The device needs to be rebooted into the bootloader mode
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port;
|
||||
mmu_reboot(ports[0]);
|
||||
wait_for_mmu_bootloader(10);
|
||||
queue_message("Found VID/PID 0x2c99/%u at `%s`, rebooting the device ...", usb_pid.app, ports[0].port);
|
||||
avr109_reboot(ports[0]);
|
||||
avr109_wait_for_bootloader(usb_pid, 10);
|
||||
|
||||
if (! port) {
|
||||
// The device in bootloader mode was not found, inform the user and wait some more...
|
||||
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 bootloader device not found after reboot, asking the user to press Reset and waiting for the device to show up ...";
|
||||
queue_status(_(L(msg_not_found)));
|
||||
wait_for_mmu_bootloader(30);
|
||||
queue_message("%s device not found after reboot", dev_name);
|
||||
queue_status(msg_not_found);
|
||||
avr109_wait_for_bootloader(usb_pid, 30);
|
||||
}
|
||||
} else {
|
||||
port = ports[0];
|
||||
@ -498,16 +517,16 @@ void FirmwareDialog::priv::prepare_mk3()
|
||||
avrdude->push_args(std::move(args));
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_mm_control()
|
||||
void FirmwareDialog::priv::prepare_avr109(Avr109Pid usb_pid)
|
||||
{
|
||||
port = boost::none;
|
||||
lookup_port_mmu();
|
||||
avr109_lookup_port(usb_pid);
|
||||
if (! port) {
|
||||
queue_error(_(L("The device could not have been found")));
|
||||
queue_error(_(L("The %s device could not have been found")), avr109_dev_name(usb_pid));
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port->port;
|
||||
queue_message("Found VID/PID 0x2c99/%u at `%s`, flashing ...", usb_pid.boot, port->port);
|
||||
queue_status(label_status_flashing);
|
||||
|
||||
std::vector<std::string> args {{
|
||||
@ -568,7 +587,11 @@ void FirmwareDialog::priv::perform_upload()
|
||||
break;
|
||||
|
||||
case HexFile::DEV_MM_CONTROL:
|
||||
this->prepare_mm_control();
|
||||
this->prepare_avr109(Avr109Pid(USB_PID_MMU_BOOT, USB_PID_MMU_APP));
|
||||
break;
|
||||
|
||||
case HexFile::DEV_CW1:
|
||||
this->prepare_avr109(Avr109Pid(USB_PID_CW1_BOOT, USB_PID_CW1_APP));
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -576,7 +599,11 @@ void FirmwareDialog::priv::perform_upload()
|
||||
break;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port->port, ex.what()));
|
||||
if (port) {
|
||||
queue_error(_(L("Error accessing port at %s: %s")), port->port, ex.what());
|
||||
} else {
|
||||
queue_error(_(L("Error: %s")), ex.what());
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) {
|
||||
@ -688,6 +715,19 @@ void FirmwareDialog::priv::ensure_joined()
|
||||
avrdude.reset();
|
||||
}
|
||||
|
||||
const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) {
|
||||
switch (usb_pid.boot) {
|
||||
case USB_PID_MMU_BOOT:
|
||||
return "Prusa MMU 2.0 Control";
|
||||
break;
|
||||
case USB_PID_CW1_BOOT:
|
||||
return "Prusa CurWa";
|
||||
break;
|
||||
|
||||
default: throw std::runtime_error((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Public
|
||||
|
||||
@ -757,7 +797,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
|
||||
|
||||
vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING);
|
||||
|
||||
p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE);
|
||||
p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: Output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE);
|
||||
auto *spoiler_pane = p->spoiler->GetPane();
|
||||
auto *spoiler_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
p->txt_stdout = new wxTextCtrl(spoiler_pane, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
|
||||
|
@ -380,7 +380,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
|
||||
|
||||
float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas)
|
||||
{
|
||||
const Point& mouse_pos = canvas.get_local_mouse_position();
|
||||
const Vec2d mouse_pos = canvas.get_local_mouse_position();
|
||||
const Rect& rect = get_bar_rect_screen(canvas);
|
||||
float x = (float)mouse_pos(0);
|
||||
float y = (float)mouse_pos(1);
|
||||
@ -3915,19 +3915,25 @@ Size GLCanvas3D::get_canvas_size() const
|
||||
w *= factor;
|
||||
h *= factor;
|
||||
#else
|
||||
const float factor = 1.0;
|
||||
const float factor = 1.0f;
|
||||
#endif
|
||||
|
||||
return Size(w, h, factor);
|
||||
}
|
||||
|
||||
Point GLCanvas3D::get_local_mouse_position() const
|
||||
Vec2d GLCanvas3D::get_local_mouse_position() const
|
||||
{
|
||||
if (m_canvas == nullptr)
|
||||
return Point();
|
||||
return Vec2d::Zero();
|
||||
|
||||
wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition());
|
||||
return Point(mouse_pos.x, mouse_pos.y);
|
||||
const double factor =
|
||||
#if ENABLE_RETINA_GL
|
||||
m_retina_helper->get_scale_factor();
|
||||
#else
|
||||
1.0;
|
||||
#endif
|
||||
return Vec2d(factor * mouse_pos.x, factor * mouse_pos.y);
|
||||
}
|
||||
|
||||
void GLCanvas3D::reset_legend_texture()
|
||||
@ -4397,11 +4403,13 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
|
||||
if ((m_canvas == nullptr) && (m_context == nullptr))
|
||||
return;
|
||||
|
||||
wxGetApp().imgui()->set_display_size((float)w, (float)h);
|
||||
auto *imgui = wxGetApp().imgui();
|
||||
imgui->set_display_size((float)w, (float)h);
|
||||
const float font_size = 1.5f * wxGetApp().em_unit();
|
||||
#if ENABLE_RETINA_GL
|
||||
wxGetApp().imgui()->set_style_scaling(m_retina_helper->get_scale_factor());
|
||||
imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor());
|
||||
#else
|
||||
wxGetApp().imgui()->set_style_scaling(m_canvas->GetContentScaleFactor());
|
||||
imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f);
|
||||
#endif
|
||||
|
||||
// ensures that this canvas is current
|
||||
@ -4979,6 +4987,9 @@ void GLCanvas3D::_render_sla_slices() const
|
||||
{
|
||||
const SLAPrintObject* obj = print_objects[i];
|
||||
|
||||
if (!obj->is_step_done(slaposSliceSupports))
|
||||
continue;
|
||||
|
||||
SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i);
|
||||
SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i);
|
||||
{
|
||||
@ -5002,24 +5013,12 @@ void GLCanvas3D::_render_sla_slices() const
|
||||
Pointf3s &top_obj_triangles = it_caps_top->second.object;
|
||||
Pointf3s &top_sup_triangles = it_caps_top->second.supports;
|
||||
|
||||
const std::vector<SLAPrintObject::Instance>& instances = obj->instances();
|
||||
struct InstanceTransform
|
||||
{
|
||||
Vec3d offset;
|
||||
float rotation;
|
||||
};
|
||||
|
||||
std::vector<InstanceTransform> instance_transforms;
|
||||
for (const SLAPrintObject::Instance& inst : instances)
|
||||
{
|
||||
instance_transforms.push_back({ to_3d(unscale(inst.shift), 0.), Geometry::rad2deg(inst.rotation) });
|
||||
}
|
||||
|
||||
if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) &&
|
||||
obj->is_step_done(slaposIndexSlices) && !obj->get_slice_index().empty())
|
||||
!obj->get_slice_index().empty())
|
||||
{
|
||||
double layer_height = print->default_object_config().layer_height.value;
|
||||
double initial_layer_height = print->material_config().initial_layer_height.value;
|
||||
bool left_handed = obj->is_left_handed();
|
||||
|
||||
coord_t key_zero = obj->get_slice_index().front().print_level();
|
||||
// Slice at the center of the slab starting at clip_min_z will be rendered for the lower plane.
|
||||
@ -5038,10 +5037,10 @@ void GLCanvas3D::_render_sla_slices() const
|
||||
const ExPolygons& sup_bottom = slice_low.get_slice(soSupport);
|
||||
// calculate model bottom cap
|
||||
if (bottom_obj_triangles.empty() && !obj_bottom.empty())
|
||||
bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, true);
|
||||
bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, ! left_handed);
|
||||
// calculate support bottom cap
|
||||
if (bottom_sup_triangles.empty() && !sup_bottom.empty())
|
||||
bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, true);
|
||||
bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, ! left_handed);
|
||||
}
|
||||
|
||||
if (slice_high.is_valid()) {
|
||||
@ -5049,49 +5048,43 @@ void GLCanvas3D::_render_sla_slices() const
|
||||
const ExPolygons& sup_top = slice_high.get_slice(soSupport);
|
||||
// calculate model top cap
|
||||
if (top_obj_triangles.empty() && !obj_top.empty())
|
||||
top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, false);
|
||||
top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed);
|
||||
// calculate support top cap
|
||||
if (top_sup_triangles.empty() && !sup_top.empty())
|
||||
top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, false);
|
||||
top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed);
|
||||
}
|
||||
}
|
||||
|
||||
if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty())
|
||||
{
|
||||
for (const InstanceTransform& inst : instance_transforms)
|
||||
for (const SLAPrintObject::Instance& inst : obj->instances())
|
||||
{
|
||||
::glPushMatrix();
|
||||
::glTranslated(inst.offset(0), inst.offset(1), inst.offset(2));
|
||||
::glRotatef(inst.rotation, 0.0, 0.0, 1.0);
|
||||
|
||||
::glBegin(GL_TRIANGLES);
|
||||
|
||||
::glTranslated(unscale<double>(inst.shift.x()), unscale<double>(inst.shift.y()), 0);
|
||||
::glRotatef(Geometry::rad2deg(inst.rotation), 0.0, 0.0, 1.0);
|
||||
if (obj->is_left_handed())
|
||||
// The polygons are mirrored by X.
|
||||
::glScalef(-1.0, 1.0, 1.0);
|
||||
::glEnableClientState(GL_VERTEX_ARRAY);
|
||||
::glColor3f(1.0f, 0.37f, 0.0f);
|
||||
|
||||
for (const Vec3d& v : bottom_obj_triangles)
|
||||
{
|
||||
::glVertex3dv((GLdouble*)v.data());
|
||||
if (!bottom_obj_triangles.empty()) {
|
||||
::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_obj_triangles.front().data());
|
||||
::glDrawArrays(GL_TRIANGLES, 0, bottom_obj_triangles.size());
|
||||
}
|
||||
|
||||
for (const Vec3d& v : top_obj_triangles)
|
||||
{
|
||||
::glVertex3dv((GLdouble*)v.data());
|
||||
if (! top_obj_triangles.empty()) {
|
||||
::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_obj_triangles.front().data());
|
||||
::glDrawArrays(GL_TRIANGLES, 0, top_obj_triangles.size());
|
||||
}
|
||||
|
||||
::glColor3f(1.0f, 0.0f, 0.37f);
|
||||
|
||||
for (const Vec3d& v : bottom_sup_triangles)
|
||||
{
|
||||
::glVertex3dv((GLdouble*)v.data());
|
||||
if (! bottom_sup_triangles.empty()) {
|
||||
::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_sup_triangles.front().data());
|
||||
::glDrawArrays(GL_TRIANGLES, 0, bottom_sup_triangles.size());
|
||||
}
|
||||
|
||||
for (const Vec3d& v : top_sup_triangles)
|
||||
{
|
||||
::glVertex3dv((GLdouble*)v.data());
|
||||
if (! top_sup_triangles.empty()) {
|
||||
::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_sup_triangles.front().data());
|
||||
::glDrawArrays(GL_TRIANGLES, 0, top_sup_triangles.size());
|
||||
}
|
||||
|
||||
::glEnd();
|
||||
|
||||
::glDisableClientState(GL_VERTEX_ARRAY);
|
||||
::glPopMatrix();
|
||||
}
|
||||
}
|
||||
@ -6209,6 +6202,8 @@ void GLCanvas3D::_load_shells_fff()
|
||||
|
||||
void GLCanvas3D::_load_shells_sla()
|
||||
{
|
||||
//FIXME use reload_scene
|
||||
#if 1
|
||||
const SLAPrint* print = this->sla_print();
|
||||
if (print->objects().empty())
|
||||
// nothing to render, return
|
||||
@ -6218,23 +6213,30 @@ void GLCanvas3D::_load_shells_sla()
|
||||
int obj_idx = 0;
|
||||
for (const SLAPrintObject* obj : print->objects())
|
||||
{
|
||||
if (!obj->is_step_done(slaposIndexSlices))
|
||||
if (!obj->is_step_done(slaposSliceSupports))
|
||||
continue;
|
||||
|
||||
unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size();
|
||||
|
||||
// selects only instances which were sliced
|
||||
const ModelObject* model_obj = obj->model_object();
|
||||
std::vector<int> instance_idxs(model_obj->instances.size());
|
||||
for (int i = 0; i < (int)model_obj->instances.size(); ++i)
|
||||
const std::vector<SLAPrintObject::Instance>& sla_instances = obj->instances();
|
||||
std::vector<int> instances_model_idxs(sla_instances.size());
|
||||
for (int i = 0; i < (int)sla_instances.size(); ++i)
|
||||
{
|
||||
instance_idxs[i] = i;
|
||||
instances_model_idxs[i] = (int)sla_instances[i].instance_id.id;
|
||||
}
|
||||
|
||||
m_volumes.load_object(model_obj, obj_idx, instance_idxs, "object", m_use_VBOs && m_initialized);
|
||||
std::vector<int> sliced_instance_idxs;
|
||||
for (int i = 0; i < (int)model_obj->instances.size(); ++i)
|
||||
{
|
||||
if (std::find(instances_model_idxs.begin(), instances_model_idxs.end(), (int)model_obj->instances[i]->id().id) != instances_model_idxs.end())
|
||||
sliced_instance_idxs.push_back(i);
|
||||
}
|
||||
|
||||
const std::vector<SLAPrintObject::Instance>& instances = obj->instances();
|
||||
m_volumes.load_object(model_obj, obj_idx, sliced_instance_idxs, "object", m_use_VBOs && m_initialized);
|
||||
|
||||
for (const SLAPrintObject::Instance& instance : instances)
|
||||
for (const SLAPrintObject::Instance& instance : sla_instances)
|
||||
{
|
||||
Vec3d offset = unscale(instance.shift(0), instance.shift(1), 0);
|
||||
Vec3d rotation(0.0, 0.0, (double)instance.rotation);
|
||||
@ -6257,6 +6259,7 @@ void GLCanvas3D::_load_shells_sla()
|
||||
v.composite_id.volume_id = -1;
|
||||
v.set_instance_offset(offset);
|
||||
v.set_instance_rotation(rotation);
|
||||
v.set_instance_mirror(X, obj->is_left_handed() ? -1. : 1.);
|
||||
}
|
||||
|
||||
// add pad
|
||||
@ -6275,6 +6278,7 @@ void GLCanvas3D::_load_shells_sla()
|
||||
v.composite_id.volume_id = -1;
|
||||
v.set_instance_offset(offset);
|
||||
v.set_instance_rotation(rotation);
|
||||
v.set_instance_mirror(X, obj->is_left_handed() ? -1. : 1.);
|
||||
}
|
||||
|
||||
// finalize volumes and sends geometry to gpu
|
||||
@ -6297,6 +6301,9 @@ void GLCanvas3D::_load_shells_sla()
|
||||
}
|
||||
|
||||
update_volumes_colors_by_extruder();
|
||||
#else
|
||||
this->reload_scene(true, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data)
|
||||
|
@ -634,6 +634,7 @@ public:
|
||||
m_sla_caps[id].reset();
|
||||
}
|
||||
}
|
||||
void reset_clipping_planes_cache() { m_sla_caps[0].triangles.clear(); m_sla_caps[1].triangles.clear(); }
|
||||
void set_use_clipping_planes(bool use) { m_use_clipping_planes = use; }
|
||||
|
||||
void set_color_by(const std::string& value);
|
||||
@ -699,7 +700,7 @@ public:
|
||||
void on_paint(wxPaintEvent& evt);
|
||||
|
||||
Size get_canvas_size() const;
|
||||
Point get_local_mouse_position() const;
|
||||
Vec2d get_local_mouse_position() const;
|
||||
|
||||
void reset_legend_texture();
|
||||
|
||||
|
@ -90,7 +90,6 @@ bool GUI_App::OnInit()
|
||||
const wxString resources_dir = from_u8(Slic3r::resources_dir());
|
||||
wxCHECK_MSG(wxDirExists(resources_dir), false,
|
||||
wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir));
|
||||
wxCHECK_MSG(m_imgui->init(), false, "Failed to initialize ImGui");
|
||||
|
||||
SetAppName("Slic3rPE-beta");
|
||||
SetAppDisplayName("Slic3r Prusa Edition");
|
||||
@ -136,6 +135,10 @@ bool GUI_App::OnInit()
|
||||
app_config->save();
|
||||
});
|
||||
|
||||
// initialize label colors and fonts
|
||||
init_label_colours();
|
||||
init_fonts();
|
||||
|
||||
load_language();
|
||||
|
||||
// Suppress the '- default -' presets.
|
||||
@ -148,9 +151,6 @@ bool GUI_App::OnInit()
|
||||
|
||||
// Let the libslic3r know the callback, which will translate messages on demand.
|
||||
Slic3r::I18N::set_translate_callback(libslic3r_translate_callback);
|
||||
// initialize label colors and fonts
|
||||
init_label_colours();
|
||||
init_fonts();
|
||||
|
||||
// application frame
|
||||
if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
|
||||
|
@ -155,7 +155,9 @@ void View3D::render()
|
||||
m_canvas->set_as_dirty();
|
||||
}
|
||||
|
||||
Preview::Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process_func)
|
||||
Preview::Preview(
|
||||
wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config,
|
||||
BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process_func)
|
||||
: m_canvas_widget(nullptr)
|
||||
, m_canvas(nullptr)
|
||||
, m_double_slider_sizer(nullptr)
|
||||
@ -179,14 +181,14 @@ Preview::Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_t
|
||||
, m_volumes_cleanup_required(false)
|
||||
#endif // __linux__
|
||||
{
|
||||
if (init(parent, bed, camera, view_toolbar))
|
||||
if (init(parent, bed, camera, view_toolbar, model))
|
||||
{
|
||||
show_hide_ui_elements("none");
|
||||
load_print();
|
||||
}
|
||||
}
|
||||
|
||||
bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar)
|
||||
bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model)
|
||||
{
|
||||
if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */))
|
||||
return false;
|
||||
@ -196,6 +198,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view
|
||||
m_canvas = _3DScene::get_canvas(this->m_canvas_widget);
|
||||
m_canvas->allow_multisample(GLCanvas3DManager::can_multisample());
|
||||
m_canvas->set_config(m_config);
|
||||
m_canvas->set_model(model);
|
||||
m_canvas->set_process(m_process);
|
||||
m_canvas->enable_legend_texture(true);
|
||||
m_canvas->enable_dynamic_background(true);
|
||||
@ -773,7 +776,7 @@ void Preview::load_print_as_sla()
|
||||
std::vector<double> zs;
|
||||
double initial_layer_height = print->material_config().initial_layer_height.value;
|
||||
for (const SLAPrintObject* obj : print->objects())
|
||||
if (obj->is_step_done(slaposIndexSlices) && !obj->get_slice_index().empty())
|
||||
if (obj->is_step_done(slaposSliceSupports) && !obj->get_slice_index().empty())
|
||||
{
|
||||
auto low_coord = obj->get_slice_index().front().print_level();
|
||||
for (auto& rec : obj->get_slice_index())
|
||||
@ -781,6 +784,8 @@ void Preview::load_print_as_sla()
|
||||
}
|
||||
sort_remove_duplicates(zs);
|
||||
|
||||
m_canvas->reset_clipping_planes_cache();
|
||||
|
||||
n_layers = (unsigned int)zs.size();
|
||||
if (n_layers == 0)
|
||||
{
|
||||
|
@ -102,7 +102,8 @@ class Preview : public wxPanel
|
||||
PrusaDoubleSlider* m_slider {nullptr};
|
||||
|
||||
public:
|
||||
Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process = [](){});
|
||||
Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config,
|
||||
BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process = [](){});
|
||||
virtual ~Preview();
|
||||
|
||||
wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; }
|
||||
@ -120,7 +121,7 @@ public:
|
||||
void refresh_print();
|
||||
|
||||
private:
|
||||
bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar);
|
||||
bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model);
|
||||
|
||||
void bind_event_handlers();
|
||||
void unbind_event_handlers();
|
||||
|
@ -185,7 +185,10 @@ void GLGizmoCut::on_render_for_picking(const Selection& selection) const
|
||||
|
||||
void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection)
|
||||
{
|
||||
const float approx_height = m_imgui->scaled(11.0f);
|
||||
y = std::min(y, bottom_limit - approx_height);
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
|
||||
m_imgui->set_next_window_bg_alpha(0.5f);
|
||||
m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
|
@ -565,8 +565,7 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
RENDER_AGAIN:
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
|
||||
const float scaling = m_imgui->get_style_scaling();
|
||||
const ImVec2 window_size(285.f * scaling, 300.f * scaling);
|
||||
const ImVec2 window_size(m_imgui->scaled(15.f, 16.5f));
|
||||
ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) ));
|
||||
ImGui::SetNextWindowSize(ImVec2(window_size));
|
||||
|
||||
@ -817,7 +816,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
|
||||
// Recalculate support structures once the editing mode is left.
|
||||
// m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
// m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
wxGetApp().plater()->reslice_SLA_supports(*m_model_object);
|
||||
wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); });
|
||||
}
|
||||
m_editing_mode = false;
|
||||
m_unsaved_changes = false;
|
||||
@ -870,7 +869,7 @@ void GLGizmoSlaSupports::auto_generate()
|
||||
m_model_object->sla_support_points.clear();
|
||||
m_model_object->sla_points_status = sla::PointsStatus::Generating;
|
||||
m_editing_mode_cache.clear();
|
||||
wxGetApp().plater()->reslice_SLA_supports(*m_model_object);
|
||||
wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,31 +27,25 @@ namespace GUI {
|
||||
|
||||
ImGuiWrapper::ImGuiWrapper()
|
||||
: m_glyph_ranges(nullptr)
|
||||
, m_font_size(18.0)
|
||||
, m_font_texture(0)
|
||||
, m_style_scaling(1.0)
|
||||
, m_mouse_buttons(0)
|
||||
, m_disabled(false)
|
||||
, m_new_frame_open(false)
|
||||
{
|
||||
}
|
||||
|
||||
ImGuiWrapper::~ImGuiWrapper()
|
||||
{
|
||||
destroy_device_objects();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::init()
|
||||
{
|
||||
ImGui::CreateContext();
|
||||
|
||||
init_default_font(m_style_scaling);
|
||||
init_input();
|
||||
init_style();
|
||||
|
||||
ImGui::GetIO().IniFilename = nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
ImGuiWrapper::~ImGuiWrapper()
|
||||
{
|
||||
destroy_font();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
void ImGuiWrapper::set_language(const std::string &language)
|
||||
@ -87,7 +81,7 @@ void ImGuiWrapper::set_language(const std::string &language)
|
||||
|
||||
if (ranges != m_glyph_ranges) {
|
||||
m_glyph_ranges = ranges;
|
||||
init_default_font(m_style_scaling);
|
||||
destroy_font();
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,13 +92,21 @@ void ImGuiWrapper::set_display_size(float w, float h)
|
||||
io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
|
||||
}
|
||||
|
||||
void ImGuiWrapper::set_style_scaling(float scaling)
|
||||
void ImGuiWrapper::set_scaling(float font_size, float scale_style, float scale_both)
|
||||
{
|
||||
if (!std::isnan(scaling) && !std::isinf(scaling) && scaling != m_style_scaling) {
|
||||
ImGui::GetStyle().ScaleAllSizes(scaling / m_style_scaling);
|
||||
init_default_font(scaling);
|
||||
m_style_scaling = scaling;
|
||||
font_size *= scale_both;
|
||||
scale_style *= scale_both;
|
||||
|
||||
if (m_font_size == font_size && m_style_scaling == scale_style) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_font_size = font_size;
|
||||
|
||||
ImGui::GetStyle().ScaleAllSizes(scale_style / m_style_scaling);
|
||||
m_style_scaling = scale_style;
|
||||
|
||||
destroy_font();
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt)
|
||||
@ -165,8 +167,9 @@ void ImGuiWrapper::new_frame()
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_font_texture == 0)
|
||||
create_device_objects();
|
||||
if (m_font_texture == 0) {
|
||||
init_font();
|
||||
}
|
||||
|
||||
ImGui::NewFrame();
|
||||
m_new_frame_open = true;
|
||||
@ -179,6 +182,12 @@ void ImGuiWrapper::render()
|
||||
m_new_frame_open = false;
|
||||
}
|
||||
|
||||
ImVec2 ImGuiWrapper::calc_text_size(const wxString &text)
|
||||
{
|
||||
auto text_utf8 = into_u8(text);
|
||||
return ImGui::CalcTextSize(text_utf8.c_str());
|
||||
}
|
||||
|
||||
void ImGuiWrapper::set_next_window_pos(float x, float y, int flag)
|
||||
{
|
||||
ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag);
|
||||
@ -259,7 +268,8 @@ void ImGuiWrapper::text(const std::string &label)
|
||||
|
||||
void ImGuiWrapper::text(const wxString &label)
|
||||
{
|
||||
this->text(into_u8(label).c_str());
|
||||
auto label_utf8 = into_u8(label);
|
||||
this->text(label_utf8.c_str());
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>& options, int& selection)
|
||||
@ -328,32 +338,23 @@ bool ImGuiWrapper::want_any_input() const
|
||||
return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput;
|
||||
}
|
||||
|
||||
void ImGuiWrapper::init_default_font(float scaling)
|
||||
void ImGuiWrapper::init_font()
|
||||
{
|
||||
static const float font_size = 18.0f;
|
||||
const float font_size = m_font_size * m_style_scaling;
|
||||
|
||||
destroy_fonts_texture();
|
||||
destroy_font();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.Fonts->Clear();
|
||||
ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), font_size * scaling, nullptr, m_glyph_ranges);
|
||||
ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), font_size, nullptr, m_glyph_ranges);
|
||||
if (font == nullptr) {
|
||||
font = io.Fonts->AddFontDefault();
|
||||
if (font == nullptr) {
|
||||
throw std::runtime_error("ImGui: Could not load deafult font");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiWrapper::create_device_objects()
|
||||
{
|
||||
create_fonts_texture();
|
||||
}
|
||||
|
||||
void ImGuiWrapper::create_fonts_texture()
|
||||
{
|
||||
// Build texture atlas
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
unsigned char* pixels;
|
||||
int width, height;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
|
||||
@ -559,14 +560,9 @@ bool ImGuiWrapper::display_initialized() const
|
||||
return io.DisplaySize.x >= 0.0f && io.DisplaySize.y >= 0.0f;
|
||||
}
|
||||
|
||||
void ImGuiWrapper::destroy_device_objects()
|
||||
void ImGuiWrapper::destroy_font()
|
||||
{
|
||||
destroy_fonts_texture();
|
||||
}
|
||||
|
||||
void ImGuiWrapper::destroy_fonts_texture()
|
||||
{
|
||||
if (m_font_texture) {
|
||||
if (m_font_texture != 0) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.Fonts->TexID = 0;
|
||||
glDeleteTextures(1, &m_font_texture);
|
||||
|
@ -18,10 +18,8 @@ namespace GUI {
|
||||
|
||||
class ImGuiWrapper
|
||||
{
|
||||
typedef std::map<std::string, ImFont*> FontsMap;
|
||||
|
||||
FontsMap m_fonts;
|
||||
const ImWchar *m_glyph_ranges;
|
||||
float m_font_size;
|
||||
unsigned m_font_texture;
|
||||
float m_style_scaling;
|
||||
unsigned m_mouse_buttons;
|
||||
@ -33,20 +31,24 @@ public:
|
||||
ImGuiWrapper();
|
||||
~ImGuiWrapper();
|
||||
|
||||
bool init();
|
||||
void read_glsl_version();
|
||||
|
||||
void set_language(const std::string &language);
|
||||
void set_display_size(float w, float h);
|
||||
void set_style_scaling(float scaling);
|
||||
void set_scaling(float font_size, float scale_style, float scale_both);
|
||||
bool update_mouse_data(wxMouseEvent &evt);
|
||||
bool update_key_data(wxKeyEvent &evt);
|
||||
|
||||
float get_font_size() const { return m_font_size; }
|
||||
float get_style_scaling() const { return m_style_scaling; }
|
||||
|
||||
void new_frame();
|
||||
void render();
|
||||
|
||||
float scaled(float x) const { return x * m_font_size * m_style_scaling; }
|
||||
ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size * m_style_scaling, y * m_font_size * m_style_scaling); }
|
||||
ImVec2 calc_text_size(const wxString &text);
|
||||
|
||||
void set_next_window_pos(float x, float y, int flag);
|
||||
void set_next_window_bg_alpha(float alpha);
|
||||
|
||||
@ -73,15 +75,12 @@ public:
|
||||
bool want_any_input() const;
|
||||
|
||||
private:
|
||||
void init_default_font(float scaling);
|
||||
void create_device_objects();
|
||||
void create_fonts_texture();
|
||||
void init_font();
|
||||
void init_input();
|
||||
void init_style();
|
||||
void render_draw_data(ImDrawData *draw_data);
|
||||
bool display_initialized() const;
|
||||
void destroy_device_objects();
|
||||
void destroy_fonts_texture();
|
||||
void destroy_font();
|
||||
|
||||
static const char* clipboard_get(void* user_data);
|
||||
static void clipboard_set(void* user_data, const char* text);
|
||||
|
@ -1350,7 +1350,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this);
|
||||
|
||||
view3D = new View3D(q, bed, camera, view_toolbar, &model, config, &background_process);
|
||||
preview = new Preview(q, bed, camera, view_toolbar, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); });
|
||||
preview = new Preview(q, bed, camera, view_toolbar, &model, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); });
|
||||
|
||||
panels.push_back(view3D);
|
||||
panels.push_back(preview);
|
||||
|
@ -118,17 +118,17 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection)
|
||||
if (needs_reset)
|
||||
clear();
|
||||
|
||||
if (volume->is_modifier)
|
||||
m_mode = Volume;
|
||||
else if (!contains_volume(volume_idx))
|
||||
m_mode = Instance;
|
||||
// else -> keep current mode
|
||||
if (!contains_volume(volume_idx))
|
||||
m_mode = volume->is_modifier ? Volume : Instance;
|
||||
else
|
||||
// keep current mode
|
||||
return;
|
||||
|
||||
switch (m_mode)
|
||||
{
|
||||
case Volume:
|
||||
{
|
||||
if (volume->volume_idx() >= 0 && (is_empty() || (volume->instance_idx() == get_instance_idx())))
|
||||
if ((volume->volume_idx() >= 0) && (is_empty() || (volume->instance_idx() == get_instance_idx())))
|
||||
_add_volume(volume_idx);
|
||||
|
||||
break;
|
||||
@ -439,6 +439,8 @@ void Selection::translate(const Vec3d& displacement, bool local)
|
||||
if (!m_valid)
|
||||
return;
|
||||
|
||||
EMode translation_type = m_mode;
|
||||
|
||||
for (unsigned int i : m_list)
|
||||
{
|
||||
if ((m_mode == Volume) || (*m_volumes)[i]->is_wipe_tower)
|
||||
@ -452,13 +454,22 @@ void Selection::translate(const Vec3d& displacement, bool local)
|
||||
}
|
||||
}
|
||||
else if (m_mode == Instance)
|
||||
{
|
||||
if (_is_from_fully_selected_instance(i))
|
||||
(*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement);
|
||||
else
|
||||
{
|
||||
Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement;
|
||||
(*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement);
|
||||
translation_type = Volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !DISABLE_INSTANCES_SYNCH
|
||||
if (m_mode == Instance)
|
||||
if (translation_type == Instance)
|
||||
_synchronize_unselected_instances(SYNC_ROTATION_NONE);
|
||||
else if (m_mode == Volume)
|
||||
else if (translation_type == Volume)
|
||||
_synchronize_unselected_volumes();
|
||||
#endif // !DISABLE_INSTANCES_SYNCH
|
||||
|
||||
@ -970,7 +981,23 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const
|
||||
::glTranslated(center(0), center(1), center(2));
|
||||
if (!boost::starts_with(sidebar_field, "position"))
|
||||
{
|
||||
Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
|
||||
Transform3d orient_matrix = Transform3d::Identity();
|
||||
if (boost::starts_with(sidebar_field, "scale"))
|
||||
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
|
||||
else if (boost::starts_with(sidebar_field, "rotation"))
|
||||
{
|
||||
if (boost::ends_with(sidebar_field, "x"))
|
||||
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
|
||||
else if (boost::ends_with(sidebar_field, "y"))
|
||||
{
|
||||
const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation();
|
||||
if (rotation(0) == 0.0)
|
||||
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
|
||||
else
|
||||
orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ()));
|
||||
}
|
||||
}
|
||||
|
||||
::glMultMatrixd(orient_matrix.data());
|
||||
}
|
||||
}
|
||||
@ -1667,5 +1694,29 @@ void Selection::_ensure_on_bed()
|
||||
}
|
||||
}
|
||||
|
||||
bool Selection::_is_from_fully_selected_instance(unsigned int volume_idx) const
|
||||
{
|
||||
struct SameInstance
|
||||
{
|
||||
int obj_idx;
|
||||
int inst_idx;
|
||||
GLVolumePtrs& volumes;
|
||||
|
||||
SameInstance(int obj_idx, int inst_idx, GLVolumePtrs& volumes) : obj_idx(obj_idx), inst_idx(inst_idx), volumes(volumes) {}
|
||||
bool operator () (unsigned int i) { return (volumes[i]->volume_idx() >= 0) && (volumes[i]->object_idx() == obj_idx) && (volumes[i]->instance_idx() == inst_idx); }
|
||||
};
|
||||
|
||||
if ((unsigned int)m_volumes->size() <= volume_idx)
|
||||
return false;
|
||||
|
||||
GLVolume* volume = (*m_volumes)[volume_idx];
|
||||
int object_idx = volume->object_idx();
|
||||
if ((int)m_model->objects.size() <= object_idx)
|
||||
return false;
|
||||
|
||||
unsigned int count = (unsigned int)std::count_if(m_list.begin(), m_list.end(), SameInstance(object_idx, volume->instance_idx(), *m_volumes));
|
||||
return count == (unsigned int)m_model->objects[object_idx]->volumes.size();
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
@ -299,6 +299,7 @@ private:
|
||||
void _synchronize_unselected_instances(SyncRotationType sync_rotation_type);
|
||||
void _synchronize_unselected_volumes();
|
||||
void _ensure_on_bed();
|
||||
bool _is_from_fully_selected_instance(unsigned int volume_idx) const;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
|
@ -18,6 +18,7 @@ static HexFile::DeviceKind parse_device_kind(const std::string &str)
|
||||
if (str == "mk2") { return HexFile::DEV_MK2; }
|
||||
else if (str == "mk3") { return HexFile::DEV_MK3; }
|
||||
else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; }
|
||||
else if (str == "cw1") { return HexFile::DEV_CW1; }
|
||||
else { return HexFile::DEV_GENERIC; }
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ struct HexFile
|
||||
DEV_MK2,
|
||||
DEV_MK3,
|
||||
DEV_MM_CONTROL,
|
||||
DEV_CW1,
|
||||
};
|
||||
|
||||
boost::filesystem::path path;
|
||||
|
Loading…
Reference in New Issue
Block a user