Merge remote-tracking branch 'origin/lm_et_bs_conflict_checker'

This commit is contained in:
enricoturri1966 2023-06-01 12:22:00 +02:00
commit eb39542f44
19 changed files with 695 additions and 13 deletions

View File

@ -138,6 +138,8 @@ set(SLIC3R_SOURCES
GCode/ThumbnailData.hpp GCode/ThumbnailData.hpp
GCode/Thumbnails.cpp GCode/Thumbnails.cpp
GCode/Thumbnails.hpp GCode/Thumbnails.hpp
GCode/ConflictChecker.cpp
GCode/ConflictChecker.hpp
GCode/CoolingBuffer.cpp GCode/CoolingBuffer.cpp
GCode/CoolingBuffer.hpp GCode/CoolingBuffer.hpp
GCode/FindReplace.cpp GCode/FindReplace.cpp

View File

@ -0,0 +1,290 @@
#include "libslic3r.h"
#include "ConflictChecker.hpp"
#include <tbb/parallel_for.h>
#include <tbb/concurrent_vector.h>
#include <map>
#include <functional>
#include <atomic>
namespace Slic3r {
namespace RasterizationImpl {
using IndexPair = std::pair<int64_t, int64_t>;
using Grids = std::vector<IndexPair>;
inline constexpr int64_t RasteXDistance = scale_(1);
inline constexpr int64_t RasteYDistance = scale_(1);
inline IndexPair point_map_grid_index(const Point &pt, int64_t xdist, int64_t ydist)
{
auto x = pt.x() / xdist;
auto y = pt.y() / ydist;
return std::make_pair(x, y);
}
inline bool nearly_equal(const Point &p1, const Point &p2) { return std::abs(p1.x() - p2.x()) < SCALED_EPSILON && std::abs(p1.y() - p2.y()) < SCALED_EPSILON; }
inline Grids line_rasterization(const Line &line, int64_t xdist = RasteXDistance, int64_t ydist = RasteYDistance)
{
Grids res;
Point rayStart = line.a;
Point rayEnd = line.b;
IndexPair currentVoxel = point_map_grid_index(rayStart, xdist, ydist);
IndexPair lastVoxel = point_map_grid_index(rayEnd, xdist, ydist);
Point ray = rayEnd - rayStart;
double stepX = ray.x() >= 0 ? 1 : -1;
double stepY = ray.y() >= 0 ? 1 : -1;
double nextVoxelBoundaryX = (currentVoxel.first + stepX) * xdist;
double nextVoxelBoundaryY = (currentVoxel.second + stepY) * ydist;
if (stepX < 0) { nextVoxelBoundaryX += xdist; }
if (stepY < 0) { nextVoxelBoundaryY += ydist; }
double tMaxX = ray.x() != 0 ? (nextVoxelBoundaryX - rayStart.x()) / ray.x() : DBL_MAX;
double tMaxY = ray.y() != 0 ? (nextVoxelBoundaryY - rayStart.y()) / ray.y() : DBL_MAX;
double tDeltaX = ray.x() != 0 ? static_cast<double>(xdist) / ray.x() * stepX : DBL_MAX;
double tDeltaY = ray.y() != 0 ? static_cast<double>(ydist) / ray.y() * stepY : DBL_MAX;
res.push_back(currentVoxel);
double tx = tMaxX;
double ty = tMaxY;
while (lastVoxel != currentVoxel) {
if (lastVoxel.first == currentVoxel.first) {
for (int64_t i = currentVoxel.second; i != lastVoxel.second; i += (int64_t) stepY) {
currentVoxel.second += (int64_t) stepY;
res.push_back(currentVoxel);
}
break;
}
if (lastVoxel.second == currentVoxel.second) {
for (int64_t i = currentVoxel.first; i != lastVoxel.first; i += (int64_t) stepX) {
currentVoxel.first += (int64_t) stepX;
res.push_back(currentVoxel);
}
break;
}
if (tx < ty) {
currentVoxel.first += (int64_t) stepX;
tx += tDeltaX;
} else {
currentVoxel.second += (int64_t) stepY;
ty += tDeltaY;
}
res.push_back(currentVoxel);
if (res.size() >= 100000) { // bug
assert(0);
}
}
return res;
}
} // namespace RasterizationImpl
void LinesBucketQueue::emplace_back_bucket(std::vector<ExtrusionPaths> &&paths, const void *objPtr, Points offsets)
{
if (_objsPtrToId.find(objPtr) == _objsPtrToId.end()) {
_objsPtrToId.insert({objPtr, _objsPtrToId.size()});
_idToObjsPtr.insert({_objsPtrToId.size() - 1, objPtr});
}
_buckets.emplace_back(std::move(paths), _objsPtrToId[objPtr], offsets);
}
void LinesBucketQueue::build_queue()
{
assert(_pq.empty());
for (LinesBucket &bucket : _buckets)
_pq.push(&bucket);
}
double LinesBucketQueue::removeLowests()
{
auto lowest = _pq.top();
_pq.pop();
double curHeight = lowest->curHeight();
std::vector<LinesBucket *> lowests;
lowests.push_back(lowest);
while (_pq.empty() == false && std::abs(_pq.top()->curHeight() - lowest->curHeight()) < EPSILON) {
lowests.push_back(_pq.top());
_pq.pop();
}
for (LinesBucket *bp : lowests) {
bp->raise();
if (bp->valid()) { _pq.push(bp); }
}
return curHeight;
}
LineWithIDs LinesBucketQueue::getCurLines() const
{
LineWithIDs lines;
for (const LinesBucket &bucket : _buckets) {
if (bucket.valid()) {
LineWithIDs tmpLines = bucket.curLines();
lines.insert(lines.end(), tmpLines.begin(), tmpLines.end());
}
}
return lines;
}
void getExtrusionPathsFromEntity(const ExtrusionEntityCollection *entity, ExtrusionPaths &paths)
{
std::function<void(const ExtrusionEntityCollection *, ExtrusionPaths &)> getExtrusionPathImpl = [&](const ExtrusionEntityCollection *entity, ExtrusionPaths &paths) {
for (auto entityPtr : entity->entities) {
if (const ExtrusionEntityCollection *collection = dynamic_cast<ExtrusionEntityCollection *>(entityPtr)) {
getExtrusionPathImpl(collection, paths);
} else if (const ExtrusionPath *path = dynamic_cast<ExtrusionPath *>(entityPtr)) {
paths.push_back(*path);
} else if (const ExtrusionMultiPath *multipath = dynamic_cast<ExtrusionMultiPath *>(entityPtr)) {
for (const ExtrusionPath &path : multipath->paths) { paths.push_back(path); }
} else if (const ExtrusionLoop *loop = dynamic_cast<ExtrusionLoop *>(entityPtr)) {
for (const ExtrusionPath &path : loop->paths) { paths.push_back(path); }
}
}
};
getExtrusionPathImpl(entity, paths);
}
ExtrusionPaths getExtrusionPathsFromLayer(LayerRegionPtrs layerRegionPtrs)
{
ExtrusionPaths paths;
for (auto regionPtr : layerRegionPtrs) {
getExtrusionPathsFromEntity(&regionPtr->perimeters(), paths);
if (!regionPtr->perimeters().empty()) { getExtrusionPathsFromEntity(&regionPtr->fills(), paths); }
}
return paths;
}
ExtrusionPaths getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer)
{
ExtrusionPaths paths;
getExtrusionPathsFromEntity(&supportLayer->support_fills, paths);
return paths;
}
std::pair<std::vector<ExtrusionPaths>, std::vector<ExtrusionPaths>> getAllLayersExtrusionPathsFromObject(PrintObject *obj)
{
std::vector<ExtrusionPaths> objPaths, supportPaths;
for (auto layerPtr : obj->layers()) { objPaths.push_back(getExtrusionPathsFromLayer(layerPtr->regions())); }
for (auto supportLayerPtr : obj->support_layers()) { supportPaths.push_back(getExtrusionPathsFromSupportLayer(supportLayerPtr)); }
return {std::move(objPaths), std::move(supportPaths)};
}
ConflictComputeOpt ConflictChecker::find_inter_of_lines(const LineWithIDs &lines)
{
using namespace RasterizationImpl;
std::map<IndexPair, std::vector<int>> indexToLine;
for (int i = 0; i < (int)lines.size(); ++i) {
const LineWithID &l1 = lines[i];
auto indexes = line_rasterization(l1._line);
for (auto index : indexes) {
const auto &possibleIntersectIdxs = indexToLine[index];
for (auto possibleIntersectIdx : possibleIntersectIdxs) {
const LineWithID &l2 = lines[possibleIntersectIdx];
if (auto interRes = line_intersect(l1, l2); interRes.has_value()) { return interRes; }
}
indexToLine[index].push_back(i);
}
}
return {};
}
ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs,
std::optional<const FakeWipeTower *> wtdptr) // find the first intersection point of lines in different objects
{
if (objs.empty() || (objs.size() == 1 && objs.front()->instances().size() == 1)) { return {}; }
LinesBucketQueue conflictQueue;
if (wtdptr.has_value()) { // wipe tower at 0 by default
std::vector<ExtrusionPaths> wtpaths = (*wtdptr)->getFakeExtrusionPathsFromWipeTower();
conflictQueue.emplace_back_bucket(std::move(wtpaths), *wtdptr, Points{Point((*wtdptr)->plate_origin)});
}
for (PrintObject *obj : objs) {
std::pair<std::vector<ExtrusionPaths>, std::vector<ExtrusionPaths>> layers = getAllLayersExtrusionPathsFromObject(obj);
Points instances_shifts;
for (const PrintInstance& inst : obj->instances())
instances_shifts.emplace_back(inst.shift);
conflictQueue.emplace_back_bucket(std::move(layers.first), obj, instances_shifts);
conflictQueue.emplace_back_bucket(std::move(layers.second), obj, instances_shifts);
}
conflictQueue.build_queue();
std::vector<LineWithIDs> layersLines;
std::vector<double> heights;
while (conflictQueue.valid()) {
LineWithIDs lines = conflictQueue.getCurLines();
double curHeight = conflictQueue.removeLowests();
heights.push_back(curHeight);
layersLines.push_back(std::move(lines));
}
bool find = false;
tbb::concurrent_vector<std::pair<ConflictComputeResult,double>> conflict;
tbb::parallel_for(tbb::blocked_range<size_t>(0, layersLines.size()), [&](tbb::blocked_range<size_t> range) {
for (size_t i = range.begin(); i < range.end(); i++) {
auto interRes = find_inter_of_lines(layersLines[i]);
if (interRes.has_value()) {
find = true;
conflict.emplace_back(*interRes, heights[i]);
break;
}
}
});
if (find) {
std::sort(conflict.begin(), conflict.end(), [](const std::pair<ConflictComputeResult, double>& i1, const std::pair<ConflictComputeResult, double>& i2) {
return i1.second < i2.second;
});
const void *ptr1 = conflictQueue.idToObjsPtr(conflict[0].first._obj1);
const void *ptr2 = conflictQueue.idToObjsPtr(conflict[0].first._obj2);
double conflictHeight = conflict[0].second;
if (wtdptr.has_value()) {
const FakeWipeTower* wtdp = *wtdptr;
if (ptr1 == wtdp || ptr2 == wtdp) {
if (ptr2 == wtdp) { std::swap(ptr1, ptr2); }
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
return std::make_optional<ConflictResult>("WipeTower", obj2->model_object()->name, conflictHeight, nullptr, ptr2);
}
}
const PrintObject *obj1 = reinterpret_cast<const PrintObject *>(ptr1);
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
return std::make_optional<ConflictResult>(obj1->model_object()->name, obj2->model_object()->name, conflictHeight, ptr1, ptr2);
} else
return {};
}
ConflictComputeOpt ConflictChecker::line_intersect(const LineWithID &l1, const LineWithID &l2)
{
if (l1._obj_id == l2._obj_id && l1._inst_id == l2._inst_id) { return {}; } // lines are from same instance
Point inter;
bool intersect = l1._line.intersection(l2._line, &inter);
if (intersect) {
auto dist1 = std::min(unscale(Point(l1._line.a - inter)).norm(), unscale(Point(l1._line.b - inter)).norm());
auto dist2 = std::min(unscale(Point(l2._line.a - inter)).norm(), unscale(Point(l2._line.b - inter)).norm());
auto dist = std::min(dist1, dist2);
if (dist > 0.01) { return std::make_optional<ConflictComputeResult>(l1._obj_id, l2._obj_id); } // the two lines intersects if dist>0.01mm
}
return {};
}
} // namespace Slic3r

View File

@ -0,0 +1,129 @@
#ifndef slic3r_ConflictChecker_hpp_
#define slic3r_ConflictChecker_hpp_
#include "../Utils.hpp"
#include "../Model.hpp"
#include "../Print.hpp"
#include "../Layer.hpp"
#include <queue>
#include <vector>
#include <optional>
namespace Slic3r {
struct LineWithID
{
Line _line;
int _obj_id;
int _inst_id;
ExtrusionRole _role;
LineWithID(const Line& line, int obj_id, int inst_id, const ExtrusionRole& role) :
_line(line), _obj_id(obj_id), _inst_id(inst_id), _role(role) {}
};
using LineWithIDs = std::vector<LineWithID>;
class LinesBucket
{
private:
double _curHeight = 0.0;
unsigned _curPileIdx = 0;
std::vector<ExtrusionPaths> _piles;
int _id;
Points _offsets;
public:
LinesBucket(std::vector<ExtrusionPaths> &&paths, int id, Points offsets) : _piles(paths), _id(id), _offsets(offsets) {}
LinesBucket(LinesBucket &&) = default;
bool valid() const { return _curPileIdx < _piles.size(); }
void raise()
{
if (valid()) {
if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height; }
_curPileIdx++;
}
}
double curHeight() const { return _curHeight; }
LineWithIDs curLines() const
{
LineWithIDs lines;
for (const ExtrusionPath &path : _piles[_curPileIdx]) {
Polyline check_polyline;
for (int i = 0; i < (int)_offsets.size(); ++i) {
check_polyline = path.polyline;
check_polyline.translate(_offsets[i]);
Lines tmpLines = check_polyline.lines();
for (const Line& line : tmpLines) { lines.emplace_back(line, _id, i, path.role()); }
}
}
return lines;
}
friend bool operator>(const LinesBucket &left, const LinesBucket &right) { return left._curHeight > right._curHeight; }
friend bool operator<(const LinesBucket &left, const LinesBucket &right) { return left._curHeight < right._curHeight; }
friend bool operator==(const LinesBucket &left, const LinesBucket &right) { return left._curHeight == right._curHeight; }
};
struct LinesBucketPtrComp
{
bool operator()(const LinesBucket *left, const LinesBucket *right) { return *left > *right; }
};
class LinesBucketQueue
{
private:
std::vector<LinesBucket> _buckets;
std::priority_queue<LinesBucket *, std::vector<LinesBucket *>, LinesBucketPtrComp> _pq;
std::map<int, const void *> _idToObjsPtr;
std::map<const void *, int> _objsPtrToId;
public:
void emplace_back_bucket(std::vector<ExtrusionPaths> &&paths, const void *objPtr, Points offset);
void build_queue();
bool valid() const { return _pq.empty() == false; }
const void *idToObjsPtr(int id)
{
if (_idToObjsPtr.find(id) != _idToObjsPtr.end())
return _idToObjsPtr[id];
else
return nullptr;
}
double removeLowests();
LineWithIDs getCurLines() const;
};
void getExtrusionPathsFromEntity(const ExtrusionEntityCollection *entity, ExtrusionPaths &paths);
ExtrusionPaths getExtrusionPathsFromLayer(LayerRegionPtrs layerRegionPtrs);
ExtrusionPaths getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer);
std::pair<std::vector<ExtrusionPaths>, std::vector<ExtrusionPaths>> getAllLayersExtrusionPathsFromObject(PrintObject *obj);
struct ConflictComputeResult
{
int _obj1;
int _obj2;
ConflictComputeResult(int o1, int o2) : _obj1(o1), _obj2(o2) {}
ConflictComputeResult() = default;
};
using ConflictComputeOpt = std::optional<ConflictComputeResult>;
using ConflictObjName = std::optional<std::pair<std::string, std::string>>;
struct ConflictChecker
{
static ConflictResultOpt find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs, std::optional<const FakeWipeTower *> wtdptr);
static ConflictComputeOpt find_inter_of_lines(const LineWithIDs &lines);
static ConflictComputeOpt line_intersect(const LineWithID &l1, const LineWithID &l2);
};
} // namespace Slic3r
#endif

View File

@ -450,6 +450,7 @@ void GCodeProcessorResult::reset() {
filament_cost = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); filament_cost = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST);
custom_gcode_per_print_z = std::vector<CustomGCode::Item>(); custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>(); spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
conflict_result = std::nullopt;
time = 0; time = 0;
} }
#else #else
@ -468,6 +469,7 @@ void GCodeProcessorResult::reset() {
filament_cost = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); filament_cost = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST);
custom_gcode_per_print_z = std::vector<CustomGCode::Item>(); custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>(); spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
conflict_result = std::nullopt;
} }
#endif // ENABLE_GCODE_VIEWER_STATISTICS #endif // ENABLE_GCODE_VIEWER_STATISTICS

View File

@ -82,6 +82,22 @@ namespace Slic3r {
} }
}; };
struct ConflictResult
{
std::string _objName1;
std::string _objName2;
double _height;
const void* _obj1; // nullptr means wipe tower
const void* _obj2;
int layer = -1;
ConflictResult(const std::string& objName1, const std::string& objName2, double height, const void* obj1, const void* obj2)
: _objName1(objName1), _objName2(objName2), _height(height), _obj1(obj1), _obj2(obj2)
{}
ConflictResult() = default;
};
using ConflictResultOpt = std::optional<ConflictResult>;
struct GCodeProcessorResult struct GCodeProcessorResult
{ {
struct SettingsIds struct SettingsIds
@ -137,6 +153,8 @@ namespace Slic3r {
std::vector<CustomGCode::Item> custom_gcode_per_print_z; std::vector<CustomGCode::Item> custom_gcode_per_print_z;
std::vector<std::pair<float, std::pair<size_t, size_t>>> spiral_vase_layers; std::vector<std::pair<float, std::pair<size_t, size_t>>> spiral_vase_layers;
ConflictResultOpt conflict_result;
#if ENABLE_GCODE_VIEWER_STATISTICS #if ENABLE_GCODE_VIEWER_STATISTICS
int64_t time{ 0 }; int64_t time{ 0 };
#endif // ENABLE_GCODE_VIEWER_STATISTICS #endif // ENABLE_GCODE_VIEWER_STATISTICS

View File

@ -1563,4 +1563,19 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
} }
} }
std::vector<std::pair<float, float>> WipeTower::get_z_and_depth_pairs() const
{
std::vector<std::pair<float, float>> out = {{0.f, m_wipe_tower_depth}};
for (const WipeTowerInfo& wti : m_plan) {
assert(wti.depth < wti.depth + WT_EPSILON);
if (wti.depth < out.back().second - WT_EPSILON)
out.emplace_back(wti.z, wti.depth);
}
if (out.back().first < m_wipe_tower_height - WT_EPSILON)
out.emplace_back(m_wipe_tower_height, 0.f);
return out;
}
} // namespace Slic3r } // namespace Slic3r

View File

@ -143,6 +143,7 @@ public:
void generate(std::vector<std::vector<ToolChangeResult>> &result); void generate(std::vector<std::vector<ToolChangeResult>> &result);
float get_depth() const { return m_wipe_tower_depth; } float get_depth() const { return m_wipe_tower_depth; }
std::vector<std::pair<float, float>> get_z_and_depth_pairs() const;
float get_brim_width() const { return m_wipe_tower_brim_width_real; } float get_brim_width() const { return m_wipe_tower_brim_width_real; }
float get_wipe_tower_height() const { return m_wipe_tower_height; } float get_wipe_tower_height() const { return m_wipe_tower_height; }

View File

@ -11,6 +11,7 @@
#include "Thread.hpp" #include "Thread.hpp"
#include "GCode.hpp" #include "GCode.hpp"
#include "GCode/WipeTower.hpp" #include "GCode/WipeTower.hpp"
#include "GCode/ConflictChecker.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include "BuildVolume.hpp" #include "BuildVolume.hpp"
#include "format.hpp" #include "format.hpp"
@ -26,7 +27,6 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/regex.hpp> #include <boost/regex.hpp>
namespace Slic3r { namespace Slic3r {
template class PrintState<PrintStep, psCount>; template class PrintState<PrintStep, psCount>;
@ -962,6 +962,18 @@ void Print::process()
this->finalize_first_layer_convex_hull(); this->finalize_first_layer_convex_hull();
this->set_done(psSkirtBrim); this->set_done(psSkirtBrim);
} }
std::optional<const FakeWipeTower*> wipe_tower_opt = {};
if (this->has_wipe_tower()) {
m_fake_wipe_tower.set_pos_and_rotation({ m_config.wipe_tower_x, m_config.wipe_tower_y }, m_config.wipe_tower_rotation_angle);
wipe_tower_opt = std::make_optional<const FakeWipeTower*>(&m_fake_wipe_tower);
}
auto conflictRes = ConflictChecker::find_inter_of_lines_in_diff_objs(m_objects, wipe_tower_opt);
m_conflict_result = conflictRes;
if (conflictRes.has_value())
BOOST_LOG_TRIVIAL(error) << boost::format("gcode path conflicts found between %1% and %2%") % conflictRes->_objName1 % conflictRes->_objName2;
BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info();
} }
@ -987,6 +999,10 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
// Create GCode on heap, it has quite a lot of data. // Create GCode on heap, it has quite a lot of data.
std::unique_ptr<GCode> gcode(new GCode); std::unique_ptr<GCode> gcode(new GCode);
gcode->do_export(this, path.c_str(), result, thumbnail_cb); gcode->do_export(this, path.c_str(), result, thumbnail_cb);
if (m_conflict_result.has_value())
result->conflict_result = *m_conflict_result;
return path.c_str(); return path.c_str();
} }
@ -1485,6 +1501,7 @@ void Print::_make_wipe_tower()
m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size()); m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size());
wipe_tower.generate(m_wipe_tower_data.tool_changes); wipe_tower.generate(m_wipe_tower_data.tool_changes);
m_wipe_tower_data.depth = wipe_tower.get_depth(); m_wipe_tower_data.depth = wipe_tower.get_depth();
m_wipe_tower_data.z_and_depth_pairs = wipe_tower.get_z_and_depth_pairs();
m_wipe_tower_data.brim_width = wipe_tower.get_brim_width(); m_wipe_tower_data.brim_width = wipe_tower.get_brim_width();
m_wipe_tower_data.height = wipe_tower.get_wipe_tower_height(); m_wipe_tower_data.height = wipe_tower.get_wipe_tower_height();
@ -1509,6 +1526,10 @@ void Print::_make_wipe_tower()
m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
const Vec3d origin = Vec3d::Zero();
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_wipe_tower_height(), config().first_layer_height, m_wipe_tower_data.depth,
m_wipe_tower_data.z_and_depth_pairs, m_wipe_tower_data.brim_width, config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle, {scale_(origin.x()), scale_(origin.y())});
} }
// Generate a recommended G-code output file name based on the format template, default extension, and template parameters // Generate a recommended G-code output file name based on the format template, default extension, and template parameters
@ -1576,4 +1597,80 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co
return final_path; return final_path;
} }
std::vector<ExtrusionPaths> FakeWipeTower::getFakeExtrusionPathsFromWipeTower() const
{
float h = height;
float lh = layer_height;
int d = scale_(depth);
int w = scale_(width);
int bd = scale_(brim_width);
Point minCorner = { -bd, -bd };
Point maxCorner = { minCorner.x() + w + bd, minCorner.y() + d + bd };
const auto [cone_base_R, cone_scale_x] = WipeTower::get_wipe_tower_cone_base(width, height, depth, cone_angle);
std::vector<ExtrusionPaths> paths;
for (float hh = 0.f; hh < h; hh += lh) {
if (hh != 0.f) {
// The wipe tower may be getting smaller. Find the depth for this layer.
size_t i = 0;
for (i=0; i<z_and_depth_pairs.size()-1; ++i)
if (hh >= z_and_depth_pairs[i].first && hh < z_and_depth_pairs[i+1].first)
break;
d = scale_(z_and_depth_pairs[i].second);
minCorner = {0.f, -d/2 + scale_(z_and_depth_pairs.front().second/2.f)};
maxCorner = { minCorner.x() + w, minCorner.y() + d };
}
ExtrusionPath path(ExtrusionRole::WipeTower, 0.0, 0.0, lh);
path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner };
paths.push_back({ path });
// We added the border, now add several parallel lines so we can detect an object that is fully inside the tower.
// For now, simply use fixed spacing of 3mm.
for (coord_t y=minCorner.y()+scale_(3.); y<maxCorner.y(); y+=scale_(3.)) {
path.polyline = { {minCorner.x(), y}, {maxCorner.x(), y} };
paths.back().emplace_back(path);
}
// And of course the stabilization cone and its base...
if (cone_base_R > 0.) {
path.polyline.clear();
double r = cone_base_R * (1 - hh/height);
for (double alpha=0; alpha<2.01*M_PI; alpha+=2*M_PI/20.)
path.polyline.points.emplace_back(Point::new_scale(width/2. + r * std::cos(alpha)/cone_scale_x, depth/2. + r * std::sin(alpha)));
paths.back().emplace_back(path);
if (hh == 0.f) { // Cone brim.
for (float bw=brim_width; bw>0.f; bw-=3.f) {
path.polyline.clear();
for (double alpha=0; alpha<2.01*M_PI; alpha+=2*M_PI/20.) // see load_wipe_tower_preview, where the same is a bit clearer
path.polyline.points.emplace_back(Point::new_scale(
width/2. + cone_base_R * std::cos(alpha)/cone_scale_x * (1. + cone_scale_x*bw/cone_base_R),
depth/2. + cone_base_R * std::sin(alpha) * (1. + bw/cone_base_R))
);
paths.back().emplace_back(path);
}
}
}
// Only the first layer has brim.
if (hh == 0.f) {
minCorner = minCorner + Point(bd, bd);
maxCorner = maxCorner - Point(bd, bd);
}
}
// Rotate and translate the tower into the final position.
for (ExtrusionPaths& ps : paths) {
for (ExtrusionPath& p : ps) {
p.polyline.rotate(Geometry::deg2rad(rotation_angle));
p.polyline.translate(scale_(pos.x()), scale_(pos.y()));
}
}
return paths;
}
} // namespace Slic3r } // namespace Slic3r

View File

@ -418,6 +418,39 @@ private:
FillLightning::GeneratorPtr m_lightning_generator; FillLightning::GeneratorPtr m_lightning_generator;
}; };
struct FakeWipeTower
{
// generate fake extrusion
Vec2f pos;
float width;
float height;
float layer_height;
float depth;
std::vector<std::pair<float, float>> z_and_depth_pairs;
float brim_width;
float rotation_angle;
float cone_angle;
Vec2d plate_origin;
void set_fake_extrusion_data(const Vec2f& p, float w, float h, float lh, float d, const std::vector<std::pair<float, float>>& zad, float bd, float ra, float ca, const Vec2d& o)
{
pos = p;
width = w;
height = h;
layer_height = lh;
depth = d;
z_and_depth_pairs = zad;
brim_width = bd;
rotation_angle = ra;
cone_angle = ca;
plate_origin = o;
}
void set_pos_and_rotation(const Vec2f& p, float rotation) { pos = p; rotation_angle = rotation; }
std::vector<ExtrusionPaths> getFakeExtrusionPathsFromWipeTower() const;
};
struct WipeTowerData struct WipeTowerData
{ {
// Following section will be consumed by the GCodeGenerator. // Following section will be consumed by the GCodeGenerator.
@ -433,6 +466,7 @@ struct WipeTowerData
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box: // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
float depth; float depth;
std::vector<std::pair<float, float>> z_and_depth_pairs;
float brim_width; float brim_width;
float height; float height;
@ -668,6 +702,9 @@ private:
friend class GCodeProcessor; friend class GCodeProcessor;
// Allow PrintObject to access m_mutex and m_cancel_callback. // Allow PrintObject to access m_mutex and m_cancel_callback.
friend class PrintObject; friend class PrintObject;
ConflictResultOpt m_conflict_result;
FakeWipeTower m_fake_wipe_tower;
}; };
} /* slic3r_Print_hpp_ */ } /* slic3r_Print_hpp_ */

View File

@ -480,11 +480,11 @@ int GLVolumeCollection::load_object_volume(
#if ENABLE_OPENGL_ES #if ENABLE_OPENGL_ES
int GLVolumeCollection::load_wipe_tower_preview( int GLVolumeCollection::load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle,
float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh) float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh)
#else #else
int GLVolumeCollection::load_wipe_tower_preview( int GLVolumeCollection::load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle,
float rotation_angle, bool size_unknown, float brim_width) float rotation_angle, bool size_unknown, float brim_width)
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
{ {
@ -534,8 +534,13 @@ int GLVolumeCollection::load_wipe_tower_preview(
mesh.scale(Vec3f(width / (n * min_width), 1.f, height)); // Scaling to proper width mesh.scale(Vec3f(width / (n * min_width), 1.f, height)); // Scaling to proper width
} }
else else {
mesh = make_cube(width, depth, height); for (size_t i=1; i<z_and_depth_pairs.size(); ++i) {
TriangleMesh m = make_cube(width, z_and_depth_pairs[i-1].second, z_and_depth_pairs[i].first-z_and_depth_pairs[i-1].first);
m.translate(0.f, -z_and_depth_pairs[i-1].second/2.f + z_and_depth_pairs[0].second/2.f, z_and_depth_pairs[i-1].first);
mesh.merge(m);
}
}
// We'll make another mesh to show the brim (fixed layer height): // We'll make another mesh to show the brim (fixed layer height):
TriangleMesh brim_mesh = make_cube(width + 2.f * brim_width, depth + 2.f * brim_width, 0.2f); TriangleMesh brim_mesh = make_cube(width + 2.f * brim_width, depth + 2.f * brim_width, 0.2f);

View File

@ -396,10 +396,10 @@ public:
#if ENABLE_OPENGL_ES #if ENABLE_OPENGL_ES
int load_wipe_tower_preview( int load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr); float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr);
#else #else
int load_wipe_tower_preview( int load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width); float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width);
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
// Load SLA auxiliary GLVolumes (for support trees or pad). // Load SLA auxiliary GLVolumes (for support trees or pad).

View File

@ -799,6 +799,9 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr
short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time))) short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time)))
m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal; m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal;
} }
m_conflict_result = gcode_result.conflict_result;
if (m_conflict_result.has_value()) { m_conflict_result->layer = m_layers.get_l_at(m_conflict_result->_height); }
} }
void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector<std::string>& str_tool_colors) void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector<std::string>& str_tool_colors)
@ -2257,9 +2260,10 @@ void GCodeViewer::load_shells(const Print& print)
if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) { if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) {
const WipeTowerData& wipe_tower_data = print.wipe_tower_data(extruders_count); const WipeTowerData& wipe_tower_data = print.wipe_tower_data(extruders_count);
const float depth = wipe_tower_data.depth; const float depth = wipe_tower_data.depth;
const std::vector<std::pair<float, float>> z_and_depth_pairs = print.wipe_tower_data(extruders_count).z_and_depth_pairs;
const float brim_width = wipe_tower_data.brim_width; const float brim_width = wipe_tower_data.brim_width;
if (depth != 0.) if (depth != 0.)
m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_cone_angle, config.wipe_tower_rotation_angle, m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, z_and_depth_pairs, max_z, config.wipe_tower_cone_angle, config.wipe_tower_rotation_angle,
!print.is_step_done(psWipeTower), brim_width); !print.is_step_done(psWipeTower), brim_width);
} }
} }

View File

@ -526,6 +526,10 @@ class GCodeViewer
std::vector<Range>& get_ranges() { return m_ranges; } std::vector<Range>& get_ranges() { return m_ranges; }
double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; } double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; }
Range get_range_at(unsigned int id) const { return (id < m_ranges.size()) ? m_ranges[id] : Range(); } Range get_range_at(unsigned int id) const { return (id < m_ranges.size()) ? m_ranges[id] : Range(); }
int get_l_at(double z) const {
auto iter = std::upper_bound(m_zs.begin(), m_zs.end(), z);
return std::distance(m_zs.begin(), iter);
}
bool operator != (const Layers& other) const { bool operator != (const Layers& other) const {
if (m_zs != other.m_zs) if (m_zs != other.m_zs)
@ -784,6 +788,8 @@ private:
bool m_contained_in_bed{ true }; bool m_contained_in_bed{ true };
ConflictResultOpt m_conflict_result;
public: public:
GCodeViewer(); GCodeViewer();
~GCodeViewer() { reset(); } ~GCodeViewer() { reset(); }
@ -841,6 +847,8 @@ public:
void invalidate_legend() { m_legend_resizer.reset(); } void invalidate_legend() { m_legend_resizer.reset(); }
const ConflictResultOpt& get_conflict_result() const { return m_conflict_result; }
private: private:
void load_toolpaths(const GCodeProcessorResult& gcode_result); void load_toolpaths(const GCodeProcessorResult& gcode_result);
void load_shells(const Print& print); void load_shells(const Print& print);

View File

@ -2554,6 +2554,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
const Print *print = m_process->fff_print(); const Print *print = m_process->fff_print();
const float depth = print->wipe_tower_data(extruders_count).depth; const float depth = print->wipe_tower_data(extruders_count).depth;
const std::vector<std::pair<float, float>> z_and_depth_pairs = print->wipe_tower_data(extruders_count).z_and_depth_pairs;
const float height_real = print->wipe_tower_data(extruders_count).height; // -1.f = unknown const float height_real = print->wipe_tower_data(extruders_count).height; // -1.f = unknown
// Height of a print (Show at least a slab). // Height of a print (Show at least a slab).
@ -2562,11 +2563,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
if (depth != 0.) { if (depth != 0.) {
#if ENABLE_OPENGL_ES #if ENABLE_OPENGL_ES
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
x, y, w, depth, (float)height, ca, a, !print->is_step_done(psWipeTower), x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !print->is_step_done(psWipeTower),
bw, &m_wipe_tower_mesh); bw, &m_wipe_tower_mesh);
#else #else
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
x, y, w, depth, (float)height, ca, a, !print->is_step_done(psWipeTower), x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !print->is_step_done(psWipeTower),
bw); bw);
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
if (volume_idx_wipe_tower_old != -1) if (volume_idx_wipe_tower_old != -1)
@ -2692,6 +2693,7 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, co
if (wxGetApp().is_editor()) { if (wxGetApp().is_editor()) {
m_gcode_viewer.update_shells_color_by_extruder(m_config); m_gcode_viewer.update_shells_color_by_extruder(m_config);
_set_warning_notification_if_needed(EWarning::ToolpathOutside); _set_warning_notification_if_needed(EWarning::ToolpathOutside);
_set_warning_notification_if_needed(EWarning::GCodeConflict);
} }
m_gcode_viewer.refresh(gcode_result, str_tool_colors); m_gcode_viewer.refresh(gcode_result, str_tool_colors);
@ -7440,8 +7442,12 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning)
} }
else { else {
if (wxGetApp().is_editor()) { if (wxGetApp().is_editor()) {
if (current_printer_technology() != ptSLA) if (current_printer_technology() != ptSLA) {
show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); if (warning == EWarning::ToolpathOutside)
show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed();
else if (warning == EWarning::GCodeConflict)
show = m_gcode_viewer.has_data() && m_gcode_viewer.is_contained_in_bed() && m_gcode_viewer.get_conflict_result().has_value();
}
} }
} }
@ -7467,8 +7473,53 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
"Resolve the current problem to continue slicing."); "Resolve the current problem to continue slicing.");
error = ErrorType::PLATER_ERROR; error = ErrorType::PLATER_ERROR;
break; break;
case EWarning::GCodeConflict: {
const ConflictResultOpt& conflict_result = m_gcode_viewer.get_conflict_result();
if (!conflict_result.has_value()) { break; }
std::string objName1 = conflict_result->_objName1;
std::string objName2 = conflict_result->_objName2;
double height = conflict_result->_height;
int layer = conflict_result->layer;
text = (boost::format(_u8L("Conflicts of gcode paths have been found at layer %d, z = %.2lf mm. Please separate the conflicted objects farther (%s <-> %s).")) % layer %
height % objName1 % objName2).str();
error = ErrorType::SLICING_ERROR;
break;
}
} }
auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); auto& notification_manager = *wxGetApp().plater()->get_notification_manager();
const ConflictResultOpt& conflict_result = m_gcode_viewer.get_conflict_result();
if (warning == EWarning::GCodeConflict) {
if (conflict_result.has_value()) {
const PrintObject* obj2 = reinterpret_cast<const PrintObject*>(conflict_result->_obj2);
auto mo = obj2->model_object();
ObjectID id = mo->id();
int layer_id = conflict_result->layer;
auto action_fn = [id, layer_id](wxEvtHandler*) {
auto& objects = wxGetApp().model().objects;
auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end();
if (iter != objects.end()) {
const unsigned int obj_idx = std::distance(objects.begin(), iter);
wxGetApp().CallAfter([obj_idx, layer_id]() {
wxGetApp().plater()->set_preview_layers_slider_values_range(0, layer_id - 1);
wxGetApp().plater()->select_view_3D("3D");
wxGetApp().plater()->canvas3D()->get_selection().add_object(obj_idx, true);
wxGetApp().obj_list()->update_selections();
});
}
return false;
};
auto hypertext = _u8L("Jump to");
hypertext += std::string(" [") + mo->name + "]";
notification_manager.push_notification(NotificationType::SlicingError, NotificationManager::NotificationLevel::ErrorNotificationLevel,
_u8L("ERROR:") + "\n" + text, hypertext, action_fn);
}
else
notification_manager.close_slicing_error_notification(text);
return;
}
switch (error) switch (error)
{ {
case PLATER_WARNING: case PLATER_WARNING:

View File

@ -362,7 +362,8 @@ class GLCanvas3D
ToolpathOutside, ToolpathOutside,
SlaSupportsOutside, SlaSupportsOutside,
SomethingNotShown, SomethingNotShown,
ObjectClashed ObjectClashed,
GCodeConflict
}; };
class RenderStats class RenderStats

View File

@ -181,6 +181,12 @@ Preview::Preview(
load_print(); load_print();
} }
void Preview::set_layers_slider_values_range(int bottom, int top)
{
m_layers_slider->SetHigherValue(std::min(top, m_layers_slider->GetMaxValue()));
m_layers_slider->SetLowerValue(std::max(bottom, m_layers_slider->GetMinValue()));
}
bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model)
{ {
if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */))

View File

@ -151,6 +151,8 @@ public:
void set_keep_current_preview_type(bool value) { m_keep_current_preview_type = value; } void set_keep_current_preview_type(bool value) { m_keep_current_preview_type = value; }
void set_layers_slider_values_range(int bottom, int top);
private: private:
bool init(wxWindow* parent, Bed3D& bed, Model* model); bool init(wxWindow* parent, Bed3D& bed, Model* model);

View File

@ -1795,6 +1795,8 @@ struct Plater::priv
bool init_view_toolbar(); bool init_view_toolbar();
bool init_collapse_toolbar(); bool init_collapse_toolbar();
void set_preview_layers_slider_values_range(int bottom, int top);
void update_preview_moves_slider(); void update_preview_moves_slider();
void enable_preview_moves_slider(bool enable); void enable_preview_moves_slider(bool enable);
@ -4633,6 +4635,11 @@ bool Plater::priv::init_collapse_toolbar()
return true; return true;
} }
void Plater::priv::set_preview_layers_slider_values_range(int bottom, int top)
{
preview->set_layers_slider_values_range(bottom, top);
}
void Plater::priv::update_preview_moves_slider() void Plater::priv::update_preview_moves_slider()
{ {
preview->update_moves_slider(); preview->update_moves_slider();
@ -7513,6 +7520,11 @@ GLToolbar& Plater::get_collapse_toolbar()
return p->collapse_toolbar; return p->collapse_toolbar;
} }
void Plater::set_preview_layers_slider_values_range(int bottom, int top)
{
p->set_preview_layers_slider_values_range(bottom, top);
}
void Plater::update_preview_moves_slider() void Plater::update_preview_moves_slider()
{ {
p->update_preview_moves_slider(); p->update_preview_moves_slider();

View File

@ -399,6 +399,8 @@ public:
const GLToolbar& get_collapse_toolbar() const; const GLToolbar& get_collapse_toolbar() const;
GLToolbar& get_collapse_toolbar(); GLToolbar& get_collapse_toolbar();
void set_preview_layers_slider_values_range(int bottom, int top);
void update_preview_moves_slider(); void update_preview_moves_slider();
void enable_preview_moves_slider(bool enable); void enable_preview_moves_slider(bool enable);