diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index b578932d4..0de0b4e51 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -138,6 +138,8 @@ set(SLIC3R_SOURCES GCode/ThumbnailData.hpp GCode/Thumbnails.cpp GCode/Thumbnails.hpp + GCode/ConflictChecker.cpp + GCode/ConflictChecker.hpp GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp GCode/FindReplace.cpp diff --git a/src/libslic3r/GCode/ConflictChecker.cpp b/src/libslic3r/GCode/ConflictChecker.cpp new file mode 100644 index 000000000..09ef1c850 --- /dev/null +++ b/src/libslic3r/GCode/ConflictChecker.cpp @@ -0,0 +1,290 @@ +#include "libslic3r.h" +#include "ConflictChecker.hpp" + +#include +#include + +#include +#include +#include + +namespace Slic3r { + +namespace RasterizationImpl { +using IndexPair = std::pair; +using Grids = std::vector; + +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(xdist) / ray.x() * stepX : DBL_MAX; + double tDeltaY = ray.y() != 0 ? static_cast(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 &&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 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 getExtrusionPathImpl = [&](const ExtrusionEntityCollection *entity, ExtrusionPaths &paths) { + for (auto entityPtr : entity->entities) { + if (const ExtrusionEntityCollection *collection = dynamic_cast(entityPtr)) { + getExtrusionPathImpl(collection, paths); + } else if (const ExtrusionPath *path = dynamic_cast(entityPtr)) { + paths.push_back(*path); + } else if (const ExtrusionMultiPath *multipath = dynamic_cast(entityPtr)) { + for (const ExtrusionPath &path : multipath->paths) { paths.push_back(path); } + } else if (const ExtrusionLoop *loop = dynamic_cast(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(®ionPtr->perimeters(), paths); + if (!regionPtr->perimeters().empty()) { getExtrusionPathsFromEntity(®ionPtr->fills(), paths); } + } + return paths; +} + +ExtrusionPaths getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer) +{ + ExtrusionPaths paths; + getExtrusionPathsFromEntity(&supportLayer->support_fills, paths); + return paths; +} + +std::pair, std::vector> getAllLayersExtrusionPathsFromObject(PrintObject *obj) +{ + std::vector 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> 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 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 wtpaths = (*wtdptr)->getFakeExtrusionPathsFromWipeTower(); + conflictQueue.emplace_back_bucket(std::move(wtpaths), *wtdptr, Points{Point((*wtdptr)->plate_origin)}); + } + for (PrintObject *obj : objs) { + std::pair, std::vector> 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 layersLines; + std::vector 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> conflict; + + tbb::parallel_for(tbb::blocked_range(0, layersLines.size()), [&](tbb::blocked_range 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& i1, const std::pair& 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(ptr2); + return std::make_optional("WipeTower", obj2->model_object()->name, conflictHeight, nullptr, ptr2); + } + } + const PrintObject *obj1 = reinterpret_cast(ptr1); + const PrintObject *obj2 = reinterpret_cast(ptr2); + return std::make_optional(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(l1._obj_id, l2._obj_id); } // the two lines intersects if dist>0.01mm + } + return {}; +} + +} // namespace Slic3r + diff --git a/src/libslic3r/GCode/ConflictChecker.hpp b/src/libslic3r/GCode/ConflictChecker.hpp new file mode 100644 index 000000000..344018f3d --- /dev/null +++ b/src/libslic3r/GCode/ConflictChecker.hpp @@ -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 +#include +#include + +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; + +class LinesBucket +{ +private: + double _curHeight = 0.0; + unsigned _curPileIdx = 0; + + std::vector _piles; + int _id; + Points _offsets; + +public: + LinesBucket(std::vector &&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 _buckets; + std::priority_queue, LinesBucketPtrComp> _pq; + std::map _idToObjsPtr; + std::map _objsPtrToId; + +public: + void emplace_back_bucket(std::vector &&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> getAllLayersExtrusionPathsFromObject(PrintObject *obj); + +struct ConflictComputeResult +{ + int _obj1; + int _obj2; + + ConflictComputeResult(int o1, int o2) : _obj1(o1), _obj2(o2) {} + ConflictComputeResult() = default; +}; + +using ConflictComputeOpt = std::optional; + +using ConflictObjName = std::optional>; + +struct ConflictChecker +{ + static ConflictResultOpt find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs, std::optional wtdptr); + static ConflictComputeOpt find_inter_of_lines(const LineWithIDs &lines); + static ConflictComputeOpt line_intersect(const LineWithID &l1, const LineWithID &l2); +}; + +} // namespace Slic3r + +#endif diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 51e4c88c1..d71dd347a 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -450,6 +450,7 @@ void GCodeProcessorResult::reset() { filament_cost = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); custom_gcode_per_print_z = std::vector(); spiral_vase_layers = std::vector>>(); + conflict_result = std::nullopt; time = 0; } #else @@ -468,6 +469,7 @@ void GCodeProcessorResult::reset() { filament_cost = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); custom_gcode_per_print_z = std::vector(); spiral_vase_layers = std::vector>>(); + conflict_result = std::nullopt; } #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 26cb89894..76395f435 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -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; + struct GCodeProcessorResult { struct SettingsIds @@ -137,6 +153,8 @@ namespace Slic3r { std::vector custom_gcode_per_print_z; std::vector>> spiral_vase_layers; + ConflictResultOpt conflict_result; + #if ENABLE_GCODE_VIEWER_STATISTICS int64_t time{ 0 }; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 5ce5353db..6e868d148 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1563,4 +1563,19 @@ void WipeTower::generate(std::vector> & } } + + +std::vector> WipeTower::get_z_and_depth_pairs() const +{ + std::vector> 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 diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 8209d13f4..969da848d 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -143,6 +143,7 @@ public: void generate(std::vector> &result); float get_depth() const { return m_wipe_tower_depth; } + std::vector> get_z_and_depth_pairs() const; float get_brim_width() const { return m_wipe_tower_brim_width_real; } float get_wipe_tower_height() const { return m_wipe_tower_height; } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index ca3feb04e..c62e27400 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -11,6 +11,7 @@ #include "Thread.hpp" #include "GCode.hpp" #include "GCode/WipeTower.hpp" +#include "GCode/ConflictChecker.hpp" #include "Utils.hpp" #include "BuildVolume.hpp" #include "format.hpp" @@ -26,7 +27,6 @@ #include #include - namespace Slic3r { template class PrintState; @@ -962,6 +962,18 @@ void Print::process() this->finalize_first_layer_convex_hull(); this->set_done(psSkirtBrim); } + + std::optional 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(&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(); } @@ -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. std::unique_ptr gcode(new GCode); 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(); } @@ -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()); wipe_tower.generate(m_wipe_tower_data.tool_changes); 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.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.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 @@ -1576,4 +1597,80 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co return final_path; } + std::vector 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 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[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 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 diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 5c42709b1..059491951 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -418,6 +418,39 @@ private: FillLightning::GeneratorPtr m_lightning_generator; }; +struct FakeWipeTower +{ + // generate fake extrusion + Vec2f pos; + float width; + float height; + float layer_height; + float depth; + std::vector> 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>& 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 getFakeExtrusionPathsFromWipeTower() const; +}; + struct WipeTowerData { // 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: float depth; + std::vector> z_and_depth_pairs; float brim_width; float height; @@ -668,6 +702,9 @@ private: friend class GCodeProcessor; // Allow PrintObject to access m_mutex and m_cancel_callback. friend class PrintObject; + + ConflictResultOpt m_conflict_result; + FakeWipeTower m_fake_wipe_tower; }; } /* slic3r_Print_hpp_ */ diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 7c5008554..679abb709 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -480,11 +480,11 @@ int GLVolumeCollection::load_object_volume( #if ENABLE_OPENGL_ES 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>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh) #else 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>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width) #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 } - else - mesh = make_cube(width, depth, height); + else { + for (size_t i=1; i>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr); #else 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>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width); #endif // ENABLE_OPENGL_ES // Load SLA auxiliary GLVolumes (for support trees or pad). diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index a9e016bb9..5b4681b9c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -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(PrintEstimatedStatistics::ETimeMode::Normal)].time))) 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& str_tool_colors) @@ -2257,9 +2260,10 @@ void GCodeViewer::load_shells(const Print& print) if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) { const WipeTowerData& wipe_tower_data = print.wipe_tower_data(extruders_count); const float depth = wipe_tower_data.depth; + const std::vector> z_and_depth_pairs = print.wipe_tower_data(extruders_count).z_and_depth_pairs; const float brim_width = wipe_tower_data.brim_width; if (depth != 0.) - m_shells.volumes.load_wipe_tower_preview(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); } } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 3eb61cfc4..32d0a91e3 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -526,6 +526,10 @@ class GCodeViewer std::vector& get_ranges() { return m_ranges; } 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(); } + 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 { if (m_zs != other.m_zs) @@ -784,6 +788,8 @@ private: bool m_contained_in_bed{ true }; + ConflictResultOpt m_conflict_result; + public: GCodeViewer(); ~GCodeViewer() { reset(); } @@ -841,6 +847,8 @@ public: void invalidate_legend() { m_legend_resizer.reset(); } + const ConflictResultOpt& get_conflict_result() const { return m_conflict_result; } + private: void load_toolpaths(const GCodeProcessorResult& gcode_result); void load_shells(const Print& print); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3beaa591a..1ca749e4e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2554,6 +2554,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const Print *print = m_process->fff_print(); const float depth = print->wipe_tower_data(extruders_count).depth; + const std::vector> 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 // 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 ENABLE_OPENGL_ES 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); #else 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); #endif // ENABLE_OPENGL_ES 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()) { m_gcode_viewer.update_shells_color_by_extruder(m_config); _set_warning_notification_if_needed(EWarning::ToolpathOutside); + _set_warning_notification_if_needed(EWarning::GCodeConflict); } m_gcode_viewer.refresh(gcode_result, str_tool_colors); @@ -7440,8 +7442,12 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) } else { if (wxGetApp().is_editor()) { - if (current_printer_technology() != ptSLA) - show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); + if (current_printer_technology() != ptSLA) { + 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."); error = ErrorType::PLATER_ERROR; 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(); + + const ConflictResultOpt& conflict_result = m_gcode_viewer.get_conflict_result(); + if (warning == EWarning::GCodeConflict) { + if (conflict_result.has_value()) { + const PrintObject* obj2 = reinterpret_cast(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) { case PLATER_WARNING: diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index a58decbcd..eb674dffe 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -362,7 +362,8 @@ class GLCanvas3D ToolpathOutside, SlaSupportsOutside, SomethingNotShown, - ObjectClashed + ObjectClashed, + GCodeConflict }; class RenderStats diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 656bc0b53..74a8719d8 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -181,6 +181,12 @@ Preview::Preview( 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) { if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 25d9fe084..c7b7fdc61 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -151,6 +151,8 @@ public: 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: bool init(wxWindow* parent, Bed3D& bed, Model* model); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ff7a7bfb2..9057ec671 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1795,6 +1795,8 @@ struct Plater::priv bool init_view_toolbar(); bool init_collapse_toolbar(); + void set_preview_layers_slider_values_range(int bottom, int top); + void update_preview_moves_slider(); void enable_preview_moves_slider(bool enable); @@ -4633,6 +4635,11 @@ bool Plater::priv::init_collapse_toolbar() 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() { preview->update_moves_slider(); @@ -7513,6 +7520,11 @@ GLToolbar& Plater::get_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() { p->update_preview_moves_slider(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 880448c86..8eeabae42 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -399,6 +399,8 @@ public: const GLToolbar& get_collapse_toolbar() const; GLToolbar& get_collapse_toolbar(); + void set_preview_layers_slider_values_range(int bottom, int top); + void update_preview_moves_slider(); void enable_preview_moves_slider(bool enable);