diff --git a/CMakeLists.txt b/CMakeLists.txt index 771bb73d91..56784de993 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,7 @@ set(engine_SRCS # Except main.cpp. src/TreeSupportTipGenerator.cpp src/TreeModelVolumes.cpp src/TreeSupport.cpp + src/TreeSupportElement.cpp src/WallsComputation.cpp src/WallToolPaths.cpp diff --git a/include/MeshMaterialSplitter.h b/include/MeshMaterialSplitter.h index aa9a97aa9b..91777dea5a 100644 --- a/include/MeshMaterialSplitter.h +++ b/include/MeshMaterialSplitter.h @@ -14,10 +14,10 @@ namespace MeshMaterialSplitter { /*! - * Generate a modifier mesh for every extruder other than 0, that has some user-painted texture data + * Generate modifier mesh based user-painted texture data, either for multi-material or support customization * @param meshgroup The group to take the sliced meshes from and add the modifier meshes to */ -void makeMaterialModifierMeshes(MeshGroup* meshgroup); +void makePaintingModifierMeshes(MeshGroup* meshgroup); } // namespace MeshMaterialSplitter diff --git a/include/TreeModelVolumes.h b/include/TreeModelVolumes.h index b6598370a6..df8e547554 100644 --- a/include/TreeModelVolumes.h +++ b/include/TreeModelVolumes.h @@ -402,7 +402,7 @@ class TreeModelVolumes /*! * \brief Does at least one mesh allow support to rest on a model. */ - bool support_rests_on_model_; + bool support_rests_on_model_{ false }; /*! * \brief The progress of the precalculate function for communicating it to the progress bar. diff --git a/include/TreeSupport.h b/include/TreeSupport.h index a4f1be756a..9211a05276 100644 --- a/include/TreeSupport.h +++ b/include/TreeSupport.h @@ -19,6 +19,8 @@ namespace cura { +class OBJ; + // The various stages of the process can be weighted differently in the progress bar. // These weights are obtained experimentally using a small sample size. Sensible weights can differ drastically based on the assumed default settings and model. constexpr auto TREE_PROGRESS_TOTAL = 10000; @@ -310,6 +312,12 @@ class TreeSupport */ void drawAreas(std::vector>& move_bounds, SliceDataStorage& storage); + /*! + * Saves the influence areas and the resulting positions of all the given elements to a 3D object + * @para move_bounds The elements to be saved, sorted per layer + */ + void saveToObj(const std::vector>& move_bounds, OBJ& obj) const; + /*! * \brief Settings with the indexes of meshes that use these settings. */ diff --git a/include/TreeSupportElement.h b/include/TreeSupportElement.h index b633aa863c..b5761ae59c 100644 --- a/include/TreeSupportElement.h +++ b/include/TreeSupportElement.h @@ -17,19 +17,13 @@ namespace cura { +class OBJ; + struct AreaIncreaseSettings { - AreaIncreaseSettings() - : type_(AvoidanceType::FAST) - , increase_speed_(0) - , increase_radius_(false) - , no_error_(false) - , use_min_distance_(false) - , move_(false) - { - } + AreaIncreaseSettings() = default; - AreaIncreaseSettings(AvoidanceType type, coord_t increase_speed, bool increase_radius, bool simplify, bool use_min_distance, bool move) + AreaIncreaseSettings(const AvoidanceType type, const coord_t increase_speed, const bool increase_radius, const bool simplify, const bool use_min_distance, const bool move) : type_(type) , increase_speed_(increase_speed) , increase_radius_(increase_radius) @@ -39,18 +33,14 @@ struct AreaIncreaseSettings { } - AvoidanceType type_; - coord_t increase_speed_; - bool increase_radius_; - bool no_error_; - bool use_min_distance_; - bool move_; + AvoidanceType type_{ AvoidanceType::FAST }; + coord_t increase_speed_{ 0 }; + bool increase_radius_{ false }; + bool no_error_{ false }; + bool use_min_distance_{ false }; + bool move_{ false }; - bool operator==(const AreaIncreaseSettings& other) const - { - return increase_radius_ == other.increase_radius_ && increase_speed_ == other.increase_speed_ && type_ == other.type_ && no_error_ == other.no_error_ - && use_min_distance_ == other.use_min_distance_ && move_ == other.move_; - } + bool operator==(const AreaIncreaseSettings& other) const = default; }; struct TreeSupportElement @@ -68,93 +58,14 @@ struct TreeSupportElement bool force_tips_to_roof, bool skip_ovalisation, bool influence_area_limit_active, - coord_t influence_area_limit_range) - : target_height_(target_height) - , target_position_(target_position) - , next_position_(target_position) - , next_height_(target_height) - , effective_radius_height_(distance_to_top) - , to_buildplate_(to_buildplate) - , distance_to_top_(distance_to_top) - , area_(nullptr) - , result_on_layer_(target_position) - , increased_to_model_radius_(0) - , to_model_gracious_(to_model_gracious) - , buildplate_radius_increases_(0) - , use_min_xy_dist_(use_min_xy_dist) - , supports_roof_(supports_roof) - , dont_move_until_(dont_move_until) - , can_use_safe_radius_(can_use_safe_radius) - , last_area_increase_(AreaIncreaseSettings(AvoidanceType::FAST, 0, false, false, false, false)) - , missing_roof_layers_(force_tips_to_roof ? dont_move_until : 0) - , skip_ovalisation_(skip_ovalisation) - , all_tips_({ target_position }) - , influence_area_limit_active_(influence_area_limit_active) - , influence_area_limit_range_(influence_area_limit_range) - { - RecreateInfluenceLimitArea(); - } + coord_t influence_area_limit_range); - TreeSupportElement(const TreeSupportElement& elem, Shape* newArea = nullptr) - : // copy constructor with possibility to set a new area - target_height_(elem.target_height_) - , target_position_(elem.target_position_) - , next_position_(elem.next_position_) - , next_height_(elem.next_height_) - , effective_radius_height_(elem.effective_radius_height_) - , to_buildplate_(elem.to_buildplate_) - , distance_to_top_(elem.distance_to_top_) - , area_(newArea != nullptr ? newArea : elem.area_) - , result_on_layer_(elem.result_on_layer_) - , increased_to_model_radius_(elem.increased_to_model_radius_) - , to_model_gracious_(elem.to_model_gracious_) - , buildplate_radius_increases_(elem.buildplate_radius_increases_) - , use_min_xy_dist_(elem.use_min_xy_dist_) - , supports_roof_(elem.supports_roof_) - , dont_move_until_(elem.dont_move_until_) - , can_use_safe_radius_(elem.can_use_safe_radius_) - , last_area_increase_(elem.last_area_increase_) - , missing_roof_layers_(elem.missing_roof_layers_) - , skip_ovalisation_(elem.skip_ovalisation_) - , all_tips_(elem.all_tips_) - , influence_area_limit_active_(elem.influence_area_limit_active_) - , influence_area_limit_range_(elem.influence_area_limit_range_) - , influence_area_limit_area_(elem.influence_area_limit_area_) - { - parents_.insert(parents_.begin(), elem.parents_.begin(), elem.parents_.end()); - } + TreeSupportElement(const TreeSupportElement& elem, Shape* newArea = nullptr); /*! * \brief Create a new Element for one layer below the element of the pointer supplied. */ - TreeSupportElement(TreeSupportElement* element_above) - : target_height_(element_above->target_height_) - , target_position_(element_above->target_position_) - , next_position_(element_above->next_position_) - , next_height_(element_above->next_height_) - , effective_radius_height_(element_above->effective_radius_height_) - , to_buildplate_(element_above->to_buildplate_) - , distance_to_top_(element_above->distance_to_top_ + 1) - , area_(element_above->area_) - , result_on_layer_(Point2LL(-1, -1)) - , // set to invalid as we are a new node on a new layer - increased_to_model_radius_(element_above->increased_to_model_radius_) - , to_model_gracious_(element_above->to_model_gracious_) - , buildplate_radius_increases_(element_above->buildplate_radius_increases_) - , use_min_xy_dist_(element_above->use_min_xy_dist_) - , supports_roof_(element_above->supports_roof_) - , dont_move_until_(element_above->dont_move_until_) - , can_use_safe_radius_(element_above->can_use_safe_radius_) - , last_area_increase_(element_above->last_area_increase_) - , missing_roof_layers_(element_above->missing_roof_layers_) - , skip_ovalisation_(false) - , all_tips_(element_above->all_tips_) - , influence_area_limit_active_(element_above->influence_area_limit_active_) - , influence_area_limit_range_(element_above->influence_area_limit_range_) - , influence_area_limit_area_(element_above->influence_area_limit_area_) - { - parents_ = { element_above }; - } + TreeSupportElement(TreeSupportElement* element_above); // ONLY to be called in merge as it assumes a few assurances made by it. TreeSupportElement( @@ -166,70 +77,52 @@ struct TreeSupportElement const std::function& getRadius, double diameter_scale_bp_radius, coord_t branch_radius, - double diameter_angle_scale_factor) - : next_position_(next_position) - , next_height_(next_height) - , area_(nullptr) - , increased_to_model_radius_(increased_to_model_radius) - , use_min_xy_dist_(first.use_min_xy_dist_ || second.use_min_xy_dist_) - , supports_roof_(first.supports_roof_ || second.supports_roof_) - , dont_move_until_(std::max(first.dont_move_until_, second.dont_move_until_)) - , can_use_safe_radius_(first.can_use_safe_radius_ || second.can_use_safe_radius_) - , missing_roof_layers_(std::min(first.missing_roof_layers_, second.missing_roof_layers_)) - , skip_ovalisation_(false) + double diameter_angle_scale_factor); + + + bool operator==(const TreeSupportElement& other) const { - if (first.target_height_ > second.target_height_) + return target_position_ == other.target_position_ && target_height_ == other.target_height_; + } + + bool operator<(const TreeSupportElement& other) const // true if me < other + { + return ! (*this == other) && ! (*this > other); + } + + bool operator>(const TreeSupportElement& other) const + { + // Doesn't really have to make sense, only required for ordering in maps to ensure deterministic behavior. + if (*this == other) { - target_height_ = first.target_height_; - target_position_ = first.target_position_; + return false; } - else + if (other.target_height_ != target_height_) { - target_height_ = second.target_height_; - target_position_ = second.target_position_; + return other.target_height_ < target_height_; } - effective_radius_height_ = std::max(first.effective_radius_height_, second.effective_radius_height_); - distance_to_top_ = std::max(first.distance_to_top_, second.distance_to_top_); + return other.target_position_.X == target_position_.X ? other.target_position_.Y < target_position_.Y : other.target_position_.X < target_position_.X; + } - to_buildplate_ = first.to_buildplate_ && second.to_buildplate_; - to_model_gracious_ = first.to_model_gracious_ && second.to_model_gracious_; // valid as we do not merge non-gracious with gracious + void AddParents(const std::vector& adding); - AddParents(first.parents_); - AddParents(second.parents_); + void RecreateInfluenceLimitArea(); - buildplate_radius_increases_ = 0; - if (diameter_scale_bp_radius > 0) - { - const coord_t foot_increase_radius = std::abs( - std::max( - getRadius(second.effective_radius_height_, second.buildplate_radius_increases_), - getRadius(first.effective_radius_height_, first.buildplate_radius_increases_)) - - getRadius(effective_radius_height_, buildplate_radius_increases_)); - // 'buildplate_radius_increases' has to be recalculated, as when a smaller tree with a larger buildplate_radius_increases merge with a larger branch, - // the buildplate_radius_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. - buildplate_radius_increases_ = foot_increase_radius / (branch_radius * (diameter_scale_bp_radius - diameter_angle_scale_factor)); - } + void setToBuildplateForAllParents(bool new_value); - // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. - last_area_increase_ = AreaIncreaseSettings( - std::min(first.last_area_increase_.type_, second.last_area_increase_.type_), - std::min(first.last_area_increase_.increase_speed_, second.last_area_increase_.increase_speed_), - first.last_area_increase_.increase_radius_ || second.last_area_increase_.increase_radius_, - first.last_area_increase_.no_error_ || second.last_area_increase_.no_error_, - first.last_area_increase_.use_min_distance_ && second.last_area_increase_.use_min_distance_, - first.last_area_increase_.move_ || second.last_area_increase_.move_); - - all_tips_ = first.all_tips_; - all_tips_.insert(all_tips_.end(), second.all_tips_.begin(), second.all_tips_.end()); - influence_area_limit_range_ = std::max(first.influence_area_limit_range_, second.influence_area_limit_range_); - influence_area_limit_active_ = first.influence_area_limit_active_ || second.influence_area_limit_active_; - RecreateInfluenceLimitArea(); - if (first.to_buildplate_ != second.to_buildplate_) - { - setToBuildplateForAllParents(to_buildplate_); - } + inline bool isResultOnLayerSet() const + { + return result_on_layer_ != Point2LL(-1, -1); } + /*! + * Saves the influence areas to a 3D object + * @param obj The object in which to save the influence area + * @param z The Z at which the area is placed + * @param layer_height The layer height at which to extrude the influence area + */ + void saveToObj(OBJ& obj, const coord_t z, const coord_t layer_height) const; + /*! * \brief The layer this support elements wants reach */ @@ -357,87 +250,6 @@ struct TreeSupportElement * \brief Additional locations that the tip should reach */ std::vector additional_ovalization_targets_; - - - bool operator==(const TreeSupportElement& other) const - { - return target_position_ == other.target_position_ && target_height_ == other.target_height_; - } - - bool operator<(const TreeSupportElement& other) const // true if me < other - { - return ! (*this == other) && ! (*this > other); - } - - bool operator>(const TreeSupportElement& other) const - { - // Doesn't really have to make sense, only required for ordering in maps to ensure deterministic behavior. - if (*this == other) - { - return false; - } - if (other.target_height_ != target_height_) - { - return other.target_height_ < target_height_; - } - return other.target_position_.X == target_position_.X ? other.target_position_.Y < target_position_.Y : other.target_position_.X < target_position_.X; - } - - void AddParents(const std::vector& adding) - { - for (TreeSupportElement* ptr : adding) - { - parents_.emplace_back(ptr); - } - } - - void RecreateInfluenceLimitArea() - { - if (influence_area_limit_active_) - { - influence_area_limit_area_.clear(); - - for (Point2LL p : all_tips_) - { - Polygon circle; - for (Point2LL corner : TreeSupportBaseCircle::getBaseCircle()) - { - circle.push_back(p + corner * influence_area_limit_range_ / double(TreeSupportBaseCircle::base_radius)); - } - if (influence_area_limit_area_.empty()) - { - influence_area_limit_area_ = circle.offset(0); - } - else - { - influence_area_limit_area_ = influence_area_limit_area_.intersection(circle.offset(0)); - } - } - } - } - - void setToBuildplateForAllParents(bool new_value) - { - to_buildplate_ = new_value; - to_model_gracious_ |= new_value; - std::vector grandparents{ parents_ }; - while (! grandparents.empty()) - { - std::vector next_parents; - for (TreeSupportElement* grandparent : grandparents) - { - next_parents.insert(next_parents.end(), grandparent->parents_.begin(), grandparent->parents_.end()); - grandparent->to_buildplate_ = new_value; - grandparent->to_model_gracious_ |= new_value; // If we set to_buildplate to true, update to_model_gracious - } - grandparents = next_parents; - } - } - - inline bool isResultOnLayerSet() const - { - return result_on_layer_ != Point2LL(-1, -1); - } }; } // namespace cura diff --git a/include/TreeSupportUtils.h b/include/TreeSupportUtils.h index f7e0622521..3dc4b7327c 100644 --- a/include/TreeSupportUtils.h +++ b/include/TreeSupportUtils.h @@ -108,6 +108,13 @@ class TreeSupportUtils const EFillMethod pattern = generate_support_supporting ? EFillMethod::GRID : roof ? config.roof_pattern : config.support_pattern; + if (pattern == EFillMethod::LIGHTNING) + { + // This can't be done for lightning infill since it requires the support to be generated first. Final result is good enough though, + // because it propagates to properly support the top area, compensating for the same feature of the tree support. + return {}; + } + const bool zig_zaggify_infill = roof ? pattern == EFillMethod::ZIG_ZAG : config.zig_zaggify_support; const bool connect_polygons = false; constexpr coord_t support_roof_overlap = 0; diff --git a/include/mesh.h b/include/mesh.h index 0f78c5c8ed..85619ec145 100644 --- a/include/mesh.h +++ b/include/mesh.h @@ -139,8 +139,9 @@ class Mesh std::shared_ptr texture_; std::shared_ptr texture_data_mapping_; - Mesh(Settings& parent); - Mesh(); + Mesh(const Settings& parent); + Mesh(Settings&& parent); + Mesh() = default; /*! * @@ -188,24 +189,17 @@ class Mesh void transform(const Matrix4x3D& transformation); /*! - * Gets whether this is a printable mesh (not an infill mesh, slicing mesh, - * etc.) - * \return True if it's a mesh that gets printed. + * Gets whether this is a printable mesh (not a modifier mesh). This includes infill meshes, because they do get printed. Cutting meshes are not considered as printable though, + * because they are early converted into regular meshes and should not be considered in subsequent algorithms. */ bool isPrinted() const; - /*! - * Certain mesh types can interlock with each other. The interlock property provides - * if this mesh can be used for interlocking with other meshes (for example support - * blockers should not have an interlocking interface). - * - * \return True if an interface of the mesh could be interlocking with another mesh - */ - bool canInterlock() const; + /*! Gets whether this is a regular model mesh (not a modifier or infill mesh) */ + bool isModelMesh() const; private: - mutable bool has_disconnected_faces; //!< Whether it has been logged that this mesh contains disconnected faces - mutable bool has_overlapping_faces; //!< Whether it has been logged that this mesh contains overlapping faces + mutable bool has_disconnected_faces{ false }; //!< Whether it has been logged that this mesh contains disconnected faces + mutable bool has_overlapping_faces{ false }; //!< Whether it has been logged that this mesh contains overlapping faces int findIndexOfVertex(const Point3LL& v); //!< find index of vertex close to the given point, or create a new vertex and return its index. /*! diff --git a/include/settings/Settings.h b/include/settings/Settings.h index 5e9a77b7a4..fef23be08a 100644 --- a/include/settings/Settings.h +++ b/include/settings/Settings.h @@ -101,7 +101,7 @@ class Settings * * If this set of settings has no value for a setting, the parent is asked. */ - void setParent(Settings* new_parent); + void setParent(const Settings* new_parent); std::unordered_map getFlattendSettings() const; @@ -112,7 +112,7 @@ class Settings * Optionally, a parent setting container to ask for the value of a setting * if this container has no value for it. */ - Settings* parent; + const Settings* parent; /*! * \brief A dictionary to map the setting keys to the actual setting values. diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index 64c5939dc5..930165aee8 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -219,6 +219,7 @@ class SupportLayer Shape support_mesh_drop_down; //!< Areas from support meshes which should be supported by more support Shape support_mesh; //!< Areas from support meshes which should NOT be supported by more support Shape anti_overhang; //!< Areas where no overhang should be detected. + Shape force_overhang; //!< Areas where overhang should be forced. /*! * Exclude the given polygons from the support infill areas and update the SupportInfillParts. @@ -327,6 +328,9 @@ class SliceMeshStorage RetractionAndWipeConfig retraction_wipe_config; //!< Per-Object retraction and wipe settings. + const bool is_printed_; //!< Whether this is an actual printed mesh + const bool is_model_mesh_; //!< Whether this is a regular model mesh + /*! * \brief Creates a storage space for slice results of a mesh. * \param mesh The mesh that the storage space belongs to. @@ -350,12 +354,14 @@ class SliceMeshStorage bool getExtruderIsUsed(const size_t extruder_nr, const LayerIndex& layer_nr) const; /*! - * Gets whether this is a printable mesh (not an infill mesh, slicing mesh, - * etc.) - * \return True if it's a mesh that gets printed. + * Gets whether this is a printable mesh (not a modifier mesh). This includes infill meshes, because they do get printed. Cutting meshes are not considered as printable though, + * because they are early converted into regular meshes and should not be considered in subsequent algorithms. */ bool isPrinted() const; + /*! Gets whether this is a regular model mesh (not a modifier or infill mesh) */ + bool isModelMesh() const; + /*! * \return the mesh's user specified z seam hint */ diff --git a/include/utils/OBJ.h b/include/utils/OBJ.h index f5d5c20ace..5bad86ae06 100644 --- a/include/utils/OBJ.h +++ b/include/utils/OBJ.h @@ -15,11 +15,12 @@ namespace cura { class Mesh; +class VoxelGrid; class OBJ : NoCopy { public: - OBJ(std::string filename, const double scale = 1.0); + OBJ(const std::string& filename, const double scale = 1.0); ~OBJ(); @@ -39,7 +40,13 @@ class OBJ : NoCopy const std::optional& uv1 = std::nullopt, const std::optional& uv2 = std::nullopt); - void writeMesh(const Mesh& mesh, const SVG::Color color = SVG::Color::BLACK); + void write(const Mesh& mesh, const SVG::Color color = SVG::Color::BLACK); + + void write(const VoxelGrid& voxel_grid); + + void write(const Polyline& polyline, const coord_t z, const coord_t height, const SVG::Color color = SVG::Color::BLACK); + + void write(const Shape& shape, const coord_t z, const coord_t height, const SVG::Color color = SVG::Color::BLACK); private: struct Triangle diff --git a/include/utils/VoxelGrid.h b/include/utils/VoxelGrid.h index c4f922dbb2..86bfab936b 100644 --- a/include/utils/VoxelGrid.h +++ b/include/utils/VoxelGrid.h @@ -153,8 +153,6 @@ class VoxelGrid */ std::vector getTraversedVoxels(const Triangle3LL& triangle) const; - void saveToObj(const std::string& filename, const double scale = 1.0) const; - private: Point3D resolution_; Point3D origin_; diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 4365b7c0bf..18f239d945 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1183,8 +1183,7 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS for (const std::shared_ptr& mesh_ptr : storage.meshes) { const auto& mesh = *mesh_ptr; - if (layer_nr >= static_cast(mesh.layers.size()) || mesh.settings.get("support_mesh") || mesh.settings.get("anti_overhang_mesh") - || mesh.settings.get("cutting_mesh") || mesh.settings.get("infill_mesh")) + if (layer_nr >= static_cast(mesh.layers.size()) || mesh.settings.get("support_mesh") || ! mesh.isModelMesh()) { continue; } @@ -1737,7 +1736,7 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(const SliceMeshStorage& return; } - if (mesh.settings.get("anti_overhang_mesh") || mesh.settings.get("support_mesh")) + if (! mesh.isPrinted() || mesh.settings.get("support_mesh")) { return; } @@ -1807,7 +1806,7 @@ void FffGcodeWriter::addMeshLayerToGCode( return; } - if (mesh.settings.get("anti_overhang_mesh") || mesh.settings.get("support_mesh")) + if (! mesh.isPrinted() || mesh.settings.get("support_mesh")) { return; } diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 0cf8ada600..d8eecda010 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -62,7 +62,7 @@ namespace cura bool FffPolygonGenerator::generateAreas(SliceDataStorage& storage, MeshGroup* meshgroup, TimeKeeper& timeKeeper) { - MeshMaterialSplitter::makeMaterialModifierMeshes(meshgroup); + MeshMaterialSplitter::makePaintingModifierMeshes(meshgroup); if (! sliceModel(meshgroup, timeKeeper, storage)) { @@ -233,7 +233,8 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe for (unsigned int mesh_idx = 0; mesh_idx < slicerList.size(); mesh_idx++) { const Mesh& mesh = scene.current_mesh_group->meshes[mesh_idx]; - if (mesh.settings_.get("conical_overhang_enabled") && ! mesh.settings_.get("anti_overhang_mesh")) + if (mesh.settings_.get("conical_overhang_enabled") && ! mesh.settings_.get("anti_overhang_mesh") + && (! mesh.settings_.has("force_support_overhang_mesh") || ! mesh.settings_.get("force_support_overhang_mesh"))) { ConicalOverhang::apply(slicerList[mesh_idx], mesh); } @@ -261,7 +262,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe { const Mesh& mesh = scene.current_mesh_group->meshes[meshIdx]; Slicer* slicer = slicerList[meshIdx]; - if (! mesh.settings_.get("anti_overhang_mesh") && ! mesh.settings_.get("infill_mesh") && ! mesh.settings_.get("cutting_mesh")) + if (mesh.isModelMesh()) { storage.print_layer_count = std::max(storage.print_layer_count, slicer->layers.size()); } @@ -356,7 +357,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& for (std::shared_ptr& mesh_ptr : storage.meshes) { auto& mesh = *mesh_ptr; - if (! mesh.settings.get("infill_mesh") && ! mesh.settings.get("anti_overhang_mesh")) + if (mesh.isModelMesh()) { slice_layer_count = std::max(slice_layer_count, mesh.layers.size()); } @@ -879,7 +880,7 @@ void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage for (std::shared_ptr& mesh_ptr : storage.meshes) { auto& mesh = *mesh_ptr; - if (mesh.settings.get("anti_overhang_mesh") || mesh.settings.get("support_mesh")) + if (! mesh.isPrinted() || mesh.settings.get("support_mesh")) { continue; // Special type of mesh that doesn't get printed. } diff --git a/src/InterlockingGenerator.cpp b/src/InterlockingGenerator.cpp index 92d921e0c4..4b085c7188 100644 --- a/src/InterlockingGenerator.cpp +++ b/src/InterlockingGenerator.cpp @@ -40,7 +40,7 @@ void InterlockingGenerator::generateInterlockingStructure(std::vector& Slicer& mesh_b = *volumes[mesh_b_idx]; size_t extruder_nr_b = mesh_b.mesh->settings_.get("wall_0_extruder_nr").extruder_nr_; - if (! mesh_a.mesh->canInterlock() || ! mesh_b.mesh->canInterlock()) + if (! mesh_a.mesh->isModelMesh() || ! mesh_b.mesh->isModelMesh()) { continue; } diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index bbb1e5de43..e8171a8fce 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -192,8 +192,8 @@ Shape LayerPlan::computeCombBoundary(const CombBoundary boundary_type) { const auto& mesh = *mesh_ptr; const SliceLayer& layer = mesh.layers[static_cast(layer_nr_)]; - // don't process infill_mesh or anti_overhang_mesh - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) + // don't process non printable meshes + if (! mesh.isModelMesh()) { continue; } diff --git a/src/MeshGroup.cpp b/src/MeshGroup.cpp index 3777b47680..ce87b52be9 100644 --- a/src/MeshGroup.cpp +++ b/src/MeshGroup.cpp @@ -58,8 +58,7 @@ Point3LL MeshGroup::min() const Point3LL ret(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()); for (const Mesh& mesh : meshes) { - if (mesh.settings_.get("infill_mesh") || mesh.settings_.get("cutting_mesh") - || mesh.settings_.get("anti_overhang_mesh")) // Don't count pieces that are not printed. + if (! mesh.isModelMesh()) // Don't count pieces that are not printed. { continue; } @@ -80,8 +79,7 @@ Point3LL MeshGroup::max() const Point3LL ret(std::numeric_limits::min(), std::numeric_limits::min(), std::numeric_limits::min()); for (const Mesh& mesh : meshes) { - if (mesh.settings_.get("infill_mesh") || mesh.settings_.get("cutting_mesh") - || mesh.settings_.get("anti_overhang_mesh")) // Don't count pieces that are not printed. + if (! mesh.isModelMesh()) // Don't count pieces that are not printed. { continue; } diff --git a/src/MeshMaterialSplitter.cpp b/src/MeshMaterialSplitter.cpp index 4220aa0c9c..d4cc3114c2 100644 --- a/src/MeshMaterialSplitter.cpp +++ b/src/MeshMaterialSplitter.cpp @@ -41,7 +41,7 @@ struct MeshGeneratorData { const Mesh& mesh; //!< The mesh for which a material modifier is to be generated AABB3D bounding_box; //!< The bounding box of the mesh - size_t estimated_iterations; //!< The roughly estimated number of voxels-propagation iterations (for progress reporting) + size_t estimated_material_iterations{ 0 }; //!< The roughly estimated number of multi-material voxels-propagation iterations (for progress reporting) coord_t depth; //!< The propagation depth retrieved from the mesh settings coord_t resolution; //!< The points cloud resolution retrieved from the mesh settings }; @@ -55,7 +55,7 @@ union ContourKey struct { uint16_t z; - uint8_t extruder; + uint8_t feature_value; // Either extruder number of support preference uint8_t black_hole{ 0 }; // Don't place anything in there, or it would be lost forever (it exists only to properly set the 4th byte of the key) } definition; }; @@ -74,21 +74,22 @@ bool operator==(const ContourKey& key1, const ContourKey& key2) * Fills the given voxels grid by setting an occupation everywhere the triangles of the mesh cross voxels. The extruder number is set according to the texture data * @param mesh The mesh to fill the voxels grid with * @param texture_data_provider The provider containing the painted texture data + * @param texture_feature The name of the feature to be looked for in the texture * @param voxel_grid The voxels grid to be filled with mesh data - * @param mesh_extruder_nr The main mesh extruder number - * @return True if this generated relevant data for multi-extruder, otherwise this means the mesh is completely filled with only extruder 0 and there is no need to go further on - * trying to calculate the modified meshes. + * @param default_value The default value to be used when the value retrieved in the texture doesn't belong to authorized_values (e.g. a disabled extruder number) + * @param authorized_values The list of authorized values that can be found in the texture data + * @return The set of unique values actually placed in the voxels grid. This helps to determine whether it actually contains multiple values and should be further processed. */ -bool makeVoxelGridFromTexture(const Mesh& mesh, const std::shared_ptr& texture_data_provider, VoxelGrid& voxel_grid, const uint8_t mesh_extruder_nr) +boost::concurrent_flat_set makeVoxelGridFromTexture( + const Mesh& mesh, + const std::shared_ptr& texture_data_provider, + const std::string& texture_feature, + VoxelGrid& voxel_grid, + const uint8_t default_value, + const std::unordered_set& authorized_values) { spdlog::stopwatch timer; - - boost::concurrent_flat_set found_extruders; - std::unordered_set active_extruders; - for (const ExtruderTrain& extruder : Application::getInstance().current_slice_->scene.extruders) - { - active_extruders.insert(extruder.extruder_nr_); - } + boost::concurrent_flat_set found_values; cura::parallel_for( mesh.faces_, @@ -121,53 +122,45 @@ bool makeVoxelGridFromTexture(const Mesh& mesh, const std::shared_ptr pixel = texture_data_provider->getTexture()->getPixelCoordinates(Point2F(point_uv_coords.x_, point_uv_coords.y_)); - std::optional extruder_nr = texture_data_provider->getValue(std::get<0>(pixel), std::get<1>(pixel), "extruder"); - if (! extruder_nr.has_value() || ! active_extruders.contains(extruder_nr.value())) + std::optional texture_value = texture_data_provider->getValue(std::get<0>(pixel), std::get<1>(pixel), texture_feature); + if (! texture_value.has_value() || ! authorized_values.contains(texture_value.value())) { - extruder_nr = mesh_extruder_nr; + texture_value = default_value; } - voxel_grid.setOrUpdateOccupation(traversed_voxel, extruder_nr.value()); - found_extruders.insert(extruder_nr.value()); + voxel_grid.setOrUpdateOccupation(traversed_voxel, texture_value.value()); + found_values.insert(texture_value.value()); } }); - if (found_extruders.size() == 1) - { - // We have found only one extruder in the texture, so return true only if this extruder is not the mesh extruder, otherwise the rest is useless - bool is_specific_extruder = true; - found_extruders.visit_all( - [&is_specific_extruder, &mesh_extruder_nr](const uint8_t extruder_nr) - { - is_specific_extruder = extruder_nr != mesh_extruder_nr; - }); - return is_specific_extruder; - } - spdlog::info("Total voxel grid creation time from texture {}ms", timer.elapsed_ms().count()); - return true; + return found_values; } /*! - * Create modifier meshes from the given voxels grid, filled with the contours of the areas that should be processed by the different extruders. - * @param voxel_grid The voxels grid containing the extruder occupations - * @param mesh_extruder_nr The main mesh extruder number + * Create modifier meshes from the given voxels grid, filled with the contours of the areas that should be processed by the different valuess. + * @param voxel_grid The voxels grid containing the values occupations + * @param ignore_value The "default" value for which no mesh should be created + * @param mesh_settings The mesh settings to be applied to the generated meshes. If not provided, the occupation values will be considered as extruder number and we will + * use the settings of the associated extruder. + * @param is_hollow Indicates whether the occupied voxels have a hollow core and are surrounded by a solid shell of ignored values (which is the case for materials voxels grid) + * or if they just form a solid block themselves (which is the case for support voxels grid) * @return A list of modifier meshes to be registered * * This function works by treating each horizontal plane separately of the voxels grid. For each plane, we apply a marching squares algorithm in order to generate 2D polygons. * Then we just have to extrude those polygons vertically. The final mesh has no horizontal face, thus it is not watertight at all. However, since it will subsequently * be re-sliced on XY planes, this is good enough. */ -std::vector makeMeshesFromVoxelsGrid(const VoxelGrid& voxel_grid, const uint8_t mesh_extruder_nr) +std::map makeMeshesFromVoxelsGrid(const VoxelGrid& voxel_grid, const uint8_t ignore_value, const std::optional& mesh_settings, const bool is_hollow) { spdlog::debug("Make modifier meshes from voxels grid"); - // First, gather all positions that should be considered for the marching square, e.g. all that have a specific extruder and around them + // First, gather all positions that should be considered for the marching square, e.g. all that have a specific value and around them boost::concurrent_flat_set marching_squares; voxel_grid.visitOccupiedVoxels( - [&marching_squares, &mesh_extruder_nr](const auto& occupied_voxel) + [&marching_squares, &ignore_value](const auto& occupied_voxel) { - if (occupied_voxel.second != mesh_extruder_nr) + if (occupied_voxel.second != ignore_value) { marching_squares.insert(occupied_voxel.first); if (occupied_voxel.first.position.x > 0) @@ -220,56 +213,60 @@ std::vector makeMeshesFromVoxelsGrid(const VoxelGrid& voxel_grid, const ui #ifdef __cpp_lib_execution std::execution::par, #endif - [&voxel_grid, &raw_contours, &position_delta_center, &marching_segments, &mesh_extruder_nr](const VoxelGrid::LocalCoordinates square_start) + [&voxel_grid, &raw_contours, &position_delta_center, &marching_segments, &ignore_value, &is_hollow](const VoxelGrid::LocalCoordinates square_start) { const int32_t x_plus1 = static_cast(square_start.position.x) + 1; const bool x_plus1_valid = x_plus1 <= std::numeric_limits::max(); const int32_t y_plus1 = static_cast(square_start.position.y) + 1; const bool y_plus1_valid = y_plus1 <= std::numeric_limits::max(); - std::unordered_set filled_extruders; + std::unordered_set filled_values; std::array occupation_bits; - auto add_occupied_extruder = [&voxel_grid, - &mesh_extruder_nr, - &filled_extruders, - &occupation_bits, - &square_start](const int32_t x, const int32_t y, const bool position_valid, const size_t occupation_bit_index) -> void + auto add_occupied_value = [&voxel_grid, &ignore_value, &filled_values, &occupation_bits, &square_start, &is_hollow]( + const int32_t x, + const int32_t y, + const bool position_valid, + const size_t occupation_bit_index) -> void { if (position_valid) { const std::optional occupation = voxel_grid.getOccupation(VoxelGrid::LocalCoordinates(x, y, square_start.position.z)); if (occupation.has_value()) { - filled_extruders.insert(occupation.value()); + filled_values.insert(occupation.value()); occupation_bits[occupation_bit_index] = occupation.value(); return; } } - occupation_bits[occupation_bit_index] = mesh_extruder_nr; + if (! is_hollow) + { + filled_values.insert(ignore_value); + } + occupation_bits[occupation_bit_index] = ignore_value; }; - add_occupied_extruder(x_plus1, y_plus1, x_plus1_valid && y_plus1_valid, 0); - add_occupied_extruder(square_start.position.x, y_plus1, y_plus1_valid, 1); - add_occupied_extruder(x_plus1, square_start.position.y, x_plus1_valid, 2); - add_occupied_extruder(square_start.position.x, square_start.position.y, true, 3); + add_occupied_value(x_plus1, y_plus1, x_plus1_valid && y_plus1_valid, 0); + add_occupied_value(square_start.position.x, y_plus1, y_plus1_valid, 1); + add_occupied_value(x_plus1, square_start.position.y, x_plus1_valid, 2); + add_occupied_value(square_start.position.x, square_start.position.y, true, 3); - if (filled_extruders.size() < 2) + if (filled_values.size() < 2) { // Early-out, since this is not going to generate any segment return; } - for (const uint8_t extruder : filled_extruders) + for (const uint8_t value : filled_values) { - if (extruder == mesh_extruder_nr) + if (value == ignore_value) { continue; } // Apply the marching squares base principle: calculate the index of the segments list to be added according to the occupations of the 4 positions - const size_t segments_index = (occupation_bits[0] == extruder ? 1 : 0) + ((occupation_bits[1] == extruder ? 1 : 0) << 1) - + ((occupation_bits[2] == extruder ? 1 : 0) << 2) + ((occupation_bits[3] == extruder ? 1 : 0) << 3); + const size_t segments_index = (occupation_bits[0] == value ? 1 : 0) + ((occupation_bits[1] == value ? 1 : 0) << 1) + ((occupation_bits[2] == value ? 1 : 0) << 2) + + ((occupation_bits[3] == value ? 1 : 0) << 3); OpenLinesSet translated_segments = marching_segments[segments_index]; if (translated_segments.empty()) @@ -291,7 +288,7 @@ std::vector makeMeshesFromVoxelsGrid(const VoxelGrid& voxel_grid, const ui // And finally, add the segments to the proper Z-plane and extruder raw_contours.emplace_or_visit( - std::make_pair(ContourKey{ .definition = { square_start.position.z, extruder } }, Contour{ .segments = translated_segments }), + std::make_pair(ContourKey{ .definition = { square_start.position.z, value } }, Contour{ .segments = translated_segments }), [&translated_segments](auto& plane_contour) { plane_contour.second.segments.push_back(translated_segments); @@ -320,10 +317,10 @@ std::vector makeMeshesFromVoxelsGrid(const VoxelGrid& voxel_grid, const ui #ifdef __cpp_lib_execution std::execution::par, #endif - [&simplifier, &voxel_grid, &meshes, &mutex](const auto& contour) + [&simplifier, &voxel_grid, &meshes, &mutex, &mesh_settings](const auto& contour) { const uint16_t z = contour.first.definition.z; - const uint8_t extruder = contour.first.definition.extruder; + const uint8_t feature_value = contour.first.definition.feature_value; const coord_t z_low = voxel_grid.toGlobalZ(z, false); const coord_t z_high = voxel_grid.toGlobalZ(z + 1, false); @@ -333,16 +330,14 @@ std::vector makeMeshesFromVoxelsGrid(const VoxelGrid& voxel_grid, const ui for (const Polygon& simplified_polygon : simplified_polygons) { const std::lock_guard lock(mutex); - const auto mesh_iterator = meshes.find(extruder); + const auto mesh_iterator = meshes.find(feature_value); if (mesh_iterator == meshes.end()) { - Mesh mesh(Application::getInstance().current_slice_->scene.extruders.at(extruder).settings_); - mesh.settings_.add("cutting_mesh", "true"); - mesh.settings_.add("extruder_nr", std::to_string(extruder)); - meshes.insert({ extruder, mesh }); + const Settings settings = mesh_settings.value_or(Application::getInstance().current_slice_->scene.extruders[feature_value].settings_); + meshes.insert({ feature_value, Mesh(settings) }); } - Mesh& mesh = meshes[extruder]; + Mesh& mesh = meshes[feature_value]; for (auto iterator = simplified_polygon.beginSegments(); iterator != simplified_polygon.endSegments(); ++iterator) { const Point2LL& start = (*iterator).start; @@ -353,12 +348,50 @@ std::vector makeMeshesFromVoxelsGrid(const VoxelGrid& voxel_grid, const ui } }); - std::vector meshes_vec; - for (Mesh& mesh : meshes | ranges::views::values) + return meshes; +} + +/*! + * Apply the proper settings to make the given meshes actual material mesh modifiers + * @param meshes The list of meshes, indexed by extruder to be used + * @return The meshes containing the proper settings that identify them as extruder modifier meshes + */ +std::vector applyMeshExtruders(std::map& meshes) +{ + std::vector output_meshes; + for (auto& [extruder_nr, mesh] : meshes) + { + mesh.settings_.add("cutting_mesh", "true"); + mesh.settings_.add("extruder_nr", std::to_string(extruder_nr)); + output_meshes.push_back(std::move(mesh)); + } + + return output_meshes; +} + +/*! + * Apply the proper settings to make the given meshes actual support mesh modifiers + * @param meshes The list of meshes, indexed by support value + * @return The meshes containing the proper settings that identify them as support modifier meshes + */ +std::vector applyMeshSupport(std::map& meshes) +{ + std::vector output_meshes; + for (auto& [support_value, mesh] : meshes) { - meshes_vec.push_back(std::move(mesh)); + if (support_value == 1) + { + mesh.settings_.add("force_support_overhang_mesh", "true"); + output_meshes.push_back(std::move(mesh)); + } + else if (support_value == 2) + { + mesh.settings_.add("anti_overhang_mesh", "true"); + output_meshes.push_back(std::move(mesh)); + } } - return meshes_vec; + + return output_meshes; } /*! @@ -457,7 +490,7 @@ boost::concurrent_flat_set * @param voxel_grid The voxel grid to be filled * @param voxels_to_evaluate The voxels to be evaluated * @param texture_data The lookup containing the rasterized texture data - * @param sliced_mesh The pre-sliced mesh matching the voxel grid + * @param sliced_mesh The pre-sliced mesh matching the voxel grid, or an empty list to skip the insideness check * @param depth_squared The maximum depth, squared * @param mesh_extruder_nr The main mesh extruder number */ @@ -477,7 +510,7 @@ void evaluateVoxels( { const Point3D position = voxel_grid.toGlobalCoordinates(voxel_to_evaluate); - if (! isInside(voxel_grid, voxel_to_evaluate, sliced_mesh)) + if (! sliced_mesh.empty() && ! isInside(voxel_grid, voxel_to_evaluate, sliced_mesh)) { voxel_grid.setOccupation(voxel_to_evaluate, mesh_extruder_nr); } @@ -583,7 +616,7 @@ void propagateVoxels( * @param total_estimated_iterations The total number of iterations to be processed, for progress reporting * @return A list of modifier meshes to be added to the slicing process */ -std::vector makeModifierMeshes( +std::vector makeMaterialModifierMeshes( const MeshGeneratorData& mesh_data, const std::shared_ptr& texture_data_provider, const size_t delta_iterations, @@ -600,7 +633,26 @@ std::vector makeModifierMeshes( // Create the voxel grid and initially fill it with the rasterized mesh triangles, which will be used as spatial reference for the texture data VoxelGrid voxel_grid(bounding_box, mesh_data.resolution); - if (! makeVoxelGridFromTexture(mesh_data.mesh, texture_data_provider, voxel_grid, mesh_extruder_nr)) + + std::unordered_set active_extruders; + for (const ExtruderTrain& extruder : Application::getInstance().current_slice_->scene.extruders) + { + active_extruders.insert(extruder.extruder_nr_); + } + + const boost::concurrent_flat_set found_extruders + = makeVoxelGridFromTexture(mesh_data.mesh, texture_data_provider, "extruder", voxel_grid, mesh_extruder_nr, active_extruders); + bool texture_contains_multimaterial_data = true; + if (found_extruders.size() == 1) + { + // We have found only one extruder in the texture, so return true only if this extruder is not the mesh extruder, otherwise the rest is useless + found_extruders.visit_all( + [&texture_contains_multimaterial_data, &mesh_extruder_nr](const uint8_t extruder_nr) + { + texture_contains_multimaterial_data = extruder_nr != mesh_extruder_nr; + }); + } + if (! texture_contains_multimaterial_data) { // Texture is filled with the main extruder, don't bother doing anything return {}; @@ -628,7 +680,7 @@ std::vector makeModifierMeshes( propagateVoxels( voxel_grid, previously_evaluated_voxels, - mesh_data.estimated_iterations, + mesh_data.estimated_material_iterations, sliced_mesh, texture_data, depth_squared, @@ -636,7 +688,79 @@ std::vector makeModifierMeshes( delta_iterations, total_estimated_iterations); - return makeMeshesFromVoxelsGrid(voxel_grid, mesh_extruder_nr); + const std::optional mesh_settings = std::nullopt; + constexpr bool is_hollow = true; + std::map meshes = makeMeshesFromVoxelsGrid(voxel_grid, mesh_extruder_nr, mesh_settings, is_hollow); + return applyMeshExtruders(meshes); +} + +/*! + * Generate a modifier mesh for every part that has some user-painted texture data + * @param mesh The mesh to be processed + * @param mesh_bounding_box The pre-calculated bounding box of the mesh + * @param texture_data_provider The provider containing the texture painted data + * @return A list of modifier meshes to be added to the slicing process + */ +std::vector makeSupportModifierMeshes(const Mesh& mesh, const AABB3D& mesh_bounding_box, const std::shared_ptr& texture_data_provider) +{ + const Settings& settings = mesh.settings_; + const auto resolution = settings.get("support_paint_resolution"); + constexpr uint8_t ignore_value = 0; + const std::unordered_set& authorized_values{ 1, 2 }; + + // Give some more space around the mesh to expand the voxels grid + AABB3D expanded_bounding_box = mesh_bounding_box; + expanded_bounding_box.expand(resolution * 2); + + // Create the voxel grid and initially fill it with the rasterized mesh triangles + VoxelGrid voxel_grid(expanded_bounding_box, resolution); + const boost::concurrent_flat_set found_support_values = makeVoxelGridFromTexture(mesh, texture_data_provider, "support", voxel_grid, ignore_value, authorized_values); + if (found_support_values.empty() || (found_support_values.size() == 1 && found_support_values.contains(ignore_value))) + { + // Texture is filled with only automatic support, don't bother doing anything + return {}; + } + + // At this point, the voxel grid contains a 1-voxel-wide version of the textured mesh. In order to make proper modifier meshes that will cover the painted area, we will expand + // it by 1 voxel in all direction, so it will end up being 3-voxels wide + + // Create the lookup structure that will help us expand only where necessary + const SpatialLookup texture_data = SpatialLookup::makeSpatialLookupFromVoxelGrid(voxel_grid); + + // Extract the voxels have an actual support value. Others don't need to be expanded since they won't generate a mesh. + boost::concurrent_flat_set valued_voxels; + voxel_grid.visitOccupiedVoxels( + [&valued_voxels, &ignore_value](const auto& voxel) + { + if (voxel.second != ignore_value) + { + valued_voxels.insert(voxel.first); + } + }); + + // Now get all the voxels around the valued voxels that have no occupation yet + const boost::concurrent_flat_set outer_voxels = findVoxelsToEvaluate(voxel_grid, valued_voxels); + + // Finally, evaluate all the voxels around and set their proper occupation + outer_voxels.visit_all( +#ifdef __cpp_lib_execution + std::execution::par, +#endif + [&voxel_grid, &texture_data, &ignore_value](const VoxelGrid::LocalCoordinates& voxel_to_evaluate) + { + const Point3D position = voxel_grid.toGlobalCoordinates(voxel_to_evaluate); + + // Find the nearest neighbor + const std::optional nearest_occupation = texture_data.findClosestOccupation(position); + if (nearest_occupation.has_value() && nearest_occupation.value().occupation != ignore_value) + { + voxel_grid.setOccupation(voxel_to_evaluate, nearest_occupation.value().occupation); + } + }); + + constexpr bool is_hollow = false; + std::map meshes = makeMeshesFromVoxelsGrid(voxel_grid, ignore_value, mesh.settings_, is_hollow); + return applyMeshSupport(meshes); } /*! @@ -650,7 +774,14 @@ std::vector makeInitialMeshesGenerationData(const MeshGroup* for (const Mesh& mesh : meshgroup->meshes) { - if (mesh.texture_ == nullptr || mesh.texture_data_mapping_ == nullptr || ! mesh.texture_data_mapping_->contains("extruder")) + if (mesh.texture_ == nullptr || mesh.texture_data_mapping_ == nullptr) + { + continue; + } + + const bool has_extruder_data = mesh.texture_data_mapping_->contains("extruder"); + const bool has_support_data = mesh.texture_data_mapping_->contains("support"); + if (! has_extruder_data && ! has_support_data) { continue; } @@ -665,11 +796,15 @@ std::vector makeInitialMeshesGenerationData(const MeshGroup* mesh_data.bounding_box.include(vertex.p_); } - // Make a rough estimation of the max number of iterations, by calculating how deep we may propagate inside the mesh - const double bounding_box_max_depth = std::max({ mesh_data.bounding_box.spanX() / 2.0, mesh_data.bounding_box.spanY() / 2.0, mesh_data.bounding_box.spanZ() / 2.0 }); - const double estimated_min_depth = std::min(static_cast(mesh_data.depth), bounding_box_max_depth); - mesh_data.estimated_iterations = estimated_min_depth / mesh_data.resolution; - spdlog::debug("Estimated {} iterations for {}", mesh_data.estimated_iterations, mesh.mesh_name_); + if (has_extruder_data) + { + // Make a rough estimation of the max number of iterations, by calculating how deep we may propagate inside the mesh + const double bounding_box_max_depth = std::max({ mesh_data.bounding_box.spanX() / 2.0, mesh_data.bounding_box.spanY() / 2.0, mesh_data.bounding_box.spanZ() / 2.0 }); + const double estimated_min_depth = std::min(static_cast(mesh_data.depth), bounding_box_max_depth); + mesh_data.estimated_material_iterations = estimated_min_depth / mesh_data.resolution; + } + + spdlog::debug("Estimated {} iterations for {}", mesh_data.estimated_material_iterations, mesh.mesh_name_); result.push_back(mesh_data); } @@ -677,7 +812,7 @@ std::vector makeInitialMeshesGenerationData(const MeshGroup* return result; } -void makeMaterialModifierMeshes(MeshGroup* meshgroup) +void makePaintingModifierMeshes(MeshGroup* meshgroup) { const std::vector mesh_generation_data = makeInitialMeshesGenerationData(meshgroup); size_t delta_iterations = 0; @@ -686,7 +821,7 @@ void makeMaterialModifierMeshes(MeshGroup* meshgroup) 0, [](const size_t total_iterations, const MeshGeneratorData& mesh_data) { - return total_iterations + mesh_data.estimated_iterations; + return total_iterations + mesh_data.estimated_material_iterations; }); std::vector modifier_meshes; @@ -694,16 +829,21 @@ void makeMaterialModifierMeshes(MeshGroup* meshgroup) { const Mesh& mesh = mesh_data.mesh; const spdlog::stopwatch timer; - spdlog::info("Start multi-material mesh generation for {}", mesh.mesh_name_); + spdlog::info("Start painting mesh-modifier generation for {}", mesh.mesh_name_); const auto texture_data_provider = std::make_shared(nullptr, mesh.texture_, mesh.texture_data_mapping_); - for (const Mesh& modifier_mesh : makeModifierMeshes(mesh_data, texture_data_provider, delta_iterations, total_estimated_iterations)) + for (const Mesh& modifier_mesh : makeMaterialModifierMeshes(mesh_data, texture_data_provider, delta_iterations, total_estimated_iterations)) + { + modifier_meshes.push_back(std::move(modifier_mesh)); + } + + for (const Mesh& modifier_mesh : makeSupportModifierMeshes(mesh_data.mesh, mesh_data.bounding_box, texture_data_provider)) { modifier_meshes.push_back(std::move(modifier_mesh)); } - delta_iterations += mesh_data.estimated_iterations; - spdlog::info("Multi-material mesh generation for {} took {} seconds", mesh.mesh_name_, timer.elapsed().count()); + delta_iterations += mesh_data.estimated_material_iterations; + spdlog::info("Painting mesh-modifier generation for {} took {} seconds", mesh.mesh_name_, timer.elapsed().count()); } // Add meshes to group afterwards to avoid re-allocating the meshes in the vector diff --git a/src/Scene.cpp b/src/Scene.cpp index ead01544e8..959c0d0bee 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -72,7 +72,7 @@ void Scene::processMeshGroup(MeshGroup& mesh_group) bool empty = true; for (Mesh& mesh : mesh_group.meshes) { - if (! mesh.settings_.get("infill_mesh") && ! mesh.settings_.get("anti_overhang_mesh")) + if (mesh.isModelMesh()) { empty = false; break; diff --git a/src/TreeModelVolumes.cpp b/src/TreeModelVolumes.cpp index 769f3acea9..d9a746a1e2 100644 --- a/src/TreeModelVolumes.cpp +++ b/src/TreeModelVolumes.cpp @@ -49,7 +49,6 @@ TreeModelVolumes::TreeModelVolumes( coord_t min_maximum_deviation = std::numeric_limits::max(); coord_t min_maximum_area_deviation = std::numeric_limits::max(); - support_rests_on_model_ = false; for (auto [mesh_idx, mesh_ptr] : storage.meshes | ranges::views::enumerate) { auto& mesh = *mesh_ptr; @@ -576,7 +575,7 @@ Shape TreeModelVolumes::extractOutlineFromMesh(const SliceMeshStorage& mesh, Lay constexpr bool external_polys_only = false; Shape total; - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) + if (! mesh.isModelMesh()) { return Shape(); } diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 2927cba71e..334ce69beb 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include "progress/Progress.h" #include "settings/EnumSettings.h" #include "support.h" //For precomputeCrossInfillTree +#include "utils/OBJ.h" #include "utils/Simplify.h" #include "utils/ThreadPool.h" #include "utils/algorithm.h" @@ -53,7 +55,7 @@ TreeSupport::TreeSupport(const SliceDataStorage& storage) for (auto [mesh_idx, mesh_ptr] : storage.meshes | ranges::views::enumerate) { SliceMeshStorage& mesh = *mesh_ptr; - const bool non_supportable_mesh = mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh") || mesh.settings.get("support_mesh"); + const bool non_supportable_mesh = ! mesh.isModelMesh() || mesh.settings.get("support_mesh"); if (mesh.settings.get("support_structure") != ESupportStructure::TREE || ! mesh.settings.get("support_enable") || non_supportable_mesh) { continue; @@ -1936,11 +1938,28 @@ void TreeSupport::dropNonGraciousAreas( && ! elem->to_buildplate_; // If an element has no child, it connects to whatever is below as no support further down for it will exist. if (non_gracious_model_contact) { + std::vector>& dropped_down_area = dropped_down_areas[idx]; Shape rest_support = layer_tree_polygons[linear_data[idx].first][elem].intersection(volumes_.getAccumulatedPlaceable0(linear_data[idx].first)); - for (LayerIndex counter = 1; rest_support.area() > 1 && counter < linear_data[idx].first; ++counter) + for (LayerIndex counter = 1; rest_support.area() > 1 && counter < linear_data[idx].first && ! rest_support.empty(); ++counter) { - rest_support = rest_support.difference(volumes_.getCollision(0, linear_data[idx].first - counter)); - dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support); + const LayerIndex layer_index = linear_data[idx].first - counter; + rest_support = rest_support.difference(volumes_.getCollision(0, layer_index)); + for (const Shape& layer_tree_polygon : layer_tree_polygons[layer_index] | ranges::views::values) + { + // Remove parts of the support that are absorbed by the actual trees + rest_support = rest_support.difference(layer_tree_polygon.offset(-EPSILON)); + } + dropped_down_area.emplace_back(layer_index, rest_support); + } + + if (! rest_support.empty()) + { + // If there is some remainder after dropping the area to the bottom, that means it could not actually reach the model or another branch, so discard the piece + rest_support = rest_support.offset(EPSILON); + for (Shape& dropped_area_on_layer : dropped_down_area | ranges::views::values) + { + dropped_area_on_layer = dropped_area_on_layer.difference(rest_support); + } } } }); @@ -2457,4 +2476,17 @@ void TreeSupport::drawAreas(std::vector>& move_bou dur_finalize); } +void TreeSupport::saveToObj(const std::vector>& move_bounds, OBJ& obj) const +{ + for (const auto [layer_index, elements] : move_bounds | ranges::views::enumerate) + { + const coord_t z = layer_index * config.layer_height; + for (const TreeSupportElement* element : elements) + { + element->saveToObj(obj, z, config.layer_height); + obj.writeSphere(Point3D(Point3LL(element->result_on_layer_, z), 1.0), config.layer_height / 2.0, SVG::Color::RED); + } + } +} + } // namespace cura diff --git a/src/TreeSupportElement.cpp b/src/TreeSupportElement.cpp new file mode 100644 index 0000000000..5b6bcb08da --- /dev/null +++ b/src/TreeSupportElement.cpp @@ -0,0 +1,240 @@ +// Copyright (c) 2026 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#include "TreeSupportElement.h" + +#include "utils/OBJ.h" + + +namespace cura +{ + +TreeSupportElement::TreeSupportElement( + coord_t distance_to_top, + size_t target_height, + Point2LL target_position, + bool to_buildplate, + bool to_model_gracious, + bool use_min_xy_dist, + size_t dont_move_until, + bool supports_roof, + bool can_use_safe_radius, + bool force_tips_to_roof, + bool skip_ovalisation, + bool influence_area_limit_active, + coord_t influence_area_limit_range) + : target_height_(target_height) + , target_position_(target_position) + , next_position_(target_position) + , next_height_(target_height) + , effective_radius_height_(distance_to_top) + , to_buildplate_(to_buildplate) + , distance_to_top_(distance_to_top) + , area_(nullptr) + , result_on_layer_(target_position) + , increased_to_model_radius_(0) + , to_model_gracious_(to_model_gracious) + , buildplate_radius_increases_(0) + , use_min_xy_dist_(use_min_xy_dist) + , supports_roof_(supports_roof) + , dont_move_until_(dont_move_until) + , can_use_safe_radius_(can_use_safe_radius) + , last_area_increase_(AreaIncreaseSettings(AvoidanceType::FAST, 0, false, false, false, false)) + , missing_roof_layers_(force_tips_to_roof ? dont_move_until : 0) + , skip_ovalisation_(skip_ovalisation) + , all_tips_({ target_position }) + , influence_area_limit_active_(influence_area_limit_active) + , influence_area_limit_range_(influence_area_limit_range) +{ + RecreateInfluenceLimitArea(); +} + +TreeSupportElement::TreeSupportElement(const TreeSupportElement& elem, Shape* newArea) + : // copy constructor with possibility to set a new area + target_height_(elem.target_height_) + , target_position_(elem.target_position_) + , next_position_(elem.next_position_) + , next_height_(elem.next_height_) + , effective_radius_height_(elem.effective_radius_height_) + , to_buildplate_(elem.to_buildplate_) + , distance_to_top_(elem.distance_to_top_) + , area_(newArea != nullptr ? newArea : elem.area_) + , result_on_layer_(elem.result_on_layer_) + , increased_to_model_radius_(elem.increased_to_model_radius_) + , to_model_gracious_(elem.to_model_gracious_) + , buildplate_radius_increases_(elem.buildplate_radius_increases_) + , use_min_xy_dist_(elem.use_min_xy_dist_) + , supports_roof_(elem.supports_roof_) + , dont_move_until_(elem.dont_move_until_) + , can_use_safe_radius_(elem.can_use_safe_radius_) + , last_area_increase_(elem.last_area_increase_) + , missing_roof_layers_(elem.missing_roof_layers_) + , skip_ovalisation_(elem.skip_ovalisation_) + , all_tips_(elem.all_tips_) + , influence_area_limit_active_(elem.influence_area_limit_active_) + , influence_area_limit_range_(elem.influence_area_limit_range_) + , influence_area_limit_area_(elem.influence_area_limit_area_) +{ + parents_.insert(parents_.begin(), elem.parents_.begin(), elem.parents_.end()); +} + +TreeSupportElement::TreeSupportElement(TreeSupportElement* element_above) + : target_height_(element_above->target_height_) + , target_position_(element_above->target_position_) + , next_position_(element_above->next_position_) + , next_height_(element_above->next_height_) + , effective_radius_height_(element_above->effective_radius_height_) + , to_buildplate_(element_above->to_buildplate_) + , distance_to_top_(element_above->distance_to_top_ + 1) + , area_(element_above->area_) + , result_on_layer_(Point2LL(-1, -1)) + , // set to invalid as we are a new node on a new layer + increased_to_model_radius_(element_above->increased_to_model_radius_) + , to_model_gracious_(element_above->to_model_gracious_) + , buildplate_radius_increases_(element_above->buildplate_radius_increases_) + , use_min_xy_dist_(element_above->use_min_xy_dist_) + , supports_roof_(element_above->supports_roof_) + , dont_move_until_(element_above->dont_move_until_) + , can_use_safe_radius_(element_above->can_use_safe_radius_) + , last_area_increase_(element_above->last_area_increase_) + , missing_roof_layers_(element_above->missing_roof_layers_) + , skip_ovalisation_(false) + , all_tips_(element_above->all_tips_) + , influence_area_limit_active_(element_above->influence_area_limit_active_) + , influence_area_limit_range_(element_above->influence_area_limit_range_) + , influence_area_limit_area_(element_above->influence_area_limit_area_) +{ + parents_ = { element_above }; +} + +TreeSupportElement::TreeSupportElement( + const TreeSupportElement& first, + const TreeSupportElement& second, + size_t next_height, + Point2LL next_position, + coord_t increased_to_model_radius, + const std::function& getRadius, + double diameter_scale_bp_radius, + coord_t branch_radius, + double diameter_angle_scale_factor) + : next_position_(next_position) + , next_height_(next_height) + , area_(nullptr) + , increased_to_model_radius_(increased_to_model_radius) + , use_min_xy_dist_(first.use_min_xy_dist_ || second.use_min_xy_dist_) + , supports_roof_(first.supports_roof_ || second.supports_roof_) + , dont_move_until_(std::max(first.dont_move_until_, second.dont_move_until_)) + , can_use_safe_radius_(first.can_use_safe_radius_ || second.can_use_safe_radius_) + , missing_roof_layers_(std::min(first.missing_roof_layers_, second.missing_roof_layers_)) + , skip_ovalisation_(false) +{ + if (first.target_height_ > second.target_height_) + { + target_height_ = first.target_height_; + target_position_ = first.target_position_; + } + else + { + target_height_ = second.target_height_; + target_position_ = second.target_position_; + } + effective_radius_height_ = std::max(first.effective_radius_height_, second.effective_radius_height_); + distance_to_top_ = std::max(first.distance_to_top_, second.distance_to_top_); + + to_buildplate_ = first.to_buildplate_ && second.to_buildplate_; + to_model_gracious_ = first.to_model_gracious_ && second.to_model_gracious_; // valid as we do not merge non-gracious with gracious + + AddParents(first.parents_); + AddParents(second.parents_); + + buildplate_radius_increases_ = 0; + if (diameter_scale_bp_radius > 0) + { + const coord_t foot_increase_radius = std::abs( + std::max(getRadius(second.effective_radius_height_, second.buildplate_radius_increases_), getRadius(first.effective_radius_height_, first.buildplate_radius_increases_)) + - getRadius(effective_radius_height_, buildplate_radius_increases_)); + // 'buildplate_radius_increases' has to be recalculated, as when a smaller tree with a larger buildplate_radius_increases merge with a larger branch, + // the buildplate_radius_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. + buildplate_radius_increases_ = foot_increase_radius / (branch_radius * (diameter_scale_bp_radius - diameter_angle_scale_factor)); + } + + // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. + last_area_increase_ = AreaIncreaseSettings( + std::min(first.last_area_increase_.type_, second.last_area_increase_.type_), + std::min(first.last_area_increase_.increase_speed_, second.last_area_increase_.increase_speed_), + first.last_area_increase_.increase_radius_ || second.last_area_increase_.increase_radius_, + first.last_area_increase_.no_error_ || second.last_area_increase_.no_error_, + first.last_area_increase_.use_min_distance_ && second.last_area_increase_.use_min_distance_, + first.last_area_increase_.move_ || second.last_area_increase_.move_); + + all_tips_ = first.all_tips_; + all_tips_.insert(all_tips_.end(), second.all_tips_.begin(), second.all_tips_.end()); + influence_area_limit_range_ = std::max(first.influence_area_limit_range_, second.influence_area_limit_range_); + influence_area_limit_active_ = first.influence_area_limit_active_ || second.influence_area_limit_active_; + RecreateInfluenceLimitArea(); + if (first.to_buildplate_ != second.to_buildplate_) + { + setToBuildplateForAllParents(to_buildplate_); + } +} + +void TreeSupportElement::AddParents(const std::vector& adding) +{ + for (TreeSupportElement* ptr : adding) + { + parents_.emplace_back(ptr); + } +} + +void TreeSupportElement::RecreateInfluenceLimitArea() +{ + if (influence_area_limit_active_) + { + influence_area_limit_area_.clear(); + + for (Point2LL p : all_tips_) + { + Polygon circle; + for (Point2LL corner : TreeSupportBaseCircle::getBaseCircle()) + { + circle.push_back(p + corner * influence_area_limit_range_ / double(TreeSupportBaseCircle::base_radius)); + } + if (influence_area_limit_area_.empty()) + { + influence_area_limit_area_ = circle.offset(0); + } + else + { + influence_area_limit_area_ = influence_area_limit_area_.intersection(circle.offset(0)); + } + } + } +} + +void TreeSupportElement::setToBuildplateForAllParents(bool new_value) +{ + to_buildplate_ = new_value; + to_model_gracious_ |= new_value; + std::vector grandparents{ parents_ }; + while (! grandparents.empty()) + { + std::vector next_parents; + for (TreeSupportElement* grandparent : grandparents) + { + next_parents.insert(next_parents.end(), grandparent->parents_.begin(), grandparent->parents_.end()); + grandparent->to_buildplate_ = new_value; + grandparent->to_model_gracious_ |= new_value; // If we set to_buildplate to true, update to_model_gracious + } + grandparents = next_parents; + } +} + +void TreeSupportElement::saveToObj(OBJ& obj, const coord_t z, const coord_t layer_height) const +{ + if (area_) + { + obj.write(*area_, z, layer_height, SVG::Color::BLUE); + } +} + +} // namespace cura diff --git a/src/TreeSupportTipGenerator.cpp b/src/TreeSupportTipGenerator.cpp index e2094b855a..02ebdbd056 100644 --- a/src/TreeSupportTipGenerator.cpp +++ b/src/TreeSupportTipGenerator.cpp @@ -385,7 +385,7 @@ std::shared_ptr TreeSupportTipGenerator::generateCrossFi if (config_.support_pattern == EFillMethod::CROSS || config_.support_pattern == EFillMethod::CROSS_3D) { AABB3D aabb; - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) + if (! mesh.isModelMesh()) { spdlog::warn("Tree support tried to generate a CrossFillProvider for a non model mesh."); return nullptr; diff --git a/src/mesh.cpp b/src/mesh.cpp index 3dae7f3fbf..012f25d6c8 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -23,17 +23,13 @@ static inline uint32_t pointHash(const Point3LL& p) ^ (((p.z_ + vertex_meld_distance / 2) / vertex_meld_distance) << 20); } -Mesh::Mesh(Settings& parent) +Mesh::Mesh(const Settings& parent) : settings_(parent) - , has_disconnected_faces(false) - , has_overlapping_faces(false) { } -Mesh::Mesh() - : settings_() - , has_disconnected_faces(false) - , has_overlapping_faces(false) +Mesh::Mesh(Settings&& parent) + : settings_(std::move(parent)) { } @@ -121,12 +117,13 @@ void Mesh::transform(const Matrix4x3D& transformation) bool Mesh::isPrinted() const { - return ! settings_.get("infill_mesh") && ! settings_.get("cutting_mesh") && ! settings_.get("anti_overhang_mesh"); + return ! settings_.get("cutting_mesh") && ! settings_.get("anti_overhang_mesh") + && (! settings_.has("force_support_overhang_mesh") || ! settings_.get("force_support_overhang_mesh")); } -bool Mesh::canInterlock() const +bool Mesh::isModelMesh() const { - return ! settings_.get("infill_mesh") && ! settings_.get("anti_overhang_mesh"); + return isPrinted() && ! settings_.get("infill_mesh"); } int Mesh::findIndexOfVertex(const Point3LL& v) diff --git a/src/multiVolumes.cpp b/src/multiVolumes.cpp index 6374a5b219..3fcb46e0ee 100644 --- a/src/multiVolumes.cpp +++ b/src/multiVolumes.cpp @@ -32,7 +32,7 @@ void carveMultipleVolumes(std::vector& volumes) for (unsigned int volume_1_idx = 1; volume_1_idx < volumes.size(); volume_1_idx++) { Slicer& volume_1 = *ranked_volumes[volume_1_idx]; - if (volume_1.mesh->settings_.get("infill_mesh") || volume_1.mesh->settings_.get("anti_overhang_mesh") || volume_1.mesh->settings_.get("support_mesh") + if (! volume_1.mesh->isModelMesh() || volume_1.mesh->settings_.get("support_mesh") || volume_1.mesh->settings_.get("magic_mesh_surface_mode") == ESurfaceMode::SURFACE) { continue; @@ -40,7 +40,7 @@ void carveMultipleVolumes(std::vector& volumes) for (unsigned int volume_2_idx = 0; volume_2_idx < volume_1_idx; volume_2_idx++) { Slicer& volume_2 = *ranked_volumes[volume_2_idx]; - if (volume_2.mesh->settings_.get("infill_mesh") || volume_2.mesh->settings_.get("anti_overhang_mesh") || volume_2.mesh->settings_.get("support_mesh") + if (! volume_2.mesh->isModelMesh() || volume_2.mesh->settings_.get("support_mesh") || volume_2.mesh->settings_.get("magic_mesh_surface_mode") == ESurfaceMode::SURFACE) { continue; @@ -81,8 +81,7 @@ void generateMultipleVolumesOverlap(std::vector& volumes) ClipperLib::PolyFillType fill_type = volume->mesh->settings_.get("meshfix_union_all") ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd; coord_t overlap = volume->mesh->settings_.get("multiple_mesh_overlap"); - if (volume->mesh->settings_.get("infill_mesh") || volume->mesh->settings_.get("anti_overhang_mesh") || volume->mesh->settings_.get("support_mesh") - || overlap == 0) + if (! volume->mesh->isModelMesh() || volume->mesh->settings_.get("support_mesh") || overlap == 0) { continue; } @@ -93,8 +92,8 @@ void generateMultipleVolumesOverlap(std::vector& volumes) Shape all_other_volumes; for (Slicer* other_volume : volumes) { - if (other_volume->mesh->settings_.get("infill_mesh") || other_volume->mesh->settings_.get("anti_overhang_mesh") - || other_volume->mesh->settings_.get("support_mesh") || ! other_volume->mesh->getAABB().hit(aabb) || other_volume == volume) + if (! other_volume->mesh->isModelMesh() || other_volume->mesh->settings_.get("support_mesh") || ! other_volume->mesh->getAABB().hit(aabb) + || other_volume == volume) { continue; } @@ -210,7 +209,7 @@ void MultiVolumes::carveCuttingMeshes(std::vector& volumes, std::vector { const Mesh& carved_mesh = meshes[carved_mesh_idx]; // Do not apply cutting_mesh for meshes which have settings (cutting_mesh, anti_overhang_mesh, support_mesh). - if (carved_mesh.settings_.get("cutting_mesh") || carved_mesh.settings_.get("anti_overhang_mesh") || carved_mesh.settings_.get("support_mesh")) + if (! carved_mesh.isPrinted() || carved_mesh.settings_.get("support_mesh")) { continue; } diff --git a/src/settings/AdaptiveLayerHeights.cpp b/src/settings/AdaptiveLayerHeights.cpp index 199bee2517..fd772857dd 100644 --- a/src/settings/AdaptiveLayerHeights.cpp +++ b/src/settings/AdaptiveLayerHeights.cpp @@ -190,8 +190,8 @@ void AdaptiveLayerHeights::calculateMeshTriangleSlopes() // loop over all mesh faces (triangles) and find their slopes for (const Mesh& mesh : Application::getInstance().current_slice_->scene.current_mesh_group->meshes) { - // Skip meshes that are not printable - if (mesh.settings_.get("infill_mesh") || mesh.settings_.get("cutting_mesh") || mesh.settings_.get("anti_overhang_mesh")) + // Skip meshes that are not regular models + if (! mesh.isModelMesh()) { continue; } diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp index d2a1ef5356..0ddf81b90b 100644 --- a/src/settings/Settings.cpp +++ b/src/settings/Settings.cpp @@ -891,7 +891,7 @@ bool Settings::has(const std::string& key, const bool parent_lookup) const return false; } -void Settings::setParent(Settings* new_parent) +void Settings::setParent(const Settings* new_parent) { parent = new_parent; } diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index 6f2e0981ee..0b2d9e017a 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -102,14 +102,15 @@ SliceMeshStorage::SliceMeshStorage(Mesh* mesh, const size_t slice_layer_count) , base_subdiv_cube(nullptr) , cross_fill_provider(nullptr) , lightning_generator(nullptr) + , is_printed_(mesh->isPrinted()) + , is_model_mesh_(mesh->isModelMesh()) { layers.resize(slice_layer_count); } - bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr) const { - if (settings.get("anti_overhang_mesh") || settings.get("support_mesh")) + if (! is_printed_ || settings.get("support_mesh")) { // object is not printed as object, but as support. return false; } @@ -164,7 +165,7 @@ bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr, const LayerIn { return false; } - if (settings.get("anti_overhang_mesh") || settings.get("support_mesh")) + if (! is_printed_ || settings.get("support_mesh")) { // object is not printed as object, but as support. return false; } @@ -250,7 +251,12 @@ bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr, const LayerIn bool SliceMeshStorage::isPrinted() const { - return ! settings.get("infill_mesh") && ! settings.get("cutting_mesh") && ! settings.get("anti_overhang_mesh"); + return is_printed_; +} + +bool SliceMeshStorage::isModelMesh() const +{ + return is_model_mesh_; } Point2LL SliceMeshStorage::getZSeamHint() const @@ -363,8 +369,7 @@ Shape SliceDataStorage::getLayerOutlines( { for (const std::shared_ptr& mesh : meshes) { - if (mesh->settings.get("infill_mesh") || mesh->settings.get("anti_overhang_mesh") - || (extruder_nr != -1 && extruder_nr != int(mesh->settings.get("wall_0_extruder_nr").extruder_nr_))) + if (! mesh->isModelMesh() || (extruder_nr != -1 && extruder_nr != int(mesh->settings.get("wall_0_extruder_nr").extruder_nr_))) { continue; } diff --git a/src/slicer.cpp b/src/slicer.cpp index 12c699d34f..cf343053e0 100644 --- a/src/slicer.cpp +++ b/src/slicer.cpp @@ -1049,8 +1049,7 @@ void Slicer::makePolygons(Mesh& mesh, SlicingTolerance slicing_tolerance, std::v } size_t layer_apply_initial_xy_offset = 0; - if (layers.size() > 0 && layers[0].polygons_.size() == 0 && ! mesh.settings_.get("support_mesh") && ! mesh.settings_.get("anti_overhang_mesh") - && ! mesh.settings_.get("cutting_mesh") && ! mesh.settings_.get("infill_mesh")) + if (layers.size() > 0 && layers[0].polygons_.size() == 0 && ! mesh.settings_.get("support_mesh") && mesh.isModelMesh()) { layer_apply_initial_xy_offset = 1; } diff --git a/src/support.cpp b/src/support.cpp index 3e59e05914..5232651a3d 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -44,35 +44,61 @@ namespace cura bool AreaSupport::handleSupportModifierMesh(SliceDataStorage& storage, const Settings& mesh_settings, const Slicer* slicer) { - if (! mesh_settings.get("anti_overhang_mesh") && ! mesh_settings.get("support_mesh")) + const bool is_anti_overhang_mesh = mesh_settings.get("anti_overhang_mesh"); + const bool is_support_mesh = mesh_settings.get("support_mesh"); + const bool is_force_support_overhang_mesh = mesh_settings.has("force_support_overhang_mesh") && mesh_settings.get("force_support_overhang_mesh"); + if (! is_anti_overhang_mesh && ! is_support_mesh && ! is_force_support_overhang_mesh) { return false; } - enum ModifierType + + enum class ModifierType { ANTI_OVERHANG, SUPPORT_DROP_DOWN, - SUPPORT_VANILLA + SUPPORT_VANILLA, + FORCE_OVERHANG, }; - ModifierType modifier_type - = (mesh_settings.get("anti_overhang_mesh")) ? ANTI_OVERHANG : ((mesh_settings.get("support_mesh_drop_down")) ? SUPPORT_DROP_DOWN : SUPPORT_VANILLA); - for (LayerIndex layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++) + + ModifierType modifier_type; + if (is_anti_overhang_mesh) + { + modifier_type = ModifierType::ANTI_OVERHANG; + } + else if (is_force_support_overhang_mesh) + { + modifier_type = ModifierType::FORCE_OVERHANG; + } + else if (mesh_settings.get("support_mesh_drop_down")) + { + modifier_type = ModifierType::SUPPORT_DROP_DOWN; + } + else + { + modifier_type = ModifierType::SUPPORT_VANILLA; + } + + for (LayerIndex layer_nr = 0; layer_nr < slicer->layers.size(); ++layer_nr) { SupportLayer& support_layer = storage.support.supportLayers[layer_nr]; const SlicerLayer& slicer_layer = slicer->layers[layer_nr]; switch (modifier_type) { - case ANTI_OVERHANG: + case ModifierType::ANTI_OVERHANG: support_layer.anti_overhang.push_back(slicer_layer.polygons_); break; - case SUPPORT_DROP_DOWN: + case ModifierType::SUPPORT_DROP_DOWN: support_layer.support_mesh_drop_down.push_back(slicer_layer.polygons_); break; - case SUPPORT_VANILLA: + case ModifierType::SUPPORT_VANILLA: support_layer.support_mesh.push_back(slicer_layer.polygons_); break; + case ModifierType::FORCE_OVERHANG: + support_layer.force_overhang.push_back(slicer_layer.polygons_); + break; } } + return true; } @@ -597,7 +623,7 @@ void AreaSupport::generateOverhangAreas(SliceDataStorage& storage) for (std::shared_ptr& mesh_ptr : storage.meshes) { auto& mesh = *mesh_ptr; - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) + if (! mesh.isModelMesh()) { continue; } @@ -625,6 +651,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage) { SupportLayer& support_layer = storage.support.supportLayers[layer_nr]; support_layer.anti_overhang = support_layer.anti_overhang.unionPolygons(); + support_layer.force_overhang = support_layer.force_overhang.unionPolygons(); support_layer.support_mesh_drop_down = support_layer.support_mesh_drop_down.unionPolygons(); support_layer.support_mesh = support_layer.support_mesh.unionPolygons(); } @@ -641,8 +668,8 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage) const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { - SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) + const SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; + if (! mesh.isModelMesh()) { continue; } @@ -689,7 +716,7 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage) // handle support interface for (auto& mesh : storage.meshes) { - if (mesh->settings.get("infill_mesh") || mesh->settings.get("anti_overhang_mesh")) + if (! mesh->isModelMesh()) { continue; } @@ -720,7 +747,7 @@ void AreaSupport::precomputeCrossInfillTree(SliceDataStorage& storage) for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { const SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) + if (! mesh.isModelMesh()) { continue; } @@ -1492,6 +1519,11 @@ std::pair AreaSupport::computeBasicAndFullOverhang(const SliceData basic_overhang = basic_overhang.difference(merged_polygons); } + if (! support_layer.force_overhang.empty()) + { + basic_overhang = basic_overhang.unionPolygons(support_layer.force_overhang); + } + Shape overhang_extended = basic_overhang // +0.1mm for easier joining with support from layer above .offset(max_dist_from_lower_layer * layers_below + MM2INT(0.1)); diff --git a/src/utils/OBJ.cpp b/src/utils/OBJ.cpp index 339eb345ec..9858625d25 100644 --- a/src/utils/OBJ.cpp +++ b/src/utils/OBJ.cpp @@ -11,11 +11,12 @@ #include "mesh.h" #include "utils/Point3D.h" +#include "utils/VoxelGrid.h" namespace cura { -OBJ::OBJ(std::string filename, const double scale) +OBJ::OBJ(const std::string& filename, const double scale) : filename_(filename) , scale_(scale) { @@ -154,14 +155,15 @@ void OBJ::writeTriangle( triangles_.push_back(Triangle{ scalePosition(p0), scalePosition(p1), scalePosition(p2), color, uv0, uv1, uv2 }); } -void OBJ::writeMesh(const Mesh& mesh, const SVG::Color color) +void OBJ::write(const Mesh& mesh, const SVG::Color color) { + constexpr double scale = 1.0; for (const MeshFace& face : mesh.faces_) { writeTriangle( - Point3D(mesh.vertices_[face.vertex_index_[0]].p_), - Point3D(mesh.vertices_[face.vertex_index_[1]].p_), - Point3D(mesh.vertices_[face.vertex_index_[2]].p_), + Point3D(mesh.vertices_[face.vertex_index_[0]].p_, scale), + Point3D(mesh.vertices_[face.vertex_index_[1]].p_, scale), + Point3D(mesh.vertices_[face.vertex_index_[2]].p_, scale), color, face.uv_coordinates_[0], face.uv_coordinates_[1], @@ -169,6 +171,41 @@ void OBJ::writeMesh(const Mesh& mesh, const SVG::Color color) } } +void OBJ::write(const VoxelGrid& voxel_grid) +{ + const double radius = std::min({ voxel_grid.getResolution().x_, voxel_grid.getResolution().y_, voxel_grid.getResolution().z_ }) / 4.0; + std::mutex mutex; + + voxel_grid.visitOccupiedVoxels( + [this, &mutex, &voxel_grid, &radius](const auto& voxel) + { + std::lock_guard lock(mutex); + writeSphere(voxel_grid.toGlobalCoordinates(voxel.first), radius, static_cast(voxel.second), 2, 4); + }); +} + +void OBJ::write(const Polyline& polyline, const coord_t z, const coord_t height, const SVG::Color color) +{ + constexpr double scale = 1.0; + const Point3D extrusion(0.0, 0.0, height); + for (auto iterator = polyline.beginSegments(); iterator != polyline.endSegments(); ++iterator) + { + const Point3D start(Point3LL((*iterator).start, z), scale); + const Point3D end(Point3LL((*iterator).end, z), scale); + + writeTriangle(start, end, end + extrusion, color); + writeTriangle(start, end + extrusion, start + extrusion, color); + } +} + +void OBJ::write(const Shape& shape, const coord_t z, const coord_t height, const SVG::Color color) +{ + for (const Polygon& polygon : shape) + { + write(polygon, z, height, color); + } +} + Point3D OBJ::scalePosition(const Point3D& p) const { return p * scale_; diff --git a/src/utils/VoxelGrid.cpp b/src/utils/VoxelGrid.cpp index 3781646f24..16b24ef4e8 100644 --- a/src/utils/VoxelGrid.cpp +++ b/src/utils/VoxelGrid.cpp @@ -7,7 +7,6 @@ #include #include "utils/AABB3D.h" -#include "utils/OBJ.h" #include "utils/PlanarPolygon3LL.h" #include "utils/Segment3LL.h" @@ -210,18 +209,4 @@ std::vector VoxelGrid::getTraversedVoxels(const Tri return traversed_voxels; } -void VoxelGrid::saveToObj(const std::string& filename, const double scale) const -{ - OBJ obj(filename, scale); - const double radius = std::min({ resolution_.x_, resolution_.y_, resolution_.z_ }) / 4.0; - std::mutex mutex; - - visitOccupiedVoxels( - [this, &mutex, &obj, &radius](const auto& voxel) - { - std::lock_guard lock(mutex); - obj.writeSphere(toGlobalCoordinates(voxel.first), radius, static_cast(voxel.second), 2, 4); - }); -} - } // namespace cura