Skip to content
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
34 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
7a938ec
Merge remote-tracking branch 'origin/main' into CURA-13056_show_possi…
rburema Jun 3, 2026
1a2a9b4
When there's any support-paint, behave like support is on.
rburema Jun 3, 2026
87f7145
Apply clang-format
rburema Jun 3, 2026
e92d32f
Bump github actions versions
wawanbreton Jun 3, 2026
871c0db
Don't include (basic) overhang from angle when auto-support is off.
rburema Jun 3, 2026
90efc35
Merge branch 'main' into CURA-13056_show_possible_support_placement
wawanbreton Jun 3, 2026
a3db3c4
Rename support painted variable.
rburema Jun 4, 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 include/MeshGroup.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class MeshGroup

std::vector<Mesh> meshes;
Settings settings;
bool has_support_paint = false;
Comment thread
wawanbreton marked this conversation as resolved.
Outdated

Point3LL min() const; //! minimal corner of bounding box
Point3LL max() const; //! maximal corner of bounding box
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
24 changes: 9 additions & 15 deletions include/mesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ class Mesh
std::shared_ptr<Image> texture_;
std::shared_ptr<TextureDataMapping> texture_data_mapping_;

Mesh(Settings& parent);
Mesh();
Mesh(const Settings& parent);
Mesh(Settings&& parent);
Mesh() = default;

/*!
*
Expand Down Expand Up @@ -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.

/*!
Expand Down
4 changes: 2 additions & 2 deletions include/settings/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string, std::string> getFlattendSettings() const;

Expand All @@ -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.
Expand Down
12 changes: 9 additions & 3 deletions include/sliceDataStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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
*/
Expand Down
3 changes: 3 additions & 0 deletions include/utils/OBJ.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace cura
{

class Mesh;
class VoxelGrid;

class OBJ : NoCopy
{
Expand All @@ -41,6 +42,8 @@ class OBJ : NoCopy

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);
Expand Down
2 changes: 0 additions & 2 deletions include/utils/VoxelGrid.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,6 @@ class VoxelGrid
*/
std::vector<LocalCoordinates> getTraversedVoxels(const Triangle3LL& triangle) const;

void saveToObj(const std::string& filename, const double scale = 1.0) const;

private:
Point3D resolution_;
Point3D origin_;
Expand Down
22 changes: 12 additions & 10 deletions src/FffGcodeWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,8 @@ void FffGcodeWriter::setConfigRetractionAndWipe(SliceDataStorage& storage)

size_t FffGcodeWriter::getStartExtruder(const SliceDataStorage& storage) const
{
const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
const auto& mesh_group = Application::getInstance().current_slice_->scene.current_mesh_group;
const Settings& mesh_group_settings = mesh_group->settings;
const EPlatformAdhesion adhesion_type = mesh_group_settings.get<EPlatformAdhesion>("adhesion_type");
const int skirt_brim_extruder_nr = mesh_group_settings.get<int>("skirt_brim_extruder_nr");
const ExtruderTrain* skirt_brim_extruder = (skirt_brim_extruder_nr < 0) ? nullptr : &mesh_group_settings.get<ExtruderTrain&>("skirt_brim_extruder_nr");
Expand All @@ -405,7 +406,7 @@ size_t FffGcodeWriter::getStartExtruder(const SliceDataStorage& storage) const
}
else // No adhesion.
{
if (mesh_group_settings.get<bool>("support_enable") && mesh_group_settings.get<bool>("support_brim_enable"))
if ((mesh_group_settings.get<bool>("support_enable") || mesh_group->has_support_paint) && mesh_group_settings.get<bool>("support_brim_enable"))
Comment thread
wawanbreton marked this conversation as resolved.
Outdated
{
start_extruder_nr = mesh_group_settings.get<ExtruderTrain&>("support_infill_extruder_nr").extruder_nr_;
}
Expand Down Expand Up @@ -1151,8 +1152,7 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS
for (const std::shared_ptr<SliceMeshStorage>& mesh_ptr : storage.meshes)
{
const auto& mesh = *mesh_ptr;
if (layer_nr >= static_cast<int>(mesh.layers.size()) || mesh.settings.get<bool>("support_mesh") || mesh.settings.get<bool>("anti_overhang_mesh")
|| mesh.settings.get<bool>("cutting_mesh") || mesh.settings.get<bool>("infill_mesh"))
if (layer_nr >= static_cast<int>(mesh.layers.size()) || mesh.settings.get<bool>("support_mesh") || ! mesh.isModelMesh())
{
continue;
}
Expand Down Expand Up @@ -1705,7 +1705,7 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(const SliceMeshStorage&
return;
}

if (mesh.settings.get<bool>("anti_overhang_mesh") || mesh.settings.get<bool>("support_mesh"))
if (! mesh.isPrinted() || mesh.settings.get<bool>("support_mesh"))
{
return;
}
Expand Down Expand Up @@ -1775,7 +1775,7 @@ void FffGcodeWriter::addMeshLayerToGCode(
return;
}

if (mesh.settings.get<bool>("anti_overhang_mesh") || mesh.settings.get<bool>("support_mesh"))
if (! mesh.isPrinted() || mesh.settings.get<bool>("support_mesh"))
{
return;
}
Expand Down Expand Up @@ -2600,8 +2600,9 @@ FffGcodeWriter::InsetsPreprocessResult FffGcodeWriter::preProcessInsets(

// if support is enabled, add the support outlines also so we don't generate bridges over support

const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
if (mesh_group_settings.get<bool>("support_enable"))
const auto& mesh_group = Application::getInstance().current_slice_->scene.current_mesh_group;
const Settings& mesh_group_settings = mesh_group->settings;
if (mesh_group_settings.get<bool>("support_enable") || mesh_group->has_support_paint)
{
const coord_t z_distance_top = mesh.settings.get<coord_t>("support_top_distance");
const size_t z_distance_top_layers = (z_distance_top / layer_height) + 1;
Expand Down Expand Up @@ -3111,7 +3112,8 @@ void FffGcodeWriter::processTopBottom(
{
return;
}
const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
const auto& mesh_group = Application::getInstance().current_slice_->scene.current_mesh_group;
const Settings& mesh_group_settings = mesh_group->settings;

const size_t layer_nr = gcode_layer.getLayerNr();

Expand All @@ -3138,7 +3140,7 @@ void FffGcodeWriter::processTopBottom(
int support_layer_nr = -1;
const SupportLayer* support_layer = nullptr;

if (mesh_group_settings.get<bool>("support_enable"))
if (mesh_group_settings.get<bool>("support_enable") || mesh_group->has_support_paint)
{
const coord_t layer_height = mesh_config.inset0_config.getLayerThickness();
const coord_t z_distance_top = mesh.settings.get<coord_t>("support_top_distance");
Expand Down
17 changes: 10 additions & 7 deletions src/FffPolygonGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down Expand Up @@ -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<bool>("conical_overhang_enabled") && ! mesh.settings_.get<bool>("anti_overhang_mesh"))
if (mesh.settings_.get<bool>("conical_overhang_enabled") && ! mesh.settings_.get<bool>("anti_overhang_mesh")
&& (! mesh.settings_.has("force_support_overhang_mesh") || ! mesh.settings_.get<bool>("force_support_overhang_mesh")))
{
ConicalOverhang::apply(slicerList[mesh_idx], mesh);
}
Expand Down Expand Up @@ -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<bool>("anti_overhang_mesh") && ! mesh.settings_.get<bool>("infill_mesh") && ! mesh.settings_.get<bool>("cutting_mesh"))
if (mesh.isModelMesh())
{
storage.print_layer_count = std::max(storage.print_layer_count, slicer->layers.size());
}
Expand Down Expand Up @@ -356,7 +357,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
for (std::shared_ptr<SliceMeshStorage>& mesh_ptr : storage.meshes)
{
auto& mesh = *mesh_ptr;
if (! mesh.settings.get<bool>("infill_mesh") && ! mesh.settings.get<bool>("anti_overhang_mesh"))
if (mesh.isModelMesh())
{
slice_layer_count = std::max<unsigned int>(slice_layer_count, mesh.layers.size());
}
Expand Down Expand Up @@ -389,14 +390,16 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper&
Progress::messageProgress(Progress::Stage::INSET_SKIN, mesh_order_idx + 1, storage.meshes.size());
}

const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
const auto& mesh_group = Application::getInstance().current_slice_->scene.current_mesh_group;
const Settings& mesh_group_settings = mesh_group->settings;
const bool mesh_group_support_paint = mesh_group->has_support_paint;

// we need to remove empty layers after we have processed the insets
// processInsets might throw away parts if they have no wall at all (cause it doesn't fit)
// brim depends on the first layer not being empty
// only remove empty layers if we haven't generate support, because then support was added underneath the model.
// for some materials it's better to print on support than on the build plate.
const auto has_support = mesh_group_settings.get<bool>("support_enable") || mesh_group_settings.get<bool>("support_mesh");
const auto has_support = mesh_group_settings.get<bool>("support_enable") || mesh_group_settings.get<bool>("support_mesh") || mesh_group_support_paint;
const auto remove_empty_first_layers = mesh_group_settings.get<bool>("remove_empty_first_layers") && ! has_support;
if (remove_empty_first_layers)
{
Expand Down Expand Up @@ -879,7 +882,7 @@ void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage
for (std::shared_ptr<SliceMeshStorage>& mesh_ptr : storage.meshes)
{
auto& mesh = *mesh_ptr;
if (mesh.settings.get<bool>("anti_overhang_mesh") || mesh.settings.get<bool>("support_mesh"))
if (! mesh.isPrinted() || mesh.settings.get<bool>("support_mesh"))
{
continue; // Special type of mesh that doesn't get printed.
}
Expand Down
2 changes: 1 addition & 1 deletion src/InterlockingGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ void InterlockingGenerator::generateInterlockingStructure(std::vector<Slicer*>&
Slicer& mesh_b = *volumes[mesh_b_idx];
size_t extruder_nr_b = mesh_b.mesh->settings_.get<ExtruderTrain&>("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;
}
Expand Down
4 changes: 2 additions & 2 deletions src/LayerPlan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ Shape LayerPlan::computeCombBoundary(const CombBoundary boundary_type)
{
const auto& mesh = *mesh_ptr;
const SliceLayer& layer = mesh.layers[static_cast<size_t>(layer_nr_)];
// don't process infill_mesh or anti_overhang_mesh
if (mesh.settings.get<bool>("infill_mesh") || mesh.settings.get<bool>("anti_overhang_mesh"))
// don't process non printable meshes
if (! mesh.isModelMesh())
{
continue;
}
Expand Down
6 changes: 2 additions & 4 deletions src/MeshGroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ Point3LL MeshGroup::min() const
Point3LL ret(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max());
for (const Mesh& mesh : meshes)
{
if (mesh.settings_.get<bool>("infill_mesh") || mesh.settings_.get<bool>("cutting_mesh")
|| mesh.settings_.get<bool>("anti_overhang_mesh")) // Don't count pieces that are not printed.
if (! mesh.isModelMesh()) // Don't count pieces that are not printed.
{
continue;
}
Expand All @@ -80,8 +79,7 @@ Point3LL MeshGroup::max() const
Point3LL ret(std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::min());
for (const Mesh& mesh : meshes)
{
if (mesh.settings_.get<bool>("infill_mesh") || mesh.settings_.get<bool>("cutting_mesh")
|| mesh.settings_.get<bool>("anti_overhang_mesh")) // Don't count pieces that are not printed.
if (! mesh.isModelMesh()) // Don't count pieces that are not printed.
{
continue;
}
Expand Down
Loading
Loading