diff --git a/src/BambuStudio.cpp b/src/BambuStudio.cpp index ba8baff12..a58c3d3c9 100644 --- a/src/BambuStudio.cpp +++ b/src/BambuStudio.cpp @@ -1735,6 +1735,7 @@ int CLI::run(int argc, char **argv) // loop through action options bool export_to_3mf = false, load_slicedata = false, export_slicedata = false, export_slicedata_error = false; + bool no_check = false; std::string export_3mf_file, load_slice_data_dir, export_slice_data_dir; std::string outfile_dir = m_config.opt_string("outputdir"); std::vector calibration_thumbnails; @@ -1791,6 +1792,8 @@ int CLI::run(int argc, char **argv) } */else if (opt_key == "export_3mf") { export_to_3mf = true; export_3mf_file = m_config.opt_string(opt_key); + }else if(opt_key=="no_check"){ + no_check = true; //} else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") { } else if (opt_key == "export_slicedata") { export_slicedata = true; @@ -1955,6 +1958,7 @@ int CLI::run(int argc, char **argv) new_print_config.apply(*part_plate->config()); new_print_config.apply(m_extra_config, true); print->apply(model, new_print_config); + print->set_no_check_flag(no_check);//BBS StringObjectException warning; auto err = print->validate(&warning); if (!err.string.empty()) { diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 0e5fd059a..2f3b9b90e 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -139,6 +139,8 @@ set(lisbslic3r_sources GCode/GCodeProcessor.hpp GCode/AvoidCrossingPerimeters.cpp GCode/AvoidCrossingPerimeters.hpp + GCode/ConflictChecker.cpp + GCode/ConflictChecker.hpp GCode.cpp GCode.hpp GCodeReader.cpp diff --git a/src/libslic3r/GCode/ConflictChecker.cpp b/src/libslic3r/GCode/ConflictChecker.cpp new file mode 100644 index 000000000..3cf1aa49a --- /dev/null +++ b/src/libslic3r/GCode/ConflictChecker.cpp @@ -0,0 +1,252 @@ +#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 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, PrintObject *objPtr, Point offset) +{ + auto oldSize = _buckets.capacity(); + _buckets.emplace_back(std::move(paths), 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); + } +} + +void LinesBucketQueue::removeLowests() +{ + auto lowest = _pq.top(); + _pq.pop(); + + 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); } + } +} + +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() == false) { 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)}; +} + +ConflictRet 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 {}; +} + +ConflictRet ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs) // find the first intersection point of lines in different objects +{ + LinesBucketQueue conflictQueue; + 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; + while (conflictQueue.valid()) { + LineWithIDs lines = conflictQueue.getCurLines(); + conflictQueue.removeLowests(); + 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.value()); + break; + } + } + }); + + if (find) { + return {conflict[0]}; + } else + return {}; +} + +ConflictRet ConflictChecker::line_intersect(const LineWithID &l1, const LineWithID &l2) +{ + if (l1._objPtr == l2._objPtr) { 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._objPtr, l2._objPtr); } // the two lines intersects if dist>0.01mm + } + return {}; +} + +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/GCode/ConflictChecker.hpp b/src/libslic3r/GCode/ConflictChecker.hpp new file mode 100644 index 000000000..0a0242e30 --- /dev/null +++ b/src/libslic3r/GCode/ConflictChecker.hpp @@ -0,0 +1,116 @@ +#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; + PrintObject *_objPtr; + int _role; + + LineWithID(const Line &line, PrintObject *objPtr, int role) : _line(line), _objPtr(objPtr), _role(role) {} +}; + +using LineWithIDs = std::vector; + +class LinesBucket +{ +private: + double _curHeight = 0.0; + unsigned _curPileIdx = 0; + + std::vector _piles; + PrintObject * _objPtr; + Point _offset; + +public: + LinesBucket(std::vector &&paths, PrintObject *objPtr, Point offset) : _piles(paths), _objPtr(objPtr), _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 (path.is_force_no_extrusion() == false) { + Polyline check_polyline = path.polyline; + if (path.role() != ExtrusionRole::erBrim) { check_polyline.translate(_offset); } + Lines tmpLines = check_polyline.lines(); + for (const Line &line : tmpLines) { lines.emplace_back(line, _objPtr, 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; + +public: + void emplace_back_bucket(std::vector &&paths, PrintObject *objPtr, Point offset); + bool valid() const { return _pq.empty() == false; } + + void 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 ConflictResult +{ + PrintObject *_obj1; + PrintObject *_obj2; + ConflictResult(PrintObject *obj1, PrintObject *obj2) : _obj1(obj1), _obj2(obj2) {} + ConflictResult() = default; +}; + +static_assert(std::is_trivial::value, "atomic value requires to be trival."); + +using ConflictRet = std::optional; + +struct ConflictChecker +{ + static ConflictRet find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs); + static ConflictRet find_inter_of_lines(const LineWithIDs &lines); + static ConflictRet line_intersect(const LineWithID &l1, const LineWithID &l2); +}; + +} // namespace Slic3r + +#endif diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index cb72ac229..5af302ead 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -27,6 +27,10 @@ //BBS: add json support #include "nlohmann/json.hpp" +#include "GCode/ConflictChecker.hpp" + +#include + using namespace nlohmann; // Mark string for localization and translate. @@ -1661,6 +1665,25 @@ void Print::process(bool use_cache) } } + // BBS + if(!m_no_check) + { + this->set_started(psConflictCheck); + this->set_status(70, L("Checking gcode path conflicts.")); + using Clock = std::chrono::high_resolution_clock; + auto startTime = Clock::now(); + auto conflictRes = ConflictChecker::find_inter_of_lines_in_diff_objs(m_objects); + auto endTime = Clock::now(); + volatile double seconds = std::chrono::duration_cast(endTime - startTime).count() / (double) 1000; + if (conflictRes.has_value()) { + auto objName1 = conflictRes.value()._obj1->m_model_object->name; + auto objName2 = conflictRes.value()._obj2->m_model_object->name; + //throw Slic3r::SlicingError((boost::format(L("Conflicts of gcode paths have been found. Please separate the conflicted objects (%s + %s) farther.")) % objName1% objName2).str()); + this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, (boost::format(L("Conflicts of gcode paths have been found. Please separate the conflicted objects (%s <-> %s) farther.")) % objName1 % objName2).str()); + } + this->set_done(psConflictCheck); + } + BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 27dcfc32f..450916271 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -79,7 +79,8 @@ enum PrintStep { // should be refreshed. psSlicingFinished = psSkirtBrim, psGCodeExport, - psCount, + psConflictCheck, + psCount }; enum PrintObjectStep { diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 0d8730b07..60087a396 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -510,6 +510,8 @@ public: //BBS: get/set plate id int get_plate_index() const { return m_plate_index; } void set_plate_index(int index) { m_plate_index = index; } + bool get_no_check_flag() const { return m_no_check; } + void set_no_check_flag(bool no_check) { m_no_check = no_check; } protected: friend class PrintObjectBase; @@ -544,6 +546,7 @@ protected: //BBS: add plate id into print base int m_plate_index{ 0 }; + bool m_no_check = false; // Callback to be evoked regularly to update state of the UI thread. status_callback_type m_status_callback; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index e265fc903..2d6d39e6c 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4608,6 +4608,14 @@ CLIActionsConfigDef::CLIActionsConfigDef() def->cli_params = "time"; def->set_default_value(new ConfigOptionInt(300)); + // must define new params here, otherwise comamnd param check will fail + def = this->add("no_check", coBool); + def->label = L("No check"); + def->tooltip = L("Do not run any validity checks, such as gcode path conflicts check."); + def->cli = "no_check"; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionBool(false)); + /*def = this->add("help_fff", coBool); def->label = L("Help (FFF options)"); def->tooltip = L("Show the full list of print/G-code configuration options.");