Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
65d6622
Prepare for paint-on-support
wawanbreton Oct 6, 2025
0269c86
Merge remote-tracking branch 'origin/5.11' into CURA-12580_paint-on-s…
wawanbreton Oct 16, 2025
945b11b
Basically working blocker mesh generation based on painting
wawanbreton Oct 17, 2025
4177843
Merge remote-tracking branch 'origin/main' into CURA-12580_paint-on-s…
wawanbreton Dec 19, 2025
506cc8b
Code cleaning
wawanbreton Dec 19, 2025
49cc4a1
Merge remote-tracking branch 'origin/main' into CURA-12580_paint-on-s…
wawanbreton Dec 19, 2025
5a20233
Fix marching squares
wawanbreton Dec 19, 2025
7a42944
Merge remote-tracking branch 'origin/main' into CURA-12580_paint-on-s…
wawanbreton Feb 2, 2026
92a5815
Naively enable support forcer
wawanbreton Feb 2, 2026
2e72ca0
Merge remote-tracking branch 'origin/main' into CURA-12580_paint-on-s…
wawanbreton Apr 1, 2026
d704011
Force support by painting using support mesh
wawanbreton Apr 1, 2026
1d6dfb3
Add a specific mesh modifier to apply painted support
wawanbreton Apr 2, 2026
38ae2fc
Apply clang-format
wawanbreton Apr 2, 2026
297de41
Do not estimate progress for support painting
wawanbreton Apr 2, 2026
90f0b84
Add documentation
wawanbreton Apr 2, 2026
3aafbc8
Restore proper mesh type testings
wawanbreton Apr 2, 2026
e8254e6
Apply clang-format
wawanbreton Apr 2, 2026
0525133
Restore proper mesh type testings
wawanbreton Apr 2, 2026
791929e
Refine documentation
wawanbreton Apr 2, 2026
4b83309
Rename variable to be more explicit
wawanbreton Apr 10, 2026
5bd228a
Merge branch '5.13' into CURA-12636_non-tree-support-inside-tree-support
HellAholic Apr 13, 2026
6328c18
Fix Mac build
wawanbreton Apr 13, 2026
ef59a0e
Merge branch 'main' into CURA-12580_paint-on-support
wawanbreton Apr 13, 2026
2a9aa76
Merge remote-tracking branch 'origin/main' into CURA-12580_paint-on-s…
wawanbreton Apr 15, 2026
ef31ac7
Move the voxel grid OBJ saving to the OBJ class
wawanbreton Apr 15, 2026
aa8c5b9
Merge remote-tracking branch 'origin/CURA-12636_non-tree-support-insi…
wawanbreton Apr 15, 2026
6b524b3
Fix missing support painted areas
wawanbreton Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions include/MeshMaterialSplitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion include/TreeModelVolumes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions include/TreeSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -310,6 +312,12 @@ class TreeSupport
*/
void drawAreas(std::vector<std::set<TreeSupportElement*>>& 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<std::set<TreeSupportElement*>>& move_bounds, OBJ& obj) const;

/*!
* \brief Settings with the indexes of meshes that use these settings.
*/
Expand Down
286 changes: 49 additions & 237 deletions include/TreeSupportElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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(
Expand All @@ -166,70 +77,52 @@ struct TreeSupportElement
const std::function<coord_t(size_t, double)>& 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<TreeSupportElement*>& 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
*/
Expand Down Expand Up @@ -357,87 +250,6 @@ struct TreeSupportElement
* \brief Additional locations that the tip should reach
*/
std::vector<Point2LL> 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<TreeSupportElement*>& 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<TreeSupportElement*> grandparents{ parents_ };
while (! grandparents.empty())
{
std::vector<TreeSupportElement*> 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
Expand Down
Loading
Loading