From 684c316a0faeb5bdcee959819b4388085a061130 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 17 Jul 2025 00:53:18 +0200 Subject: [PATCH 01/21] Hackathon '25: Slice/Print-ID label: Determine axis/ranges of projection-plane. (WIP) Wanted: Print-ID (or failing that), Slice-ID -- Let the user draw on the model where a label should be applied wiht an unique ID per slice (or maybe even per print if we can also manage firmware involvement...) part of UMH-2025 --- include/TextureDataMapping.h | 20 +++++++++++ include/mesh.h | 50 ++++++++++++++++++++++++++++ include/utils/AABB3D.h | 8 +++++ src/FffPolygonGenerator.cpp | 64 +++++++++++++++++++++++++++++++++--- src/mesh.cpp | 41 +++++++++++++++++++++++ src/slicer.cpp | 2 +- src/utils/AABB3D.cpp | 5 +++ 7 files changed, 185 insertions(+), 5 deletions(-) diff --git a/include/TextureDataMapping.h b/include/TextureDataMapping.h index 09b89e49fd..0c982f55a9 100644 --- a/include/TextureDataMapping.h +++ b/include/TextureDataMapping.h @@ -4,6 +4,7 @@ #ifndef TEXTUREDATAMAPPING_H #define TEXTUREDATAMAPPING_H +#include #include #include @@ -33,4 +34,23 @@ enum class TextureArea }; } // namespace cura + +namespace fmt +{ + template<> + struct formatter + { + constexpr auto parse(format_parse_context& ctx) + { + return ctx.end(); + } + + template + auto format(const cura::TextureBitField& tbf, FormatContext& ctx) const + { + return format_to(ctx.out(), "[{} -- {}]", tbf.bit_range_start_index, tbf.bit_range_end_index); + } + }; +} + #endif // MESH_H diff --git a/include/mesh.h b/include/mesh.h index 51c5c58a9b..869247e34b 100644 --- a/include/mesh.h +++ b/include/mesh.h @@ -100,6 +100,40 @@ class Image return getPixel(static_cast(uv_coordinates.x_ * width_), static_cast(uv_coordinates.y_ * height_)); } + void visitLinePerPixel(const Point2F& a, const Point2F& b, const std::function& func) const + { + // Bresenham variant. Altered from Wikipedia. + auto x0 = static_cast(a.x_ * width_); + auto y0 = static_cast(a.y_ * height_); + const auto x1 = static_cast(b.x_ * width_); + const auto y1 = static_cast(b.y_ * height_); + const size_t dx = std::llabs(x1 - x0); + const size_t sx = x0 < x1 ? 1 : -1; + const size_t dy = -std::llabs(y1 - y0); + const size_t sy = y0 < y1 ? 1 : -1; + size_t error = dx + dy; + while (true) + { + func( + getPixel(x0, y0), + Point2F(static_cast(x0) / width_, static_cast(y0) / height_) + ); + const size_t e2 = error * 2; + if (e2 >= dy) + { + if (x0 == x1) break; + error += dy; + x0 += sx; + } + if (e2 <= dx) + { + if (y0 == y1) break; + error += dx; + y0 += sy; + } + } + } + private: std::vector data_; // The raw pixels, data size_t width_{ 0 }; // The image width @@ -108,6 +142,17 @@ class Image size_t bytes_per_row_{ 0 }; }; +struct IdFieldInfo +{ + enum class Axis { X, Y, Z }; + Axis primary_axis_ = Axis::X; + Axis secondary_axis_ = Axis::Z; + AABB projection_field_; + +public: + static std::optional from_aabb3d(const AABB3D& aabb); +}; + /*! A Mesh is the most basic representation of a 3D model. It contains all the faces as MeshFaces. @@ -115,10 +160,13 @@ See MeshFace for the specifics of how/when faces are connected. */ class Mesh { +private: //! The vertex_hash_map stores a index reference of each vertex for the hash of that location. Allows for quick retrieval of points with the same location. std::unordered_map> vertex_hash_map_; AABB3D aabb_; + std::optional id_field_info_; + public: std::vector vertices_; //!< list of all vertices in the mesh std::vector faces_; //!< list of all faces in the mesh @@ -149,6 +197,8 @@ class Mesh void clear(); //!< clears all data void finish(); //!< complete the model : set the connected_face_index fields of the faces. + void setIdFieldInfo(const AABB3D& aabb); + Point3LL min() const; //!< min (in x,y and z) vertex of the bounding box Point3LL max() const; //!< max (in x,y and z) vertex of the bounding box AABB3D getAABB() const; //!< Get the axis aligned bounding box diff --git a/include/utils/AABB3D.h b/include/utils/AABB3D.h index e7d4b9eda5..3bf56c2e59 100644 --- a/include/utils/AABB3D.h +++ b/include/utils/AABB3D.h @@ -42,6 +42,14 @@ struct AABB3D */ AABB flatten() const; + /*! + * Wether or not this repressents an actual box in 'real' space, not a _negative_ volume. + * Note that the volume might still be empty (== 0.0), which can happen if it repressents a(n axis aligned) plane, a line, or a point. + * + * \return Wether or not the space the box envelops is at least 0. + */ + bool exists() const; + /*! * Check whether this aabb overlaps with another. * diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index e7d8083a24..473f27c5f7 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -291,15 +291,26 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe // check one if raft offset is needed const bool has_raft = mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::RAFT; + // init variable(s) & logging for the support 'painting' type operations + if (mesh.texture_ && mesh.texture_data_mapping_) + { + spdlog::info("Mesh `{}` painting-operation info:", static_cast(&mesh)); + for (auto [paint_type, tex_data] : *mesh.texture_data_mapping_) + { + spdlog::info("Texture-data for type '{}', mapped to pixel-bits: {}", paint_type, tex_data); + } + } + else + { + spdlog::info("No painting-operation data specified for mesh `{}`.", static_cast(&mesh)); + } + AABB3D label_aabb; + // calculate the height at which each layer is actually printed (printZ) for (LayerIndex layer_nr = 0; layer_nr < meshStorage.layers.size(); layer_nr++) { SliceLayer& layer = meshStorage.layers[layer_nr]; const SlicerLayer& slicer_layer = slicer->layers[layer_nr]; - if (slicer_layer.sliced_uv_coordinates_ && mesh.texture_ && mesh.texture_data_mapping_) - { - layer.texture_data_provider_ = std::make_shared(slicer_layer.sliced_uv_coordinates_, mesh.texture_, mesh.texture_data_mapping_); - } if (use_variable_layer_heights) { @@ -332,8 +343,53 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe layer.printZ += train.settings_.get("layer_0_z_overlap"); // undo shifting down of first layer } } + + // support for 'painting' type operations + if (slicer_layer.sliced_uv_coordinates_ && mesh.texture_ && mesh.texture_data_mapping_) + { + layer.texture_data_provider_ = std::make_shared(slicer_layer.sliced_uv_coordinates_, mesh.texture_, mesh.texture_data_mapping_); + + // slice/print-ID label specific: (update/)calculate bounding-box + if (mesh.texture_data_mapping_->count("label") > 0) + { + const auto match_pixel = static_cast(TextureArea::Preferred) << mesh.texture_data_mapping_->at("label").bit_range_start_index; + const auto& height = layer.printZ; + for (const auto& segment : slicer_layer.segments_) + // TODO: Deal with the fact that we _actually_ don't have the segments in that place anymore (I temporarily disabled the clear). + // (We've still got all we need in the slicer_layer.sliced_uv_coordinates_.segments, but that's all private at the moment! + { + if (segment.uv_start.has_value() && segment.uv_end.has_value()) + { + const auto& uv_a = segment.uv_start.value(); + const auto& uv_b = segment.uv_end.value(); + const auto& a = segment.start; + const auto& b = segment.end; + mesh.texture_->visitLinePerPixel( + uv_a, + uv_b, + [&label_aabb, &match_pixel, &uv_a, &uv_b, &a, &b, &height](const int32_t& pixel, const Point2F& uv) { + if ((pixel & match_pixel) != 0b0) + { + const auto param = + std::llabs(b.X - a.X) >= std::llabs(b.Y - a.Y) ? + (uv.x_ - uv_a.x_) / (uv_b.x_ - uv_a.x_) : + (uv.y_ - uv_a.y_) / (uv_b.y_ - uv_a.y_); + label_aabb.include(Point3LL(a + param * (b - a), height)); + } + } + ); + } + } + } + } } + std::fprintf(stderr, "<%ld, %ld, %ld> -- <%ld, %ld, %ld>\n", label_aabb.min_.x_, label_aabb.min_.y_, label_aabb.min_.z_, label_aabb.max_.x_, label_aabb.max_.y_, label_aabb.max_.z_); + // FIXME/TODO: REMOVE print + + mesh.setIdFieldInfo(label_aabb); + // BOOKMARK! + delete slicerList[meshIdx]; Progress::messageProgress(Progress::Stage::PARTS, meshIdx + 1, slicerList.size()); diff --git a/src/mesh.cpp b/src/mesh.cpp index 9e60d45454..8f57e6ed88 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -3,6 +3,7 @@ #include "mesh.h" +#include #include #include @@ -23,6 +24,37 @@ static inline uint32_t pointHash(const Point3LL& p) ^ (((p.z_ + vertex_meld_distance / 2) / vertex_meld_distance) << 20); } +std::optional IdFieldInfo::from_aabb3d(const AABB3D& aabb) +{ + if (! aabb.exists()) + { + return std::make_optional(); + } + + typedef std::tuple axis_span_t; + std::array dif_per_axis = { + std::make_tuple(IdFieldInfo::Axis::X, aabb.min_.x_, aabb.max_.x_), + std::make_tuple(IdFieldInfo::Axis::Y, aabb.min_.y_, aabb.max_.y_), + std::make_tuple(IdFieldInfo::Axis::Z, aabb.min_.z_, aabb.max_.z_) + }; + std::stable_sort( + dif_per_axis.begin(), + dif_per_axis.end(), + [](const axis_span_t& a, const axis_span_t& b) + { return std::llabs(std::get<2>(a) - std::get<1>(a)) > std::llabs(std::get<2>(b) - std::get<1>(b)); } + ); + + return std::make_optional( + IdFieldInfo{ + .primary_axis_ = std::get<0>(dif_per_axis[0]), + .secondary_axis_ = std::get<0>(dif_per_axis[1]), + .projection_field_ = AABB( + Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), + Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1])) + ) + }); +} + Mesh::Mesh(Settings& parent) : settings_(parent) , has_disconnected_faces(false) @@ -88,6 +120,15 @@ void Mesh::finish() } } +void Mesh::setIdFieldInfo(const AABB3D& aabb) +{ + id_field_info_ = IdFieldInfo::from_aabb3d(aabb); + + const auto& dbgval = id_field_info_.value(); + std::fprintf(stderr, "label %d: %ld - %ld // %d: %ld - %ld\n", dbgval.primary_axis_, dbgval.projection_field_.min_.X, dbgval.projection_field_.max_.X, dbgval.secondary_axis_, dbgval.projection_field_.min_.Y, dbgval.projection_field_.max_.Y); + // FIXME/TODO: REMOVE print +} + Point3LL Mesh::min() const { return aabb_.min_; diff --git a/src/slicer.cpp b/src/slicer.cpp index 6151cf15e2..0a68e7d8f1 100644 --- a/src/slicer.cpp +++ b/src/slicer.cpp @@ -801,7 +801,7 @@ void SlicerLayer::makePolygons(const Mesh* mesh) sliced_uv_coordinates_ = std::make_shared(segments_); // Clear the segment list to save memory, it is no longer needed after this point. - segments_.clear(); + //segments_.clear(); // FIXME!!! -> put back once this code needs to be production quality (no longer hackathon proof-of-concept) } Slicer::Slicer(Mesh* i_mesh, const coord_t thickness, const size_t slice_layer_count, bool use_variable_layer_heights, std::vector* adaptive_layers) diff --git a/src/utils/AABB3D.cpp b/src/utils/AABB3D.cpp index 698c4745b7..fb22b55f41 100644 --- a/src/utils/AABB3D.cpp +++ b/src/utils/AABB3D.cpp @@ -42,6 +42,11 @@ bool AABB3D::hit(const AABB3D& other) const return true; } +bool AABB3D::exists() const +{ + return min_.x_ <= max_.x_ && min_.y_ <= max_.y_ && min_.z_ <= max_.z_; +} + AABB3D AABB3D::include(Point3LL p) { min_.x_ = std::min(min_.x_, p.x_); From 96e439026f4a43066d4aa8a243c6e3014ed93a8f Mon Sep 17 00:00:00 2001 From: rburema <41987080+rburema@users.noreply.github.com> Date: Wed, 16 Jul 2025 22:54:07 +0000 Subject: [PATCH 02/21] Apply clang-format --- include/TextureDataMapping.h | 31 ++++++++++++++++--------------- include/mesh.h | 18 +++++++++++------- src/FffPolygonGenerator.cpp | 26 ++++++++++++++++---------- src/mesh.cpp | 36 +++++++++++++++++++----------------- src/slicer.cpp | 2 +- 5 files changed, 63 insertions(+), 50 deletions(-) diff --git a/include/TextureDataMapping.h b/include/TextureDataMapping.h index 0c982f55a9..b0712a0c6d 100644 --- a/include/TextureDataMapping.h +++ b/include/TextureDataMapping.h @@ -4,10 +4,11 @@ #ifndef TEXTUREDATAMAPPING_H #define TEXTUREDATAMAPPING_H -#include #include #include +#include + namespace cura { @@ -37,20 +38,20 @@ enum class TextureArea namespace fmt { - template<> - struct formatter +template<> +struct formatter +{ + constexpr auto parse(format_parse_context& ctx) { - constexpr auto parse(format_parse_context& ctx) - { - return ctx.end(); - } - - template - auto format(const cura::TextureBitField& tbf, FormatContext& ctx) const - { - return format_to(ctx.out(), "[{} -- {}]", tbf.bit_range_start_index, tbf.bit_range_end_index); - } - }; -} + return ctx.end(); + } + + template + auto format(const cura::TextureBitField& tbf, FormatContext& ctx) const + { + return format_to(ctx.out(), "[{} -- {}]", tbf.bit_range_start_index, tbf.bit_range_end_index); + } +}; +} // namespace fmt #endif // MESH_H diff --git a/include/mesh.h b/include/mesh.h index 869247e34b..58a253324f 100644 --- a/include/mesh.h +++ b/include/mesh.h @@ -114,20 +114,19 @@ class Image size_t error = dx + dy; while (true) { - func( - getPixel(x0, y0), - Point2F(static_cast(x0) / width_, static_cast(y0) / height_) - ); + func(getPixel(x0, y0), Point2F(static_cast(x0) / width_, static_cast(y0) / height_)); const size_t e2 = error * 2; if (e2 >= dy) { - if (x0 == x1) break; + if (x0 == x1) + break; error += dy; x0 += sx; } if (e2 <= dx) { - if (y0 == y1) break; + if (y0 == y1) + break; error += dx; y0 += sy; } @@ -144,7 +143,12 @@ class Image struct IdFieldInfo { - enum class Axis { X, Y, Z }; + enum class Axis + { + X, + Y, + Z + }; Axis primary_axis_ = Axis::X; Axis secondary_axis_ = Axis::Z; AABB projection_field_; diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 473f27c5f7..fbedd322a7 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -355,8 +355,8 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe const auto match_pixel = static_cast(TextureArea::Preferred) << mesh.texture_data_mapping_->at("label").bit_range_start_index; const auto& height = layer.printZ; for (const auto& segment : slicer_layer.segments_) - // TODO: Deal with the fact that we _actually_ don't have the segments in that place anymore (I temporarily disabled the clear). - // (We've still got all we need in the slicer_layer.sliced_uv_coordinates_.segments, but that's all private at the moment! + // TODO: Deal with the fact that we _actually_ don't have the segments in that place anymore (I temporarily disabled the clear). + // (We've still got all we need in the slicer_layer.sliced_uv_coordinates_.segments, but that's all private at the moment! { if (segment.uv_start.has_value() && segment.uv_end.has_value()) { @@ -367,24 +367,30 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe mesh.texture_->visitLinePerPixel( uv_a, uv_b, - [&label_aabb, &match_pixel, &uv_a, &uv_b, &a, &b, &height](const int32_t& pixel, const Point2F& uv) { + [&label_aabb, &match_pixel, &uv_a, &uv_b, &a, &b, &height](const int32_t& pixel, const Point2F& uv) + { if ((pixel & match_pixel) != 0b0) { - const auto param = - std::llabs(b.X - a.X) >= std::llabs(b.Y - a.Y) ? - (uv.x_ - uv_a.x_) / (uv_b.x_ - uv_a.x_) : - (uv.y_ - uv_a.y_) / (uv_b.y_ - uv_a.y_); + const auto param + = std::llabs(b.X - a.X) >= std::llabs(b.Y - a.Y) ? (uv.x_ - uv_a.x_) / (uv_b.x_ - uv_a.x_) : (uv.y_ - uv_a.y_) / (uv_b.y_ - uv_a.y_); label_aabb.include(Point3LL(a + param * (b - a), height)); } - } - ); + }); } } } } } - std::fprintf(stderr, "<%ld, %ld, %ld> -- <%ld, %ld, %ld>\n", label_aabb.min_.x_, label_aabb.min_.y_, label_aabb.min_.z_, label_aabb.max_.x_, label_aabb.max_.y_, label_aabb.max_.z_); + std::fprintf( + stderr, + "<%ld, %ld, %ld> -- <%ld, %ld, %ld>\n", + label_aabb.min_.x_, + label_aabb.min_.y_, + label_aabb.min_.z_, + label_aabb.max_.x_, + label_aabb.max_.y_, + label_aabb.max_.z_); // FIXME/TODO: REMOVE print mesh.setIdFieldInfo(label_aabb); diff --git a/src/mesh.cpp b/src/mesh.cpp index 8f57e6ed88..03c53b2855 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -32,27 +32,21 @@ std::optional IdFieldInfo::from_aabb3d(const AABB3D& aabb) } typedef std::tuple axis_span_t; - std::array dif_per_axis = { - std::make_tuple(IdFieldInfo::Axis::X, aabb.min_.x_, aabb.max_.x_), - std::make_tuple(IdFieldInfo::Axis::Y, aabb.min_.y_, aabb.max_.y_), - std::make_tuple(IdFieldInfo::Axis::Z, aabb.min_.z_, aabb.max_.z_) - }; + std::array dif_per_axis = { std::make_tuple(IdFieldInfo::Axis::X, aabb.min_.x_, aabb.max_.x_), + std::make_tuple(IdFieldInfo::Axis::Y, aabb.min_.y_, aabb.max_.y_), + std::make_tuple(IdFieldInfo::Axis::Z, aabb.min_.z_, aabb.max_.z_) }; std::stable_sort( dif_per_axis.begin(), dif_per_axis.end(), [](const axis_span_t& a, const axis_span_t& b) - { return std::llabs(std::get<2>(a) - std::get<1>(a)) > std::llabs(std::get<2>(b) - std::get<1>(b)); } - ); - - return std::make_optional( - IdFieldInfo{ - .primary_axis_ = std::get<0>(dif_per_axis[0]), - .secondary_axis_ = std::get<0>(dif_per_axis[1]), - .projection_field_ = AABB( - Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), - Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1])) - ) + { + return std::llabs(std::get<2>(a) - std::get<1>(a)) > std::llabs(std::get<2>(b) - std::get<1>(b)); }); + + return std::make_optional(IdFieldInfo{ + .primary_axis_ = std::get<0>(dif_per_axis[0]), + .secondary_axis_ = std::get<0>(dif_per_axis[1]), + .projection_field_ = AABB(Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1]))) }); } Mesh::Mesh(Settings& parent) @@ -125,7 +119,15 @@ void Mesh::setIdFieldInfo(const AABB3D& aabb) id_field_info_ = IdFieldInfo::from_aabb3d(aabb); const auto& dbgval = id_field_info_.value(); - std::fprintf(stderr, "label %d: %ld - %ld // %d: %ld - %ld\n", dbgval.primary_axis_, dbgval.projection_field_.min_.X, dbgval.projection_field_.max_.X, dbgval.secondary_axis_, dbgval.projection_field_.min_.Y, dbgval.projection_field_.max_.Y); + std::fprintf( + stderr, + "label %d: %ld - %ld // %d: %ld - %ld\n", + dbgval.primary_axis_, + dbgval.projection_field_.min_.X, + dbgval.projection_field_.max_.X, + dbgval.secondary_axis_, + dbgval.projection_field_.min_.Y, + dbgval.projection_field_.max_.Y); // FIXME/TODO: REMOVE print } diff --git a/src/slicer.cpp b/src/slicer.cpp index 0a68e7d8f1..7f2d5bff28 100644 --- a/src/slicer.cpp +++ b/src/slicer.cpp @@ -801,7 +801,7 @@ void SlicerLayer::makePolygons(const Mesh* mesh) sliced_uv_coordinates_ = std::make_shared(segments_); // Clear the segment list to save memory, it is no longer needed after this point. - //segments_.clear(); // FIXME!!! -> put back once this code needs to be production quality (no longer hackathon proof-of-concept) + // segments_.clear(); // FIXME!!! -> put back once this code needs to be production quality (no longer hackathon proof-of-concept) } Slicer::Slicer(Mesh* i_mesh, const coord_t thickness, const size_t slice_layer_count, bool use_variable_layer_heights, std::vector* adaptive_layers) From 3d63cf5d8ea5750069a80b86bfe8ec229345b3ec Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 17 Jul 2025 23:32:53 +0200 Subject: [PATCH 03/21] Hackathon '25: Slice/Print-ID label: Apply label-extents to model/gcode -- but not any ID yet. (WIP) One the one hand, a lot got done, on the other, this commit is about 90% FIXME's an TODO's by weight -- What is done here is that you can see that it applies a gcode offset where the label is to be printed (for the slice ID), and it alters the gcode (with a comment) as well (for future use in the firmware hopefully). It's just that at the moment, no actual ID is 'printed' so it doesn't function as a slice-ID yet, and the gcode may be commented, but not with the UV's from the ID-label (not to be confused with the paint-texture) so it can't function as a print-ID yet. -- Furthermore, I think I only got the projection 'working' on one side of the cube, and even then it projects it scrimped in the horizontal direction on the opposit side -- to say nothing of the IdFieldInfo properties that've been set up in the previous commit (and should be altered anyway) but that isn't even properly used yet either. part of UMH-2025 --- CMakeLists.txt | 2 + include/ExtruderPlan.h | 4 ++ include/LayerPlan.h | 5 +++ include/TextureDataProvider.h | 2 + include/gcodeExport.h | 6 ++- include/mesh.h | 74 ++++++++++++------------------- include/pathPlanning/GCodePath.h | 1 + include/sliceDataStorage.h | 6 +++ include/utils/types/idfieldinfo.h | 40 +++++++++++++++++ src/ExtruderPlan.cpp | 68 ++++++++++++++++++++++++++++ src/FffGcodeWriter.cpp | 2 + src/FffPolygonGenerator.cpp | 19 +++----- src/LayerPlan.cpp | 31 +++++++++++-- src/TextureDataProvider.cpp | 33 ++++++++++++++ src/gcodeExport.cpp | 18 +++++--- src/mesh.cpp | 43 +----------------- src/sliceDataStorage.cpp | 5 +++ src/utils/types/idfieldinfo.cpp | 39 ++++++++++++++++ 18 files changed, 284 insertions(+), 114 deletions(-) create mode 100644 include/utils/types/idfieldinfo.h create mode 100644 src/utils/types/idfieldinfo.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b8c38a6d41..f4ff76c9dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,6 +168,8 @@ set(engine_SRCS # Except main.cpp. src/utils/scoring/RandomScoringCriterion.cpp src/utils/scoring/TextureScoringCriterion.cpp + src/utils/types/idfieldinfo.cpp + src/geometry/Point2LL.cpp src/geometry/Point3LL.cpp src/geometry/Polygon.cpp diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index e4d301969a..6d9e0d4c2a 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -124,6 +124,10 @@ class ExtruderPlan */ void applyBackPressureCompensation(const Ratio back_pressure_compensation); + /*! + */ + void applyIdLabel(); + /*! * Gets the mesh being printed first on this plan */ diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 81f7271cfc..0fcea9edd6 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -835,6 +835,10 @@ class LayerPlan : public NoCopy */ void applyGradualFlow(); + /*! + */ + void applyIdLabel(); + /*! * Gets the mesh being printed first on this layer */ @@ -917,6 +921,7 @@ class LayerPlan : public NoCopy const coord_t path_z_offset, double extrusion_mm3_per_mm, PrintFeatureType feature, + const std::optional& inline_comment = std::nullopt, bool update_extrusion_offset = false); /*! diff --git a/include/TextureDataProvider.h b/include/TextureDataProvider.h index 843583108b..771a592cc5 100644 --- a/include/TextureDataProvider.h +++ b/include/TextureDataProvider.h @@ -28,6 +28,8 @@ class TextureDataProvider std::optional getAreaPreference(const Point2LL& position, const std::string& feature) const; + bool getAreaPreferencesForSpan(const Point2LL& a, const Point2LL& b, const std::string& feature, std::vector& res) const; + private: std::shared_ptr uv_coordinates_; std::shared_ptr texture_; diff --git a/include/gcodeExport.h b/include/gcodeExport.h index 5a67ea3fc3..97f6baff9a 100644 --- a/include/gcodeExport.h +++ b/include/gcodeExport.h @@ -372,7 +372,7 @@ class GCodeExport : public NoCopy * \param feature the feature that's currently printing * \param update_extrusion_offset whether to update the extrusion offset to match the current flow rate */ - void writeExtrusion(const Point2LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, bool update_extrusion_offset = false); + void writeExtrusion(const Point2LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, const std::optional& inline_comment = std::nullopt, bool update_extrusion_offset = false); /*! * Go to a X/Y location with the z-hopped Z value @@ -396,7 +396,7 @@ class GCodeExport : public NoCopy * \param feature the feature that's currently printing * \param update_extrusion_offset whether to update the extrusion offset to match the current flow rate */ - void writeExtrusion(const Point3LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, bool update_extrusion_offset = false); + void writeExtrusion(const Point3LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, const std::optional& inline_comment = std::nullopt, bool update_extrusion_offset = false); /*! * Initialize the extruder trains. @@ -471,6 +471,7 @@ class GCodeExport : public NoCopy const Velocity& speed, const double extrusion_mm3_per_mm, const PrintFeatureType& feature, + const std::optional& inline_comment = std::nullopt, const bool update_extrusion_offset = false); /*! @@ -490,6 +491,7 @@ class GCodeExport : public NoCopy const coord_t z, const double e, const PrintFeatureType& feature, + const std::optional& inline_comment = std::nullopt, const std::optional& retraction_amounts = std::nullopt); /*! diff --git a/include/mesh.h b/include/mesh.h index 58a253324f..bf2ca09360 100644 --- a/include/mesh.h +++ b/include/mesh.h @@ -11,6 +11,7 @@ #include "utils/AABB3D.h" #include "utils/Matrix4x3D.h" #include "utils/Point2F.h" +#include "utils/types/idfieldinfo.h" namespace cura { @@ -100,36 +101,32 @@ class Image return getPixel(static_cast(uv_coordinates.x_ * width_), static_cast(uv_coordinates.y_ * height_)); } - void visitLinePerPixel(const Point2F& a, const Point2F& b, const std::function& func) const + void visitSpanPerPixel(const Point2F& a, const Point2F& b, const std::function& func) const { - // Bresenham variant. Altered from Wikipedia. - auto x0 = static_cast(a.x_ * width_); - auto y0 = static_cast(a.y_ * height_); - const auto x1 = static_cast(b.x_ * width_); - const auto y1 = static_cast(b.y_ * height_); - const size_t dx = std::llabs(x1 - x0); - const size_t sx = x0 < x1 ? 1 : -1; - const size_t dy = -std::llabs(y1 - y0); - const size_t sy = y0 < y1 ? 1 : -1; - size_t error = dx + dy; - while (true) + constexpr auto func_major_stepper = [](const int64_t& da, const int64_t& abs_db, const int64_t& i) { return da < 0 ? -i : i; }; + constexpr auto func_minor_stepper = [](const int64_t& da, const int64_t& abs_db, const int64_t& i) { return (i * da) / abs_db; }; + + const auto x0 = static_cast(a.x_ * width_); + const auto y0 = static_cast(a.y_ * height_); + const auto x1 = static_cast(b.x_ * width_); + const auto y1 = static_cast(b.y_ * height_); + + const auto dx = x1 - x0; + const auto dy = y1 - y0; + const auto abs_dx = std::llabs(dx); + const auto abs_dy = std::llabs(dy); + const auto max_span = std::max(abs_dx, abs_dy); + + const auto func_x = abs_dy <= abs_dx ? func_major_stepper : func_minor_stepper; + const auto func_y = abs_dx <= abs_dy ? func_major_stepper : func_minor_stepper; + for (size_t i_pix = 0; i_pix <= max_span; i_pix++) { - func(getPixel(x0, y0), Point2F(static_cast(x0) / width_, static_cast(y0) / height_)); - const size_t e2 = error * 2; - if (e2 >= dy) - { - if (x0 == x1) - break; - error += dy; - x0 += sx; - } - if (e2 <= dx) - { - if (y0 == y1) - break; - error += dx; - y0 += sy; - } + const auto xi = x0 + func_x(dx, abs_dy, i_pix); + const auto yi = y0 + func_y(dy, abs_dx, i_pix); + func( + getPixel(xi, yi), + Point2F(static_cast(xi) / width_, static_cast(yi) / height_) + ); } } @@ -141,22 +138,6 @@ class Image size_t bytes_per_row_{ 0 }; }; -struct IdFieldInfo -{ - enum class Axis - { - X, - Y, - Z - }; - Axis primary_axis_ = Axis::X; - Axis secondary_axis_ = Axis::Z; - AABB projection_field_; - -public: - static std::optional from_aabb3d(const AABB3D& aabb); -}; - /*! A Mesh is the most basic representation of a 3D model. It contains all the faces as MeshFaces. @@ -169,7 +150,7 @@ class Mesh std::unordered_map> vertex_hash_map_; AABB3D aabb_; - std::optional id_field_info_; + //std::optional id_field_info_; public: std::vector vertices_; //!< list of all vertices in the mesh @@ -201,7 +182,8 @@ class Mesh void clear(); //!< clears all data void finish(); //!< complete the model : set the connected_face_index fields of the faces. - void setIdFieldInfo(const AABB3D& aabb); + //void setIdFieldInfo(const AABB3D& aabb); + //std::optional getIdFieldInfo() const; Point3LL min() const; //!< min (in x,y and z) vertex of the bounding box Point3LL max() const; //!< max (in x,y and z) vertex of the bounding box diff --git a/include/pathPlanning/GCodePath.h b/include/pathPlanning/GCodePath.h index 6c7c0df21a..ee73fa3417 100644 --- a/include/pathPlanning/GCodePath.h +++ b/include/pathPlanning/GCodePath.h @@ -46,6 +46,7 @@ struct GCodePath bool perform_z_hop{ false }; //!< Whether to perform a z_hop in this path, which is assumed to be a travel path. bool perform_prime{ false }; //!< Whether this path is preceded by a prime (blob) std::vector points{}; //!< The points constituting this path. The Z coordinate is an offset relative to the actual layer height, added to the global z_offset. + std::optional> message_bit_per_point = std::nullopt; bool done{ false }; //!< Path is finished, no more moves should be added, and a new path should be started instead of any appending done to this one. double fan_speed{ GCodePathConfig::FAN_SPEED_DEFAULT }; //!< fan speed override for this path, value should be within range 0-100 (inclusive) and ignored otherwise TimeMaterialEstimates estimates{}; //!< Naive time and material estimates diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index 75c977a598..0b3fb9685d 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -24,6 +24,7 @@ #include "utils/AABB.h" #include "utils/AABB3D.h" #include "utils/NoCopy.h" +#include "utils/types/IdFieldInfo.h" namespace cura { @@ -307,6 +308,7 @@ class SliceMeshStorage Settings& settings; std::vector layers; std::string mesh_name; + std::optional id_field_info; LayerIndex layer_nr_max_filled_layer; //!< the layer number of the uppermost layer with content (modified while infill meshes are processed) @@ -337,6 +339,10 @@ class SliceMeshStorage */ SliceMeshStorage(Mesh* mesh, const size_t slice_layer_count); + /*! + */ + void setIdFieldInfo(const AABB3D& aabb); + /*! * \param extruder_nr The extruder for which to check * \return whether a particular extruder is used by this mesh diff --git a/include/utils/types/idfieldinfo.h b/include/utils/types/idfieldinfo.h new file mode 100644 index 0000000000..29f32e3b66 --- /dev/null +++ b/include/utils/types/idfieldinfo.h @@ -0,0 +1,40 @@ +// Copyright (c) 2025 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef ID_FIELD_INFO_H +#define ID_FIELD_INFO_H + +#include "utils/AABB3D.h" + +#include + +namespace cura +{ + // FIXME?: This now goes per-axis, because that's what we get by creating it from the AABB, but it should be a 'free' plane-normal(s). + // We'd probably need to do a little principal component analysis to _properly_ get the primary and secondary axii. + + struct IdFieldInfo + { + enum class Axis { X, Y, Z }; + Axis primary_axis_ = Axis::X; + Axis secondary_axis_ = Axis::Z; + Axis normal_ = Axis::Y; + AABB projection_field_; + + public: + static std::optional from_aabb3d(const AABB3D& aabb); + + Point3LL normal_offset(coord_t offset) const + { + switch (normal_) + { + case cura::IdFieldInfo::Axis::X: return Point3LL(offset, 0, 0); + case cura::IdFieldInfo::Axis::Y: return Point3LL(0, offset, 0); + case cura::IdFieldInfo::Axis::Z: return Point3LL(0, 0, offset); + default: return Point3LL(0, 0, 0); + } + } + }; +}// namespace cura + +#endif // ID_FIELD_INFO_H diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 58a6bb3f86..6f114ab766 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -3,6 +3,8 @@ #include "ExtruderPlan.h" +#include "TextureDataProvider.h" + namespace cura { ExtruderPlan::ExtruderPlan( @@ -71,6 +73,72 @@ void ExtruderPlan::applyBackPressureCompensation(const Ratio back_pressure_compe } } +void ExtruderPlan::applyIdLabel() +{ + // TODO?: message (format) should be a (string) setting, like 'ID: \H:\M:\S' or something + + constexpr coord_t inset_dist = 40; // TODO?: make this configurable as well? + for (auto& path : paths_) + { + if ( + path.mesh == nullptr || + path.mesh->layers[layer_nr_].texture_data_provider_ == nullptr || + (! path.mesh->id_field_info) || + (path.mesh->id_field_info.value().normal_ != IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::OuterWall) || + (path.mesh->id_field_info.value().normal_ == IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::Skin) + ) + { + continue; + } + + const auto zero_pt = Point3LL(0, 0, 0); + const auto offset_pt = path.mesh->id_field_info.value().normal_offset(-inset_dist); + + std::vector new_points; + std::vector message_bits; + new_points.push_back(path.points[0]); + message_bits.push_back(false); + for (const auto& window : path.points | ranges::views::sliding(2)) + { + const auto& a = window[0]; + const auto& b = window[1]; + + std::vector span_pixels; + if (path.mesh->layers[layer_nr_].texture_data_provider_->getAreaPreferencesForSpan(a.toPoint2LL(), b.toPoint2LL(), "label", span_pixels)) + { + const auto pixel_span_3d = (b - a) / static_cast(span_pixels.size()); + auto last_pixel = TextureArea::Normal; + for (auto [idx, pixel] : span_pixels | ranges::views::enumerate) + { + // TODO: Just make it 'random' for now -- later, use: + // - A message/id of some sort. + // - Some sort of simple 'text to pixels' method (lookup table?) + // - IdFieldInfo (already made) to get the plane-normal(s) right (well, at least approximately -- it now does so only coursely, by axis) + const bool raw_val = (std::rand() % 2 == 0); + + const bool preferred = (pixel == TextureArea::Preferred); + if (preferred || last_pixel != pixel) + { + const bool val = preferred && raw_val; + new_points.push_back(a + (idx * pixel_span_3d) + (pixel_span_3d / 2) + (val ? offset_pt : zero_pt)); + message_bits.push_back(val); + } + last_pixel = pixel; + } + } + + new_points.push_back(b); + message_bits.push_back(false); + } + + if (new_points.size() != path.points.size()) + { + path.points = new_points; + path.message_bit_per_point = message_bits; + } + } +} + std::shared_ptr ExtruderPlan::findFirstPrintedMesh() const { for (const GCodePath& path : paths_) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 12dc4b1ea3..bf8cd8c7a0 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1310,6 +1310,8 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS gcode_layer.applyBackPressureCompensation(); time_keeper.registerTime("Back pressure comp."); + gcode_layer.applyIdLabel(); + return { &gcode_layer, timer_total.elapsed().count(), time_keeper.getRegisteredTimes() }; } diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index fbedd322a7..77863f1026 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -305,6 +305,9 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe spdlog::info("No painting-operation data specified for mesh `{}`.", static_cast(&mesh)); } AABB3D label_aabb; + // FIXME: We probably actually need an _inscribed_ AABB (which is way less trivial), since otherwise the message will overlap the edges of the label-space. + // FIXME: Find the _proper_ plane-normal(s) (primaty and secondary vectors), by doing principle component analysis + // (which fortunately _also_ means we can reduce the first above problem to 2D instead, which is at least somewhat easier). // calculate the height at which each layer is actually printed (printZ) for (LayerIndex layer_nr = 0; layer_nr < meshStorage.layers.size(); layer_nr++) @@ -364,7 +367,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe const auto& uv_b = segment.uv_end.value(); const auto& a = segment.start; const auto& b = segment.end; - mesh.texture_->visitLinePerPixel( + mesh.texture_->visitSpanPerPixel( uv_a, uv_b, [&label_aabb, &match_pixel, &uv_a, &uv_b, &a, &b, &height](const int32_t& pixel, const Point2F& uv) @@ -382,19 +385,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe } } - std::fprintf( - stderr, - "<%ld, %ld, %ld> -- <%ld, %ld, %ld>\n", - label_aabb.min_.x_, - label_aabb.min_.y_, - label_aabb.min_.z_, - label_aabb.max_.x_, - label_aabb.max_.y_, - label_aabb.max_.z_); - // FIXME/TODO: REMOVE print - - mesh.setIdFieldInfo(label_aabb); - // BOOKMARK! + meshStorage.setIdFieldInfo(label_aabb); delete slicerList[meshIdx]; diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index dcaff54d00..e2cedad4a3 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -28,6 +28,7 @@ #include "pathPlanning/CombPaths.h" #include "plugins/slots.h" #include "raft.h" // getTotalExtraLayers +#include "range/v3/view/enumerate.hpp" #include "range/v3/view/chunk_by.hpp" #include "settings/types/Ratio.h" #include "sliceDataStorage.h" @@ -2472,9 +2473,10 @@ void LayerPlan::writeExtrusionRelativeZ( const coord_t path_z_offset, double extrusion_mm3_per_mm, PrintFeatureType feature, + const std::optional& inline_comment, bool update_extrusion_offset) { - gcode.writeExtrusion(position + Point3LL(0, 0, z_ + path_z_offset), speed, extrusion_mm3_per_mm, feature, update_extrusion_offset); + gcode.writeExtrusion(position + Point3LL(0, 0, z_ + path_z_offset), speed, extrusion_mm3_per_mm, feature, inline_comment, update_extrusion_offset); } void LayerPlan::addLinesMonotonic( @@ -3376,13 +3378,19 @@ void LayerPlan::writeGCode(GCodeExport& gcode) if (! coasting) // not same as 'else', cause we might have changed [coasting] in the line above... { // normal path to gcode algorithm Point3LL prev_point = gcode.getPosition(); - for (const auto& pt : path.points) + for (auto [idx, pt] : path.points | ranges::views::enumerate) { const auto [_, time] = extruder_plan.getPointToPointTime(prev_point, pt, path); insertTempOnTime(time, path_idx); const double extrude_speed = speed * path.speed_back_pressure_factor; - writeExtrusionRelativeZ(gcode, pt, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type, update_extrusion_offset); + + // FIXME: Find a less ugly way to do this (and then don't copy the snipped all over the place). + constexpr std::string_view buzz_off = "BUZZ:0"; // FIXME: should be UV pixel coordinates! + constexpr std::string_view buzz_on = "BUZZ:1"; // FIXME: should be UV pixel coordinates! + const std::optional inline_comment = path.message_bit_per_point.has_value() ? std::make_optional(path.message_bit_per_point.value()[idx] ? buzz_on : buzz_off) : std::nullopt; + + writeExtrusionRelativeZ(gcode, pt, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type, inline_comment, update_extrusion_offset); sendLineTo(path, pt, extrude_speed); prev_point = pt; @@ -3424,6 +3432,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) path.z_offset + z_offset, spiral_path.getExtrusionMM3perMM(), spiral_path.config.type, + std::nullopt, // FIXME?: ID should probably work for spiralize as well. update_extrusion_offset); sendLineTo(spiral_path, Point3LL(p1.x_, p1.y_, z_offset), extrude_speed, layer_thickness_); } @@ -3556,7 +3565,12 @@ bool LayerPlan::writePathWithCoasting( auto [_, time] = extruder_plan.getPointToPointTime(previous_position, path.points[point_idx], path); insertTempOnTime(time, path_idx); - writeExtrusionRelativeZ(gcode, path.points[point_idx], extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); + // FIXME: Find a less ugly way to do this (and then don't copy the snipped all over the place). + constexpr std::string_view buzz_off = "BUZZ:0"; // FIXME: should be UV pixel coordinates! + constexpr std::string_view buzz_on = "BUZZ:1"; // FIXME: should be UV pixel coordinates! + const std::optional inline_comment = path.message_bit_per_point.has_value() ? std::make_optional(path.message_bit_per_point.value()[point_idx] ? buzz_on : buzz_off) : std::nullopt; + + writeExtrusionRelativeZ(gcode, path.points[point_idx], extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type, inline_comment); sendLineTo(path, path.points[point_idx], extrude_speed); previous_position = path.points[point_idx]; @@ -3682,6 +3696,15 @@ void LayerPlan::applyGradualFlow() } } +void LayerPlan::applyIdLabel() +{ + for (ExtruderPlan& extruder_plan : extruder_plans_) + { + // TODO: opt-out sure-to-be unaffected layers + extruder_plan.applyIdLabel(); + } +} + std::shared_ptr LayerPlan::findFirstPrintedMesh() const { for (const ExtruderPlan& extruder_plan : extruder_plans_) diff --git a/src/TextureDataProvider.cpp b/src/TextureDataProvider.cpp index c2df61f485..f8df934466 100644 --- a/src/TextureDataProvider.cpp +++ b/src/TextureDataProvider.cpp @@ -51,4 +51,37 @@ std::optional TextureDataProvider::getAreaPreference(const Point2LL return std::nullopt; } +bool TextureDataProvider::getAreaPreferencesForSpan(const Point2LL& a, const Point2LL& b, const std::string& feature, std::vector& res) const +{ + //FIXME: This doesn't take into account the whole 'a and b aren't necesarily in the same texture patch' thing, as the plain 'Image' class and its 'visitSpanPerPixels' method are of course unaware of it. + + auto data_mapping_iterator = texture_data_mapping_->find(feature); + if (data_mapping_iterator == texture_data_mapping_->end()) + { + return false; + } + + const TextureBitField& bit_field = data_mapping_iterator->second; + const std::optional point_uv_a = uv_coordinates_->getClosestUVCoordinates(a); + const std::optional point_uv_b = uv_coordinates_->getClosestUVCoordinates(b); + if (! (point_uv_a.has_value() && point_uv_b.has_value())) + { + return false; + } + + bool has_any = false; + texture_->visitSpanPerPixel( + point_uv_a.value(), + point_uv_b.value(), + [&bit_field, &res, &has_any](const int32_t pixel_data, const Point2F& pt) { + res.push_back( + static_cast( + // FIXME: The method to shift left & right here is the same as above, should be an inline method? + (pixel_data << (32 - 1 - bit_field.bit_range_end_index)) >> (32 - 1 - (bit_field.bit_range_end_index - bit_field.bit_range_start_index)) + )); + has_any |= (res.back() == TextureArea::Preferred); + }); + return has_any; +} + } // namespace cura diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index efaf31d39e..aa8398cb5c 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -925,9 +925,9 @@ void GCodeExport::writeTravel(const Point2LL& p, const Velocity& speed) { writeTravel(Point3LL(p.X, p.Y, current_layer_z_), speed); } -void GCodeExport::writeExtrusion(const Point2LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, bool update_extrusion_offset) +void GCodeExport::writeExtrusion(const Point2LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, const std::optional& inline_comment, bool update_extrusion_offset) { - writeExtrusion(Point3LL(p.X, p.Y, current_layer_z_), speed, extrusion_mm3_per_mm, feature, update_extrusion_offset); + writeExtrusion(Point3LL(p.X, p.Y, current_layer_z_), speed, extrusion_mm3_per_mm, feature, inline_comment, update_extrusion_offset); } void GCodeExport::writeTravel(const Point3LL& p, const Velocity& speed, const std::optional retract_distance) @@ -940,14 +940,14 @@ void GCodeExport::writeTravel(const Point3LL& p, const Velocity& speed, const st writeTravel(p.x_, p.y_, p.z_ + is_z_hopped_, speed, retract_distance); } -void GCodeExport::writeExtrusion(const Point3LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, bool update_extrusion_offset) +void GCodeExport::writeExtrusion(const Point3LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, const std::optional& inline_comment, bool update_extrusion_offset) { if (flavor_ == EGCodeFlavor::BFB) { writeMoveBFB(p.x_, p.y_, p.z_, speed, extrusion_mm3_per_mm, feature); return; } - writeExtrusion(p.x_, p.y_, p.z_, speed, extrusion_mm3_per_mm, feature, update_extrusion_offset); + writeExtrusion(p.x_, p.y_, p.z_, speed, extrusion_mm3_per_mm, feature, inline_comment, update_extrusion_offset); } void GCodeExport::writeMoveBFB(const int x, const int y, const int z, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature) @@ -1039,7 +1039,7 @@ void GCodeExport::writeTravel(const coord_t x, const coord_t y, const coord_t z, const PrintFeatureType travel_move_type = sendTravel(Point3LL(x, y, z), speed, extruder_attr, retraction_amounts); *output_stream_ << "G0"; - writeFXYZE(speed, x, y, z, current_e_value_, travel_move_type, retraction_amounts); + writeFXYZE(speed, x, y, z, current_e_value_, travel_move_type, std::nullopt, retraction_amounts); } void GCodeExport::writeExtrusion( @@ -1049,6 +1049,7 @@ void GCodeExport::writeExtrusion( const Velocity& speed, const double extrusion_mm3_per_mm, const PrintFeatureType& feature, + const std::optional& inline_comment, const bool update_extrusion_offset) { if (current_position_.x_ == x && current_position_.y_ == y && current_position_.z_ == z) @@ -1117,7 +1118,7 @@ void GCodeExport::writeExtrusion( const double new_e_value = current_e_value_ + extrusion_per_mm * diff_length; *output_stream_ << "G1"; - writeFXYZE(speed, x, y, z, new_e_value, feature); + writeFXYZE(speed, x, y, z, new_e_value, feature, inline_comment); } void GCodeExport::writeFXYZE( @@ -1127,6 +1128,7 @@ void GCodeExport::writeFXYZE( const coord_t z, const double e, const PrintFeatureType& feature, + const std::optional& inline_comment, const std::optional& retraction_amounts) { if (current_speed_ != speed) @@ -1155,6 +1157,10 @@ void GCodeExport::writeFXYZE( { const double output_e = (relative_extrusion_) ? e + current_e_offset_ - current_e_value_ : e + current_e_offset_; *output_stream_ << " " << extruder_attr_[current_extruder_].extruder_character_ << PrecisionedDouble{ 5, output_e }; + if (inline_comment.has_value()) + { + *output_stream_ << "; " << inline_comment.value(); + } current_e_value_ = e; } *output_stream_ << new_line_; diff --git a/src/mesh.cpp b/src/mesh.cpp index 03c53b2855..430d41fe71 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -9,6 +9,7 @@ #include #include "utils/Point3D.h" +#include "utils/types/idfieldinfo.h" namespace cura { @@ -24,31 +25,6 @@ static inline uint32_t pointHash(const Point3LL& p) ^ (((p.z_ + vertex_meld_distance / 2) / vertex_meld_distance) << 20); } -std::optional IdFieldInfo::from_aabb3d(const AABB3D& aabb) -{ - if (! aabb.exists()) - { - return std::make_optional(); - } - - typedef std::tuple axis_span_t; - std::array dif_per_axis = { std::make_tuple(IdFieldInfo::Axis::X, aabb.min_.x_, aabb.max_.x_), - std::make_tuple(IdFieldInfo::Axis::Y, aabb.min_.y_, aabb.max_.y_), - std::make_tuple(IdFieldInfo::Axis::Z, aabb.min_.z_, aabb.max_.z_) }; - std::stable_sort( - dif_per_axis.begin(), - dif_per_axis.end(), - [](const axis_span_t& a, const axis_span_t& b) - { - return std::llabs(std::get<2>(a) - std::get<1>(a)) > std::llabs(std::get<2>(b) - std::get<1>(b)); - }); - - return std::make_optional(IdFieldInfo{ - .primary_axis_ = std::get<0>(dif_per_axis[0]), - .secondary_axis_ = std::get<0>(dif_per_axis[1]), - .projection_field_ = AABB(Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1]))) }); -} - Mesh::Mesh(Settings& parent) : settings_(parent) , has_disconnected_faces(false) @@ -114,23 +90,6 @@ void Mesh::finish() } } -void Mesh::setIdFieldInfo(const AABB3D& aabb) -{ - id_field_info_ = IdFieldInfo::from_aabb3d(aabb); - - const auto& dbgval = id_field_info_.value(); - std::fprintf( - stderr, - "label %d: %ld - %ld // %d: %ld - %ld\n", - dbgval.primary_axis_, - dbgval.projection_field_.min_.X, - dbgval.projection_field_.max_.X, - dbgval.secondary_axis_, - dbgval.projection_field_.min_.Y, - dbgval.projection_field_.max_.Y); - // FIXME/TODO: REMOVE print -} - Point3LL Mesh::min() const { return aabb_.min_; diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index d26e2aaa8a..6e9cab4e64 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -97,6 +97,7 @@ void SliceLayer::getOutlines(Shape& result, bool external_polys_only) const SliceMeshStorage::SliceMeshStorage(Mesh* mesh, const size_t slice_layer_count) : settings(mesh->settings_) , mesh_name(mesh->mesh_name_) + , id_field_info(std::nullopt) , layer_nr_max_filled_layer(0) , bounding_box(mesh->getAABB()) , base_subdiv_cube(nullptr) @@ -106,6 +107,10 @@ SliceMeshStorage::SliceMeshStorage(Mesh* mesh, const size_t slice_layer_count) layers.resize(slice_layer_count); } +void SliceMeshStorage::setIdFieldInfo(const AABB3D& aabb) +{ + id_field_info = IdFieldInfo::from_aabb3d(aabb); +} bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr) const { diff --git a/src/utils/types/idfieldinfo.cpp b/src/utils/types/idfieldinfo.cpp new file mode 100644 index 0000000000..a6b4ab102d --- /dev/null +++ b/src/utils/types/idfieldinfo.cpp @@ -0,0 +1,39 @@ +#include "utils/types/idfieldinfo.h" + +#include +#include +#include + +using namespace cura; + +std::optional IdFieldInfo::from_aabb3d(const AABB3D& aabb) +{ + if (! aabb.exists()) + { + return std::nullopt; + } + + typedef std::tuple axis_span_t; + std::array dif_per_axis = { + std::make_tuple(IdFieldInfo::Axis::X, aabb.min_.x_, aabb.max_.x_), + std::make_tuple(IdFieldInfo::Axis::Y, aabb.min_.y_, aabb.max_.y_), + std::make_tuple(IdFieldInfo::Axis::Z, aabb.min_.z_, aabb.max_.z_) + }; + std::stable_sort( + dif_per_axis.begin(), + dif_per_axis.end(), + [](const axis_span_t& a, const axis_span_t& b) + { return std::llabs(std::get<2>(a) - std::get<1>(a)) > std::llabs(std::get<2>(b) - std::get<1>(b)); } + ); + + return std::make_optional( + IdFieldInfo{ + .primary_axis_ = std::get<0>(dif_per_axis[0]), + .secondary_axis_ = std::get<0>(dif_per_axis[1]), + .normal_ = std::get<0>(dif_per_axis[2]), + .projection_field_ = AABB( + Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), + Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1])) + ) + }); +} From 7eb5fdb26372672157e1a9ef8e22304999108033 Mon Sep 17 00:00:00 2001 From: rburema <41987080+rburema@users.noreply.github.com> Date: Thu, 17 Jul 2025 21:40:44 +0000 Subject: [PATCH 04/21] Apply clang-format --- include/gcodeExport.h | 16 ++++++++-- include/mesh.h | 21 ++++++------ include/utils/types/idfieldinfo.h | 53 ++++++++++++++++++------------- src/ExtruderPlan.cpp | 10 ++---- src/LayerPlan.cpp | 18 ++++++----- src/TextureDataProvider.cpp | 14 ++++---- src/gcodeExport.cpp | 16 ++++++++-- src/utils/types/idfieldinfo.cpp | 30 +++++++---------- 8 files changed, 103 insertions(+), 75 deletions(-) diff --git a/include/gcodeExport.h b/include/gcodeExport.h index 97f6baff9a..b69b5a6ded 100644 --- a/include/gcodeExport.h +++ b/include/gcodeExport.h @@ -372,7 +372,13 @@ class GCodeExport : public NoCopy * \param feature the feature that's currently printing * \param update_extrusion_offset whether to update the extrusion offset to match the current flow rate */ - void writeExtrusion(const Point2LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, const std::optional& inline_comment = std::nullopt, bool update_extrusion_offset = false); + void writeExtrusion( + const Point2LL& p, + const Velocity& speed, + double extrusion_mm3_per_mm, + PrintFeatureType feature, + const std::optional& inline_comment = std::nullopt, + bool update_extrusion_offset = false); /*! * Go to a X/Y location with the z-hopped Z value @@ -396,7 +402,13 @@ class GCodeExport : public NoCopy * \param feature the feature that's currently printing * \param update_extrusion_offset whether to update the extrusion offset to match the current flow rate */ - void writeExtrusion(const Point3LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, const std::optional& inline_comment = std::nullopt, bool update_extrusion_offset = false); + void writeExtrusion( + const Point3LL& p, + const Velocity& speed, + double extrusion_mm3_per_mm, + PrintFeatureType feature, + const std::optional& inline_comment = std::nullopt, + bool update_extrusion_offset = false); /*! * Initialize the extruder trains. diff --git a/include/mesh.h b/include/mesh.h index bf2ca09360..15b90a8823 100644 --- a/include/mesh.h +++ b/include/mesh.h @@ -103,8 +103,14 @@ class Image void visitSpanPerPixel(const Point2F& a, const Point2F& b, const std::function& func) const { - constexpr auto func_major_stepper = [](const int64_t& da, const int64_t& abs_db, const int64_t& i) { return da < 0 ? -i : i; }; - constexpr auto func_minor_stepper = [](const int64_t& da, const int64_t& abs_db, const int64_t& i) { return (i * da) / abs_db; }; + constexpr auto func_major_stepper = [](const int64_t& da, const int64_t& abs_db, const int64_t& i) + { + return da < 0 ? -i : i; + }; + constexpr auto func_minor_stepper = [](const int64_t& da, const int64_t& abs_db, const int64_t& i) + { + return (i * da) / abs_db; + }; const auto x0 = static_cast(a.x_ * width_); const auto y0 = static_cast(a.y_ * height_); @@ -123,10 +129,7 @@ class Image { const auto xi = x0 + func_x(dx, abs_dy, i_pix); const auto yi = y0 + func_y(dy, abs_dx, i_pix); - func( - getPixel(xi, yi), - Point2F(static_cast(xi) / width_, static_cast(yi) / height_) - ); + func(getPixel(xi, yi), Point2F(static_cast(xi) / width_, static_cast(yi) / height_)); } } @@ -150,7 +153,7 @@ class Mesh std::unordered_map> vertex_hash_map_; AABB3D aabb_; - //std::optional id_field_info_; + // std::optional id_field_info_; public: std::vector vertices_; //!< list of all vertices in the mesh @@ -182,8 +185,8 @@ class Mesh void clear(); //!< clears all data void finish(); //!< complete the model : set the connected_face_index fields of the faces. - //void setIdFieldInfo(const AABB3D& aabb); - //std::optional getIdFieldInfo() const; + // void setIdFieldInfo(const AABB3D& aabb); + // std::optional getIdFieldInfo() const; Point3LL min() const; //!< min (in x,y and z) vertex of the bounding box Point3LL max() const; //!< max (in x,y and z) vertex of the bounding box diff --git a/include/utils/types/idfieldinfo.h b/include/utils/types/idfieldinfo.h index 29f32e3b66..c0297d8839 100644 --- a/include/utils/types/idfieldinfo.h +++ b/include/utils/types/idfieldinfo.h @@ -4,37 +4,46 @@ #ifndef ID_FIELD_INFO_H #define ID_FIELD_INFO_H -#include "utils/AABB3D.h" - #include +#include "utils/AABB3D.h" + namespace cura { - // FIXME?: This now goes per-axis, because that's what we get by creating it from the AABB, but it should be a 'free' plane-normal(s). - // We'd probably need to do a little principal component analysis to _properly_ get the primary and secondary axii. +// FIXME?: This now goes per-axis, because that's what we get by creating it from the AABB, but it should be a 'free' plane-normal(s). +// We'd probably need to do a little principal component analysis to _properly_ get the primary and secondary axii. - struct IdFieldInfo +struct IdFieldInfo +{ + enum class Axis { - enum class Axis { X, Y, Z }; - Axis primary_axis_ = Axis::X; - Axis secondary_axis_ = Axis::Z; - Axis normal_ = Axis::Y; - AABB projection_field_; + X, + Y, + Z + }; + Axis primary_axis_ = Axis::X; + Axis secondary_axis_ = Axis::Z; + Axis normal_ = Axis::Y; + AABB projection_field_; - public: - static std::optional from_aabb3d(const AABB3D& aabb); +public: + static std::optional from_aabb3d(const AABB3D& aabb); - Point3LL normal_offset(coord_t offset) const + Point3LL normal_offset(coord_t offset) const + { + switch (normal_) { - switch (normal_) - { - case cura::IdFieldInfo::Axis::X: return Point3LL(offset, 0, 0); - case cura::IdFieldInfo::Axis::Y: return Point3LL(0, offset, 0); - case cura::IdFieldInfo::Axis::Z: return Point3LL(0, 0, offset); - default: return Point3LL(0, 0, 0); - } + case cura::IdFieldInfo::Axis::X: + return Point3LL(offset, 0, 0); + case cura::IdFieldInfo::Axis::Y: + return Point3LL(0, offset, 0); + case cura::IdFieldInfo::Axis::Z: + return Point3LL(0, 0, offset); + default: + return Point3LL(0, 0, 0); } - }; -}// namespace cura + } +}; +} // namespace cura #endif // ID_FIELD_INFO_H diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 6f114ab766..300caf9fc2 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -80,13 +80,9 @@ void ExtruderPlan::applyIdLabel() constexpr coord_t inset_dist = 40; // TODO?: make this configurable as well? for (auto& path : paths_) { - if ( - path.mesh == nullptr || - path.mesh->layers[layer_nr_].texture_data_provider_ == nullptr || - (! path.mesh->id_field_info) || - (path.mesh->id_field_info.value().normal_ != IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::OuterWall) || - (path.mesh->id_field_info.value().normal_ == IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::Skin) - ) + if (path.mesh == nullptr || path.mesh->layers[layer_nr_].texture_data_provider_ == nullptr || (! path.mesh->id_field_info) + || (path.mesh->id_field_info.value().normal_ != IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::OuterWall) + || (path.mesh->id_field_info.value().normal_ == IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::Skin)) { continue; } diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index e2cedad4a3..2370e9855d 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -28,8 +28,8 @@ #include "pathPlanning/CombPaths.h" #include "plugins/slots.h" #include "raft.h" // getTotalExtraLayers -#include "range/v3/view/enumerate.hpp" #include "range/v3/view/chunk_by.hpp" +#include "range/v3/view/enumerate.hpp" #include "settings/types/Ratio.h" #include "sliceDataStorage.h" #include "utils/Simplify.h" @@ -3386,9 +3386,10 @@ void LayerPlan::writeGCode(GCodeExport& gcode) const double extrude_speed = speed * path.speed_back_pressure_factor; // FIXME: Find a less ugly way to do this (and then don't copy the snipped all over the place). - constexpr std::string_view buzz_off = "BUZZ:0"; // FIXME: should be UV pixel coordinates! - constexpr std::string_view buzz_on = "BUZZ:1"; // FIXME: should be UV pixel coordinates! - const std::optional inline_comment = path.message_bit_per_point.has_value() ? std::make_optional(path.message_bit_per_point.value()[idx] ? buzz_on : buzz_off) : std::nullopt; + constexpr std::string_view buzz_off = "BUZZ:0"; // FIXME: should be UV pixel coordinates! + constexpr std::string_view buzz_on = "BUZZ:1"; // FIXME: should be UV pixel coordinates! + const std::optional inline_comment + = path.message_bit_per_point.has_value() ? std::make_optional(path.message_bit_per_point.value()[idx] ? buzz_on : buzz_off) : std::nullopt; writeExtrusionRelativeZ(gcode, pt, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type, inline_comment, update_extrusion_offset); sendLineTo(path, pt, extrude_speed); @@ -3432,7 +3433,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) path.z_offset + z_offset, spiral_path.getExtrusionMM3perMM(), spiral_path.config.type, - std::nullopt, // FIXME?: ID should probably work for spiralize as well. + std::nullopt, // FIXME?: ID should probably work for spiralize as well. update_extrusion_offset); sendLineTo(spiral_path, Point3LL(p1.x_, p1.y_, z_offset), extrude_speed, layer_thickness_); } @@ -3566,9 +3567,10 @@ bool LayerPlan::writePathWithCoasting( insertTempOnTime(time, path_idx); // FIXME: Find a less ugly way to do this (and then don't copy the snipped all over the place). - constexpr std::string_view buzz_off = "BUZZ:0"; // FIXME: should be UV pixel coordinates! - constexpr std::string_view buzz_on = "BUZZ:1"; // FIXME: should be UV pixel coordinates! - const std::optional inline_comment = path.message_bit_per_point.has_value() ? std::make_optional(path.message_bit_per_point.value()[point_idx] ? buzz_on : buzz_off) : std::nullopt; + constexpr std::string_view buzz_off = "BUZZ:0"; // FIXME: should be UV pixel coordinates! + constexpr std::string_view buzz_on = "BUZZ:1"; // FIXME: should be UV pixel coordinates! + const std::optional inline_comment + = path.message_bit_per_point.has_value() ? std::make_optional(path.message_bit_per_point.value()[point_idx] ? buzz_on : buzz_off) : std::nullopt; writeExtrusionRelativeZ(gcode, path.points[point_idx], extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type, inline_comment); sendLineTo(path, path.points[point_idx], extrude_speed); diff --git a/src/TextureDataProvider.cpp b/src/TextureDataProvider.cpp index f8df934466..6f23c96de1 100644 --- a/src/TextureDataProvider.cpp +++ b/src/TextureDataProvider.cpp @@ -53,7 +53,8 @@ std::optional TextureDataProvider::getAreaPreference(const Point2LL bool TextureDataProvider::getAreaPreferencesForSpan(const Point2LL& a, const Point2LL& b, const std::string& feature, std::vector& res) const { - //FIXME: This doesn't take into account the whole 'a and b aren't necesarily in the same texture patch' thing, as the plain 'Image' class and its 'visitSpanPerPixels' method are of course unaware of it. + // FIXME: This doesn't take into account the whole 'a and b aren't necesarily in the same texture patch' thing, as the plain 'Image' class and its 'visitSpanPerPixels' method + // are of course unaware of it. auto data_mapping_iterator = texture_data_mapping_->find(feature); if (data_mapping_iterator == texture_data_mapping_->end()) @@ -73,12 +74,11 @@ bool TextureDataProvider::getAreaPreferencesForSpan(const Point2LL& a, const Poi texture_->visitSpanPerPixel( point_uv_a.value(), point_uv_b.value(), - [&bit_field, &res, &has_any](const int32_t pixel_data, const Point2F& pt) { - res.push_back( - static_cast( - // FIXME: The method to shift left & right here is the same as above, should be an inline method? - (pixel_data << (32 - 1 - bit_field.bit_range_end_index)) >> (32 - 1 - (bit_field.bit_range_end_index - bit_field.bit_range_start_index)) - )); + [&bit_field, &res, &has_any](const int32_t pixel_data, const Point2F& pt) + { + res.push_back(static_cast( + // FIXME: The method to shift left & right here is the same as above, should be an inline method? + (pixel_data << (32 - 1 - bit_field.bit_range_end_index)) >> (32 - 1 - (bit_field.bit_range_end_index - bit_field.bit_range_start_index)))); has_any |= (res.back() == TextureArea::Preferred); }); return has_any; diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index aa8398cb5c..0d37b833b5 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -925,7 +925,13 @@ void GCodeExport::writeTravel(const Point2LL& p, const Velocity& speed) { writeTravel(Point3LL(p.X, p.Y, current_layer_z_), speed); } -void GCodeExport::writeExtrusion(const Point2LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, const std::optional& inline_comment, bool update_extrusion_offset) +void GCodeExport::writeExtrusion( + const Point2LL& p, + const Velocity& speed, + double extrusion_mm3_per_mm, + PrintFeatureType feature, + const std::optional& inline_comment, + bool update_extrusion_offset) { writeExtrusion(Point3LL(p.X, p.Y, current_layer_z_), speed, extrusion_mm3_per_mm, feature, inline_comment, update_extrusion_offset); } @@ -940,7 +946,13 @@ void GCodeExport::writeTravel(const Point3LL& p, const Velocity& speed, const st writeTravel(p.x_, p.y_, p.z_ + is_z_hopped_, speed, retract_distance); } -void GCodeExport::writeExtrusion(const Point3LL& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, const std::optional& inline_comment, bool update_extrusion_offset) +void GCodeExport::writeExtrusion( + const Point3LL& p, + const Velocity& speed, + double extrusion_mm3_per_mm, + PrintFeatureType feature, + const std::optional& inline_comment, + bool update_extrusion_offset) { if (flavor_ == EGCodeFlavor::BFB) { diff --git a/src/utils/types/idfieldinfo.cpp b/src/utils/types/idfieldinfo.cpp index a6b4ab102d..daf5255567 100644 --- a/src/utils/types/idfieldinfo.cpp +++ b/src/utils/types/idfieldinfo.cpp @@ -1,7 +1,7 @@ #include "utils/types/idfieldinfo.h" -#include #include +#include #include using namespace cura; @@ -14,26 +14,20 @@ std::optional IdFieldInfo::from_aabb3d(const AABB3D& aabb) } typedef std::tuple axis_span_t; - std::array dif_per_axis = { - std::make_tuple(IdFieldInfo::Axis::X, aabb.min_.x_, aabb.max_.x_), - std::make_tuple(IdFieldInfo::Axis::Y, aabb.min_.y_, aabb.max_.y_), - std::make_tuple(IdFieldInfo::Axis::Z, aabb.min_.z_, aabb.max_.z_) - }; + std::array dif_per_axis = { std::make_tuple(IdFieldInfo::Axis::X, aabb.min_.x_, aabb.max_.x_), + std::make_tuple(IdFieldInfo::Axis::Y, aabb.min_.y_, aabb.max_.y_), + std::make_tuple(IdFieldInfo::Axis::Z, aabb.min_.z_, aabb.max_.z_) }; std::stable_sort( dif_per_axis.begin(), dif_per_axis.end(), [](const axis_span_t& a, const axis_span_t& b) - { return std::llabs(std::get<2>(a) - std::get<1>(a)) > std::llabs(std::get<2>(b) - std::get<1>(b)); } - ); - - return std::make_optional( - IdFieldInfo{ - .primary_axis_ = std::get<0>(dif_per_axis[0]), - .secondary_axis_ = std::get<0>(dif_per_axis[1]), - .normal_ = std::get<0>(dif_per_axis[2]), - .projection_field_ = AABB( - Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), - Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1])) - ) + { + return std::llabs(std::get<2>(a) - std::get<1>(a)) > std::llabs(std::get<2>(b) - std::get<1>(b)); }); + + return std::make_optional(IdFieldInfo{ + .primary_axis_ = std::get<0>(dif_per_axis[0]), + .secondary_axis_ = std::get<0>(dif_per_axis[1]), + .normal_ = std::get<0>(dif_per_axis[2]), + .projection_field_ = AABB(Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1]))) }); } From 8890a048d961190eee0b8a1fb3d03822ce0f56c4 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 18 Jul 2025 11:25:43 +0200 Subject: [PATCH 05/21] Label points didn't include the one _just_ before the label. This caused issues when the next point (that's actually part of the label) is then inset, as the line before the label will then slope inwards toward the label. -- Hackathon 2025: Slice-ID or Print-ID. part of UMH-2025 --- src/ExtruderPlan.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 300caf9fc2..8281c46f8e 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -80,9 +80,12 @@ void ExtruderPlan::applyIdLabel() constexpr coord_t inset_dist = 40; // TODO?: make this configurable as well? for (auto& path : paths_) { - if (path.mesh == nullptr || path.mesh->layers[layer_nr_].texture_data_provider_ == nullptr || (! path.mesh->id_field_info) - || (path.mesh->id_field_info.value().normal_ != IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::OuterWall) - || (path.mesh->id_field_info.value().normal_ == IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::Skin)) + if (path.points.empty() || + path.mesh == nullptr || + path.mesh->layers[layer_nr_].texture_data_provider_ == nullptr || + (! path.mesh->id_field_info) || + (path.mesh->id_field_info.value().normal_ != IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::OuterWall) || + (path.mesh->id_field_info.value().normal_ == IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::Skin)) { continue; } @@ -104,6 +107,7 @@ void ExtruderPlan::applyIdLabel() { const auto pixel_span_3d = (b - a) / static_cast(span_pixels.size()); auto last_pixel = TextureArea::Normal; + auto last_pt = a; for (auto [idx, pixel] : span_pixels | ranges::views::enumerate) { // TODO: Just make it 'random' for now -- later, use: @@ -111,15 +115,23 @@ void ExtruderPlan::applyIdLabel() // - Some sort of simple 'text to pixels' method (lookup table?) // - IdFieldInfo (already made) to get the plane-normal(s) right (well, at least approximately -- it now does so only coursely, by axis) const bool raw_val = (std::rand() % 2 == 0); + const auto raw_pt = a + (idx * pixel_span_3d) + (pixel_span_3d / 2); const bool preferred = (pixel == TextureArea::Preferred); if (preferred || last_pixel != pixel) { + if (last_pixel != TextureArea::Preferred) + { + new_points.push_back(last_pt); + message_bits.push_back(false); + } + const bool val = preferred && raw_val; - new_points.push_back(a + (idx * pixel_span_3d) + (pixel_span_3d / 2) + (val ? offset_pt : zero_pt)); + new_points.push_back(raw_pt + (val ? offset_pt : zero_pt)); message_bits.push_back(val); } last_pixel = pixel; + last_pt = raw_pt; } } From 1febbdd3d50959363b123ebbf4f65f7c329fc3f1 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 18 Jul 2025 12:34:03 +0200 Subject: [PATCH 06/21] Fix int-vector * integer multiplication. (Not sure if the floating-point one works.) At least the integer multiplication one works as expected now. Necesary for Hackathon '25 -- Print-ID/Slice-ID. done as part of UMH-2025 --- include/geometry/Point3LL.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/geometry/Point3LL.h b/include/geometry/Point3LL.h index 97fb00167e..039460fe42 100644 --- a/include/geometry/Point3LL.h +++ b/include/geometry/Point3LL.h @@ -49,12 +49,19 @@ class Point3LL Point3LL operator*(const Point3LL& p) const; //!< Element-wise multiplication. For dot product, use .dot()! Point3LL operator/(const Point3LL& p) const; - template + template Point3LL operator*(const T& i) const { return { std::llround(static_cast(x_) * i), std::llround(static_cast(y_) * i), std::llround(static_cast(z_) * i) }; } + template + Point3LL operator*(const T& i) const + { + const coord_t ii = static_cast(i); + return { x_ * ii, y_ * ii, z_ * ii }; + } + template Point3LL operator/(const T& i) const { From 0e7f28903697de4b6fcfdf07511f802dc79b5dc3 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 18 Jul 2025 14:15:34 +0200 Subject: [PATCH 07/21] Print-ID/Slice-ID: Pass UV coords through to gcode (not yet mapped to label-uvs). WIP At least they're there now -- now 'all' that's needed is to map the feature-texture UV's to ID-label-texture UV's (and for the Slice-ID specifically, generate a message, to pixels, then get the relevant pixel from that -- Print-ID should be done on the printer of course, hence the gcode changes) -- leave the other TODO's and FIXME's for now -- Necesary for Hackathon '25 -- Print-ID/Slice-ID. part of UMH-2025 --- include/TextureDataMapping.h | 4 ++++ include/TextureDataProvider.h | 2 +- include/pathPlanning/GCodePath.h | 3 ++- include/utils/Point2F.h | 2 ++ src/ExtruderPlan.cpp | 30 +++++++++++++++--------------- src/LayerPlan.cpp | 26 ++++++++++++-------------- src/TextureDataProvider.cpp | 15 +++++++++------ src/gcodeExport.cpp | 2 +- 8 files changed, 46 insertions(+), 38 deletions(-) diff --git a/include/TextureDataMapping.h b/include/TextureDataMapping.h index b0712a0c6d..270b9ed0a6 100644 --- a/include/TextureDataMapping.h +++ b/include/TextureDataMapping.h @@ -4,6 +4,8 @@ #ifndef TEXTUREDATAMAPPING_H #define TEXTUREDATAMAPPING_H +#include "utils/Point2F.h" + #include #include @@ -34,6 +36,8 @@ enum class TextureArea Avoid = 2, // Area is to be avoided }; +using Texel = std::pair; + } // namespace cura namespace fmt diff --git a/include/TextureDataProvider.h b/include/TextureDataProvider.h index 771a592cc5..6ecb915d95 100644 --- a/include/TextureDataProvider.h +++ b/include/TextureDataProvider.h @@ -28,7 +28,7 @@ class TextureDataProvider std::optional getAreaPreference(const Point2LL& position, const std::string& feature) const; - bool getAreaPreferencesForSpan(const Point2LL& a, const Point2LL& b, const std::string& feature, std::vector& res) const; + bool getTexelsForSpan(const Point2LL& a, const Point2LL& b, const std::string& feature, std::vector& res) const; private: std::shared_ptr uv_coordinates_; diff --git a/include/pathPlanning/GCodePath.h b/include/pathPlanning/GCodePath.h index ee73fa3417..2c0e856b57 100644 --- a/include/pathPlanning/GCodePath.h +++ b/include/pathPlanning/GCodePath.h @@ -13,6 +13,7 @@ #include "geometry/Point2LL.h" #include "settings/types/Ratio.h" #include "sliceDataStorage.h" +#include "utils/Point2F.h" namespace cura { @@ -46,7 +47,7 @@ struct GCodePath bool perform_z_hop{ false }; //!< Whether to perform a z_hop in this path, which is assumed to be a travel path. bool perform_prime{ false }; //!< Whether this path is preceded by a prime (blob) std::vector points{}; //!< The points constituting this path. The Z coordinate is an offset relative to the actual layer height, added to the global z_offset. - std::optional> message_bit_per_point = std::nullopt; + std::optional> idlabel_uv_per_point = std::nullopt; //!< If this path is part of an ID-label, contains for each path-point an UV-point into the label-texture. bool done{ false }; //!< Path is finished, no more moves should be added, and a new path should be started instead of any appending done to this one. double fan_speed{ GCodePathConfig::FAN_SPEED_DEFAULT }; //!< fan speed override for this path, value should be within range 0-100 (inclusive) and ignored otherwise TimeMaterialEstimates estimates{}; //!< Naive time and material estimates diff --git a/include/utils/Point2F.h b/include/utils/Point2F.h index 07f10c1849..265be46763 100644 --- a/include/utils/Point2F.h +++ b/include/utils/Point2F.h @@ -4,6 +4,8 @@ #ifndef POINT2F_H #define POINT2F_H +#include + namespace cura { diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 8281c46f8e..5df8e1cf19 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -91,24 +91,25 @@ void ExtruderPlan::applyIdLabel() } const auto zero_pt = Point3LL(0, 0, 0); - const auto offset_pt = path.mesh->id_field_info.value().normal_offset(-inset_dist); + const auto offset_pt = ((path.points.front() + path.points.back()) / 2 - path.mesh->bounding_box.getMiddle()).resized(inset_dist); + const auto signal_no_uv = Point2F(std::numeric_limits::signaling_NaN(), std::numeric_limits::signaling_NaN()); std::vector new_points; - std::vector message_bits; - new_points.push_back(path.points[0]); - message_bits.push_back(false); + std::vector idlabel_uvs; + new_points.push_back(path.points.front()); + idlabel_uvs.push_back(signal_no_uv); for (const auto& window : path.points | ranges::views::sliding(2)) { const auto& a = window[0]; const auto& b = window[1]; - std::vector span_pixels; - if (path.mesh->layers[layer_nr_].texture_data_provider_->getAreaPreferencesForSpan(a.toPoint2LL(), b.toPoint2LL(), "label", span_pixels)) + std::vector span_pixels; + if (path.mesh->layers[layer_nr_].texture_data_provider_->getTexelsForSpan(a.toPoint2LL(), b.toPoint2LL(), "label", span_pixels)) { const auto pixel_span_3d = (b - a) / static_cast(span_pixels.size()); auto last_pixel = TextureArea::Normal; auto last_pt = a; - for (auto [idx, pixel] : span_pixels | ranges::views::enumerate) + for (auto [idx, texel] : span_pixels | ranges::views::enumerate) { // TODO: Just make it 'random' for now -- later, use: // - A message/id of some sort. @@ -116,33 +117,32 @@ void ExtruderPlan::applyIdLabel() // - IdFieldInfo (already made) to get the plane-normal(s) right (well, at least approximately -- it now does so only coursely, by axis) const bool raw_val = (std::rand() % 2 == 0); const auto raw_pt = a + (idx * pixel_span_3d) + (pixel_span_3d / 2); - - const bool preferred = (pixel == TextureArea::Preferred); - if (preferred || last_pixel != pixel) + const bool preferred = (texel.first == TextureArea::Preferred); + if (preferred || last_pixel != texel.first) { if (last_pixel != TextureArea::Preferred) { new_points.push_back(last_pt); - message_bits.push_back(false); + idlabel_uvs.push_back(signal_no_uv); } const bool val = preferred && raw_val; new_points.push_back(raw_pt + (val ? offset_pt : zero_pt)); - message_bits.push_back(val); + idlabel_uvs.push_back(texel.second); } - last_pixel = pixel; + last_pixel = texel.first; last_pt = raw_pt; } } new_points.push_back(b); - message_bits.push_back(false); + idlabel_uvs.push_back(Point2F(NAN, NAN)); } if (new_points.size() != path.points.size()) { path.points = new_points; - path.message_bit_per_point = message_bits; + path.idlabel_uv_per_point = idlabel_uvs; } } } diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 2370e9855d..74db7ffb34 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2976,6 +2976,16 @@ void LayerPlan::processFanSpeedAndMinimalLayerTime(Point2LL starting_position) last_extruder_plan.processFanSpeedForMinimalLayerTime(maximum_cool_min_layer_time, other_extr_plan_time); } +std::optional getIdLabelUvComment(const std::optional>& idlabel_uvs, const size_t idx) +{ + if (! idlabel_uvs.has_value()) + { + return std::nullopt; + } + const auto uv_pt = idlabel_uvs.value()[idx]; + return (std::isnan(uv_pt.x_) || std::isnan(uv_pt.y_)) ? std::nullopt : std::make_optional(fmt::format("UV: {0:.4f} {1:.4f}", uv_pt.x_, uv_pt.y_)); +} + void LayerPlan::writeGCode(GCodeExport& gcode) { auto communication = Application::getInstance().communication_; @@ -3385,13 +3395,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) const double extrude_speed = speed * path.speed_back_pressure_factor; - // FIXME: Find a less ugly way to do this (and then don't copy the snipped all over the place). - constexpr std::string_view buzz_off = "BUZZ:0"; // FIXME: should be UV pixel coordinates! - constexpr std::string_view buzz_on = "BUZZ:1"; // FIXME: should be UV pixel coordinates! - const std::optional inline_comment - = path.message_bit_per_point.has_value() ? std::make_optional(path.message_bit_per_point.value()[idx] ? buzz_on : buzz_off) : std::nullopt; - - writeExtrusionRelativeZ(gcode, pt, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type, inline_comment, update_extrusion_offset); + writeExtrusionRelativeZ(gcode, pt, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type, getIdLabelUvComment(path.idlabel_uv_per_point, idx), update_extrusion_offset); sendLineTo(path, pt, extrude_speed); prev_point = pt; @@ -3566,13 +3570,7 @@ bool LayerPlan::writePathWithCoasting( auto [_, time] = extruder_plan.getPointToPointTime(previous_position, path.points[point_idx], path); insertTempOnTime(time, path_idx); - // FIXME: Find a less ugly way to do this (and then don't copy the snipped all over the place). - constexpr std::string_view buzz_off = "BUZZ:0"; // FIXME: should be UV pixel coordinates! - constexpr std::string_view buzz_on = "BUZZ:1"; // FIXME: should be UV pixel coordinates! - const std::optional inline_comment - = path.message_bit_per_point.has_value() ? std::make_optional(path.message_bit_per_point.value()[point_idx] ? buzz_on : buzz_off) : std::nullopt; - - writeExtrusionRelativeZ(gcode, path.points[point_idx], extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type, inline_comment); + writeExtrusionRelativeZ(gcode, path.points[point_idx], extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type, getIdLabelUvComment(path.idlabel_uv_per_point, path_idx)); sendLineTo(path, path.points[point_idx], extrude_speed); previous_position = path.points[point_idx]; diff --git a/src/TextureDataProvider.cpp b/src/TextureDataProvider.cpp index 6f23c96de1..4aadc6222e 100644 --- a/src/TextureDataProvider.cpp +++ b/src/TextureDataProvider.cpp @@ -51,7 +51,7 @@ std::optional TextureDataProvider::getAreaPreference(const Point2LL return std::nullopt; } -bool TextureDataProvider::getAreaPreferencesForSpan(const Point2LL& a, const Point2LL& b, const std::string& feature, std::vector& res) const +bool TextureDataProvider::getTexelsForSpan(const Point2LL& a, const Point2LL& b, const std::string& feature, std::vector& res) const { // FIXME: This doesn't take into account the whole 'a and b aren't necesarily in the same texture patch' thing, as the plain 'Image' class and its 'visitSpanPerPixels' method // are of course unaware of it. @@ -74,12 +74,15 @@ bool TextureDataProvider::getAreaPreferencesForSpan(const Point2LL& a, const Poi texture_->visitSpanPerPixel( point_uv_a.value(), point_uv_b.value(), - [&bit_field, &res, &has_any](const int32_t pixel_data, const Point2F& pt) + [&bit_field, &res, &has_any](const int32_t pixel_data, const Point2F& uv_pt) { - res.push_back(static_cast( - // FIXME: The method to shift left & right here is the same as above, should be an inline method? - (pixel_data << (32 - 1 - bit_field.bit_range_end_index)) >> (32 - 1 - (bit_field.bit_range_end_index - bit_field.bit_range_start_index)))); - has_any |= (res.back() == TextureArea::Preferred); + res.push_back({ + static_cast( + // FIXME: The method to shift left & right here is the same as above, should be an inline method? + (pixel_data << (32 - 1 - bit_field.bit_range_end_index)) >> (32 - 1 - (bit_field.bit_range_end_index - bit_field.bit_range_start_index))), + uv_pt + }); + has_any |= (res.back().first == TextureArea::Preferred); }); return has_any; } diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index 0d37b833b5..3dbfb8fad6 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -1171,7 +1171,7 @@ void GCodeExport::writeFXYZE( *output_stream_ << " " << extruder_attr_[current_extruder_].extruder_character_ << PrecisionedDouble{ 5, output_e }; if (inline_comment.has_value()) { - *output_stream_ << "; " << inline_comment.value(); + *output_stream_ << " ;" << inline_comment.value(); } current_e_value_ = e; } From b7c9201514486378711630068589351d179cff11 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 18 Jul 2025 15:41:35 +0200 Subject: [PATCH 08/21] Slice-ID/Print-ID: Should now use the actual label-UV, instead of data-tex-UV coords. That _should_ take care of the 'Cura-part' of the print-ID (minus all the FIXME's) -- Image is still random at the moment though, that should be the next step for the Slice-ID -- Necesary for Hackathon '25 -- Print-ID/Slice-ID. part of UMH-2025 --- include/ExtruderPlan.h | 3 ++- include/LayerPlan.h | 2 +- include/utils/types/idfieldinfo.h | 36 ++++++++++++++++++++----------- src/ExtruderPlan.cpp | 12 ++++++++--- src/FffGcodeWriter.cpp | 16 +++++++++++++- src/LayerPlan.cpp | 4 ++-- src/sliceDataStorage.cpp | 2 +- src/utils/types/idfieldinfo.cpp | 7 ++++-- 8 files changed, 58 insertions(+), 24 deletions(-) diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index 6d9e0d4c2a..7dc5f11a3c 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -25,6 +25,7 @@ namespace cura { +class Image; class LayerPlanBuffer; class LayerPlan; /*! @@ -126,7 +127,7 @@ class ExtruderPlan /*! */ - void applyIdLabel(); + void applyIdLabel(const Image& slice_id_texture); /*! * Gets the mesh being printed first on this plan diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 0fcea9edd6..a941b926ce 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -837,7 +837,7 @@ class LayerPlan : public NoCopy /*! */ - void applyIdLabel(); + void applyIdLabel(const Image& slice_id_texture); /*! * Gets the mesh being printed first on this layer diff --git a/include/utils/types/idfieldinfo.h b/include/utils/types/idfieldinfo.h index c0297d8839..e7fb16e230 100644 --- a/include/utils/types/idfieldinfo.h +++ b/include/utils/types/idfieldinfo.h @@ -7,6 +7,7 @@ #include #include "utils/AABB3D.h" +#include "utils/Point2F.h" namespace cura { @@ -21,27 +22,36 @@ struct IdFieldInfo Y, Z }; + static coord_t getAxisValue(const Axis ax, const Point3LL& pt) + { + switch (ax) + { + case Axis::X: return pt.x_; + case Axis::Y: return pt.y_; + case Axis::Z: return pt.z_; + default: return 0; + } + } + + static float mirrorNegative(const float in) + { + return in < 0.0f ? 1.0 + in : in; + } + Axis primary_axis_ = Axis::X; Axis secondary_axis_ = Axis::Z; Axis normal_ = Axis::Y; AABB projection_field_; public: - static std::optional from_aabb3d(const AABB3D& aabb); + static std::optional fromAabb3d(const AABB3D& aabb); - Point3LL normal_offset(coord_t offset) const + Point2F worldPointToLabelUv(const Point3LL& pt) const { - switch (normal_) - { - case cura::IdFieldInfo::Axis::X: - return Point3LL(offset, 0, 0); - case cura::IdFieldInfo::Axis::Y: - return Point3LL(0, offset, 0); - case cura::IdFieldInfo::Axis::Z: - return Point3LL(0, 0, offset); - default: - return Point3LL(0, 0, 0); - } + return Point2F( + mirrorNegative(static_cast(getAxisValue(primary_axis_, pt) - projection_field_.min_.X) / (projection_field_.max_.X - projection_field_.min_.X)), + mirrorNegative(static_cast(getAxisValue(secondary_axis_, pt) - projection_field_.min_.Y) / (projection_field_.max_.Y - projection_field_.min_.Y)) + ); } }; } // namespace cura diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 5df8e1cf19..5860a053dc 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -3,6 +3,7 @@ #include "ExtruderPlan.h" +#include "mesh.h" // For 'Image' class. #include "TextureDataProvider.h" namespace cura @@ -73,7 +74,7 @@ void ExtruderPlan::applyBackPressureCompensation(const Ratio back_pressure_compe } } -void ExtruderPlan::applyIdLabel() +void ExtruderPlan::applyIdLabel(const Image& slice_id_texture) { // TODO?: message (format) should be a (string) setting, like 'ID: \H:\M:\S' or something @@ -94,6 +95,8 @@ void ExtruderPlan::applyIdLabel() const auto offset_pt = ((path.points.front() + path.points.back()) / 2 - path.mesh->bounding_box.getMiddle()).resized(inset_dist); const auto signal_no_uv = Point2F(std::numeric_limits::signaling_NaN(), std::numeric_limits::signaling_NaN()); + const auto& id_field_info = path.mesh->id_field_info.value(); + std::vector new_points; std::vector idlabel_uvs; new_points.push_back(path.points.front()); @@ -115,7 +118,7 @@ void ExtruderPlan::applyIdLabel() // - A message/id of some sort. // - Some sort of simple 'text to pixels' method (lookup table?) // - IdFieldInfo (already made) to get the plane-normal(s) right (well, at least approximately -- it now does so only coursely, by axis) - const bool raw_val = (std::rand() % 2 == 0); + const auto raw_pt = a + (idx * pixel_span_3d) + (pixel_span_3d / 2); const bool preferred = (texel.first == TextureArea::Preferred); if (preferred || last_pixel != texel.first) @@ -126,9 +129,12 @@ void ExtruderPlan::applyIdLabel() idlabel_uvs.push_back(signal_no_uv); } + const auto label_uv = id_field_info.worldPointToLabelUv(raw_pt); + const bool raw_val = slice_id_texture.getPixel(label_uv) > 0x0; const bool val = preferred && raw_val; + new_points.push_back(raw_pt + (val ? offset_pt : zero_pt)); - idlabel_uvs.push_back(texel.second); + idlabel_uvs.push_back(label_uv); } last_pixel = texel.first; last_pt = raw_pt; diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index bf8cd8c7a0..f5d08f3a4d 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1310,7 +1310,21 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS gcode_layer.applyBackPressureCompensation(); time_keeper.registerTime("Back pressure comp."); - gcode_layer.applyIdLabel(); + constexpr size_t label_w = 256; + constexpr size_t label_h = 256; + std::vector buffer(label_w * label_h, 0); + { // TODO: generate actual label-image + // let's just fill it alternatingly for now, to test if things work + for (int y = 0; y < label_h; ++y) + { + for (int x = 0; x < label_w; ++x) + { + buffer[y * label_w + x] = std::rand() % 2; + } + } + } + Image slice_id_texture(256, 256, 1, std::move(buffer)); + gcode_layer.applyIdLabel(slice_id_texture); return { &gcode_layer, timer_total.elapsed().count(), time_keeper.getRegisteredTimes() }; } diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 74db7ffb34..c382a9029e 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -3696,12 +3696,12 @@ void LayerPlan::applyGradualFlow() } } -void LayerPlan::applyIdLabel() +void LayerPlan::applyIdLabel(const Image& slice_id_texture) { for (ExtruderPlan& extruder_plan : extruder_plans_) { // TODO: opt-out sure-to-be unaffected layers - extruder_plan.applyIdLabel(); + extruder_plan.applyIdLabel(slice_id_texture); } } diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index 6e9cab4e64..8f18eb665d 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -109,7 +109,7 @@ SliceMeshStorage::SliceMeshStorage(Mesh* mesh, const size_t slice_layer_count) void SliceMeshStorage::setIdFieldInfo(const AABB3D& aabb) { - id_field_info = IdFieldInfo::from_aabb3d(aabb); + id_field_info = IdFieldInfo::fromAabb3d(aabb); } bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr) const diff --git a/src/utils/types/idfieldinfo.cpp b/src/utils/types/idfieldinfo.cpp index daf5255567..9b17cbe861 100644 --- a/src/utils/types/idfieldinfo.cpp +++ b/src/utils/types/idfieldinfo.cpp @@ -6,7 +6,7 @@ using namespace cura; -std::optional IdFieldInfo::from_aabb3d(const AABB3D& aabb) +std::optional IdFieldInfo::fromAabb3d(const AABB3D& aabb) { if (! aabb.exists()) { @@ -29,5 +29,8 @@ std::optional IdFieldInfo::from_aabb3d(const AABB3D& aabb) .primary_axis_ = std::get<0>(dif_per_axis[0]), .secondary_axis_ = std::get<0>(dif_per_axis[1]), .normal_ = std::get<0>(dif_per_axis[2]), - .projection_field_ = AABB(Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1]))) }); + .projection_field_ = AABB( + Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), + Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1]))) + }); } From 5ed6cf04d3fbd3fed5a52787f7f2f9386501576e Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 18 Jul 2025 16:37:09 +0200 Subject: [PATCH 09/21] Slice-ID/Print-ID: (errrr ... fix recreating the image each time and z-coord input being 0 all the time). (OK,) That (really) _should_ take care of most of the 'Cura-part' of the print-ID (this time) (minus all the FIXME's) -- Image is still a checkerboard bitfield at the moment though, that should be the next step for the Slice-ID -- Necesary for Hackathon '25 -- Print-ID/Slice-ID. part of UMH-2025 --- include/ExtruderPlan.h | 2 +- include/FffGcodeWriter.h | 3 ++- src/ExtruderPlan.cpp | 5 +++-- src/FffGcodeWriter.cpp | 41 +++++++++++++++++++++++----------------- src/LayerPlan.cpp | 2 +- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index 7dc5f11a3c..f7126b42e2 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -127,7 +127,7 @@ class ExtruderPlan /*! */ - void applyIdLabel(const Image& slice_id_texture); + void applyIdLabel(const Image& slice_id_texture, const coord_t current_z); /*! * Gets the mesh being printed first on this plan diff --git a/include/FffGcodeWriter.h b/include/FffGcodeWriter.h index 4591e5c754..48cfbc0f7b 100644 --- a/include/FffGcodeWriter.h +++ b/include/FffGcodeWriter.h @@ -20,6 +20,7 @@ namespace cura { class AngleDegrees; +class Image; class Shape; class SkinPart; class SliceDataStorage; @@ -229,7 +230,7 @@ class FffGcodeWriter : public NoCopy * \param total_layers The total number of layers. * \return The layer plans */ - ProcessLayerResult processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers) const; + ProcessLayerResult processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers, const std::optional& slice_id_texture) const; /*! * This function checks whether prime blob should happen for any extruder on the first layer. diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 5860a053dc..7596836bd5 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -74,7 +74,7 @@ void ExtruderPlan::applyBackPressureCompensation(const Ratio back_pressure_compe } } -void ExtruderPlan::applyIdLabel(const Image& slice_id_texture) +void ExtruderPlan::applyIdLabel(const Image& slice_id_texture, const coord_t current_z) { // TODO?: message (format) should be a (string) setting, like 'ID: \H:\M:\S' or something @@ -93,6 +93,7 @@ void ExtruderPlan::applyIdLabel(const Image& slice_id_texture) const auto zero_pt = Point3LL(0, 0, 0); const auto offset_pt = ((path.points.front() + path.points.back()) / 2 - path.mesh->bounding_box.getMiddle()).resized(inset_dist); + const auto offset_z = Point3LL(0, 0, current_z + path.z_offset); const auto signal_no_uv = Point2F(std::numeric_limits::signaling_NaN(), std::numeric_limits::signaling_NaN()); const auto& id_field_info = path.mesh->id_field_info.value(); @@ -129,7 +130,7 @@ void ExtruderPlan::applyIdLabel(const Image& slice_id_texture) idlabel_uvs.push_back(signal_no_uv); } - const auto label_uv = id_field_info.worldPointToLabelUv(raw_pt); + const auto label_uv = id_field_info.worldPointToLabelUv(raw_pt + offset_z); const bool raw_val = slice_id_texture.getPixel(label_uv) > 0x0; const bool val = preferred && raw_val; diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index f5d08f3a4d..c81727c9b8 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -86,6 +86,24 @@ bool FffGcodeWriter::setTargetFile(const char* filename) void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keeper) { + std::optional slice_id_texture = std::nullopt; + if (std::any_of(storage.meshes.begin(), storage.meshes.end(), [](std::shared_ptr mesh) { return mesh->id_field_info.has_value(); })) + { + constexpr size_t label_width = 256; + constexpr size_t label_height = 256; + std::vector buffer(label_width * label_height, 0); + { + for (int y = 0; y < label_height; ++y) + { + for (int x = 0; x < label_width; ++x) + { + buffer[y * label_width + x] = (x + y) % 2; + } + } + } + slice_id_texture = std::make_optional(Image(256, 256, 1, std::move(buffer))); + } + const size_t start_extruder_nr = getStartExtruder(storage); gcode.preSetup(start_extruder_nr); gcode.setSliceUUID(slice_uuid); @@ -186,9 +204,9 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep run_multiple_producers_ordered_consumer( process_layer_starting_layer_nr, total_layers, - [&storage, total_layers, this](int layer_nr) + [&storage, total_layers, &slice_id_texture, this](int layer_nr) { - return std::make_optional(processLayer(storage, layer_nr, total_layers)); + return std::make_optional(processLayer(storage, layer_nr, total_layers, slice_id_texture)); }, [this, total_layers](std::optional result_opt) { @@ -1151,7 +1169,7 @@ void FffGcodeWriter::endRaftLayer(const SliceDataStorage& storage, LayerPlan& gc } } -FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers) const +FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers, const std::optional& slice_id_texture) const { spdlog::debug("GcodeWriter processing layer {} of {}", layer_nr, total_layers); TimeKeeper time_keeper; @@ -1310,21 +1328,10 @@ FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataS gcode_layer.applyBackPressureCompensation(); time_keeper.registerTime("Back pressure comp."); - constexpr size_t label_w = 256; - constexpr size_t label_h = 256; - std::vector buffer(label_w * label_h, 0); - { // TODO: generate actual label-image - // let's just fill it alternatingly for now, to test if things work - for (int y = 0; y < label_h; ++y) - { - for (int x = 0; x < label_w; ++x) - { - buffer[y * label_w + x] = std::rand() % 2; - } - } + if (slice_id_texture.has_value()) + { + gcode_layer.applyIdLabel(slice_id_texture.value()); } - Image slice_id_texture(256, 256, 1, std::move(buffer)); - gcode_layer.applyIdLabel(slice_id_texture); return { &gcode_layer, timer_total.elapsed().count(), time_keeper.getRegisteredTimes() }; } diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index c382a9029e..8d30ea21de 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -3701,7 +3701,7 @@ void LayerPlan::applyIdLabel(const Image& slice_id_texture) for (ExtruderPlan& extruder_plan : extruder_plans_) { // TODO: opt-out sure-to-be unaffected layers - extruder_plan.applyIdLabel(slice_id_texture); + extruder_plan.applyIdLabel(slice_id_texture, z_); } } From 4750542f41401d2af8c014b5e573b94b681af719 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 18 Jul 2025 18:08:58 +0200 Subject: [PATCH 10/21] Slice-ID/Print-ID: Prints a timestamp on the painted area. There's an enourmous amount of FIXME's and TODO's, but if you paint one of the sides of a basic cube it should now a) print a timestamp in the designated area, b) (also) save the correct label-UV's via/in gcode -- Necesary for Hackathon '25 -- Print-ID/Slice-ID. part of UMH-2025 --- CMakeLists.txt | 1 + include/utils/LabelMaker.h | 15 ++ include/utils/types/idfieldinfo.h | 4 +- src/ExtruderPlan.cpp | 9 +- src/FffGcodeWriter.cpp | 25 ++-- src/LayerPlan.cpp | 2 +- src/utils/LabelMaker.cpp | 229 ++++++++++++++++++++++++++++++ src/utils/types/idfieldinfo.cpp | 3 + 8 files changed, 267 insertions(+), 21 deletions(-) create mode 100644 include/utils/LabelMaker.h create mode 100644 src/utils/LabelMaker.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f4ff76c9dc..0c91e57d6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,7 @@ set(engine_SRCS # Except main.cpp. src/utils/ExtrusionLine.cpp src/utils/ExtrusionSegment.cpp src/utils/gettime.cpp + src/utils/LabelMaker.cpp src/utils/linearAlg2D.cpp src/utils/ListPolyIt.cpp src/utils/Matrix4x3D.cpp diff --git a/include/utils/LabelMaker.h b/include/utils/LabelMaker.h new file mode 100644 index 0000000000..f4b26780c6 --- /dev/null +++ b/include/utils/LabelMaker.h @@ -0,0 +1,15 @@ +// Copyright (c) 2025 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef LABEL_MAKER_H +#define LABEL_MAKER_H + +#include +#include + +namespace cura +{ + void paintStringToBuffer(const std::string_view& str, const size_t buffer_width, const size_t buffer_height, std::vector& buffer); +} + +#endif // LABEL_MAKER_H diff --git a/include/utils/types/idfieldinfo.h b/include/utils/types/idfieldinfo.h index e7fb16e230..27e6cedef5 100644 --- a/include/utils/types/idfieldinfo.h +++ b/include/utils/types/idfieldinfo.h @@ -49,8 +49,8 @@ struct IdFieldInfo Point2F worldPointToLabelUv(const Point3LL& pt) const { return Point2F( - mirrorNegative(static_cast(getAxisValue(primary_axis_, pt) - projection_field_.min_.X) / (projection_field_.max_.X - projection_field_.min_.X)), - mirrorNegative(static_cast(getAxisValue(secondary_axis_, pt) - projection_field_.min_.Y) / (projection_field_.max_.Y - projection_field_.min_.Y)) + 1.0f - mirrorNegative(static_cast(getAxisValue(primary_axis_, pt) - projection_field_.min_.X) / (projection_field_.max_.X - projection_field_.min_.X)), + 1.0f - mirrorNegative(static_cast(getAxisValue(secondary_axis_, pt) - projection_field_.min_.Y) / (projection_field_.max_.Y - projection_field_.min_.Y)) ); } }; diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 7596836bd5..89c4a4098f 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -76,6 +76,8 @@ void ExtruderPlan::applyBackPressureCompensation(const Ratio back_pressure_compe void ExtruderPlan::applyIdLabel(const Image& slice_id_texture, const coord_t current_z) { + // FIXME: properly deal with (mostly or wholly) top or bottom (normal = Z-axis) labels + // TODO?: message (format) should be a (string) setting, like 'ID: \H:\M:\S' or something constexpr coord_t inset_dist = 40; // TODO?: make this configurable as well? @@ -113,13 +115,8 @@ void ExtruderPlan::applyIdLabel(const Image& slice_id_texture, const coord_t cur const auto pixel_span_3d = (b - a) / static_cast(span_pixels.size()); auto last_pixel = TextureArea::Normal; auto last_pt = a; - for (auto [idx, texel] : span_pixels | ranges::views::enumerate) + for (const auto& [idx, texel] : span_pixels | ranges::views::enumerate) { - // TODO: Just make it 'random' for now -- later, use: - // - A message/id of some sort. - // - Some sort of simple 'text to pixels' method (lookup table?) - // - IdFieldInfo (already made) to get the plane-normal(s) right (well, at least approximately -- it now does so only coursely, by axis) - const auto raw_pt = a + (idx * pixel_span_3d) + (pixel_span_3d / 2); const bool preferred = (texel.first == TextureArea::Preferred); if (preferred || last_pixel != texel.first) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index c81727c9b8..33150a207c 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -33,6 +33,7 @@ #include "infill.h" #include "progress/Progress.h" #include "raft.h" +#include "utils/LabelMaker.h" #include "utils/Simplify.h" //Removing micro-segments created by offsetting. #include "utils/ThreadPool.h" #include "utils/linearAlg2D.h" @@ -84,24 +85,24 @@ bool FffGcodeWriter::setTargetFile(const char* filename) return false; } +std::string getTimeStamp() +{ + std::time_t time = std::time({}); + char str[std::size(" yyyy-mm-dd hh:mm:ss")]; + std::strftime(std::data(str), std::size(str), " %F %T", std::gmtime(&time)); + return std::string(str); +} + void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keeper) { std::optional slice_id_texture = std::nullopt; if (std::any_of(storage.meshes.begin(), storage.meshes.end(), [](std::shared_ptr mesh) { return mesh->id_field_info.has_value(); })) { - constexpr size_t label_width = 256; - constexpr size_t label_height = 256; + constexpr size_t label_width = 128; + constexpr size_t label_height = 128; std::vector buffer(label_width * label_height, 0); - { - for (int y = 0; y < label_height; ++y) - { - for (int x = 0; x < label_width; ++x) - { - buffer[y * label_width + x] = (x + y) % 2; - } - } - } - slice_id_texture = std::make_optional(Image(256, 256, 1, std::move(buffer))); + paintStringToBuffer(getTimeStamp(), label_width, label_height, buffer); + slice_id_texture = std::make_optional(Image(label_width, label_height, 1, std::move(buffer))); } const size_t start_extruder_nr = getStartExtruder(storage); diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 8d30ea21de..9d4f05e554 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -3388,7 +3388,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) if (! coasting) // not same as 'else', cause we might have changed [coasting] in the line above... { // normal path to gcode algorithm Point3LL prev_point = gcode.getPosition(); - for (auto [idx, pt] : path.points | ranges::views::enumerate) + for (const auto& [idx, pt] : path.points | ranges::views::enumerate) { const auto [_, time] = extruder_plan.getPointToPointTime(prev_point, pt, path); insertTempOnTime(time, path_idx); diff --git a/src/utils/LabelMaker.cpp b/src/utils/LabelMaker.cpp new file mode 100644 index 0000000000..c6eab1d888 --- /dev/null +++ b/src/utils/LabelMaker.cpp @@ -0,0 +1,229 @@ +// Copyright (c) 2025 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#include "utils/LabelMaker.h" + +#include +#include + +// TODO: put 'font8x8' into config file instead +constexpr std::array font8x8 = { +R"( +..####.. +.######. +##...### +##..#.## +##.#..## +###...## +.######. +..####.. +)", +R"( +..####.. +.#####.. +....##.. +....##.. +....##.. +....##.. +.######. +.######. +)", +R"( +.######. +######## +##....## +.....##. +....##.. +..###... +.####### +######## +)", +R"( +.######. +######## +##.. ### +...###.. +..####.. +.... ### +######## +.######. +)", +R"( +....###. +...##### +..##..## +.##...## +######## +.####### +......## +......## +)", +R"( +.####### +######## +##...... +###..... +.######. +......## +.####### +######.. +)", +R"( +..###### +.####### +##...... +######.. +#######. +##....## +######## +.#####.. +)", +R"( +#######. +######## +......## +.....##. +....##.. +...##... +####.... +####.... +)", +R"( +..####.. +.######. +##....## +.######. +.######. +##....## +.######. +..####.. +)", +R"( +..#####. +######## +##....## +.####### +..###### +......## +#######. +######.. +)", +R"( +...##... +..####.. +.######. +##....## +######## +######## +##....## +##....## +)", +R"( +######.. +######## +##....## +#######. +#######. +##....## +######## +######.. +)", +R"( +..###### +######## +##...... +##...... +##...... +##...... +######## +..###### +)", +R"( +######.. +######## +##....## +##....## +##....## +##....## +######## +######.. +)", +R"( +.####### +######## +##...... +######## +######## +##...... +######## +.####### +)", +R"( +.####### +######## +##...... +######## +######## +##...... +##...... +##...... +)", +}; + +std::vector> makeFont(const std::array& font) +{ + std::vector> res; + for (const std::string_view& glyph : font) + { + res.emplace_back(); + for (const char c : glyph) + { + switch (c) + { + case '.': res.back().push_back(false); break; + case '#': res.back().push_back(true); break; + } + } + } + return res; +} + +void cura::paintStringToBuffer(const std::string_view& str, const size_t buffer_width, const size_t buffer_height, std::vector& buffer) +{ + static auto proc_font = makeFont(font8x8); + + constexpr size_t base_offset_x = 3; + size_t offset_x = base_offset_x; + size_t offset_y = 3; + for (char c : str) + { + char offset = 0; + if (c >= 'a' && c <= 'z') { offset = 'a' - 0xA; } + else if (c >= 'A' && c <= 'Z') { offset = 'A' - 0xA; } + else if (c >= '0' && c <= '9') { offset = '0'; } + else { c = -1; } + c -= offset; + + if (c >= 0 && c < proc_font.size()) + { + const auto& glyph = proc_font[c]; + for (const auto& [idx, pix] : glyph | ranges::views::enumerate) + { + const size_t x = offset_x + idx % 8; + const size_t y = offset_y + idx / 8; + buffer[y * buffer_width + x] = pix; + } + } + + offset_x += 10; + if (offset_x + 1 >= buffer_width) + { + offset_x = base_offset_x; + offset_y += 10; + } + if (offset_y + 1 >= buffer_height) + { + return; + } + } +} diff --git a/src/utils/types/idfieldinfo.cpp b/src/utils/types/idfieldinfo.cpp index 9b17cbe861..80df19a92b 100644 --- a/src/utils/types/idfieldinfo.cpp +++ b/src/utils/types/idfieldinfo.cpp @@ -1,3 +1,6 @@ +// Copyright (c) 2025 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + #include "utils/types/idfieldinfo.h" #include From 410fc23e002b8a41ffc5218b0360d99ab0f6269c Mon Sep 17 00:00:00 2001 From: rburema <41987080+rburema@users.noreply.github.com> Date: Fri, 18 Jul 2025 16:09:47 +0000 Subject: [PATCH 11/21] Apply clang-format --- include/TextureDataMapping.h | 4 +- include/pathPlanning/GCodePath.h | 3 +- include/utils/LabelMaker.h | 2 +- include/utils/types/idfieldinfo.h | 15 +++++--- src/ExtruderPlan.cpp | 11 ++---- src/FffGcodeWriter.cpp | 11 +++++- src/LayerPlan.cpp | 19 +++++++++- src/TextureDataProvider.cpp | 10 ++--- src/utils/LabelMaker.cpp | 61 ++++++++++++++++++++----------- src/utils/types/idfieldinfo.cpp | 5 +-- 10 files changed, 88 insertions(+), 53 deletions(-) diff --git a/include/TextureDataMapping.h b/include/TextureDataMapping.h index 270b9ed0a6..42b202aaed 100644 --- a/include/TextureDataMapping.h +++ b/include/TextureDataMapping.h @@ -4,13 +4,13 @@ #ifndef TEXTUREDATAMAPPING_H #define TEXTUREDATAMAPPING_H -#include "utils/Point2F.h" - #include #include #include +#include "utils/Point2F.h" + namespace cura { diff --git a/include/pathPlanning/GCodePath.h b/include/pathPlanning/GCodePath.h index 2c0e856b57..cc4fc44284 100644 --- a/include/pathPlanning/GCodePath.h +++ b/include/pathPlanning/GCodePath.h @@ -47,7 +47,8 @@ struct GCodePath bool perform_z_hop{ false }; //!< Whether to perform a z_hop in this path, which is assumed to be a travel path. bool perform_prime{ false }; //!< Whether this path is preceded by a prime (blob) std::vector points{}; //!< The points constituting this path. The Z coordinate is an offset relative to the actual layer height, added to the global z_offset. - std::optional> idlabel_uv_per_point = std::nullopt; //!< If this path is part of an ID-label, contains for each path-point an UV-point into the label-texture. + std::optional> idlabel_uv_per_point + = std::nullopt; //!< If this path is part of an ID-label, contains for each path-point an UV-point into the label-texture. bool done{ false }; //!< Path is finished, no more moves should be added, and a new path should be started instead of any appending done to this one. double fan_speed{ GCodePathConfig::FAN_SPEED_DEFAULT }; //!< fan speed override for this path, value should be within range 0-100 (inclusive) and ignored otherwise TimeMaterialEstimates estimates{}; //!< Naive time and material estimates diff --git a/include/utils/LabelMaker.h b/include/utils/LabelMaker.h index f4b26780c6..0ab4299a18 100644 --- a/include/utils/LabelMaker.h +++ b/include/utils/LabelMaker.h @@ -9,7 +9,7 @@ namespace cura { - void paintStringToBuffer(const std::string_view& str, const size_t buffer_width, const size_t buffer_height, std::vector& buffer); +void paintStringToBuffer(const std::string_view& str, const size_t buffer_width, const size_t buffer_height, std::vector& buffer); } #endif // LABEL_MAKER_H diff --git a/include/utils/types/idfieldinfo.h b/include/utils/types/idfieldinfo.h index 27e6cedef5..4000497335 100644 --- a/include/utils/types/idfieldinfo.h +++ b/include/utils/types/idfieldinfo.h @@ -26,10 +26,14 @@ struct IdFieldInfo { switch (ax) { - case Axis::X: return pt.x_; - case Axis::Y: return pt.y_; - case Axis::Z: return pt.z_; - default: return 0; + case Axis::X: + return pt.x_; + case Axis::Y: + return pt.y_; + case Axis::Z: + return pt.z_; + default: + return 0; } } @@ -50,8 +54,7 @@ struct IdFieldInfo { return Point2F( 1.0f - mirrorNegative(static_cast(getAxisValue(primary_axis_, pt) - projection_field_.min_.X) / (projection_field_.max_.X - projection_field_.min_.X)), - 1.0f - mirrorNegative(static_cast(getAxisValue(secondary_axis_, pt) - projection_field_.min_.Y) / (projection_field_.max_.Y - projection_field_.min_.Y)) - ); + 1.0f - mirrorNegative(static_cast(getAxisValue(secondary_axis_, pt) - projection_field_.min_.Y) / (projection_field_.max_.Y - projection_field_.min_.Y))); } }; } // namespace cura diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 89c4a4098f..138771be11 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -3,8 +3,8 @@ #include "ExtruderPlan.h" -#include "mesh.h" // For 'Image' class. #include "TextureDataProvider.h" +#include "mesh.h" // For 'Image' class. namespace cura { @@ -83,12 +83,9 @@ void ExtruderPlan::applyIdLabel(const Image& slice_id_texture, const coord_t cur constexpr coord_t inset_dist = 40; // TODO?: make this configurable as well? for (auto& path : paths_) { - if (path.points.empty() || - path.mesh == nullptr || - path.mesh->layers[layer_nr_].texture_data_provider_ == nullptr || - (! path.mesh->id_field_info) || - (path.mesh->id_field_info.value().normal_ != IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::OuterWall) || - (path.mesh->id_field_info.value().normal_ == IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::Skin)) + if (path.points.empty() || path.mesh == nullptr || path.mesh->layers[layer_nr_].texture_data_provider_ == nullptr || (! path.mesh->id_field_info) + || (path.mesh->id_field_info.value().normal_ != IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::OuterWall) + || (path.mesh->id_field_info.value().normal_ == IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::Skin)) { continue; } diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 33150a207c..d87ce6a4fd 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -96,7 +96,13 @@ std::string getTimeStamp() void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keeper) { std::optional slice_id_texture = std::nullopt; - if (std::any_of(storage.meshes.begin(), storage.meshes.end(), [](std::shared_ptr mesh) { return mesh->id_field_info.has_value(); })) + if (std::any_of( + storage.meshes.begin(), + storage.meshes.end(), + [](std::shared_ptr mesh) + { + return mesh->id_field_info.has_value(); + })) { constexpr size_t label_width = 128; constexpr size_t label_height = 128; @@ -1170,7 +1176,8 @@ void FffGcodeWriter::endRaftLayer(const SliceDataStorage& storage, LayerPlan& gc } } -FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers, const std::optional& slice_id_texture) const +FffGcodeWriter::ProcessLayerResult + FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers, const std::optional& slice_id_texture) const { spdlog::debug("GcodeWriter processing layer {} of {}", layer_nr, total_layers); TimeKeeper time_keeper; diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 9d4f05e554..b6b43e51ed 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -3395,7 +3395,15 @@ void LayerPlan::writeGCode(GCodeExport& gcode) const double extrude_speed = speed * path.speed_back_pressure_factor; - writeExtrusionRelativeZ(gcode, pt, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type, getIdLabelUvComment(path.idlabel_uv_per_point, idx), update_extrusion_offset); + writeExtrusionRelativeZ( + gcode, + pt, + extrude_speed, + path.z_offset, + path.getExtrusionMM3perMM(), + path.config.type, + getIdLabelUvComment(path.idlabel_uv_per_point, idx), + update_extrusion_offset); sendLineTo(path, pt, extrude_speed); prev_point = pt; @@ -3570,7 +3578,14 @@ bool LayerPlan::writePathWithCoasting( auto [_, time] = extruder_plan.getPointToPointTime(previous_position, path.points[point_idx], path); insertTempOnTime(time, path_idx); - writeExtrusionRelativeZ(gcode, path.points[point_idx], extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type, getIdLabelUvComment(path.idlabel_uv_per_point, path_idx)); + writeExtrusionRelativeZ( + gcode, + path.points[point_idx], + extrude_speed, + path.z_offset, + path.getExtrusionMM3perMM(), + path.config.type, + getIdLabelUvComment(path.idlabel_uv_per_point, path_idx)); sendLineTo(path, path.points[point_idx], extrude_speed); previous_position = path.points[point_idx]; diff --git a/src/TextureDataProvider.cpp b/src/TextureDataProvider.cpp index 4aadc6222e..3fd3126fc6 100644 --- a/src/TextureDataProvider.cpp +++ b/src/TextureDataProvider.cpp @@ -76,12 +76,10 @@ bool TextureDataProvider::getTexelsForSpan(const Point2LL& a, const Point2LL& b, point_uv_b.value(), [&bit_field, &res, &has_any](const int32_t pixel_data, const Point2F& uv_pt) { - res.push_back({ - static_cast( - // FIXME: The method to shift left & right here is the same as above, should be an inline method? - (pixel_data << (32 - 1 - bit_field.bit_range_end_index)) >> (32 - 1 - (bit_field.bit_range_end_index - bit_field.bit_range_start_index))), - uv_pt - }); + res.push_back({ static_cast( + // FIXME: The method to shift left & right here is the same as above, should be an inline method? + (pixel_data << (32 - 1 - bit_field.bit_range_end_index)) >> (32 - 1 - (bit_field.bit_range_end_index - bit_field.bit_range_start_index))), + uv_pt }); has_any |= (res.back().first == TextureArea::Preferred); }); return has_any; diff --git a/src/utils/LabelMaker.cpp b/src/utils/LabelMaker.cpp index c6eab1d888..53507867b3 100644 --- a/src/utils/LabelMaker.cpp +++ b/src/utils/LabelMaker.cpp @@ -4,11 +4,12 @@ #include "utils/LabelMaker.h" #include + #include // TODO: put 'font8x8' into config file instead constexpr std::array font8x8 = { -R"( + R"( ..####.. .######. ##...### @@ -18,7 +19,7 @@ R"( .######. ..####.. )", -R"( + R"( ..####.. .#####.. ....##.. @@ -28,7 +29,7 @@ R"( .######. .######. )", -R"( + R"( .######. ######## ##....## @@ -38,7 +39,7 @@ R"( .####### ######## )", -R"( + R"( .######. ######## ##.. ### @@ -48,7 +49,7 @@ R"( ######## .######. )", -R"( + R"( ....###. ...##### ..##..## @@ -58,7 +59,7 @@ R"( ......## ......## )", -R"( + R"( .####### ######## ##...... @@ -68,7 +69,7 @@ R"( .####### ######.. )", -R"( + R"( ..###### .####### ##...... @@ -78,7 +79,7 @@ R"( ######## .#####.. )", -R"( + R"( #######. ######## ......## @@ -88,7 +89,7 @@ R"( ####.... ####.... )", -R"( + R"( ..####.. .######. ##....## @@ -98,7 +99,7 @@ R"( .######. ..####.. )", -R"( + R"( ..#####. ######## ##....## @@ -108,7 +109,7 @@ R"( #######. ######.. )", -R"( + R"( ...##... ..####.. .######. @@ -118,7 +119,7 @@ R"( ##....## ##....## )", -R"( + R"( ######.. ######## ##....## @@ -128,7 +129,7 @@ R"( ######## ######.. )", -R"( + R"( ..###### ######## ##...... @@ -138,7 +139,7 @@ R"( ######## ..###### )", -R"( + R"( ######.. ######## ##....## @@ -148,7 +149,7 @@ R"( ######## ######.. )", -R"( + R"( .####### ######## ##...... @@ -158,7 +159,7 @@ R"( ######## .####### )", -R"( + R"( .####### ######## ##...... @@ -180,8 +181,12 @@ std::vector> makeFont(const std::array& { switch (c) { - case '.': res.back().push_back(false); break; - case '#': res.back().push_back(true); break; + case '.': + res.back().push_back(false); + break; + case '#': + res.back().push_back(true); + break; } } } @@ -198,10 +203,22 @@ void cura::paintStringToBuffer(const std::string_view& str, const size_t buffer_ for (char c : str) { char offset = 0; - if (c >= 'a' && c <= 'z') { offset = 'a' - 0xA; } - else if (c >= 'A' && c <= 'Z') { offset = 'A' - 0xA; } - else if (c >= '0' && c <= '9') { offset = '0'; } - else { c = -1; } + if (c >= 'a' && c <= 'z') + { + offset = 'a' - 0xA; + } + else if (c >= 'A' && c <= 'Z') + { + offset = 'A' - 0xA; + } + else if (c >= '0' && c <= '9') + { + offset = '0'; + } + else + { + c = -1; + } c -= offset; if (c >= 0 && c < proc_font.size()) diff --git a/src/utils/types/idfieldinfo.cpp b/src/utils/types/idfieldinfo.cpp index 80df19a92b..143b4aae75 100644 --- a/src/utils/types/idfieldinfo.cpp +++ b/src/utils/types/idfieldinfo.cpp @@ -32,8 +32,5 @@ std::optional IdFieldInfo::fromAabb3d(const AABB3D& aabb) .primary_axis_ = std::get<0>(dif_per_axis[0]), .secondary_axis_ = std::get<0>(dif_per_axis[1]), .normal_ = std::get<0>(dif_per_axis[2]), - .projection_field_ = AABB( - Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), - Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1]))) - }); + .projection_field_ = AABB(Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1]))) }); } From 0a0b434ef1ebcad1b473a36fe89f766d026990d2 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 18 Jul 2025 21:52:03 +0200 Subject: [PATCH 12/21] Slice-ID: Clean up a little. It's hackathon code, but we won't have that much time afterwards to make it production-ready, so clean up _just_ a little before things become permanent. part of UMH-2025 --- CMakeLists.txt | 3 +- include/mesh.h | 37 +------------------ include/sliceDataStorage.h | 2 +- .../{types/idfieldinfo.h => IDFieldInfo.h} | 27 +------------- src/mesh.cpp | 36 +++++++++++++++++- .../idfieldinfo.cpp => IdFieldInfo.cpp} | 29 ++++++++++++++- 6 files changed, 68 insertions(+), 66 deletions(-) rename include/utils/{types/idfieldinfo.h => IDFieldInfo.h} (53%) rename src/utils/{types/idfieldinfo.cpp => IdFieldInfo.cpp} (63%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c91e57d6b..bb556fcc83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,7 @@ set(engine_SRCS # Except main.cpp. src/utils/ExtrusionLine.cpp src/utils/ExtrusionSegment.cpp src/utils/gettime.cpp + src/utils/IdFieldInfo.cpp src/utils/LabelMaker.cpp src/utils/linearAlg2D.cpp src/utils/ListPolyIt.cpp @@ -169,8 +170,6 @@ set(engine_SRCS # Except main.cpp. src/utils/scoring/RandomScoringCriterion.cpp src/utils/scoring/TextureScoringCriterion.cpp - src/utils/types/idfieldinfo.cpp - src/geometry/Point2LL.cpp src/geometry/Point3LL.cpp src/geometry/Polygon.cpp diff --git a/include/mesh.h b/include/mesh.h index 15b90a8823..c299b3062b 100644 --- a/include/mesh.h +++ b/include/mesh.h @@ -9,9 +9,9 @@ #include "TextureDataMapping.h" #include "settings/Settings.h" #include "utils/AABB3D.h" +#include "utils/IdFieldInfo.h" #include "utils/Matrix4x3D.h" #include "utils/Point2F.h" -#include "utils/types/idfieldinfo.h" namespace cura { @@ -101,37 +101,7 @@ class Image return getPixel(static_cast(uv_coordinates.x_ * width_), static_cast(uv_coordinates.y_ * height_)); } - void visitSpanPerPixel(const Point2F& a, const Point2F& b, const std::function& func) const - { - constexpr auto func_major_stepper = [](const int64_t& da, const int64_t& abs_db, const int64_t& i) - { - return da < 0 ? -i : i; - }; - constexpr auto func_minor_stepper = [](const int64_t& da, const int64_t& abs_db, const int64_t& i) - { - return (i * da) / abs_db; - }; - - const auto x0 = static_cast(a.x_ * width_); - const auto y0 = static_cast(a.y_ * height_); - const auto x1 = static_cast(b.x_ * width_); - const auto y1 = static_cast(b.y_ * height_); - - const auto dx = x1 - x0; - const auto dy = y1 - y0; - const auto abs_dx = std::llabs(dx); - const auto abs_dy = std::llabs(dy); - const auto max_span = std::max(abs_dx, abs_dy); - - const auto func_x = abs_dy <= abs_dx ? func_major_stepper : func_minor_stepper; - const auto func_y = abs_dx <= abs_dy ? func_major_stepper : func_minor_stepper; - for (size_t i_pix = 0; i_pix <= max_span; i_pix++) - { - const auto xi = x0 + func_x(dx, abs_dy, i_pix); - const auto yi = y0 + func_y(dy, abs_dx, i_pix); - func(getPixel(xi, yi), Point2F(static_cast(xi) / width_, static_cast(yi) / height_)); - } - } + void visitSpanPerPixel(const Point2F& a, const Point2F& b, const std::function& func) const; private: std::vector data_; // The raw pixels, data @@ -185,9 +155,6 @@ class Mesh void clear(); //!< clears all data void finish(); //!< complete the model : set the connected_face_index fields of the faces. - // void setIdFieldInfo(const AABB3D& aabb); - // std::optional getIdFieldInfo() const; - Point3LL min() const; //!< min (in x,y and z) vertex of the bounding box Point3LL max() const; //!< max (in x,y and z) vertex of the bounding box AABB3D getAABB() const; //!< Get the axis aligned bounding box diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index 0b3fb9685d..0a0b25e39b 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -23,8 +23,8 @@ #include "settings/types/LayerIndex.h" #include "utils/AABB.h" #include "utils/AABB3D.h" +#include "utils/IdFieldInfo.h" #include "utils/NoCopy.h" -#include "utils/types/IdFieldInfo.h" namespace cura { diff --git a/include/utils/types/idfieldinfo.h b/include/utils/IDFieldInfo.h similarity index 53% rename from include/utils/types/idfieldinfo.h rename to include/utils/IDFieldInfo.h index 4000497335..d3c2754602 100644 --- a/include/utils/types/idfieldinfo.h +++ b/include/utils/IDFieldInfo.h @@ -22,25 +22,7 @@ struct IdFieldInfo Y, Z }; - static coord_t getAxisValue(const Axis ax, const Point3LL& pt) - { - switch (ax) - { - case Axis::X: - return pt.x_; - case Axis::Y: - return pt.y_; - case Axis::Z: - return pt.z_; - default: - return 0; - } - } - - static float mirrorNegative(const float in) - { - return in < 0.0f ? 1.0 + in : in; - } + static coord_t getAxisValue(const Axis ax, const Point3LL& pt); Axis primary_axis_ = Axis::X; Axis secondary_axis_ = Axis::Z; @@ -50,12 +32,7 @@ struct IdFieldInfo public: static std::optional fromAabb3d(const AABB3D& aabb); - Point2F worldPointToLabelUv(const Point3LL& pt) const - { - return Point2F( - 1.0f - mirrorNegative(static_cast(getAxisValue(primary_axis_, pt) - projection_field_.min_.X) / (projection_field_.max_.X - projection_field_.min_.X)), - 1.0f - mirrorNegative(static_cast(getAxisValue(secondary_axis_, pt) - projection_field_.min_.Y) / (projection_field_.max_.Y - projection_field_.min_.Y))); - } + Point2F worldPointToLabelUv(const Point3LL& pt) const; }; } // namespace cura diff --git a/src/mesh.cpp b/src/mesh.cpp index 430d41fe71..471b2b58a2 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -3,17 +3,49 @@ #include "mesh.h" -#include #include #include #include "utils/Point3D.h" -#include "utils/types/idfieldinfo.h" namespace cura { +void Image::visitSpanPerPixel(const Point2F& a, const Point2F& b, const std::function& func) const +{ + constexpr auto func_major_stepper = + [](const int64_t& da, const int64_t& abs_db, const int64_t& i) + { + return da < 0 ? -i : i; + }; + constexpr auto func_minor_stepper = + [](const int64_t& da, const int64_t& abs_db, const int64_t& i) + { + return (i * da) / abs_db; + }; + + const auto x0 = static_cast(a.x_ * width_); + const auto y0 = static_cast(a.y_ * height_); + const auto x1 = static_cast(b.x_ * width_); + const auto y1 = static_cast(b.y_ * height_); + + const auto dx = x1 - x0; + const auto dy = y1 - y0; + const auto abs_dx = std::llabs(dx); + const auto abs_dy = std::llabs(dy); + const auto max_span = std::max(abs_dx, abs_dy); + + const auto func_x = abs_dy <= abs_dx ? func_major_stepper : func_minor_stepper; + const auto func_y = abs_dx <= abs_dy ? func_major_stepper : func_minor_stepper; + for (size_t i_pix = 0; i_pix <= max_span; i_pix++) + { + const auto xi = x0 + func_x(dx, abs_dy, i_pix); + const auto yi = y0 + func_y(dy, abs_dx, i_pix); + func(getPixel(xi, yi), Point2F(static_cast(xi) / width_, static_cast(yi) / height_)); + } +} + const int vertex_meld_distance = MM2INT(0.03); /*! * returns a hash for the location, but first divides by the vertex_meld_distance, diff --git a/src/utils/types/idfieldinfo.cpp b/src/utils/IdFieldInfo.cpp similarity index 63% rename from src/utils/types/idfieldinfo.cpp rename to src/utils/IdFieldInfo.cpp index 143b4aae75..43b82b3a16 100644 --- a/src/utils/types/idfieldinfo.cpp +++ b/src/utils/IdFieldInfo.cpp @@ -1,7 +1,7 @@ // Copyright (c) 2025 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include "utils/types/idfieldinfo.h" +#include "utils/IdFieldInfo.h" #include #include @@ -9,6 +9,21 @@ using namespace cura; +coord_t IdFieldInfo::getAxisValue(const Axis ax, const Point3LL& pt) +{ + switch (ax) + { + case Axis::X: + return pt.x_; + case Axis::Y: + return pt.y_; + case Axis::Z: + return pt.z_; + default: + return 0; + } +} + std::optional IdFieldInfo::fromAabb3d(const AABB3D& aabb) { if (! aabb.exists()) @@ -34,3 +49,15 @@ std::optional IdFieldInfo::fromAabb3d(const AABB3D& aabb) .normal_ = std::get<0>(dif_per_axis[2]), .projection_field_ = AABB(Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1]))) }); } + +float mirrorNegative(const float in) +{ + return in < 0.0f ? 1.0 + in : in; +} + +Point2F IdFieldInfo::worldPointToLabelUv(const Point3LL& pt) const +{ + return Point2F( + 1.0f - mirrorNegative(static_cast(getAxisValue(primary_axis_, pt) - projection_field_.min_.X) / (projection_field_.max_.X - projection_field_.min_.X)), + 1.0f - mirrorNegative(static_cast(getAxisValue(secondary_axis_, pt) - projection_field_.min_.Y) / (projection_field_.max_.Y - projection_field_.min_.Y))); +} From e6c2b896906e044aeefa6c74300e6a62388555a7 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 19 Jul 2025 12:38:28 +0200 Subject: [PATCH 13/21] Slice-ID/Print-ID: Squashed commit of the following: commit 430e9fc519be284911942c49bc72fc654dd95655 Author: Remco Burema Date: Sat Jul 19 12:30:44 2025 +0200 Clean accidentally committed files. commit 7dc6a5c6cc2b077178d9127d29a54db048ce9158 Author: Remco Burema Date: Sat Jul 19 12:26:19 2025 +0200 Slice-ID/Print-ID: Fix (not-really-)arbitrary label projection axis issues. Hackathon '25 -- It's not really 100% there yet, but at least the primary and secondary axes are orthagonal to the normal and properly use their spans to project now. part of UMH-2025 commit 5c22ac642bd0b118408dbb0d6e17005ec3bc5d23 Author: Remco Burema Date: Sat Jul 19 01:26:10 2025 +0200 Slice-ID/Print-ID: WIP move away from (too) 'fixed' projection-axii. Hackathon '25 -- Plesae see the previous commit if you want something that has a chance of actually working (but this'll be better in the long run). part of UMH-2025 --- include/sliceDataStorage.h | 2 +- include/utils/IDFieldInfo.h | 32 +++++------ src/ExtruderPlan.cpp | 16 +++--- src/FffPolygonGenerator.cpp | 19 +++---- src/mesh.cpp | 2 +- src/sliceDataStorage.cpp | 4 +- src/utils/IdFieldInfo.cpp | 102 +++++++++++++++++++++++++----------- 7 files changed, 112 insertions(+), 65 deletions(-) diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index 0a0b25e39b..4fb24ab730 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -341,7 +341,7 @@ class SliceMeshStorage /*! */ - void setIdFieldInfo(const AABB3D& aabb); + void setIdFieldInfo(const std::vector& label_pt_cloud); /*! * \param extruder_nr The extruder for which to check diff --git a/include/utils/IDFieldInfo.h b/include/utils/IDFieldInfo.h index d3c2754602..29cc575d12 100644 --- a/include/utils/IDFieldInfo.h +++ b/include/utils/IDFieldInfo.h @@ -6,31 +6,31 @@ #include -#include "utils/AABB3D.h" +#include "geometry/Point3LL.h" +#include "utils/AABB.h" #include "utils/Point2F.h" +#include "utils/Point3D.h" namespace cura { -// FIXME?: This now goes per-axis, because that's what we get by creating it from the AABB, but it should be a 'free' plane-normal(s). -// We'd probably need to do a little principal component analysis to _properly_ get the primary and secondary axii. +const double CLOSE_1D = std::nextafter(1.0, 0.0); +const float CLOSE_1F = std::nextafter(1.0f, 0.0f); +// NOTE: nextafter isn't constexpr yet in c++20, replace with constexpr when we do C++23 struct IdFieldInfo { - enum class Axis - { - X, - Y, - Z - }; - static coord_t getAxisValue(const Axis ax, const Point3LL& pt); - - Axis primary_axis_ = Axis::X; - Axis secondary_axis_ = Axis::Z; - Axis normal_ = Axis::Y; - AABB projection_field_; + typedef std::pair span_t; + + Point3D primary_axis_; + span_t primary_span_; + + Point3D secondary_axis_; + span_t secondary_span_; + + Point3D normal_; public: - static std::optional fromAabb3d(const AABB3D& aabb); + static std::optional fromPointCloud(const std::vector& points); Point2F worldPointToLabelUv(const Point3LL& pt) const; }; diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 138771be11..33aff3d7e6 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -84,8 +84,9 @@ void ExtruderPlan::applyIdLabel(const Image& slice_id_texture, const coord_t cur for (auto& path : paths_) { if (path.points.empty() || path.mesh == nullptr || path.mesh->layers[layer_nr_].texture_data_provider_ == nullptr || (! path.mesh->id_field_info) - || (path.mesh->id_field_info.value().normal_ != IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::OuterWall) - || (path.mesh->id_field_info.value().normal_ == IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::Skin)) + //|| (path.mesh->id_field_info.value().normal_ != IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::OuterWall) + //|| (path.mesh->id_field_info.value().normal_ == IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::Skin)) + || (path.config.type != PrintFeatureType::OuterWall && path.config.type != PrintFeatureType::Skin)) { continue; } @@ -125,11 +126,14 @@ void ExtruderPlan::applyIdLabel(const Image& slice_id_texture, const coord_t cur } const auto label_uv = id_field_info.worldPointToLabelUv(raw_pt + offset_z); - const bool raw_val = slice_id_texture.getPixel(label_uv) > 0x0; - const bool val = preferred && raw_val; + if (std::clamp(label_uv.x_, 0.0f, CLOSE_1F) == label_uv.x_ && std::clamp(label_uv.y_, 0.0f, CLOSE_1F) == label_uv.y_) + { + const bool raw_val = slice_id_texture.getPixel(label_uv) > 0b0; + const bool val = preferred && raw_val; - new_points.push_back(raw_pt + (val ? offset_pt : zero_pt)); - idlabel_uvs.push_back(label_uv); + new_points.push_back(raw_pt + (val ? offset_pt : zero_pt)); + idlabel_uvs.push_back(label_uv); + } } last_pixel = texel.first; last_pt = raw_pt; diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 77863f1026..14aaa63a3c 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -304,10 +304,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe { spdlog::info("No painting-operation data specified for mesh `{}`.", static_cast(&mesh)); } - AABB3D label_aabb; - // FIXME: We probably actually need an _inscribed_ AABB (which is way less trivial), since otherwise the message will overlap the edges of the label-space. - // FIXME: Find the _proper_ plane-normal(s) (primaty and secondary vectors), by doing principle component analysis - // (which fortunately _also_ means we can reduce the first above problem to 2D instead, which is at least somewhat easier). + std::vector label_pt_cloud; // calculate the height at which each layer is actually printed (printZ) for (LayerIndex layer_nr = 0; layer_nr < meshStorage.layers.size(); layer_nr++) @@ -361,6 +358,8 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe // TODO: Deal with the fact that we _actually_ don't have the segments in that place anymore (I temporarily disabled the clear). // (We've still got all we need in the slicer_layer.sliced_uv_coordinates_.segments, but that's all private at the moment! { + + if (segment.uv_start.has_value() && segment.uv_end.has_value()) { const auto& uv_a = segment.uv_start.value(); @@ -370,13 +369,15 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe mesh.texture_->visitSpanPerPixel( uv_a, uv_b, - [&label_aabb, &match_pixel, &uv_a, &uv_b, &a, &b, &height](const int32_t& pixel, const Point2F& uv) + [&label_pt_cloud, &match_pixel, &uv_a, &uv_b, &a, &b, &height](const int32_t& pixel, const Point2F& uv) { if ((pixel & match_pixel) != 0b0) { - const auto param - = std::llabs(b.X - a.X) >= std::llabs(b.Y - a.Y) ? (uv.x_ - uv_a.x_) / (uv_b.x_ - uv_a.x_) : (uv.y_ - uv_a.y_) / (uv_b.y_ - uv_a.y_); - label_aabb.include(Point3LL(a + param * (b - a), height)); + const auto param = + std::llabs(uv_b.x_ - uv_a.x_) >= std::llabs(uv_b.x_ - uv_a.y_) ? + (uv.x_ - uv_a.x_) / (uv_b.x_ - uv_a.x_) : + (uv.y_ - uv_a.y_) / (uv_b.y_ - uv_a.y_); + label_pt_cloud.emplace_back(a + param * (b - a), height); } }); } @@ -385,7 +386,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe } } - meshStorage.setIdFieldInfo(label_aabb); + meshStorage.setIdFieldInfo(label_pt_cloud); delete slicerList[meshIdx]; diff --git a/src/mesh.cpp b/src/mesh.cpp index 471b2b58a2..766f903304 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -38,7 +38,7 @@ void Image::visitSpanPerPixel(const Point2F& a, const Point2F& b, const std::fun const auto func_x = abs_dy <= abs_dx ? func_major_stepper : func_minor_stepper; const auto func_y = abs_dx <= abs_dy ? func_major_stepper : func_minor_stepper; - for (size_t i_pix = 0; i_pix <= max_span; i_pix++) + for (size_t i_pix = 0; i_pix < max_span; ++i_pix) { const auto xi = x0 + func_x(dx, abs_dy, i_pix); const auto yi = y0 + func_y(dy, abs_dx, i_pix); diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index 8f18eb665d..1c8c0b7fa7 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -107,9 +107,9 @@ SliceMeshStorage::SliceMeshStorage(Mesh* mesh, const size_t slice_layer_count) layers.resize(slice_layer_count); } -void SliceMeshStorage::setIdFieldInfo(const AABB3D& aabb) +void SliceMeshStorage::setIdFieldInfo(const std::vector& label_pt_cloud) { - id_field_info = IdFieldInfo::fromAabb3d(aabb); + id_field_info = IdFieldInfo::fromPointCloud(label_pt_cloud); } bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr) const diff --git a/src/utils/IdFieldInfo.cpp b/src/utils/IdFieldInfo.cpp index 43b82b3a16..49ab29f259 100644 --- a/src/utils/IdFieldInfo.cpp +++ b/src/utils/IdFieldInfo.cpp @@ -3,51 +3,91 @@ #include "utils/IdFieldInfo.h" +#include "utils/AABB3D.h" + #include #include #include +#include +#include +#include + using namespace cura; -coord_t IdFieldInfo::getAxisValue(const Axis ax, const Point3LL& pt) +double proj(const Point3D& b, const Point3LL& p) { - switch (ax) - { - case Axis::X: - return pt.x_; - case Axis::Y: - return pt.y_; - case Axis::Z: - return pt.z_; - default: - return 0; - } + return (Point3D(p) * b) / (b * b); } -std::optional IdFieldInfo::fromAabb3d(const AABB3D& aabb) +// FIMXE: something doesn't quite work right yet -- the normal is OK (I think?) but the primary and secondary axii don't do what I want yet... + +std::optional IdFieldInfo::fromPointCloud(const std::vector& points) { - if (! aabb.exists()) + if (points.size() < 3) { return std::nullopt; } - typedef std::tuple axis_span_t; - std::array dif_per_axis = { std::make_tuple(IdFieldInfo::Axis::X, aabb.min_.x_, aabb.max_.x_), - std::make_tuple(IdFieldInfo::Axis::Y, aabb.min_.y_, aabb.max_.y_), - std::make_tuple(IdFieldInfo::Axis::Z, aabb.min_.z_, aabb.max_.z_) }; - std::stable_sort( - dif_per_axis.begin(), - dif_per_axis.end(), - [](const axis_span_t& a, const axis_span_t& b) - { - return std::llabs(std::get<2>(a) - std::get<1>(a)) > std::llabs(std::get<2>(b) - std::get<1>(b)); + // instead of doing full PCA (which is more efficient probably), for now just try to find the 'flattest' of 13 orientations (full grid: 9 * 3 = 27, then: (27 - 1) / 2 = 13 to get rid of the origin and mirrored directions) + constexpr double D2 = 0.70710678118654752440084436210485; + constexpr double D3 = 0.57735026918962576450914878050196; + static std::vector normal_candidates = + std::vector({ + Point3D(0, 0, 1), + Point3D(0, 1, 0), + Point3D(1, 0, 0), + Point3D(0, D2, D2), + Point3D(0, D2, -D2), + Point3D(D2, 0, D2), + Point3D(D2, 0, -D2), + Point3D(D2, D2, 0), + Point3D(D2, -D2, 0), + Point3D(D3, D3, D3), + Point3D(D3, D3, -D3), + Point3D(D3, -D3, D3), + Point3D(D3, -D3, -D3), }); + std::unordered_map remapped_spans; + for (const auto& [idx, norm] : normal_candidates | ranges::views::enumerate) + { + auto& [remapped_min, remapped_max] = (remapped_spans[idx] = { std::numeric_limits::infinity(), -std::numeric_limits::infinity() }); + for (const auto& pt : points) + { + const auto val = proj(norm, pt); + remapped_min = std::min(remapped_min, val); + remapped_max = std::max(remapped_max, val); + } + } + + auto span_sizes = + remapped_spans | + ranges::view::transform([](const auto& key_value) { return std::make_pair(key_value.second.second - key_value.second.first, key_value.first); }) | + ranges::to>>(); + std::stable_sort(span_sizes.begin(), span_sizes.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); + + const auto& normal = normal_candidates[span_sizes.front().second]; + const auto& secondary = std::find_if(normal_candidates.begin(), normal_candidates.end(), + [&normal](const auto& ax) { return normal.cross(ax).vSize() > CLOSE_1D; }); + if (secondary == normal_candidates.end() || *secondary == normal) + { + return std::nullopt; + } + const auto& primary = std::find_if(normal_candidates.begin(), normal_candidates.end(), + [&normal, &secondary](const auto& ax) { return (*secondary).cross(ax).vSize() > CLOSE_1D && normal.cross(ax).vSize() > CLOSE_1D; }); + if (primary == normal_candidates.end() || *primary == normal || primary == secondary) + { + return std::nullopt; + } + const int primary_idx = std::distance(normal_candidates.begin(), primary); + const int secondary_idx = std::distance(normal_candidates.begin(), secondary); return std::make_optional(IdFieldInfo{ - .primary_axis_ = std::get<0>(dif_per_axis[0]), - .secondary_axis_ = std::get<0>(dif_per_axis[1]), - .normal_ = std::get<0>(dif_per_axis[2]), - .projection_field_ = AABB(Point2LL(std::get<1>(dif_per_axis[0]), std::get<1>(dif_per_axis[1])), Point2LL(std::get<2>(dif_per_axis[0]), std::get<2>(dif_per_axis[1]))) }); + .primary_axis_ = *primary, + .primary_span_ = remapped_spans[primary_idx], + .secondary_axis_ = *secondary, + .secondary_span_ = remapped_spans[secondary_idx], + .normal_ = normal }); } float mirrorNegative(const float in) @@ -57,7 +97,9 @@ float mirrorNegative(const float in) Point2F IdFieldInfo::worldPointToLabelUv(const Point3LL& pt) const { + // FOXME: (but should make smaller? -> get the '''furthest''' points in the '''diagonal''' directions of the chosen primary and secondary axii, should approximate well enough) + return Point2F( - 1.0f - mirrorNegative(static_cast(getAxisValue(primary_axis_, pt) - projection_field_.min_.X) / (projection_field_.max_.X - projection_field_.min_.X)), - 1.0f - mirrorNegative(static_cast(getAxisValue(secondary_axis_, pt) - projection_field_.min_.Y) / (projection_field_.max_.Y - projection_field_.min_.Y))); + 1.0f - (proj(primary_axis_, pt) - primary_span_.first) / (primary_span_.second - primary_span_.first), + 1.0f - (proj(secondary_axis_, pt) - secondary_span_.first) / (secondary_span_.second - secondary_span_.first) ); } From e2520a9beb65586b65cfb7de429bc4d59f64682f Mon Sep 17 00:00:00 2001 From: rburema <41987080+rburema@users.noreply.github.com> Date: Sat, 19 Jul 2025 10:40:51 +0000 Subject: [PATCH 14/21] Apply clang-format --- src/ExtruderPlan.cpp | 3 +- src/FffPolygonGenerator.cpp | 8 +--- src/mesh.cpp | 18 ++++---- src/utils/IdFieldInfo.cpp | 88 ++++++++++++++++++++++--------------- 4 files changed, 65 insertions(+), 52 deletions(-) diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 33aff3d7e6..1ff99f9ab1 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -83,7 +83,8 @@ void ExtruderPlan::applyIdLabel(const Image& slice_id_texture, const coord_t cur constexpr coord_t inset_dist = 40; // TODO?: make this configurable as well? for (auto& path : paths_) { - if (path.points.empty() || path.mesh == nullptr || path.mesh->layers[layer_nr_].texture_data_provider_ == nullptr || (! path.mesh->id_field_info) + if (path.points.empty() || path.mesh == nullptr || path.mesh->layers[layer_nr_].texture_data_provider_ == nullptr + || (! path.mesh->id_field_info) //|| (path.mesh->id_field_info.value().normal_ != IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::OuterWall) //|| (path.mesh->id_field_info.value().normal_ == IdFieldInfo::Axis::Z && path.config.type != PrintFeatureType::Skin)) || (path.config.type != PrintFeatureType::OuterWall && path.config.type != PrintFeatureType::Skin)) diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 14aaa63a3c..14c1d9b560 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -358,8 +358,6 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe // TODO: Deal with the fact that we _actually_ don't have the segments in that place anymore (I temporarily disabled the clear). // (We've still got all we need in the slicer_layer.sliced_uv_coordinates_.segments, but that's all private at the moment! { - - if (segment.uv_start.has_value() && segment.uv_end.has_value()) { const auto& uv_a = segment.uv_start.value(); @@ -373,10 +371,8 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe { if ((pixel & match_pixel) != 0b0) { - const auto param = - std::llabs(uv_b.x_ - uv_a.x_) >= std::llabs(uv_b.x_ - uv_a.y_) ? - (uv.x_ - uv_a.x_) / (uv_b.x_ - uv_a.x_) : - (uv.y_ - uv_a.y_) / (uv_b.y_ - uv_a.y_); + const auto param = std::llabs(uv_b.x_ - uv_a.x_) >= std::llabs(uv_b.x_ - uv_a.y_) ? (uv.x_ - uv_a.x_) / (uv_b.x_ - uv_a.x_) + : (uv.y_ - uv_a.y_) / (uv_b.y_ - uv_a.y_); label_pt_cloud.emplace_back(a + param * (b - a), height); } }); diff --git a/src/mesh.cpp b/src/mesh.cpp index 766f903304..9dfb484610 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -14,16 +14,14 @@ namespace cura void Image::visitSpanPerPixel(const Point2F& a, const Point2F& b, const std::function& func) const { - constexpr auto func_major_stepper = - [](const int64_t& da, const int64_t& abs_db, const int64_t& i) - { - return da < 0 ? -i : i; - }; - constexpr auto func_minor_stepper = - [](const int64_t& da, const int64_t& abs_db, const int64_t& i) - { - return (i * da) / abs_db; - }; + constexpr auto func_major_stepper = [](const int64_t& da, const int64_t& abs_db, const int64_t& i) + { + return da < 0 ? -i : i; + }; + constexpr auto func_minor_stepper = [](const int64_t& da, const int64_t& abs_db, const int64_t& i) + { + return (i * da) / abs_db; + }; const auto x0 = static_cast(a.x_ * width_); const auto y0 = static_cast(a.y_ * height_); diff --git a/src/utils/IdFieldInfo.cpp b/src/utils/IdFieldInfo.cpp index 49ab29f259..0a5e31a8f5 100644 --- a/src/utils/IdFieldInfo.cpp +++ b/src/utils/IdFieldInfo.cpp @@ -3,8 +3,6 @@ #include "utils/IdFieldInfo.h" -#include "utils/AABB3D.h" - #include #include #include @@ -13,6 +11,8 @@ #include #include +#include "utils/AABB3D.h" + using namespace cura; double proj(const Point3D& b, const Point3LL& p) @@ -29,25 +29,25 @@ std::optional IdFieldInfo::fromPointCloud(const std::vector normal_candidates = - std::vector({ - Point3D(0, 0, 1), - Point3D(0, 1, 0), - Point3D(1, 0, 0), - Point3D(0, D2, D2), - Point3D(0, D2, -D2), - Point3D(D2, 0, D2), - Point3D(D2, 0, -D2), - Point3D(D2, D2, 0), - Point3D(D2, -D2, 0), - Point3D(D3, D3, D3), - Point3D(D3, D3, -D3), - Point3D(D3, -D3, D3), - Point3D(D3, -D3, -D3), - }); + static std::vector normal_candidates = std::vector({ + Point3D(0, 0, 1), + Point3D(0, 1, 0), + Point3D(1, 0, 0), + Point3D(0, D2, D2), + Point3D(0, D2, -D2), + Point3D(D2, 0, D2), + Point3D(D2, 0, -D2), + Point3D(D2, D2, 0), + Point3D(D2, -D2, 0), + Point3D(D3, D3, D3), + Point3D(D3, D3, -D3), + Point3D(D3, -D3, D3), + Point3D(D3, -D3, -D3), + }); std::unordered_map remapped_spans; for (const auto& [idx, norm] : normal_candidates | ranges::views::enumerate) @@ -61,33 +61,51 @@ std::optional IdFieldInfo::fromPointCloud(const std::vector>>(); - std::stable_sort(span_sizes.begin(), span_sizes.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); + auto span_sizes = remapped_spans + | ranges::view::transform( + [](const auto& key_value) + { + return std::make_pair(key_value.second.second - key_value.second.first, key_value.first); + }) + | ranges::to>>(); + std::stable_sort( + span_sizes.begin(), + span_sizes.end(), + [](const auto& a, const auto& b) + { + return a.first < b.first; + }); const auto& normal = normal_candidates[span_sizes.front().second]; - const auto& secondary = std::find_if(normal_candidates.begin(), normal_candidates.end(), - [&normal](const auto& ax) { return normal.cross(ax).vSize() > CLOSE_1D; }); + const auto& secondary = std::find_if( + normal_candidates.begin(), + normal_candidates.end(), + [&normal](const auto& ax) + { + return normal.cross(ax).vSize() > CLOSE_1D; + }); if (secondary == normal_candidates.end() || *secondary == normal) { return std::nullopt; } - const auto& primary = std::find_if(normal_candidates.begin(), normal_candidates.end(), - [&normal, &secondary](const auto& ax) { return (*secondary).cross(ax).vSize() > CLOSE_1D && normal.cross(ax).vSize() > CLOSE_1D; }); + const auto& primary = std::find_if( + normal_candidates.begin(), + normal_candidates.end(), + [&normal, &secondary](const auto& ax) + { + return (*secondary).cross(ax).vSize() > CLOSE_1D && normal.cross(ax).vSize() > CLOSE_1D; + }); if (primary == normal_candidates.end() || *primary == normal || primary == secondary) { return std::nullopt; } const int primary_idx = std::distance(normal_candidates.begin(), primary); const int secondary_idx = std::distance(normal_candidates.begin(), secondary); - return std::make_optional(IdFieldInfo{ - .primary_axis_ = *primary, - .primary_span_ = remapped_spans[primary_idx], - .secondary_axis_ = *secondary, - .secondary_span_ = remapped_spans[secondary_idx], - .normal_ = normal }); + return std::make_optional(IdFieldInfo{ .primary_axis_ = *primary, + .primary_span_ = remapped_spans[primary_idx], + .secondary_axis_ = *secondary, + .secondary_span_ = remapped_spans[secondary_idx], + .normal_ = normal }); } float mirrorNegative(const float in) @@ -101,5 +119,5 @@ Point2F IdFieldInfo::worldPointToLabelUv(const Point3LL& pt) const return Point2F( 1.0f - (proj(primary_axis_, pt) - primary_span_.first) / (primary_span_.second - primary_span_.first), - 1.0f - (proj(secondary_axis_, pt) - secondary_span_.first) / (secondary_span_.second - secondary_span_.first) ); + 1.0f - (proj(secondary_axis_, pt) - secondary_span_.first) / (secondary_span_.second - secondary_span_.first)); } From 60984088daa33fb87b00f86b13fa6350c29ee1d3 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 19 Jul 2025 13:36:53 +0200 Subject: [PATCH 15/21] Fix Capitalizaton for *nix (Mac and Linux) builds. --- include/mesh.h | 2 +- include/sliceDataStorage.h | 2 +- src/utils/IdFieldInfo.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mesh.h b/include/mesh.h index c299b3062b..9b44353dc2 100644 --- a/include/mesh.h +++ b/include/mesh.h @@ -9,7 +9,7 @@ #include "TextureDataMapping.h" #include "settings/Settings.h" #include "utils/AABB3D.h" -#include "utils/IdFieldInfo.h" +#include "utils/IDFieldInfo.h" #include "utils/Matrix4x3D.h" #include "utils/Point2F.h" diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index 4fb24ab730..147aa895d3 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -23,7 +23,7 @@ #include "settings/types/LayerIndex.h" #include "utils/AABB.h" #include "utils/AABB3D.h" -#include "utils/IdFieldInfo.h" +#include "utils/IDFieldInfo.h" #include "utils/NoCopy.h" namespace cura diff --git a/src/utils/IdFieldInfo.cpp b/src/utils/IdFieldInfo.cpp index 0a5e31a8f5..c978da6ab9 100644 --- a/src/utils/IdFieldInfo.cpp +++ b/src/utils/IdFieldInfo.cpp @@ -1,7 +1,7 @@ // Copyright (c) 2025 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include "utils/IdFieldInfo.h" +#include "utils/IDFieldInfo.h" #include #include From d5bce7548025c9b5ff6f03e73a7e8e03b5ac40ec Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Sat, 19 Jul 2025 18:02:28 +0200 Subject: [PATCH 16/21] Slide-ID/Print-ID: Fix little code and spelling stuff (and hopefully linux compilation). part of UMH-2025 -- hackathon '25 --- include/mesh.h | 2 -- include/slicer.h | 4 ++++ include/utils/AABB3D.h | 6 +++--- include/utils/LabelMaker.h | 1 + src/FffPolygonGenerator.cpp | 4 +++- src/slicer.cpp | 5 ++++- src/utils/IdFieldInfo.cpp | 9 ++------- src/utils/LabelMaker.cpp | 8 ++++---- 8 files changed, 21 insertions(+), 18 deletions(-) diff --git a/include/mesh.h b/include/mesh.h index 9b44353dc2..ffb4ecea45 100644 --- a/include/mesh.h +++ b/include/mesh.h @@ -123,8 +123,6 @@ class Mesh std::unordered_map> vertex_hash_map_; AABB3D aabb_; - // std::optional id_field_info_; - public: std::vector vertices_; //!< list of all vertices in the mesh std::vector faces_; //!< list of all faces in the mesh diff --git a/include/slicer.h b/include/slicer.h index e3aa9cf260..9086b2e6b0 100644 --- a/include/slicer.h +++ b/include/slicer.h @@ -77,6 +77,10 @@ class SlicerLayer */ void makePolygons(const Mesh* mesh); + /*! + */ + void clearSegments(); + protected: /*! * Connect the segments into loops which correctly form polygons (don't perform stitching here) diff --git a/include/utils/AABB3D.h b/include/utils/AABB3D.h index 3bf56c2e59..4a41df1a44 100644 --- a/include/utils/AABB3D.h +++ b/include/utils/AABB3D.h @@ -43,10 +43,10 @@ struct AABB3D AABB flatten() const; /*! - * Wether or not this repressents an actual box in 'real' space, not a _negative_ volume. - * Note that the volume might still be empty (== 0.0), which can happen if it repressents a(n axis aligned) plane, a line, or a point. + * Whether or not this represents an actual box in 'real' space, not a _negative_ volume. + * Note that the volume might still be empty (== 0.0), which can happen if it represents a(n axis aligned) plane, a line, or a point. * - * \return Wether or not the space the box envelops is at least 0. + * \return Whether or not the space the box envelops is at least 0. */ bool exists() const; diff --git a/include/utils/LabelMaker.h b/include/utils/LabelMaker.h index 0ab4299a18..36f216a749 100644 --- a/include/utils/LabelMaker.h +++ b/include/utils/LabelMaker.h @@ -4,6 +4,7 @@ #ifndef LABEL_MAKER_H #define LABEL_MAKER_H +#include #include #include diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 14c1d9b560..40fa1e2619 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -310,7 +310,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe for (LayerIndex layer_nr = 0; layer_nr < meshStorage.layers.size(); layer_nr++) { SliceLayer& layer = meshStorage.layers[layer_nr]; - const SlicerLayer& slicer_layer = slicer->layers[layer_nr]; + SlicerLayer& slicer_layer = slicer->layers[layer_nr]; if (use_variable_layer_heights) { @@ -380,6 +380,8 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe } } } + + slicer_layer.clearSegments(); } meshStorage.setIdFieldInfo(label_pt_cloud); diff --git a/src/slicer.cpp b/src/slicer.cpp index 7f2d5bff28..a1a4f03486 100644 --- a/src/slicer.cpp +++ b/src/slicer.cpp @@ -799,9 +799,12 @@ void SlicerLayer::makePolygons(const Mesh* mesh) open_polylines_.removeDegenerateVerts(); sliced_uv_coordinates_ = std::make_shared(segments_); +} +void SlicerLayer::clearSegments() +{ // Clear the segment list to save memory, it is no longer needed after this point. - // segments_.clear(); // FIXME!!! -> put back once this code needs to be production quality (no longer hackathon proof-of-concept) + segments_.clear(); } Slicer::Slicer(Mesh* i_mesh, const coord_t thickness, const size_t slice_layer_count, bool use_variable_layer_heights, std::vector* adaptive_layers) diff --git a/src/utils/IdFieldInfo.cpp b/src/utils/IdFieldInfo.cpp index c978da6ab9..7ab548c174 100644 --- a/src/utils/IdFieldInfo.cpp +++ b/src/utils/IdFieldInfo.cpp @@ -20,7 +20,7 @@ double proj(const Point3D& b, const Point3LL& p) return (Point3D(p) * b) / (b * b); } -// FIMXE: something doesn't quite work right yet -- the normal is OK (I think?) but the primary and secondary axii don't do what I want yet... +// FIXME: something doesn't quite work right yet -- the normal is OK (I think?) but the primary and secondary axii don't do what I want yet... std::optional IdFieldInfo::fromPointCloud(const std::vector& points) { @@ -108,14 +108,9 @@ std::optional IdFieldInfo::fromPointCloud(const std::vector get the '''furthest''' points in the '''diagonal''' directions of the chosen primary and secondary axii, should approximate well enough) + // TODO: Get the '''furthest''' points in the '''diagonal''' directions of the chosen primary and secondary axii, should approximate well enough to 'inscribed' bounding rect. return Point2F( 1.0f - (proj(primary_axis_, pt) - primary_span_.first) / (primary_span_.second - primary_span_.first), diff --git a/src/utils/LabelMaker.cpp b/src/utils/LabelMaker.cpp index 53507867b3..0d6cb6480a 100644 --- a/src/utils/LabelMaker.cpp +++ b/src/utils/LabelMaker.cpp @@ -192,17 +192,17 @@ std::vector> makeFont(const std::array& } return res; } +const auto proc_font = makeFont(font8x8); void cura::paintStringToBuffer(const std::string_view& str, const size_t buffer_width, const size_t buffer_height, std::vector& buffer) { - static auto proc_font = makeFont(font8x8); - constexpr size_t base_offset_x = 3; size_t offset_x = base_offset_x; size_t offset_y = 3; - for (char c : str) + for (char cs : str) { - char offset = 0; + int c = cs; + int offset = 0; if (c >= 'a' && c <= 'z') { offset = 'a' - 0xA; From 6acf96bb7a22169bdb8b694df51000b1dc8c5862 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Mon, 21 Jul 2025 19:47:57 +0200 Subject: [PATCH 17/21] Small readability adjustments. Done for the UltiMaker Hackathon '25 -- Slice-ID/Print-ID project. part of UMH-2025 --- src/FffPolygonGenerator.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 40fa1e2619..498efff1a6 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -350,7 +350,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe layer.texture_data_provider_ = std::make_shared(slicer_layer.sliced_uv_coordinates_, mesh.texture_, mesh.texture_data_mapping_); // slice/print-ID label specific: (update/)calculate bounding-box - if (mesh.texture_data_mapping_->count("label") > 0) + if (mesh.texture_data_mapping_->contains("label")) { const auto match_pixel = static_cast(TextureArea::Preferred) << mesh.texture_data_mapping_->at("label").bit_range_start_index; const auto& height = layer.printZ; @@ -371,8 +371,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe { if ((pixel & match_pixel) != 0b0) { - const auto param = std::llabs(uv_b.x_ - uv_a.x_) >= std::llabs(uv_b.x_ - uv_a.y_) ? (uv.x_ - uv_a.x_) / (uv_b.x_ - uv_a.x_) - : (uv.y_ - uv_a.y_) / (uv_b.y_ - uv_a.y_); + const auto param = (uv - uv_a).vSize() / (uv_b - uv_a).vSize(); label_pt_cloud.emplace_back(a + param * (b - a), height); } }); From 5f1f08d63e5a4566cef45b12f8bb2a60abf76bfc Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Mon, 21 Jul 2025 19:58:02 +0200 Subject: [PATCH 18/21] Rewrite 'sliced UV coords' to use _line_ grid instead of _point_ grid. This gets rid of the awkward '99% of cases then handle the 1% with a different algorithm' thing, _but more importantly_ it sets the stage for properly using that class (in the next commit(s) hopefully) to get 'lines' of UV-coordinates that match up with the lines across face(s). The previous approach was adequate if you just wanted to know of a corner what UV-coords it had and didn't care which face it 'choose', because the paint would overlap the entire corner anyway (such as with the seam). However, since the UV-textures may be disconnected along the face-borders, and the points we're using _likely match up with the face corners_, this repressents a problem if you have to have a line across a face, as the function can only return one of the (maximum of) two possitbilities (an edge is part of at most 2 faces), and currently does so arbitrary -- I mean it still does kinda, but like I said, stage set for next steps. part of (Hackathon '25 project Slice-ID/Print-ID) UMH-2025 --- include/SlicedUVCoordinates.h | 15 +++++-- include/utils/Point2F.h | 15 +++++++ src/SlicedUVCoordinates.cpp | 77 +++++++++++------------------------ 3 files changed, 50 insertions(+), 57 deletions(-) diff --git a/include/SlicedUVCoordinates.h b/include/SlicedUVCoordinates.h index f9c9467c5c..83e08af89c 100644 --- a/include/SlicedUVCoordinates.h +++ b/include/SlicedUVCoordinates.h @@ -5,10 +5,11 @@ #define SLICEDUVCOORDINATES_H #include +#include #include "geometry/Point2LL.h" #include "utils/Point2F.h" -#include "utils/SparsePointGridInclusive.h" +#include "utils/SparseLineGrid.h" namespace cura { @@ -29,11 +30,19 @@ class SlicedUVCoordinates Point2LL start, end; Point2F uv_start, uv_end; }; + struct SegmentLocator + { + public: + std::pair operator() (const Segment& seg) + { + return {seg.start, seg.end}; + } + }; static constexpr coord_t cell_size{ 1000 }; static constexpr coord_t search_radius{ 1000 }; - SparsePointGridInclusive located_uv_coordinates_; - std::vector segments_; + + SparseLineGrid located_uv_coords_segs_; }; } // namespace cura diff --git a/include/utils/Point2F.h b/include/utils/Point2F.h index 265be46763..c3f85a317c 100644 --- a/include/utils/Point2F.h +++ b/include/utils/Point2F.h @@ -39,6 +39,11 @@ class Point2F return *this / vSize(); } + Point2F operator*(const float scale) const + { + return Point2F(x_ * scale, y_ * scale); + } + Point2F operator/(const double scale) const { return Point2F(x_ / scale, y_ / scale); @@ -47,6 +52,16 @@ class Point2F auto operator<=>(const Point2F&) const = default; }; +static Point2F operator+(const Point2F& a, const Point2F& b) +{ + return Point2F(a.x_ + b.x_, a.y_ + b.y_); +} + +static Point2F operator-(const Point2F& a, const Point2F& b) +{ + return Point2F(a.x_ - b.x_, a.y_ - b.y_); +} + static Point2F lerp(const Point2F& a, const Point2F& b, const float t) { return Point2F(std::lerp(a.x_, b.x_, t), std::lerp(a.y_, b.y_, t)); diff --git a/src/SlicedUVCoordinates.cpp b/src/SlicedUVCoordinates.cpp index f7137adc98..69fa202548 100644 --- a/src/SlicedUVCoordinates.cpp +++ b/src/SlicedUVCoordinates.cpp @@ -3,78 +3,47 @@ #include "SlicedUVCoordinates.h" +#include "utils/linearAlg2D.h" #include "slicer.h" namespace cura { SlicedUVCoordinates::SlicedUVCoordinates(const std::vector& segments) - : located_uv_coordinates_(cell_size) + : located_uv_coords_segs_(cell_size) { - segments_.reserve(segments.size()); - for (const SlicerSegment& segment : segments) { if (segment.uv_start.has_value() && segment.uv_end.has_value()) { - located_uv_coordinates_.insert(segment.start, segment.uv_start.value()); - located_uv_coordinates_.insert(segment.end, segment.uv_end.value()); - - segments_.push_back(Segment{ segment.start, segment.end, segment.uv_start.value(), segment.uv_end.value() }); + located_uv_coords_segs_.insert(Segment{ segment.start, segment.end, segment.uv_start.value(), segment.uv_end.value() }); } } } std::optional SlicedUVCoordinates::getClosestUVCoordinates(const Point2LL& position) const { - // First try the quick method, which will work in 99% cases - SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem nearest_uv_coordinates; - if (located_uv_coordinates_.getNearest(position, search_radius, nearest_uv_coordinates)) - { - return nearest_uv_coordinates.val; - } - - // We couldn't find a close point with UV coordinates, so try to find the closest segment and project the point to it - double closest_distance = std::numeric_limits::max(); - std::optional closest_uv_coordinates; - - for (const Segment& segment : segments_) - { - const double segment_length = vSizef(segment.end - segment.start); - if (std::abs(segment_length) < 0.001) - { - continue; - } - - const double dot_product = dot((position - segment.start), (segment.end - segment.start)) / segment_length; - double distance_to_segment; - double interpolate_factor; - - if (dot_product > segment_length) + float closest_dist2 = std::numeric_limits::infinity(); + const Segment* res = nullptr; + float res_param = 0.0f; + const auto find_closest = + [&position, &closest_dist2, &res, &res_param](const Segment& seg) { - interpolate_factor = 1.0; - distance_to_segment = vSizef(position - segment.end); - } - else if (dot_product < 0.0) - { - interpolate_factor = 0.0; - distance_to_segment = vSizef(position - segment.start); - } - else - { - interpolate_factor = dot_product / segment_length; - const Point2LL projected_position = cura::lerp(segment.start, segment.end, interpolate_factor); - distance_to_segment = vSizef(position - projected_position); - } - - if (distance_to_segment < closest_distance) - { - closest_distance = distance_to_segment; - closest_uv_coordinates = cura::lerp(segment.uv_start, segment.uv_end, static_cast(interpolate_factor)); - } - } - - return closest_uv_coordinates; + const auto on_line = LinearAlg2D::getClosestOnLineSegment(position, seg.start, seg.end); + const float err2 = vSize2(on_line - position); + if (err2 < closest_dist2) + { + closest_dist2 = err2; + res = &seg; + res_param = vSize(on_line - seg.start) / static_cast(vSize(seg.end - seg.start)); + } + return true; // Don't stop searching at any point during this loop. + }; + located_uv_coords_segs_.processNearby(position, search_radius, find_closest); + return + closest_dist2 < std::numeric_limits::infinity() ? + std::make_optional(res->uv_start + (res->uv_end - res->uv_start) * res_param) : + std::nullopt; } } // namespace cura From 37d47e8ffcc77123d86893d16798ec81e9bf9ef5 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Mon, 21 Jul 2025 20:50:46 +0200 Subject: [PATCH 19/21] Going to need the Segments outside of the lne-grid as well. (Needed for the 'segments by point' strucute, which will be needed to do face-adjacency if we want to search by spans instead of single points, in the latter which case we didn't really care about what face, as long as the point is correct, but for spans of UV-pixels, we _do_ case.) -- As line-grid doesn't really have a proper way to access the elements, move the storage (back) to a vector and make the elements pointers instead. part of (Hackathon '25 Slice-ID/Print-ID project) UMH-2025 --- include/SlicedUVCoordinates.h | 11 ++++++++--- src/SlicedUVCoordinates.cpp | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/include/SlicedUVCoordinates.h b/include/SlicedUVCoordinates.h index 83e08af89c..3c8c0451ad 100644 --- a/include/SlicedUVCoordinates.h +++ b/include/SlicedUVCoordinates.h @@ -5,6 +5,7 @@ #define SLICEDUVCOORDINATES_H #include +#include #include #include "geometry/Point2LL.h" @@ -24,6 +25,8 @@ class SlicedUVCoordinates std::optional getClosestUVCoordinates(const Point2LL& position) const; + std::optional> getUVCoordsLineSegment(const Point2LL& from, const Point2LL& to) const; + private: struct Segment { @@ -33,16 +36,18 @@ class SlicedUVCoordinates struct SegmentLocator { public: - std::pair operator() (const Segment& seg) + std::pair operator() (Segment* const& seg) { - return {seg.start, seg.end}; + return {seg->start, seg->end}; } }; static constexpr coord_t cell_size{ 1000 }; static constexpr coord_t search_radius{ 1000 }; - SparseLineGrid located_uv_coords_segs_; + std::vector segments_; + SparseLineGrid located_uv_coords_segs_; + std::unordered_multimap segs_by_point_; }; } // namespace cura diff --git a/src/SlicedUVCoordinates.cpp b/src/SlicedUVCoordinates.cpp index 69fa202548..0bf1f1a0f7 100644 --- a/src/SlicedUVCoordinates.cpp +++ b/src/SlicedUVCoordinates.cpp @@ -12,11 +12,18 @@ namespace cura SlicedUVCoordinates::SlicedUVCoordinates(const std::vector& segments) : located_uv_coords_segs_(cell_size) { + segments_.reserve(segments.size()); for (const SlicerSegment& segment : segments) { if (segment.uv_start.has_value() && segment.uv_end.has_value()) { - located_uv_coords_segs_.insert(Segment{ segment.start, segment.end, segment.uv_start.value(), segment.uv_end.value() }); + segments_.emplace_back(segment.start, segment.end, segment.uv_start.value(), segment.uv_end.value()); + Segment* seg = &segments_.back(); + + located_uv_coords_segs_.insert(seg); + + segs_by_point_.insert({ seg->start, seg }); + segs_by_point_.insert({ seg->end, seg }); } } } @@ -27,15 +34,15 @@ std::optional SlicedUVCoordinates::getClosestUVCoordinates(const Point2 const Segment* res = nullptr; float res_param = 0.0f; const auto find_closest = - [&position, &closest_dist2, &res, &res_param](const Segment& seg) + [&position, &closest_dist2, &res, &res_param](Segment* const& seg) { - const auto on_line = LinearAlg2D::getClosestOnLineSegment(position, seg.start, seg.end); + const auto on_line = LinearAlg2D::getClosestOnLineSegment(position, seg->start, seg->end); const float err2 = vSize2(on_line - position); if (err2 < closest_dist2) { closest_dist2 = err2; - res = &seg; - res_param = vSize(on_line - seg.start) / static_cast(vSize(seg.end - seg.start)); + res = seg; + res_param = vSize(on_line - seg->start) / static_cast(vSize(seg->end - seg->start)); } return true; // Don't stop searching at any point during this loop. }; From 05b7f9995dd90291533021f1c1135b765abfcbfc Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 22 Jul 2025 00:57:16 +0200 Subject: [PATCH 20/21] State of Slice-ID/Print-ID at the end of the Hackathon '25. Tried to fix a few things bit mostly diagnosed where the problems still are. Besides all of the fixme/todo's that're already in the code, the biggest problems are: - reliably getting from 'real-world' points to usable UV coordinates, as we need _lines_ and not indivisual points, it starts mattering from which UV-face we pull those coordinates - this should be _really_ simple actually -- but the 'sliding window' over the path-points/polygon doesn't do the last leg -- but if I add it forcefully, it adds an extra line that was the UltiMaker Hackaton '25 Slice-ID/Print-ID project -- we hoped you liked flying with remco airlines -- UMH-2025 --- include/SlicedUVCoordinates.h | 5 +++ src/ExtruderPlan.cpp | 5 ++- src/SlicedUVCoordinates.cpp | 72 +++++++++++++++++++++++++++++++++++ src/TextureDataProvider.cpp | 12 ++---- 4 files changed, 85 insertions(+), 9 deletions(-) diff --git a/include/SlicedUVCoordinates.h b/include/SlicedUVCoordinates.h index 3c8c0451ad..2ebd84cfa4 100644 --- a/include/SlicedUVCoordinates.h +++ b/include/SlicedUVCoordinates.h @@ -25,6 +25,11 @@ class SlicedUVCoordinates std::optional getClosestUVCoordinates(const Point2LL& position) const; + /*! /!\ WARNINGS /!\ + * - Currently assumes straight spans in UV-space will be over at most 2 faces, but this isn't _universally_ true. + * - Currently returns the UV-coords of the _entire_ segment from and to are part of, + * even if from and to repressent some points other than begin and end (though it should handle reversed). + */ std::optional> getUVCoordsLineSegment(const Point2LL& from, const Point2LL& to) const; private: diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index 1ff99f9ab1..a1bcab3757 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -99,6 +99,9 @@ void ExtruderPlan::applyIdLabel(const Image& slice_id_texture, const coord_t cur const auto& id_field_info = path.mesh->id_field_info.value(); + // FIMXE!: The sliding here is only going over 3 sides of the polygon, which means it's not 'closed' at this moment in time. + // Attempts to fix this have so far not been successful (but tired r.n. will look at it when less so later). + std::vector new_points; std::vector idlabel_uvs; new_points.push_back(path.points.front()); @@ -142,7 +145,7 @@ void ExtruderPlan::applyIdLabel(const Image& slice_id_texture, const coord_t cur } new_points.push_back(b); - idlabel_uvs.push_back(Point2F(NAN, NAN)); + idlabel_uvs.push_back(signal_no_uv); } if (new_points.size() != path.points.size()) diff --git a/src/SlicedUVCoordinates.cpp b/src/SlicedUVCoordinates.cpp index 0bf1f1a0f7..d0cbfba787 100644 --- a/src/SlicedUVCoordinates.cpp +++ b/src/SlicedUVCoordinates.cpp @@ -6,6 +6,8 @@ #include "utils/linearAlg2D.h" #include "slicer.h" +#include + namespace cura { @@ -53,4 +55,74 @@ std::optional SlicedUVCoordinates::getClosestUVCoordinates(const Point2 std::nullopt; } +std::optional> SlicedUVCoordinates::getUVCoordsLineSegment(const Point2LL& a, const Point2LL& b) const +{ + // TODO/FIXME: This is currently not optimized. + // (First tried to write the optimized version but it didn't work out... will have to try again later when more clearheaded.) + // _ALSO!_ this'll only work reliably for quite simple UV-meshes, so it really _needs_ to change. + + typedef std::tuple seg_dist2_t; + + const Point2LL* ptr_pt; + std::vector* ptr_dist2_list; + const auto gather_segments = + [&ptr_pt, &ptr_dist2_list](Segment* const& seg) + { + const auto on_line = LinearAlg2D::getClosestOnLineSegment(*ptr_pt, seg->start, seg->end); + const float err2 = vSize2(on_line - *ptr_pt); + const float param = vSize(on_line - seg->start) / static_cast(vSize(seg->end - seg->start)); + ptr_dist2_list->emplace_back(seg, param < 0.5f, err2); + return true; + }; + const auto sort_by_dist2 = + [](const seg_dist2_t& q, const seg_dist2_t& r) + { + return std::get<2>(q) < std::get<2>(r); + }; + + std::vector closest_to_a; + ptr_pt = &a; + ptr_dist2_list = &closest_to_a; + located_uv_coords_segs_.processNearby(a, search_radius, gather_segments); + std::stable_sort(closest_to_a.begin(), closest_to_a.end(), sort_by_dist2); + + std::vector closest_to_b; + ptr_pt = &b; + ptr_dist2_list = &closest_to_b; + located_uv_coords_segs_.processNearby(b, search_radius, gather_segments); + std::stable_sort(closest_to_b.begin(), closest_to_b.end(), sort_by_dist2); + + float closest_uv_dist2 = std::numeric_limits::infinity(); + std::pair pts; + + for (const auto& a_seg_info : closest_to_a) + { + for (const auto& b_seg_info : closest_to_b) + { + Segment* a_seg = std::get<0>(a_seg_info); + Segment* b_seg = std::get<0>(b_seg_info); + + const bool a_side = std::get<1>(a_seg_info); + const bool b_side = std::get<1>(b_seg_info); + + const auto& a_uv = a_side ? a_seg->uv_start : a_seg->uv_end; + const auto& b_uv = b_side ? b_seg->uv_start : b_seg->uv_end; + //assert(a_uv != b_uv); + if (a_uv == b_uv) + { + return std::nullopt; + } + + const float uv_dist2 = (b_uv - a_uv).vSize2(); + if (uv_dist2 < closest_uv_dist2) + { + closest_uv_dist2 = uv_dist2; + pts = { a_uv, b_uv }; + } + } + } + + return closest_uv_dist2 < std::numeric_limits::infinity() ? std::make_optional(pts) : std::nullopt; +} + } // namespace cura diff --git a/src/TextureDataProvider.cpp b/src/TextureDataProvider.cpp index 3fd3126fc6..43be98fc0f 100644 --- a/src/TextureDataProvider.cpp +++ b/src/TextureDataProvider.cpp @@ -53,9 +53,6 @@ std::optional TextureDataProvider::getAreaPreference(const Point2LL bool TextureDataProvider::getTexelsForSpan(const Point2LL& a, const Point2LL& b, const std::string& feature, std::vector& res) const { - // FIXME: This doesn't take into account the whole 'a and b aren't necesarily in the same texture patch' thing, as the plain 'Image' class and its 'visitSpanPerPixels' method - // are of course unaware of it. - auto data_mapping_iterator = texture_data_mapping_->find(feature); if (data_mapping_iterator == texture_data_mapping_->end()) { @@ -63,17 +60,16 @@ bool TextureDataProvider::getTexelsForSpan(const Point2LL& a, const Point2LL& b, } const TextureBitField& bit_field = data_mapping_iterator->second; - const std::optional point_uv_a = uv_coordinates_->getClosestUVCoordinates(a); - const std::optional point_uv_b = uv_coordinates_->getClosestUVCoordinates(b); - if (! (point_uv_a.has_value() && point_uv_b.has_value())) + const std::optional> points_uv = uv_coordinates_->getUVCoordsLineSegment(a, b); + if (! points_uv.has_value()) { return false; } bool has_any = false; texture_->visitSpanPerPixel( - point_uv_a.value(), - point_uv_b.value(), + points_uv.value().first, + points_uv.value().second, [&bit_field, &res, &has_any](const int32_t pixel_data, const Point2F& uv_pt) { res.push_back({ static_cast( From 96efb6437595de589f36ba9fcf88a80dcb5051b0 Mon Sep 17 00:00:00 2001 From: rburema <41987080+rburema@users.noreply.github.com> Date: Mon, 21 Jul 2025 22:58:05 +0000 Subject: [PATCH 21/21] Apply clang-format --- include/SlicedUVCoordinates.h | 4 +-- src/SlicedUVCoordinates.cpp | 62 ++++++++++++++++------------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/include/SlicedUVCoordinates.h b/include/SlicedUVCoordinates.h index 2ebd84cfa4..85ec801601 100644 --- a/include/SlicedUVCoordinates.h +++ b/include/SlicedUVCoordinates.h @@ -41,9 +41,9 @@ class SlicedUVCoordinates struct SegmentLocator { public: - std::pair operator() (Segment* const& seg) + std::pair operator()(Segment* const& seg) { - return {seg->start, seg->end}; + return { seg->start, seg->end }; } }; diff --git a/src/SlicedUVCoordinates.cpp b/src/SlicedUVCoordinates.cpp index d0cbfba787..5c14835c8f 100644 --- a/src/SlicedUVCoordinates.cpp +++ b/src/SlicedUVCoordinates.cpp @@ -3,11 +3,11 @@ #include "SlicedUVCoordinates.h" -#include "utils/linearAlg2D.h" -#include "slicer.h" - #include +#include "slicer.h" +#include "utils/linearAlg2D.h" + namespace cura { @@ -35,24 +35,20 @@ std::optional SlicedUVCoordinates::getClosestUVCoordinates(const Point2 float closest_dist2 = std::numeric_limits::infinity(); const Segment* res = nullptr; float res_param = 0.0f; - const auto find_closest = - [&position, &closest_dist2, &res, &res_param](Segment* const& seg) + const auto find_closest = [&position, &closest_dist2, &res, &res_param](Segment* const& seg) + { + const auto on_line = LinearAlg2D::getClosestOnLineSegment(position, seg->start, seg->end); + const float err2 = vSize2(on_line - position); + if (err2 < closest_dist2) { - const auto on_line = LinearAlg2D::getClosestOnLineSegment(position, seg->start, seg->end); - const float err2 = vSize2(on_line - position); - if (err2 < closest_dist2) - { - closest_dist2 = err2; - res = seg; - res_param = vSize(on_line - seg->start) / static_cast(vSize(seg->end - seg->start)); - } - return true; // Don't stop searching at any point during this loop. - }; + closest_dist2 = err2; + res = seg; + res_param = vSize(on_line - seg->start) / static_cast(vSize(seg->end - seg->start)); + } + return true; // Don't stop searching at any point during this loop. + }; located_uv_coords_segs_.processNearby(position, search_radius, find_closest); - return - closest_dist2 < std::numeric_limits::infinity() ? - std::make_optional(res->uv_start + (res->uv_end - res->uv_start) * res_param) : - std::nullopt; + return closest_dist2 < std::numeric_limits::infinity() ? std::make_optional(res->uv_start + (res->uv_end - res->uv_start) * res_param) : std::nullopt; } std::optional> SlicedUVCoordinates::getUVCoordsLineSegment(const Point2LL& a, const Point2LL& b) const @@ -65,20 +61,18 @@ std::optional> SlicedUVCoordinates::getUVCoordsLineS const Point2LL* ptr_pt; std::vector* ptr_dist2_list; - const auto gather_segments = - [&ptr_pt, &ptr_dist2_list](Segment* const& seg) - { - const auto on_line = LinearAlg2D::getClosestOnLineSegment(*ptr_pt, seg->start, seg->end); - const float err2 = vSize2(on_line - *ptr_pt); - const float param = vSize(on_line - seg->start) / static_cast(vSize(seg->end - seg->start)); - ptr_dist2_list->emplace_back(seg, param < 0.5f, err2); - return true; - }; - const auto sort_by_dist2 = - [](const seg_dist2_t& q, const seg_dist2_t& r) - { - return std::get<2>(q) < std::get<2>(r); - }; + const auto gather_segments = [&ptr_pt, &ptr_dist2_list](Segment* const& seg) + { + const auto on_line = LinearAlg2D::getClosestOnLineSegment(*ptr_pt, seg->start, seg->end); + const float err2 = vSize2(on_line - *ptr_pt); + const float param = vSize(on_line - seg->start) / static_cast(vSize(seg->end - seg->start)); + ptr_dist2_list->emplace_back(seg, param < 0.5f, err2); + return true; + }; + const auto sort_by_dist2 = [](const seg_dist2_t& q, const seg_dist2_t& r) + { + return std::get<2>(q) < std::get<2>(r); + }; std::vector closest_to_a; ptr_pt = &a; @@ -107,7 +101,7 @@ std::optional> SlicedUVCoordinates::getUVCoordsLineS const auto& a_uv = a_side ? a_seg->uv_start : a_seg->uv_end; const auto& b_uv = b_side ? b_seg->uv_start : b_seg->uv_end; - //assert(a_uv != b_uv); + // assert(a_uv != b_uv); if (a_uv == b_uv) { return std::nullopt;