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..7f8ba2d0c --- /dev/null +++ b/src/libslic3r/GCode/ConflictChecker.cpp @@ -0,0 +1,301 @@ +#include "libslic3r.h" +#include "ConflictChecker.hpp" + +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + +#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 firstVoxel = currentVoxel; + 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, Point offset) +{ + auto oldSize = _buckets.capacity(); + 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], offset); + _pq.push(&_buckets.back()); + auto newSize = _buckets.capacity(); + if (oldSize != newSize) { // pointers change + decltype(_pq) newQueue; + for (LinesBucket &bucket : _buckets) { newQueue.push(&bucket); } + std::swap(_pq, newQueue); + } +} + +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) { +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + getExtrusionPathsFromEntity(®ionPtr->perimeters(), paths); + if (!regionPtr->perimeters().empty()) { getExtrusionPathsFromEntity(®ionPtr->fills(), paths); } +#else + getExtrusionPathsFromEntity(®ionPtr->perimeters, paths); + if (regionPtr->perimeters.empty() == false) { getExtrusionPathsFromEntity(®ionPtr->fills, paths); } +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + } + 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 < 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.size() <= 1) { return {}; } + LinesBucketQueue conflictQueue; + if (wtdptr.has_value()) { // wipe tower at 0 by default +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + auto wtpaths = (*wtdptr)->getFakeExtrusionPathsFromWipeTower(); + conflictQueue.emplace_back_bucket(std::move(wtpaths), *wtdptr, { (*wtdptr)->plate_origin.x(), (*wtdptr)->plate_origin.y() }); +#else + auto wtpaths = wtdptr.value()->getFakeExtrusionPathsFromWipeTower(); + conflictQueue.emplace_back_bucket(std::move(wtpaths), wtdptr.value(), {wtdptr.value()->plate_origin.x(),wtdptr.value()->plate_origin.y()}); +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + } + for (PrintObject *obj : objs) { + auto layers = getAllLayersExtrusionPathsFromObject(obj); + conflictQueue.emplace_back_bucket(std::move(layers.first), obj, obj->instances().front().shift); + conflictQueue.emplace_back_bucket(std::move(layers.second), obj, obj->instances().front().shift); + } + + 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; +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + conflict.emplace_back(*interRes, heights[i]); +#else + conflict.emplace_back(interRes.value(),heights[i]); +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + break; + } + } + }); + + if (find) { + 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()) { +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + const FakeWipeTower* wtdp = *wtdptr; +#else + const FakeWipeTower *wtdp = wtdptr.value(); +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + 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._id == l2._id) { return {}; } // return true if lines are from same object + 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._id, l2._id); } // the two lines intersects if dist>0.01mm + } + return {}; +} + +} // namespace Slic3r + +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION diff --git a/src/libslic3r/GCode/ConflictChecker.hpp b/src/libslic3r/GCode/ConflictChecker.hpp new file mode 100644 index 000000000..00c975c37 --- /dev/null +++ b/src/libslic3r/GCode/ConflictChecker.hpp @@ -0,0 +1,143 @@ +#ifndef slic3r_ConflictChecker_hpp_ +#define slic3r_ConflictChecker_hpp_ + +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + +#include "../Utils.hpp" +#include "../Model.hpp" +#include "../Print.hpp" +#include "../Layer.hpp" + +#include +#include +#include + +namespace Slic3r { + +struct LineWithID +{ + Line _line; + int _id; +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + ExtrusionRole _role; + + LineWithID(const Line& line, int id, const ExtrusionRole& role) : _line(line), _id(id), _role(role) {} +#else + int _role; + + LineWithID(const Line& line, int id, int role) : _line(line), _id(id), _role(role) {} +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD +}; + +using LineWithIDs = std::vector; + +class LinesBucket +{ +private: + double _curHeight = 0.0; + unsigned _curPileIdx = 0; + + std::vector _piles; + int _id; + Point _offset; + +public: + LinesBucket(std::vector &&paths, int id, Point offset) : _piles(paths), _id(id), _offset(offset) {} + 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]) { +#if !ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + // + // INTEGRATION WHAT TO DO ??? + // we do not define path.is_force_no_extrusion() + // + if (path.is_force_no_extrusion() == false) { +#endif // !ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + Polyline check_polyline = path.polyline; + check_polyline.translate(_offset); + Lines tmpLines = check_polyline.lines(); + for (const Line &line : tmpLines) { lines.emplace_back(line, _id, path.role()); } +#if !ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + } +#endif // !ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + } + 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, Point offset); + 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 // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + +#endif diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 26cb89894..6a9d5606e 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -82,6 +82,24 @@ namespace Slic3r { } }; +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + 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; +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + struct GCodeProcessorResult { struct SettingsIds @@ -137,6 +155,10 @@ namespace Slic3r { std::vector custom_gcode_per_print_z; std::vector>> spiral_vase_layers; +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + ConflictResultOpt conflict_result; +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + #if ENABLE_GCODE_VIEWER_STATISTICS int64_t time{ 0 }; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index ca3feb04e..b00b8e218 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -11,6 +11,9 @@ #include "Thread.hpp" #include "GCode.hpp" #include "GCode/WipeTower.hpp" +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION +#include "GCode/ConflictChecker.hpp" +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION #include "Utils.hpp" #include "BuildVolume.hpp" #include "format.hpp" @@ -962,6 +965,43 @@ void Print::process() this->finalize_first_layer_convex_hull(); this->set_done(psSkirtBrim); } + +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION +#if !ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + // + // INTEGRATION WHAT TO DO ??? + // we do not define m_no_check + // + if (!m_no_check) { + using Clock = std::chrono::high_resolution_clock; + auto startTime = Clock::now(); +#endif // !ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + std::optional wipe_tower_opt = {}; + if (this->has_wipe_tower()) { +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + m_fake_wipe_tower.set_pos({ m_config.wipe_tower_x, m_config.wipe_tower_y }); +#else + m_fake_wipe_tower.set_pos({ m_config.wipe_tower_x.get_at(m_plate_index), m_config.wipe_tower_y.get_at(m_plate_index) }); +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + 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); +#if !ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + auto endTime = Clock::now(); + volatile double seconds = std::chrono::duration_cast(endTime - startTime).count() / (double)1000; + BOOST_LOG_TRIVIAL(info) << "gcode path conflicts check takes " << seconds << " secs."; +#endif // !ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + + m_conflict_result = conflictRes; + if (conflictRes.has_value()) +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + BOOST_LOG_TRIVIAL(error) << boost::format("gcode path conflicts found between %1% and %2%") % conflictRes->_objName1 % conflictRes->_objName2; +#else + BOOST_LOG_TRIVIAL(error) << boost::format("gcode path conflicts found between %1% and %2%") % conflictRes.value()._objName1 % conflictRes.value()._objName2; + } +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); } @@ -987,6 +1027,11 @@ 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 ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + result->conflict_result = m_conflict_result; +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + return path.c_str(); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 5c42709b1..12058734b 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -418,6 +418,78 @@ private: FillLightning::GeneratorPtr m_lightning_generator; }; +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION +struct FakeWipeTower +{ + // generate fake extrusion + Vec2f pos; + float width; + float height; + float layer_height; + float depth; + float brim_width; + Vec2d plate_origin; + +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + void set_fake_extrusion_data(const Vec2f& p, float w, float h, float lh, float d, float bd, const Vec2d& o) +#else + void set_fake_extrusion_data(Vec2f p, float w, float h, float lh, float d, float bd, Vec2d o) +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + { + pos = p; + width = w; + height = h; + layer_height = lh; + depth = d; + brim_width = bd; + plate_origin = o; + } + +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + void set_pos(const Vec2f& p) { pos = p; } +#else + void set_pos(Vec2f p) { pos = p; } +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + + std::vector getFakeExtrusionPathsFromWipeTower() const + { + float h = height; + float lh = layer_height; + int d = scale_(depth); + int w = scale_(width); + int bd = scale_(brim_width); + Point minCorner = { scale_(pos.x()), scale_(pos.y()) }; + Point maxCorner = { minCorner.x() + w, minCorner.y() + d }; + + std::vector paths; + for (float hh = 0.f; hh < h; hh += lh) { +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + ExtrusionPath path(ExtrusionRole::WipeTower, 0.0, 0.0, lh); +#else + ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, lh); +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner }; + paths.push_back({ path }); + +#if !ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + // + // INTEGRATION WHAT TO DO ??? + // we do not define erBrim + // + if (hh == 0.f) { // add brim + ExtrusionPath fakeBrim(ExtrusionRole::erBrim, 0.0, 0.0, lh); + Point wtbminCorner = { minCorner - Point{bd, bd} }; + Point wtbmaxCorner = { maxCorner + Point{bd, bd} }; + fakeBrim.polyline = { wtbminCorner, {wtbmaxCorner.x(), wtbminCorner.y()}, wtbmaxCorner, {wtbminCorner.x(), wtbmaxCorner.y()}, wtbminCorner }; + paths.back().push_back(fakeBrim); + } +#endif // !ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + } + return paths; + } +}; +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + struct WipeTowerData { // Following section will be consumed by the GCodeGenerator. @@ -668,6 +740,11 @@ private: friend class GCodeProcessor; // Allow PrintObject to access m_mutex and m_cancel_callback. friend class PrintObject; + +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + ConflictResultOpt m_conflict_result; + FakeWipeTower m_fake_wipe_tower; +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION }; } /* slic3r_Print_hpp_ */ diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 602654633..36b51fe2d 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -62,4 +62,27 @@ #define ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR (1 && ENABLE_2_6_0_ALPHA1) +//==================== +// 2.6.0.beta1 techs +//==================== +#define ENABLE_2_6_0_BETA1 1 + +// Toolpaths conflicts detection +// Succesfully enhanced and integrated into PrusaSlicer from BambuStudio: +// https://github.com/bambulab/BambuStudio/commit/d43c7d5c9293508241ee88cee860ea7ea51c2080 +// https://github.com/bambulab/BambuStudio/commit/d72b4c1bfe3f0c275241250a0b1797944147b3b7 +// https://github.com/bambulab/BambuStudio/commit/87eb0f366560b895cb7219a2eb6820027aaf69d3 +// https://github.com/bambulab/BambuStudio/commit/b5b7264e99ea0872d39455bf61af56209cb4cd39 +// Co-authored-by: manch1n +// Co-authored-by: miaoxin +// Co-authored-by: lane.wei + +#define ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION (1 && ENABLE_2_6_0_BETA1) + +// Modifications to original BambuStudio code: +// * compliance with PS code +// * removed code which has no clear counterpart in PS +#define ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD (1 && ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION) + + #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index a9e016bb9..5261113e0 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -799,6 +799,15 @@ 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; } + +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + m_conflict_result = gcode_result.conflict_result; +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + if (m_conflict_result.has_value()) { m_conflict_result->layer = m_layers.get_l_at(m_conflict_result->_height); } +#else + if (m_conflict_result) { m_conflict_result.value().layer = m_layers.get_l_at(m_conflict_result.value()._height); } +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION } void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 3eb61cfc4..a8290350d 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -526,6 +526,12 @@ 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(); } +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + 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); + } +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION bool operator != (const Layers& other) const { if (m_zs != other.m_zs) @@ -784,6 +790,10 @@ private: bool m_contained_in_bed{ true }; +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + ConflictResultOpt m_conflict_result; +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + public: GCodeViewer(); ~GCodeViewer() { reset(); } @@ -841,6 +851,10 @@ public: void invalidate_legend() { m_legend_resizer.reset(); } +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + const ConflictResultOpt& get_conflict_result() const { return m_conflict_result; } +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + 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..14db2f8df 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2692,6 +2692,9 @@ 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); +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + _set_warning_notification_if_needed(EWarning::GCodeConflict); +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION } m_gcode_viewer.refresh(gcode_result, str_tool_colors); @@ -7440,8 +7443,21 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) } else { if (wxGetApp().is_editor()) { - if (current_printer_technology() != ptSLA) +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + 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) +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + show = m_gcode_viewer.has_data() && m_gcode_viewer.is_contained_in_bed() && m_gcode_viewer.get_conflict_result().has_value(); +#else + show = m_gcode_viewer.has_data() && m_gcode_viewer.is_contained_in_bed() && m_gcode_viewer.m_conflict_result.has_value(); +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + } +#else + if (current_printer_technology() != ptSLA) show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION } } @@ -7467,8 +7483,88 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) "Resolve the current problem to continue slicing."); error = ErrorType::PLATER_ERROR; break; +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + case EWarning::GCodeConflict: { +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + 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(); +#else + if (!m_gcode_viewer.m_conflict_result) { break; } + std::string objName1 = m_gcode_viewer.m_conflict_result.value()._objName1; + std::string objName2 = m_gcode_viewer.m_conflict_result.value()._objName2; + double height = m_gcode_viewer.m_conflict_result.value()._height; + int layer = m_gcode_viewer.m_conflict_result.value().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(); +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + error = ErrorType::SLICING_ERROR; + break; + } +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION } auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); + +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + 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); +#else + if (warning == EWarning::GCodeConflict && m_gcode_viewer.m_conflict_result) { + const PrintObject* obj2 = reinterpret_cast(m_gcode_viewer.m_conflict_result.value()._obj2); +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + auto mo = obj2->model_object(); + ObjectID id = mo->id(); +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + auto action_fn = [this, id](wxEvtHandler*) { +#else + auto action_fn = [id](wxEvtHandler*) { +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + 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()) { +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + const unsigned int obj_idx = std::distance(objects.begin(), iter); + wxGetApp().CallAfter([this, obj_idx]() { + wxGetApp().plater()->select_view_3D("3D"); + wxGetApp().plater()->canvas3D()->get_selection().add_object(obj_idx, true); + wxGetApp().obj_list()->update_selections(); + }); +#else + wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor); + wxGetApp().obj_list()->select_items({ { *iter, nullptr } }); +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + } + return false; + }; + auto hypertext = _u8L("Jump to"); + hypertext += std::string(" [") + mo->name + "]"; +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + notification_manager.push_notification(NotificationType::SlicingError, NotificationManager::NotificationLevel::ErrorNotificationLevel, + _u8L("ERROR:") + "\n" + text, hypertext, action_fn); +#else + notification_manager.push_notification(NotificationType::PlaterError, NotificationManager::NotificationLevel::ErrorNotificationLevel, + _u8L("ERROR:") + "\n" + text, hypertext, action_fn); + return; +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + } +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD + else + notification_manager.close_slicing_error_notification(text); + + return; + } +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION_MOD +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + switch (error) { case PLATER_WARNING: diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index a58decbcd..613d97698 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -362,7 +362,12 @@ class GLCanvas3D ToolpathOutside, SlaSupportsOutside, SomethingNotShown, +#if ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION + ObjectClashed, + GCodeConflict +#else ObjectClashed +#endif // ENABLE_BAMBUSTUDIO_TOOLPATHS_CONFLICTS_DETECTION }; class RenderStats