From e3407b3c178ec10aebcc66955ff8ea5797917144 Mon Sep 17 00:00:00 2001 From: "zhou.xu" Date: Wed, 17 Apr 2024 17:33:19 +0800 Subject: [PATCH] NEW:import vertex and mtl color from obj file Jira: STUDIO-6805 Change-Id: Iaacb13ee2451effdb83e5aba4b7fe1637b7fc95f --- src/BambuStudio.cpp | 3 +- src/libslic3r/CMakeLists.txt | 3 + src/libslic3r/Color.cpp | 10 +- src/libslic3r/Color.hpp | 9 +- src/libslic3r/Format/OBJ.cpp | 122 +++- src/libslic3r/Format/OBJ.hpp | 20 +- src/libslic3r/Format/objparser.cpp | 285 +++++++++- src/libslic3r/Format/objparser.hpp | 46 +- src/libslic3r/Model.cpp | 215 +++++++- src/libslic3r/Model.hpp | 11 +- src/libslic3r/ObjColorUtils.hpp | 187 +++++++ src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/ObjColorDialog.cpp | 824 ++++++++++++++++++++++++++++ src/slic3r/GUI/ObjColorDialog.hpp | 113 ++++ src/slic3r/GUI/Plater.cpp | 45 +- src/slic3r/GUI/Plater.hpp | 3 + src/slic3r/GUI/Widgets/ComboBox.hpp | 1 + src/slic3r/GUI/wxExtensions.cpp | 5 +- src/slic3r/GUI/wxExtensions.hpp | 3 +- 19 files changed, 1830 insertions(+), 77 deletions(-) create mode 100644 src/libslic3r/ObjColorUtils.hpp create mode 100644 src/slic3r/GUI/ObjColorDialog.cpp create mode 100644 src/slic3r/GUI/ObjColorDialog.hpp diff --git a/src/BambuStudio.cpp b/src/BambuStudio.cpp index 67a348bf0..a6a0ccb67 100644 --- a/src/BambuStudio.cpp +++ b/src/BambuStudio.cpp @@ -791,7 +791,8 @@ static int construct_assemble_list(std::vector &assemble_ else if (boost::algorithm::iends_with(assemble_object.path, ".obj")) { std::string message; - bool result = load_obj(path_str, &mesh, message); + ObjInfo obj_info; + bool result = load_obj(path_str, &mesh, obj_info, message); if (!result) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": failed to read a valid mesh from obj file %1%, plate index %2%, object index %3%, error %4%") % assemble_object.path % (index + 1) % (obj_index + 1) % message; return CLI_DATA_FILE_ERROR; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index e4d5c0c6f..ccf30d842 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -213,6 +213,7 @@ set(lisbslic3r_sources Arrange.cpp NormalUtils.cpp NormalUtils.hpp + ObjColorUtils.hpp Orient.hpp Orient.cpp MultiPoint.cpp @@ -432,6 +433,7 @@ set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE) cmake_policy(PUSH) cmake_policy(SET CMP0011 NEW) find_package(CGAL REQUIRED) +find_package(OpenCV REQUIRED core) cmake_policy(POP) add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp TryCatchSignal.hpp @@ -523,6 +525,7 @@ target_link_libraries(libslic3r ${OCCT_LIBS} Clipper2 mcut + opencv_world ) if(NOT WIN32) diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp index 7777b0e3e..654b248b8 100644 --- a/src/libslic3r/Color.cpp +++ b/src/libslic3r/Color.cpp @@ -6,7 +6,15 @@ static const float INV_255 = 1.0f / 255.0f; namespace Slic3r { - +bool color_is_equal(const RGBA a, const RGBA& b) +{ + for (size_t i = 0; i < 4; i++) { + if (abs(a[i] - b[i]) > 0.01) { + return false; + } + } + return true; +} // Conversion from RGB to HSV color space // The input RGB values are in the range [0, 1] // The output HSV values are in the ranges h = [0, 360], and s, v = [0, 1] diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp index 5719d0eb9..870481d19 100644 --- a/src/libslic3r/Color.hpp +++ b/src/libslic3r/Color.hpp @@ -5,7 +5,10 @@ #include namespace Slic3r { - +using RGB = std::array; +using RGBA = std::array; +const RGBA UNDEFINE_COLOR = {0,0,0,0}; +bool color_is_equal(const RGBA a, const RGBA &b); class ColorRGB { std::array m_data{1.0f, 1.0f, 1.0f}; @@ -81,7 +84,9 @@ public: ColorRGBA& operator = (const ColorRGBA& other) { m_data = other.m_data; return *this; } - bool operator == (const ColorRGBA& other) const { return m_data == other.m_data; } + bool operator==(const ColorRGBA &other) const{ + return color_is_equal(m_data, other.m_data); + } bool operator != (const ColorRGBA& other) const { return !operator==(other); } bool operator < (const ColorRGBA& other) const; bool operator > (const ColorRGBA& other) const; diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp index 4e621e5f7..d208165cc 100644 --- a/src/libslic3r/Format/OBJ.cpp +++ b/src/libslic3r/Format/OBJ.cpp @@ -21,19 +21,39 @@ namespace Slic3r { -bool load_obj(const char *path, TriangleMesh *meshptr, std::string &message) +bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::string &message) { if (meshptr == nullptr) return false; - // Parse the OBJ file. ObjParser::ObjData data; + ObjParser::MtlData mtl_data; if (! ObjParser::objparse(path, data)) { BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path; message = _L("load_obj: failed to parse"); return false; } - + bool exist_mtl = false; + if (data.mtllibs.size() > 0) { // read mtl + for (auto mtl_name : data.mtllibs) { + boost::filesystem::path full_path(path); + std::string dir = full_path.parent_path().string(); + auto mtl_file = dir + "/" + mtl_name; + boost::filesystem::path mtl_path(mtl_file); + auto _mtl_path = mtl_path.string().c_str(); + if (boost::filesystem::exists(mtl_path)) { + if (!ObjParser::mtlparse(_mtl_path, mtl_data)) { + BOOST_LOG_TRIVIAL(error) << "load_obj:load_mtl: failed to parse " << _mtl_path; + message = _L("load mtl in obj: failed to parse"); + return false; + } + } + else { + BOOST_LOG_TRIVIAL(error) << "load_obj: failed to load mtl_path:" << _mtl_path; + } + } + exist_mtl = true; + } // Count the faces and verify, that all faces are triangular. size_t num_faces = 0; size_t num_quads = 0; @@ -59,17 +79,27 @@ bool load_obj(const char *path, TriangleMesh *meshptr, std::string &message) i = j; } } - // Convert ObjData into indexed triangle set. indexed_triangle_set its; - size_t num_vertices = data.coordinates.size() / 4; + size_t num_vertices = data.coordinates.size() / OBJ_VERTEX_LENGTH; its.vertices.reserve(num_vertices); its.indices.reserve(num_faces + num_quads); - for (size_t i = 0; i < num_vertices; ++ i) { - size_t j = i << 2; - its.vertices.emplace_back(data.coordinates[j], data.coordinates[j + 1], data.coordinates[j + 2]); + if (exist_mtl) { + obj_info.is_single_mtl = data.usemtls.size() == 1 && mtl_data.new_mtl_unmap.size() == 1; + obj_info.face_colors.reserve(num_faces + num_quads); } - int indices[4]; + bool has_color = data.has_vertex_color; + for (size_t i = 0; i < num_vertices; ++ i) { + size_t j = i * OBJ_VERTEX_LENGTH; + its.vertices.emplace_back(data.coordinates[j], data.coordinates[j + 1], data.coordinates[j + 2]); + if (data.has_vertex_color) { + RGBA color{std::clamp(data.coordinates[j + 3], 0.f, 1.f), std::clamp(data.coordinates[j + 4], 0.f, 1.f), std::clamp(data.coordinates[j + 5], 0.f, 1.f), + std::clamp(data.coordinates[j + 6], 0.f, 1.f)}; + obj_info.vertex_colors.emplace_back(color); + } + } + int indices[ONE_FACE_SIZE]; + int uvs[ONE_FACE_SIZE]; for (size_t i = 0; i < data.vertices.size();) if (data.vertices[i].coordIdx == -1) ++ i; @@ -79,20 +109,77 @@ bool load_obj(const char *path, TriangleMesh *meshptr, std::string &message) if (const ObjParser::ObjVertex &vertex = data.vertices[i ++]; vertex.coordIdx == -1) { break; } else { - assert(cnt < 4); + assert(cnt < OBJ_VERTEX_LENGTH); if (vertex.coordIdx < 0 || vertex.coordIdx >= int(its.vertices.size())) { BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains invalid vertex index."; message = _L("The file contains invalid vertex index."); return false; } - indices[cnt ++] = vertex.coordIdx; + indices[cnt] = vertex.coordIdx; + uvs[cnt] = vertex.textureCoordIdx; + cnt++; } if (cnt) { assert(cnt == 3 || cnt == 4); // Insert one or two faces (triangulate a quad). its.indices.emplace_back(indices[0], indices[1], indices[2]); - if (cnt == 4) + int face_index =its.indices.size() - 1; + RGBA face_color; + auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name) { + if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) { + for (size_t n = 0; n < 3; n++) { + if (float(mtl_data.new_mtl_unmap[mtl_name]->Ka[n] + mtl_data.new_mtl_unmap[mtl_name]->Kd[n]) > 1.0) { + face_color[n] = std::clamp(float(mtl_data.new_mtl_unmap[mtl_name]->Kd[n]), 0.f, 1.f); + } + else { + face_color[n] = std::clamp(float(mtl_data.new_mtl_unmap[mtl_name]->Ka[n] + mtl_data.new_mtl_unmap[mtl_name]->Kd[n]), 0.f, 1.f); + } + } + face_color[3] = 1.0; // default alpha + if (mtl_data.new_mtl_unmap[mtl_name]->map_Kd.size() > 0) { + auto png_name = mtl_data.new_mtl_unmap[mtl_name]->map_Kd; + obj_info.has_uv_png = true; + if (obj_info.pngs.find(png_name) == obj_info.pngs.end()) { obj_info.pngs[png_name] = false; } + obj_info.uv_map_pngs[face_index] = png_name; + } + if (data.textureCoordinates.size() > 0) { + Vec2f uv0(data.textureCoordinates[uvs[0] * 2], data.textureCoordinates[uvs[0] * 2 + 1]); + Vec2f uv1(data.textureCoordinates[uvs[1] * 2], data.textureCoordinates[uvs[1] * 2 + 1]); + Vec2f uv2(data.textureCoordinates[uvs[2] * 2], data.textureCoordinates[uvs[2] * 2 + 1]); + std::array uv_array{uv0, uv1, uv2}; + obj_info.uvs.emplace_back(uv_array); + } + obj_info.face_colors.emplace_back(face_color); + } + }; + auto set_face_color_by_mtl = [&data, &set_face_color](int face_index) { + if (data.usemtls.size() == 1) { + set_face_color(face_index, data.usemtls[0].name); + } else { + for (size_t k = 0; k < data.usemtls.size(); k++) { + auto mtl = data.usemtls[k]; + if (mtl.vertexIdxEnd == -1 && face_index >= (mtl.vertexIdxFirst / ONE_FACE_SIZE)) { + set_face_color(face_index, data.usemtls[k].name); + break; + } else if (mtl.vertexIdxEnd != -1 && + face_index >= (mtl.vertexIdxFirst / ONE_FACE_SIZE) + && face_index < (mtl.vertexIdxEnd / ONE_FACE_SIZE)) { + set_face_color(face_index, data.usemtls[k].name); + break; + } + } + } + }; + if (exist_mtl) { + set_face_color_by_mtl(face_index); + } + if (cnt == 4) { its.indices.emplace_back(indices[0], indices[2], indices[3]); + int face_index = its.indices.size() - 1; + if (exist_mtl) { + set_face_color_by_mtl(face_index); + } + } } } @@ -107,12 +194,12 @@ bool load_obj(const char *path, TriangleMesh *meshptr, std::string &message) return true; } -bool load_obj(const char *path, Model *model, std::string &message, const char *object_name_in) +bool load_obj(const char *path, Model *model, ObjInfo& obj_info, std::string &message, const char *object_name_in) { TriangleMesh mesh; - - bool ret = load_obj(path, &mesh, message); - + + bool ret = load_obj(path, &mesh, obj_info, message); + if (ret) { std::string object_name; if (object_name_in == nullptr) { @@ -120,10 +207,9 @@ bool load_obj(const char *path, Model *model, std::string &message, const char * object_name.assign((last_slash == nullptr) ? path : last_slash + 1); } else object_name.assign(object_name_in); - model->add_object(object_name.c_str(), path, std::move(mesh)); } - + return ret; } diff --git a/src/libslic3r/Format/OBJ.hpp b/src/libslic3r/Format/OBJ.hpp index e9a3817e4..9b618bd27 100644 --- a/src/libslic3r/Format/OBJ.hpp +++ b/src/libslic3r/Format/OBJ.hpp @@ -1,15 +1,27 @@ #ifndef slic3r_Format_OBJ_hpp_ #define slic3r_Format_OBJ_hpp_ - +#include "libslic3r/Color.hpp" +#include namespace Slic3r { class TriangleMesh; class Model; class ModelObject; - +typedef std::function &input_colors, bool is_single_color, std::vector &filament_ids, unsigned char &first_extruder_id)> ObjImportColorFn; // Load an OBJ file into a provided model. -extern bool load_obj(const char *path, TriangleMesh *mesh, std::string &message); -extern bool load_obj(const char *path, Model *model, std::string &message, const char *object_name = nullptr); +struct ObjInfo { + std::vector vertex_colors; + std::vector face_colors; + bool is_single_mtl{false}; + std::vector> uvs; + std::string obj_dircetory; + std::map pngs; + std::unordered_map uv_map_pngs; + bool has_uv_png{false}; + +}; +extern bool load_obj(const char *path, TriangleMesh *mesh, ObjInfo &vertex_colors, std::string &message); +extern bool load_obj(const char *path, Model *model, ObjInfo &vertex_colors, std::string &message, const char *object_name = nullptr); extern bool store_obj(const char *path, TriangleMesh *mesh); extern bool store_obj(const char *path, ModelObject *model); diff --git a/src/libslic3r/Format/objparser.cpp b/src/libslic3r/Format/objparser.cpp index 16a3f84dd..c33ee3bfb 100644 --- a/src/libslic3r/Format/objparser.cpp +++ b/src/libslic3r/Format/objparser.cpp @@ -9,16 +9,12 @@ #include "libslic3r/LocalesUtils.hpp" namespace ObjParser { - +#define EATWS() while (*line == ' ' || *line == '\t') ++line static bool obj_parseline(const char *line, ObjData &data) { -#define EATWS() while (*line == ' ' || *line == '\t') ++ line - if (*line == 0) return true; - assert(Slic3r::is_decimal_separator_point()); - // Ignore whitespaces at the beginning of the line. //FIXME is this a good idea? EATWS(); @@ -55,19 +51,19 @@ static bool obj_parseline(const char *line, ObjData &data) line = endptr; EATWS(); } - double w = 0; + /*double w = 0; if (*line != 0) { w = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); - } + }*/ if (*line != 0) return false; data.textureCoordinates.push_back((float)u); data.textureCoordinates.push_back((float)v); - data.textureCoordinates.push_back((float)w); + //data.textureCoordinates.push_back((float)w); break; } case 'n': @@ -156,23 +152,46 @@ static bool obj_parseline(const char *line, ObjData &data) return false; line = endptr; EATWS(); - double w = 1.0; - if (*line != 0) { - w = strtod(line, &endptr); - if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) - return false; - line = endptr; - EATWS(); + double color_x = 0.0, color_y = 0.0, color_z = 0.0, color_w = 0.0;//undefine color + if (*line != 0) { + if (!data.has_vertex_color) { + data.has_vertex_color = true; + } + color_x = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + color_y = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + color_z = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + color_w = 1.0;//default define alpha = 1.0 + if (*line != 0) { + color_w = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; + line = endptr; + EATWS(); + } } // the following check is commented out because there may be obj files containing extra data, as those generated by Meshlab, // see https://dev.prusa3d.com/browse/SPE-1019 for an example, - // and this would lead to a crash because no vertex would be stored + // and this would lead to a crash because no vertex would be stored // if (*line != 0) // return false; data.coordinates.push_back((float)x); data.coordinates.push_back((float)y); data.coordinates.push_back((float)z); - data.coordinates.push_back((float)w); + data.coordinates.push_back((float) color_x); + data.coordinates.push_back((float) color_y); + data.coordinates.push_back((float) color_z); + data.coordinates.push_back((float) color_w); break; } } @@ -263,6 +282,9 @@ static bool obj_parseline(const char *line, ObjData &data) // usemtl [material name] // printf("usemtl %s\r\n", line); EATWS(); + if (data.usemtls.size()>0) { + data.usemtls.back().vertexIdxEnd = (int) data.vertices.size(); + } ObjUseMtl usemtl; usemtl.vertexIdxFirst = (int)data.vertices.size(); usemtl.name = line; @@ -323,6 +345,191 @@ static bool obj_parseline(const char *line, ObjData &data) return true; } +static std::string cur_mtl_name = ""; +static bool mtl_parseline(const char *line, MtlData &data) +{ + if (*line == 0) return true; + assert(Slic3r::is_decimal_separator_point()); + // Ignore whitespaces at the beginning of the line. + // FIXME is this a good idea? + EATWS(); + + char c1 = *line++; + switch (c1) { + case '#': {// Comment, ignore the rest of the line. + break; + } + case 'n': { + if (*(line++) != 'e' || *(line++) != 'w' || *(line++) != 'm' || *(line++) != 't' || *(line++) != 'l') + return false; + EATWS(); + ObjNewMtl new_mtl; + cur_mtl_name = line; + data.new_mtl_unmap[cur_mtl_name] = std::make_shared(); + break; + } + case 'm': { + if (*(line++) != 'a' || *(line++) != 'p' || *(line++) != '_' || *(line++) != 'K' || *(line++) != 'd') return false; + EATWS(); + if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) { + data.new_mtl_unmap[cur_mtl_name]->map_Kd = line; + } + break; + } + case 'N': { + char cur_char = *(line++); + if (cur_char == 's') { + EATWS(); + char * endptr = 0; + double ns = strtod(line, &endptr); + if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) { + data.new_mtl_unmap[cur_mtl_name]->Ns = (float) ns; + } + } else if (cur_char == 'i') { + EATWS(); + char * endptr = 0; + double ni = strtod(line, &endptr); + if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) { + data.new_mtl_unmap[cur_mtl_name]->Ni = (float) ni; + } + } + break; + } + case 'K': { + char cur_char = *(line++); + if (cur_char == 'a') { + EATWS(); + char * endptr = 0; + double x = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; + line = endptr; + EATWS(); + double y = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; + line = endptr; + EATWS(); + double z = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; + line = endptr; + EATWS(); + if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) { + data.new_mtl_unmap[cur_mtl_name]->Ka[0] = x; + data.new_mtl_unmap[cur_mtl_name]->Ka[1] = y; + data.new_mtl_unmap[cur_mtl_name]->Ka[2] = z; + } + } else if (cur_char == 'd') { + EATWS(); + char * endptr = 0; + double x = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; + line = endptr; + EATWS(); + double y = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; + line = endptr; + EATWS(); + double z = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; + line = endptr; + EATWS(); + if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) { + data.new_mtl_unmap[cur_mtl_name]->Kd[0] = x; + data.new_mtl_unmap[cur_mtl_name]->Kd[1] = y; + data.new_mtl_unmap[cur_mtl_name]->Kd[2] = z; + } + } else if (cur_char == 's') { + EATWS(); + char * endptr = 0; + double x = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; + line = endptr; + EATWS(); + double y = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; + line = endptr; + EATWS(); + double z = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; + line = endptr; + EATWS(); + if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) { + data.new_mtl_unmap[cur_mtl_name]->Ks[0] = x; + data.new_mtl_unmap[cur_mtl_name]->Ks[1] = y; + data.new_mtl_unmap[cur_mtl_name]->Ks[2] = z; + } + } else if (cur_char == 'e') { + EATWS(); + char * endptr = 0; + double x = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; + line = endptr; + EATWS(); + double y = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; + line = endptr; + EATWS(); + double z = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; + line = endptr; + EATWS(); + if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) { + data.new_mtl_unmap[cur_mtl_name]->Ke[0] = x; + data.new_mtl_unmap[cur_mtl_name]->Ke[1] = y; + data.new_mtl_unmap[cur_mtl_name]->Ke[2] = z; + } + } + break; + } + case 'i': { + if (*(line++) != 'l' || *(line++) != 'l' || *(line++) != 'u' || *(line++) != 'm') + return false; + EATWS(); + char * endptr = 0; + double illum = strtod(line, &endptr); + if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) { + data.new_mtl_unmap[cur_mtl_name]->illum = (float) illum; + } + break; + } + case 'd': { + EATWS(); + char * endptr = 0; + double d = strtod(line, &endptr); + if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) { + data.new_mtl_unmap[cur_mtl_name]->d = (float) d; + } + break; + } + case 'T': { + if (*(line++) != 'f') + return false; + EATWS(); + char * endptr = 0; + double x = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) + return false; + line = endptr; + EATWS(); + double y = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) + return false; + line = endptr; + EATWS(); + double z = strtod(line, &endptr); + if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) + return false; + line = endptr; + EATWS(); + if (data.new_mtl_unmap.find(cur_mtl_name) != data.new_mtl_unmap.end()) { + data.new_mtl_unmap[cur_mtl_name]->Tf[0] = x; + data.new_mtl_unmap[cur_mtl_name]->Tf[1] = y; + data.new_mtl_unmap[cur_mtl_name]->Tf[2] = z; + } + break; + } + } + return true; +} bool objparse(const char *path, ObjData &data) { @@ -369,10 +576,52 @@ bool objparse(const char *path, ObjData &data) return true; } +bool mtlparse(const char *path, MtlData &data) +{ + Slic3r::CNumericLocalesSetter locales_setter; + + FILE *pFile = boost::nowide::fopen(path, "rt"); + if (pFile == 0) return false; + cur_mtl_name = ""; + try { + char buf[65536 * 2]; + size_t len = 0; + size_t lenPrev = 0; + while ((len = ::fread(buf + lenPrev, 1, 65536, pFile)) != 0) { + len += lenPrev; + size_t lastLine = 0; + for (size_t i = 0; i < len; ++i) + if (buf[i] == '\r' || buf[i] == '\n') { + buf[i] = 0; + char *c = buf + lastLine; + while (*c == ' ' || *c == '\t') ++c; + // FIXME check the return value and exit on error? + // Will it break parsing of some obj files? + mtl_parseline(c, data); + lastLine = i + 1; + } + lenPrev = len - lastLine; + if (lenPrev > 65536) { + BOOST_LOG_TRIVIAL(error) << "MtlParser: Excessive line length"; + ::fclose(pFile); + return false; + } + memmove(buf, buf + lastLine, lenPrev); + } + } catch (std::bad_alloc &) { + BOOST_LOG_TRIVIAL(error) << "MtlParser: Out of memory"; + } + ::fclose(pFile); + + // printf("vertices: %d\r\n", data.vertices.size() / 4); + // printf("coords: %d\r\n", data.coordinates.size()); + return true; +} + bool objparse(std::istream &stream, ObjData &data) { Slic3r::CNumericLocalesSetter locales_setter; - + try { char buf[65536 * 2]; size_t len = 0; diff --git a/src/libslic3r/Format/objparser.hpp b/src/libslic3r/Format/objparser.hpp index 5f3f010e4..3d51bc1dd 100644 --- a/src/libslic3r/Format/objparser.hpp +++ b/src/libslic3r/Format/objparser.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include namespace ObjParser { @@ -16,22 +18,36 @@ struct ObjVertex inline bool operator==(const ObjVertex &v1, const ObjVertex &v2) { - return - v1.coordIdx == v2.coordIdx && - v1.textureCoordIdx == v2.textureCoordIdx && + return v1.coordIdx == v2.coordIdx && + v1.textureCoordIdx == v2.textureCoordIdx && v1.normalIdx == v2.normalIdx; } struct ObjUseMtl { int vertexIdxFirst; + int vertexIdxEnd{-1}; std::string name; }; +struct ObjNewMtl +{ + std::string name; + float Ns; + float Ni; + float d; + float illum; + std::array Tf; + std::array Ka; + std::array Kd; + std::array Ks; + std::array Ke; + std::string map_Kd;//defalut png +}; + inline bool operator==(const ObjUseMtl &v1, const ObjUseMtl &v2) { - return - v1.vertexIdxFirst == v2.vertexIdxFirst && + return v1.vertexIdxFirst == v2.vertexIdxFirst && v1.name.compare(v2.name) == 0; } @@ -56,8 +72,7 @@ struct ObjGroup inline bool operator==(const ObjGroup &v1, const ObjGroup &v2) { - return - v1.vertexIdxFirst == v2.vertexIdxFirst && + return v1.vertexIdxFirst == v2.vertexIdxFirst && v1.name.compare(v2.name) == 0; } @@ -69,17 +84,19 @@ struct ObjSmoothingGroup inline bool operator==(const ObjSmoothingGroup &v1, const ObjSmoothingGroup &v2) { - return - v1.vertexIdxFirst == v2.vertexIdxFirst && + return v1.vertexIdxFirst == v2.vertexIdxFirst && v1.smoothingGroupID == v2.smoothingGroupID; } - +#define OBJ_VERTEX_COLOR_ALPHA 6 +#define OBJ_VERTEX_LENGTH 7 // x, y, z, color_x,color_y,color_z,color_w +#define ONE_FACE_SIZE 4//ONE_FACE format: f 8/4/6 7/3/6 6/2/6 -1/-1/-1 struct ObjData { // Version of the data structure for load / store in the private binary format. int version; - // x, y, z, w + // x, y, z, color_x,color_y,color_z,color_w std::vector coordinates; + bool has_vertex_color{false}; // u, v, w std::vector textureCoordinates; // x, y, z @@ -97,7 +114,14 @@ struct ObjData { std::vector vertices; }; +struct MtlData +{ + // Version of the data structure for load / store in the private binary format. + int version; + std::unordered_map> new_mtl_unmap; +}; extern bool objparse(const char *path, ObjData &data); +extern bool mtlparse(const char *path, MtlData &data); extern bool objparse(std::istream &stream, ObjData &data); extern bool objbinsave(const char *path, const ObjData &data); diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d4b1af8be..69d16ba9d 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -12,9 +12,6 @@ #include "TriangleSelector.hpp" #include "Format/AMF.hpp" -#include "Format/OBJ.hpp" -#include "Format/STL.hpp" -#include "Format/STEP.hpp" #include "Format/svg.hpp" // BBS #include "FaceDetector.hpp" @@ -50,6 +47,9 @@ #define _L(s) Slic3r::I18N::translate(s) namespace Slic3r { +const std::vector CONST_FILAMENTS = { + "", "4", "8", "0C", "1C", "2C", "3C", "4C", "5C", "6C", "7C", "8C", "9C", "AC", "BC", "CC", "DC", +}; // 5 10 15 16 // BBS initialization of static variables std::map Model::extruderParamsMap = { {0,{"",0,0}}}; GlobalSpeedMap Model::printSpeedMap{}; @@ -172,7 +172,12 @@ Model::~Model() // Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well. Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadStrategy options, PlateDataPtrs* plate_data, std::vector* project_presets, bool *is_xxx, Semver* file_version, Import3mfProgressFn proFn, - ImportstlProgressFn stlFn, ImportStepProgressFn stepFn, StepIsUtf8Fn stepIsUtf8Fn, BBLProject* project, int plate_id) + ImportstlProgressFn stlFn, + ImportStepProgressFn stepFn, + StepIsUtf8Fn stepIsUtf8Fn, + BBLProject * project, + int plate_id, + ObjImportColorFn objFn) { Model model; @@ -203,8 +208,49 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c result = load_stl(input_file.c_str(), &model, nullptr, stlFn); else if (boost::algorithm::iends_with(input_file, ".oltp")) result = load_stl(input_file.c_str(), &model, nullptr, stlFn,256); - else if (boost::algorithm::iends_with(input_file, ".obj")) - result = load_obj(input_file.c_str(), &model, message); + else if (boost::algorithm::iends_with(input_file, ".obj")) { + ObjInfo obj_info; + result = load_obj(input_file.c_str(), &model, obj_info, message); + if (result){ + unsigned char first_extruder_id; + if (obj_info.vertex_colors.size() > 0) { + std::vector vertex_filament_ids; + if (objFn) { // 1.result is ok and pop up a dialog + objFn(obj_info.vertex_colors, false, vertex_filament_ids, first_extruder_id); + if (vertex_filament_ids.size() > 0) { + result = obj_import_vertex_color_deal(vertex_filament_ids, first_extruder_id, & model); + } + } else { // test //todo delete + vertex_filament_ids.push_back(2); + vertex_filament_ids.push_back(3); + vertex_filament_ids.push_back(4); + vertex_filament_ids.push_back(1); // 4 + vertex_filament_ids.push_back(1); + vertex_filament_ids.push_back(1); + vertex_filament_ids.push_back(1); + vertex_filament_ids.push_back(1); // 8 + result = obj_import_vertex_color_deal(vertex_filament_ids, first_extruder_id, &model); + } + } else if (obj_info.face_colors.size() > 0 && obj_info.has_uv_png == false) { // mtl file + std::vector face_filament_ids; + if (objFn) { // 1.result is ok and pop up a dialog + objFn(obj_info.face_colors, obj_info.is_single_mtl, face_filament_ids, first_extruder_id); + if (face_filament_ids.size() > 0) { + result = obj_import_face_color_deal(face_filament_ids, first_extruder_id, &model); + } + } + } else if (obj_info.has_uv_png && obj_info.uvs.size() > 0) { + boost::filesystem::path full_path(input_file); + std::string obj_directory = full_path.parent_path().string(); + obj_info.obj_dircetory = obj_directory; + result = false; + message = _L("Importing obj with png function is developing."); + } else { + result = false; + message = _L("Importing obj occurred an unknown error."); + } + } + } else if (boost::algorithm::iends_with(input_file, ".svg")) result = load_svg(input_file.c_str(), &model, message); //BBS: remove the old .amf.xml files @@ -3394,6 +3440,163 @@ void Model::setExtruderParams(const DynamicPrintConfig& config, int extruders_co } } +static void get_real_filament_id(const unsigned char &id, std::string &result) { + if (id < CONST_FILAMENTS.size()) { + result = CONST_FILAMENTS[id]; + } else { + result = "";//error + } +}; + +bool Model::obj_import_vertex_color_deal(const std::vector &vertex_filament_ids, const unsigned char &first_extruder_id, Model *model) +{ + if (vertex_filament_ids.size() == 0) { + return false; + } + // 2.generate mmu_segmentation_facets + if (model->objects.size() == 1 ) { + auto obj = model->objects[0]; + obj->config.set("extruder", first_extruder_id); + if (obj->volumes.size() == 1) { + enum VertexColorCase { + _3_SAME_COLOR, + _3_DIFF_COLOR, + _2_SAME_1_DIFF_COLOR, + }; + auto calc_vertex_color_case = [](const unsigned char &c0, const unsigned char &c1, const unsigned char &c2, VertexColorCase &vertex_color_case, + unsigned char &iso_index) { + if (c0 == c1 && c1 == c2) { + vertex_color_case = VertexColorCase::_3_SAME_COLOR; + } else if (c0 != c1 && c1 != c2 && c0 != c2) { + vertex_color_case = VertexColorCase::_3_DIFF_COLOR; + } else if (c0 == c1) { + vertex_color_case = _2_SAME_1_DIFF_COLOR; + iso_index = 2; + } else if (c1 == c2) { + vertex_color_case = _2_SAME_1_DIFF_COLOR; + iso_index = 0; + } else if (c0 == c2) { + vertex_color_case = _2_SAME_1_DIFF_COLOR; + iso_index = 1; + } else { + std::cout << "error"; + } + }; + auto calc_tri_area = [](const Vec3f &v0, const Vec3f &v1, const Vec3f &v2) { + return std::abs((v0 - v1).cross(v0 - v2).norm()) / 2; + }; + auto volume = obj->volumes[0]; + volume->config.set("extruder", first_extruder_id); + auto face_count = volume->mesh().its.indices.size(); + volume->mmu_segmentation_facets.reserve(face_count); + if (volume->mesh().its.vertices.size() != vertex_filament_ids.size()) { + return false; + } + for (size_t i = 0; i < volume->mesh().its.indices.size(); i++) { + auto face = volume->mesh().its.indices[i]; + auto filament_id0 = vertex_filament_ids[face[0]]; + auto filament_id1 = vertex_filament_ids[face[1]]; + auto filament_id2 = vertex_filament_ids[face[2]]; + if (filament_id0 <= 1 && filament_id1 <= 1 && filament_id2 <= 2) { + continue; + } + if (i == 0) { + std::cout << ""; + } + VertexColorCase vertex_color_case; + unsigned char iso_index; + calc_vertex_color_case(filament_id0, filament_id1, filament_id2, vertex_color_case, iso_index); + switch (vertex_color_case) { + case _3_SAME_COLOR: { + std::string result; + get_real_filament_id(filament_id0, result); + volume->mmu_segmentation_facets.set_triangle_from_string(i, result); + break; + } + case _3_DIFF_COLOR: { + std::string result0, result1, result2; + get_real_filament_id(filament_id0, result0); + get_real_filament_id(filament_id1, result1); + get_real_filament_id(filament_id2, result2); + + auto v0 = volume->mesh().its.vertices[face[0]]; + auto v1 = volume->mesh().its.vertices[face[1]]; + auto v2 = volume->mesh().its.vertices[face[2]]; + auto dir_0_1 = (v1 - v0).normalized(); + auto dir_0_2 = (v2 - v0).normalized(); + float sita0 = acos(dir_0_1.dot(dir_0_2)); + auto dir_1_0 = -dir_0_1; + auto dir_1_2 = (v2 - v1).normalized(); + float sita1 = acos(dir_1_0.dot(dir_1_2)); + float sita2 = PI - sita0 - sita1; + std::array sitas = {sita0, sita1, sita2}; + float max_sita = sitas[0]; + int max_sita_vertex_index = 0; + for (size_t j = 1; j < sitas.size(); j++) { + if (sitas[j] > max_sita) { + max_sita_vertex_index = j; + max_sita = sitas[j]; + } + } + if (max_sita_vertex_index == 0) { + volume->mmu_segmentation_facets.set_triangle_from_string(i, result0 + result1 + result2 + (result1 + result2 + "5" )+ "3"); //"1C0C2C0C1C13" + } else if (max_sita_vertex_index == 1) { + volume->mmu_segmentation_facets.set_triangle_from_string(i, result0 + result1 + result2 + (result0 + result2 + "9") + "3"); + } else{// if (max_sita_vertex_index == 2) + volume->mmu_segmentation_facets.set_triangle_from_string(i, result0 + result1 + result2 + (result1 + result0 + "1") + "3"); + } + break; + } + case _2_SAME_1_DIFF_COLOR: { + std::string result0, result1, result2; + get_real_filament_id(filament_id0, result0); + get_real_filament_id(filament_id1, result1); + get_real_filament_id(filament_id2, result2); + if (iso_index == 0) { + volume->mmu_segmentation_facets.set_triangle_from_string(i, result0 + result1 + result1 + "2"); + } else if (iso_index == 1) { + volume->mmu_segmentation_facets.set_triangle_from_string(i, result1 + result0 + result0 + "6"); + } else if (iso_index == 2) { + volume->mmu_segmentation_facets.set_triangle_from_string(i, result2 + result0 + result0 + "A"); + } + break; + } + default: break; + } + } + return true; + } + } + return false; +} + +bool Model::obj_import_face_color_deal(const std::vector &face_filament_ids, const unsigned char &first_extruder_id, Model *model) +{ + if (face_filament_ids.size() == 0) { return false; } + // 2.generate mmu_segmentation_facets + if (model->objects.size() == 1) { + auto obj = model->objects[0]; + obj->config.set("extruder", first_extruder_id); + if (obj->volumes.size() == 1) { + auto volume = obj->volumes[0]; + volume->config.set("extruder", first_extruder_id); + auto face_count = volume->mesh().its.indices.size(); + volume->mmu_segmentation_facets.reserve(face_count); + if (volume->mesh().its.indices.size() != face_filament_ids.size()) { return false; } + for (size_t i = 0; i < volume->mesh().its.indices.size(); i++) { + auto face = volume->mesh().its.indices[i]; + auto filament_id = face_filament_ids[i]; + if (filament_id <= 1) { continue; } + std::string result; + get_real_filament_id(filament_id, result); + volume->mmu_segmentation_facets.set_triangle_from_string(i, result); + } + return true; + } + } + return false; +} + // update the maxSpeed of an object if it is different from the global configuration double Model::findMaxSpeed(const ModelObject* object) { auto objectKeys = object->config.keys(); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7d3cc1820..e484fb024 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -20,6 +20,7 @@ #include "Format/STEP.hpp" //BBS: add stl #include "Format/STL.hpp" +#include "Format/OBJ.hpp" #include "Calib.hpp" @@ -1569,8 +1570,16 @@ public: DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr, LoadStrategy options = LoadStrategy::AddDefaultInstances, PlateDataPtrs* plate_data = nullptr, std::vector* project_presets = nullptr, bool* is_xxx = nullptr, Semver* file_version = nullptr, Import3mfProgressFn proFn = nullptr, - ImportstlProgressFn stlFn = nullptr, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn stepIsUtf8Fn = nullptr, BBLProject* project = nullptr, int plate_id = 0); + ImportstlProgressFn stlFn = nullptr, + ImportStepProgressFn stepFn = nullptr, + StepIsUtf8Fn stepIsUtf8Fn = nullptr, + BBLProject * project = nullptr, + int plate_id = 0, + ObjImportColorFn objFn = nullptr + ); // BBS + static bool obj_import_vertex_color_deal(const std::vector &vertex_filament_ids, const unsigned char &first_extruder_id, Model *model); + static bool obj_import_face_color_deal(const std::vector &face_filament_ids, const unsigned char &first_extruder_id, Model *model); static double findMaxSpeed(const ModelObject* object); static double getThermalLength(const ModelVolume* modelVolumePtr); static double getThermalLength(const std::vector modelVolumePtrs); diff --git a/src/libslic3r/ObjColorUtils.hpp b/src/libslic3r/ObjColorUtils.hpp new file mode 100644 index 000000000..2e8565e42 --- /dev/null +++ b/src/libslic3r/ObjColorUtils.hpp @@ -0,0 +1,187 @@ +#pragma once +#include +#include + +#include "opencv2/opencv.hpp" + +class QuantKMeans +{ +public: + int m_alpha_thres; + cv::Mat m_flatten_labels; + cv::Mat m_centers8UC3; + QuantKMeans(int alpha_thres = 10) : m_alpha_thres(alpha_thres) {} + void apply(cv::Mat &ori_image, cv::Mat &new_image, int num_cluster, int color_space) + { + cv::Mat image; + convert_color_space(ori_image, image, color_space); + cv::Mat flatten_image = flatten(image); + + apply(flatten_image, num_cluster, color_space); + replace_centers(ori_image, new_image); + } + void apply_aplha(cv::Mat &ori_image, cv::Mat &new_image, int num_cluster, int color_space) + { + // cout << " *** DoAlpha *** " << endl; + cv::Mat flatten_image8UC3 = flatten_alpha(ori_image); + cv::Mat image8UC3; + convert_color_space(flatten_image8UC3, image8UC3, color_space); + cv::Mat image32FC3(image8UC3.rows, 1, CV_32FC3); + for (int i = 0; i < image8UC3.rows; i++) image32FC3.at(i, 0) = image8UC3.at(i, 0); + + apply(image32FC3, num_cluster, color_space); + repalce_centers_aplha(ori_image, new_image); + } + void apply(cv::Mat &flatten_image, int num_cluster, int color_space) + { + cv::Mat centers32FC3; + num_cluster = fmin(flatten_image.rows, num_cluster); + kmeans(flatten_image, num_cluster, this->m_flatten_labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 300, 0.5), 3, cv::KMEANS_PP_CENTERS, + centers32FC3); + this->m_centers8UC3 = cv::Mat(num_cluster, 1, CV_8UC3); + for (int i = 0; i < num_cluster; i++) this->m_centers8UC3.at(i) = centers32FC3.at(i); + + convert_color_space(this->m_centers8UC3, this->m_centers8UC3, color_space, true); + } + + void apply(const std::vector> &ori_colors, + std::vector> & cluster_results, + std::vector & labels, + int num_cluster = -1, + int color_space = 2) + { + // 0~255 + cv::Mat flatten_image8UC3 = flatten_vector(ori_colors); + + cv::Mat image8UC3; + convert_color_space(flatten_image8UC3, image8UC3, color_space); + + cv::Mat image32FC3(image8UC3.rows, 1, CV_32FC3); + for (int i = 0; i < image8UC3.rows; i++) image32FC3.at(i, 0) = image8UC3.at(i, 0); + + int best_cluster = 1, cur_score = 0, best_score = 100; + int max_cluster = ori_colors.size(); + num_cluster = fmin(num_cluster, max_cluster); + if (num_cluster < 1) { + cur_score = kmeans(image32FC3, 1, this->m_flatten_labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 300, 0.5), 3, cv::KMEANS_PP_CENTERS); + best_score = cur_score; + for (int cur_cluster = 2; cur_cluster < 16; cur_cluster++) { + if (cur_cluster > max_cluster) break; + cur_score = kmeans(image32FC3, cur_cluster, this->m_flatten_labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 300, 0.5), 3, + cv::KMEANS_PP_CENTERS); + best_cluster = cur_score < best_score ? cur_cluster : best_cluster; + best_score = cur_score < best_score ? cur_score : best_score; + } + } else + best_cluster = num_cluster; + + cv::Mat centers32FC3; + kmeans(image32FC3, best_cluster, this->m_flatten_labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 300, 0.5), 3, cv::KMEANS_PP_CENTERS, + centers32FC3); + this->m_centers8UC3 = cv::Mat(best_cluster, 1, CV_8UC3); + for (int i = 0; i < best_cluster; i++) this->m_centers8UC3.at(i) = centers32FC3.at(i); + + convert_color_space(this->m_centers8UC3, this->m_centers8UC3, color_space, true); + + cluster_results.clear(); + labels.clear(); + for (int i = 0; i < ori_colors.size(); i++) + labels.emplace_back(this->m_flatten_labels.at(i, 0)); + for (int i = 0; i < best_cluster; i++) { + cv::Vec3f center = this->m_centers8UC3.at(i, 0); + cluster_results.emplace_back(std::array{center[0] / 255.f, center[1] / 255.f, center[2] / 255.f, 1.f}); + } + } + + void replace_centers(cv::Mat &ori_image, cv::Mat &new_image) + { + for (int i = 0; i < ori_image.rows; i++) { + for (int j = 0; j < ori_image.cols; j++) { + int idx = this->m_flatten_labels.at(i * ori_image.cols + j, 0); + cv::Vec3b pixel = this->m_centers8UC3.at(idx); + new_image.at(i, j) = pixel; + } + } + } + void repalce_centers_aplha(cv::Mat &ori_image, cv::Mat &new_image) + { + int cnt = 0; + int idx; + cv::Vec3b center; + for (int i = 0; i < ori_image.rows; i++) { + for (int j = 0; j < ori_image.cols; j++) { + cv::Vec4b pixel = ori_image.at(i, j); + if ((int) pixel[3] < this->m_alpha_thres) + new_image.at(i, j) = pixel; + else { + idx = this->m_flatten_labels.at(cnt++, 0); + center = this->m_centers8UC3.at(idx); + new_image.at(i, j) = cv::Vec4b(center[0], center[1], center[2], pixel[3]); + } + } + } + } + + void convert_color_space(cv::Mat &ori_image, cv::Mat &image, int color_space, bool reverse = false) + { + switch (color_space) { + case 0: image = ori_image; break; + case 1: + if (reverse) + cvtColor(ori_image, image, cv::COLOR_HSV2BGR); + else + cvtColor(ori_image, image, cv::COLOR_BGR2HSV); + break; + case 2: + if (reverse) + cvtColor(ori_image, image, cv::COLOR_Lab2BGR); + else + cvtColor(ori_image, image, cv::COLOR_BGR2Lab); + break; + default: break; + } + } + + cv::Mat flatten(cv::Mat &image) + { + int num_pixels = image.rows * image.cols; + cv::Mat img(num_pixels, 1, CV_32FC3); + for (int i = 0; i < image.rows; i++) { + for (int j = 0; j < image.cols; j++) { + cv::Vec3f pixel = image.at(i, j); + img.at(i * image.cols + j, 0) = pixel; + } + } + return img; + } + cv::Mat flatten_alpha(cv::Mat &image) + { + int num_pixels = image.rows * image.cols; + for (int i = 0; i < image.rows; i++) + for (int j = 0; j < image.cols; j++) { + cv::Vec4b pixel = image.at(i, j); + if ((int) pixel[3] < this->m_alpha_thres) num_pixels--; + } + + cv::Mat img(num_pixels, 1, CV_8UC3); + int cnt = 0; + for (int i = 0; i < image.rows; i++) { + for (int j = 0; j < image.cols; j++) { + cv::Vec4b pixel = image.at(i, j); + if ((int) pixel[3] >= this->m_alpha_thres) img.at(cnt++, 0) = cv::Vec3b(pixel[0], pixel[1], pixel[2]); + } + } + return img; + } + cv::Mat flatten_vector(const std::vector> &ori_colors) + { + int num_pixels = ori_colors.size(); + + cv::Mat image8UC3(num_pixels, 1, CV_8UC3); + for (int i = 0; i < num_pixels; i++) { + std::array pixel = ori_colors[i]; + image8UC3.at(i, 0) = cv::Vec3b((int) (pixel[0] * 255.f), (int) (pixel[1] * 255.f), (int) (pixel[2] * 255.f)); + } + return image8UC3; + } +}; diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index f4696cc0f..647743431 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -283,6 +283,8 @@ set(SLIC3R_GUI_SOURCES GUI/CameraUtils.hpp GUI/wxExtensions.cpp GUI/wxExtensions.hpp + GUI/ObjColorDialog.cpp + GUI/ObjColorDialog.hpp GUI/WipeTowerDialog.cpp GUI/WipeTowerDialog.hpp GUI/RemovableDriveManager.cpp diff --git a/src/slic3r/GUI/ObjColorDialog.cpp b/src/slic3r/GUI/ObjColorDialog.cpp new file mode 100644 index 000000000..25cdca57b --- /dev/null +++ b/src/slic3r/GUI/ObjColorDialog.cpp @@ -0,0 +1,824 @@ +#include +#include +//#include "libslic3r/FlushVolCalc.hpp" +#include "ObjColorDialog.hpp" +#include "BitmapCache.hpp" +#include "GUI.hpp" +#include "I18N.hpp" +#include "GUI_App.hpp" +#include "MsgDialog.hpp" +#include "Widgets/Button.hpp" +#include "slic3r/Utils/ColorSpaceConvert.hpp" +#include "MainFrame.hpp" +#include "libslic3r/Config.hpp" +#include "BitmapComboBox.hpp" +#include "Widgets/ComboBox.hpp" +#include + +#include "libslic3r/ObjColorUtils.hpp" + +using namespace Slic3r; +using namespace Slic3r::GUI; + +int objcolor_scale(const int val) { return val * Slic3r::GUI::wxGetApp().em_unit() / 10; } +int OBJCOLOR_ITEM_WIDTH() { return objcolor_scale(30); } +static const wxColour g_text_color = wxColour(107, 107, 107, 255); +const int HEADER_BORDER = 5; +const int CONTENT_BORDER = 3; +const int PANEL_WIDTH = 320; +const int COLOR_LABEL_WIDTH = 150; +#define ICON_SIZE wxSize(FromDIP(16), FromDIP(16)) +#define MIN_OBJCOLOR_DIALOG_WIDTH FromDIP(530) +#define FIX_SCROLL_HEIGTH FromDIP(400) +#define BTN_SIZE wxSize(FromDIP(58), FromDIP(24)) +#define BTN_GAP FromDIP(20) + +static void update_ui(wxWindow* window) +{ + Slic3r::GUI::wxGetApp().UpdateDarkUI(window); +} + +static const char g_min_cluster_color = 1; +//static const char g_max_cluster_color = 15; +static const char g_max_color = 16; +const StateColor ok_btn_bg(std::pair(wxColour(27, 136, 68), StateColor::Pressed), + std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); +const StateColor ok_btn_disable_bg(std::pair(wxColour(205, 201, 201), StateColor::Pressed), + std::pair(wxColour(205, 201, 201), StateColor::Hovered), + std::pair(wxColour(205, 201, 201), StateColor::Normal)); +wxBoxSizer* ObjColorDialog::create_btn_sizer(long flags) +{ + auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); + btn_sizer->AddStretchSpacer(); + + StateColor ok_btn_bd( + std::pair(wxColour(0, 174, 66), StateColor::Normal) + ); + StateColor ok_btn_text( + std::pair(wxColour(255, 255, 254), StateColor::Normal) + ); + StateColor cancel_btn_bg( + std::pair(wxColour(206, 206, 206), StateColor::Pressed), + std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(wxColour(255, 255, 255), StateColor::Normal) + ); + StateColor cancel_btn_bd_( + std::pair(wxColour(38, 46, 48), StateColor::Normal) + ); + StateColor cancel_btn_text( + std::pair(wxColour(38, 46, 48), StateColor::Normal) + ); + StateColor calc_btn_bg( + std::pair(wxColour(27, 136, 68), StateColor::Pressed), + std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal) + ); + StateColor calc_btn_bd( + std::pair(wxColour(0, 174, 66), StateColor::Normal) + ); + StateColor calc_btn_text( + std::pair(wxColour(255, 255, 254), StateColor::Normal) + ); + if (flags & wxOK) { + Button* ok_btn = new Button(this, _L("OK")); + ok_btn->SetMinSize(BTN_SIZE); + ok_btn->SetCornerRadius(FromDIP(12)); + ok_btn->Enable(false); + ok_btn->SetBackgroundColor(ok_btn_disable_bg); + ok_btn->SetBorderColor(ok_btn_bd); + ok_btn->SetTextColor(ok_btn_text); + ok_btn->SetFocus(); + ok_btn->SetId(wxID_OK); + btn_sizer->Add(ok_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BTN_GAP); + m_button_list[wxOK] = ok_btn; + } + if (flags & wxCANCEL) { + Button* cancel_btn = new Button(this, _L("Cancel")); + cancel_btn->SetMinSize(BTN_SIZE); + cancel_btn->SetCornerRadius(FromDIP(12)); + cancel_btn->SetBackgroundColor(cancel_btn_bg); + cancel_btn->SetBorderColor(cancel_btn_bd_); + cancel_btn->SetTextColor(cancel_btn_text); + cancel_btn->SetId(wxID_CANCEL); + btn_sizer->Add(cancel_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BTN_GAP); + m_button_list[wxCANCEL] = cancel_btn; + } + return btn_sizer; +} + +void ObjColorDialog::on_dpi_changed(const wxRect &suggested_rect) +{ + for (auto button_item : m_button_list) + { + if (button_item.first == wxRESET) + { + button_item.second->SetMinSize(wxSize(FromDIP(75), FromDIP(24))); + button_item.second->SetCornerRadius(FromDIP(12)); + } + if (button_item.first == wxOK) { + button_item.second->SetMinSize(BTN_SIZE); + button_item.second->SetCornerRadius(FromDIP(12)); + } + if (button_item.first == wxCANCEL) { + button_item.second->SetMinSize(BTN_SIZE); + button_item.second->SetCornerRadius(FromDIP(12)); + } + } + m_panel_ObjColor->msw_rescale(); + this->Refresh(); +}; + +ObjColorDialog::ObjColorDialog(wxWindow * parent, + std::vector & input_colors, + bool is_single_color, + const std::vector &extruder_colours, + std::vector & filament_ids, + unsigned char & first_extruder_id) + : DPIDialog(parent ? parent : static_cast(wxGetApp().mainframe), + wxID_ANY, + _(L("Obj file Import color")), + wxDefaultPosition, + wxDefaultSize, + wxDEFAULT_DIALOG_STYLE /* | wxRESIZE_BORDER*/) + , m_filament_ids(filament_ids) + , m_first_extruder_id(first_extruder_id) +{ + std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % Slic3r::resources_dir()).str(); + SetIcon(wxIcon(Slic3r::encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); + + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1)); + m_line_top->SetBackgroundColour(wxColour(166, 169, 170)); + + this->SetBackgroundColour(*wxWHITE); + this->SetMinSize(wxSize(MIN_OBJCOLOR_DIALOG_WIDTH, -1)); + + m_panel_ObjColor = new ObjColorPanel(this, input_colors, is_single_color, extruder_colours, filament_ids, first_extruder_id); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(m_line_top, 0, wxEXPAND, 0); + // set min sizer width according to extruders count + auto sizer_width = (int) (2.8 * OBJCOLOR_ITEM_WIDTH()); + sizer_width = sizer_width > MIN_OBJCOLOR_DIALOG_WIDTH ? sizer_width : MIN_OBJCOLOR_DIALOG_WIDTH; + main_sizer->SetMinSize(wxSize(sizer_width, -1)); + main_sizer->Add(m_panel_ObjColor, 1, wxEXPAND | wxALL, 0); + + auto btn_sizer = create_btn_sizer(wxOK | wxCANCEL); + { + m_button_list[wxOK]->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent &e) { + if (m_panel_ObjColor->is_ok() == m_button_list[wxOK]->IsEnabled()) { return; } + m_button_list[wxOK]->Enable(m_panel_ObjColor->is_ok()); + m_button_list[wxOK]->SetBackgroundColor(m_panel_ObjColor->is_ok() ? ok_btn_bg : ok_btn_disable_bg); + })); + } + main_sizer->Add(btn_sizer, 0, wxBOTTOM | wxRIGHT | wxEXPAND, BTN_GAP); + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); + + if (this->FindWindowById(wxID_OK, this)) { + this->FindWindowById(wxID_OK, this)->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {// if OK button is clicked.. + m_panel_ObjColor->update_filament_ids(); + EndModal(wxID_OK); + }, wxID_OK); + } + if (this->FindWindowById(wxID_CANCEL, this)) { + update_ui(static_cast(this->FindWindowById(wxID_CANCEL, this))); + this->FindWindowById(wxID_CANCEL, this)->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { EndModal(wxCANCEL); }); + } + this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); }); + + wxGetApp().UpdateDlgDarkUI(this); +} +RGBA convert_to_rgba(const wxColour &color) +{ + RGBA rgba; + rgba[0] = std::clamp(color.Red() / 255.f, 0.f, 1.f); + rgba[1] = std::clamp(color.Green() / 255.f, 0.f, 1.f); + rgba[2] = std::clamp(color.Blue() / 255.f, 0.f, 1.f); + rgba[3] = std::clamp(color.Alpha() / 255.f, 0.f, 1.f); + return rgba; +} +wxColour convert_to_wxColour(const RGBA &color) +{ + auto r = std::clamp((int) (color[0] * 255.f), 0, 255); + auto g = std::clamp((int) (color[1] * 255.f), 0, 255); + auto b = std::clamp((int) (color[2] * 255.f), 0, 255); + auto a = std::clamp((int) (color[3] * 255.f), 0, 255); + wxColour wx_color(r,g,b,a); + return wx_color; +} +// This panel contains all control widgets for both simple and advanced mode (these reside in separate sizers) +ObjColorPanel::ObjColorPanel(wxWindow * parent, + std::vector& input_colors, + bool is_single_color, + const std::vector& extruder_colours, + std::vector & filament_ids, + unsigned char & first_extruder_id) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize /*,wxBORDER_RAISED*/) + , m_input_colors(input_colors) + , m_filament_ids(filament_ids) + , m_first_extruder_id(first_extruder_id) +{ + if (input_colors.size() == 0) { return; } + for (const std::string& color : extruder_colours) { + m_colours.push_back(wxColor(color)); + } + //deal input_colors + m_input_colors_size = input_colors.size(); + for (size_t i = 0; i < input_colors.size(); i++) { + if (color_is_equal(input_colors[i] , UNDEFINE_COLOR)) { // not define color range:0~1 + input_colors[i]=convert_to_rgba( m_colours[0]); + } + } + if (is_single_color && input_colors.size() >=1) { + m_cluster_colors_from_algo.emplace_back(input_colors[0]); + m_cluster_colours.emplace_back(convert_to_wxColour(input_colors[0])); + m_cluster_labels_from_algo.reserve(m_input_colors_size); + for (size_t i = 0; i < m_input_colors_size; i++) { + m_cluster_labels_from_algo.emplace_back(0); + } + m_cluster_map_filaments.resize(m_cluster_colors_from_algo.size()); + m_color_num_recommend = m_color_cluster_num_by_algo = m_cluster_colors_from_algo.size(); + } else {//cluster deal + deal_algo(-1); + } + //end first cluster + //draw ui + auto sizer_width = FromDIP(300); + // Create two switched panels with their own sizers + m_sizer_simple = new wxBoxSizer(wxVERTICAL); + m_page_simple = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + m_page_simple->SetSizer(m_sizer_simple); + m_page_simple->SetBackgroundColour(*wxWHITE); + + update_ui(m_page_simple); + // BBS + m_sizer_simple->AddSpacer(FromDIP(10)); + // BBS: for tunning flush volumes + { + //color cluster results + wxBoxSizer * cluster_sizer = new wxBoxSizer(wxHORIZONTAL); + wxStaticText *color_cluster_result_title = new wxStaticText(m_page_simple, wxID_ANY, _L("Recommend number of colors:")); + color_cluster_result_title->SetFont(Label::Head_14); + cluster_sizer->Add(color_cluster_result_title, 0, wxALIGN_CENTER | wxALL, 0); + cluster_sizer->AddSpacer(FromDIP(5)); + + wxStaticText *color_cluster_result_value = new wxStaticText(m_page_simple, wxID_ANY, std::to_string(m_color_num_recommend).c_str()); + color_cluster_result_value->SetFont(Label::Head_14); + cluster_sizer->Add(color_cluster_result_value, 0, wxALIGN_CENTER | wxALL, 0); + cluster_sizer->AddSpacer(FromDIP(20)); + + wxBoxSizer * specify_cluster_sizer = new wxBoxSizer(wxHORIZONTAL); + wxStaticText *specify_color_cluster_title = new wxStaticText(m_page_simple, wxID_ANY, _L("Specify number of colors:")); + specify_color_cluster_title->SetFont(Label::Head_14); + specify_cluster_sizer->Add(specify_color_cluster_title, 0, wxALIGN_CENTER | wxALL, 0); + specify_cluster_sizer->AddSpacer(FromDIP(5)); + + m_color_cluster_num_by_user_ebox = new wxTextCtrl(m_page_simple, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(25), -1), wxTE_PROCESS_ENTER); + if (m_color_num_recommend == 1) { + m_color_cluster_num_by_user_ebox->Enable(false); + } + m_color_cluster_num_by_user_ebox->SetValue(std::to_string(m_color_cluster_num_by_algo).c_str()); + {//event + auto on_apply_color_cluster_text_modify = [this](wxEvent &e) { + wxString str = m_color_cluster_num_by_user_ebox->GetValue(); + int number = wxAtoi(str); + if (number > m_color_num_recommend || number < g_min_cluster_color) { + number = number < g_min_cluster_color ? g_min_cluster_color : m_color_num_recommend; + str = wxString::Format(("%d"), number); + m_color_cluster_num_by_user_ebox->SetValue(str); + MessageDialog dlg(nullptr, wxString::Format(_L("The color count should be in range [%d, %d]."), g_min_cluster_color, m_color_num_recommend), + _L("Warning"), wxICON_WARNING | wxOK); + dlg.ShowModal(); + } + e.Skip(); + }; + m_color_cluster_num_by_user_ebox->Bind(wxEVT_TEXT_ENTER, on_apply_color_cluster_text_modify); + m_color_cluster_num_by_user_ebox->Bind(wxEVT_KILL_FOCUS, on_apply_color_cluster_text_modify); + m_color_cluster_num_by_user_ebox->Bind(wxEVT_COMMAND_TEXT_UPDATED, [this](wxCommandEvent &) { + wxString str = m_color_cluster_num_by_user_ebox->GetValue(); + int number = wxAtof(str); + if (number > m_color_num_recommend || number < g_min_cluster_color) { + number = number < g_min_cluster_color ? g_min_cluster_color : m_color_num_recommend; + str = wxString::Format(("%d"), number); + m_color_cluster_num_by_user_ebox->SetValue(str); + m_color_cluster_num_by_user_ebox->SetInsertionPointEnd(); + } + if (m_last_cluster_num != number) { + deal_algo(number, true); + Layout(); + //Fit(); + Refresh(); + Update(); + m_last_cluster_num = number; + } + }); + m_color_cluster_num_by_user_ebox->Bind(wxEVT_CHAR, [this](wxKeyEvent &e) { + int keycode = e.GetKeyCode(); + wxString input_char = wxString::Format("%c", keycode); + long value; + if (!input_char.ToLong(&value)) + return; + e.Skip(); + }); + } + specify_cluster_sizer->Add(m_color_cluster_num_by_user_ebox, 0, wxALIGN_CENTER | wxALL, 0); + + m_sizer_simple->Add(cluster_sizer, 0, wxEXPAND | wxLEFT, FromDIP(20)); + m_sizer_simple->Add(specify_cluster_sizer, 0, wxEXPAND | wxLEFT, FromDIP(20)); + //colors table + m_scrolledWindow = new wxScrolledWindow(m_page_simple,wxID_ANY,wxDefaultPosition,wxDefaultSize,wxSB_VERTICAL); + m_sizer_simple->Add(m_scrolledWindow, 0, wxEXPAND | wxALL, FromDIP(5)); + draw_table(); + //buttons + wxBoxSizer *quick_set_sizer = new wxBoxSizer(wxHORIZONTAL); + wxStaticText *quick_set_title = new wxStaticText(m_page_simple, wxID_ANY, _L("Quick set:")); + quick_set_sizer->Add(quick_set_title, 0, wxALIGN_CENTER | wxALL, 0); + quick_set_sizer->AddSpacer(FromDIP(10)); + + auto calc_approximate_match_btn_sizer = create_approximate_match_btn_sizer(m_page_simple); + auto calc_add_btn_sizer = create_add_btn_sizer(m_page_simple); + auto calc_reset_btn_sizer = create_reset_btn_sizer(m_page_simple); + quick_set_sizer->Add(calc_add_btn_sizer, 0, wxALIGN_CENTER | wxALL, 0); + quick_set_sizer->AddSpacer(FromDIP(10)); + quick_set_sizer->Add(calc_approximate_match_btn_sizer, 0, wxALIGN_CENTER | wxALL, 0); + quick_set_sizer->AddSpacer(FromDIP(10)); + quick_set_sizer->Add(calc_reset_btn_sizer, 0, wxALIGN_CENTER | wxALL, 0); + quick_set_sizer->AddSpacer(FromDIP(10)); + m_sizer_simple->Add(quick_set_sizer, 0, wxEXPAND | wxLEFT, FromDIP(30)); + + wxBoxSizer *warning_sizer = new wxBoxSizer(wxHORIZONTAL); + m_warning_text = new wxStaticText(m_page_simple, wxID_ANY, ""); + warning_sizer->Add(m_warning_text, 0, wxALIGN_CENTER | wxALL, 0); + m_sizer_simple->Add(warning_sizer, 0, wxEXPAND | wxLEFT, FromDIP(30)); + + m_sizer_simple->AddSpacer(10); + } + //page_simple//page_advanced + m_sizer = new wxBoxSizer(wxVERTICAL); + m_sizer->Add(m_page_simple, 0, wxEXPAND, 0); + + m_sizer->SetSizeHints(this); + SetSizer(m_sizer); + this->Layout(); +} + +void ObjColorPanel::msw_rescale() +{ + for (unsigned int i = 0; i < m_extruder_icon_list.size(); ++i) { + auto bitmap = *get_extruder_color_icon(m_colours[i].GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), std::to_string(i + 1), FromDIP(16), FromDIP(16)); + m_extruder_icon_list[i]->SetBitmap(bitmap); + } + /* for (unsigned int i = 0; i < m_color_cluster_icon_list.size(); ++i) { + auto bitmap = *get_extruder_color_icon(m_cluster_colours[i].GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), std::to_string(i + 1), FromDIP(16), FromDIP(16)); + m_color_cluster_icon_list[i]->SetBitmap(bitmap); + }*/ +} + +bool ObjColorPanel::is_ok() { + for (auto item : m_result_icon_list) { + if (item->bitmap_combox->IsShown()) { + auto selection = item->bitmap_combox->GetSelection(); + if (selection < 1) { + return false; + } + } + } + return true; +} + +void ObjColorPanel::update_filament_ids() +{ + if (m_is_add_filament) { + for (auto c:m_new_add_colors) { + auto evt = new ColorEvent(EVT_ADD_CUSTOM_FILAMENT, c); + wxQueueEvent(wxGetApp().plater(), evt); + } + } + //deal m_filament_ids + m_filament_ids.clear(); + m_filament_ids.reserve(m_input_colors_size); + for (size_t i = 0; i < m_input_colors_size; i++) { + auto label = m_cluster_labels_from_algo[i]; + m_filament_ids.emplace_back(m_cluster_map_filaments[label]); + } + m_first_extruder_id = m_cluster_map_filaments[0]; +} + +wxBoxSizer *ObjColorPanel::create_approximate_match_btn_sizer(wxWindow *parent) +{ + auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); + StateColor calc_btn_bg(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + StateColor calc_btn_bd(std::pair(wxColour(0, 174, 66), StateColor::Normal)); + StateColor calc_btn_text(std::pair(wxColour(255, 255, 254), StateColor::Normal)); + //create btn + m_quick_approximate_match_btn = new Button(parent, _L("Approximate match")); + auto cur_btn = m_quick_approximate_match_btn; + cur_btn->SetFont(Label::Body_13); + cur_btn->SetMinSize(wxSize(FromDIP(60), FromDIP(20))); + cur_btn->SetCornerRadius(FromDIP(10)); + cur_btn->SetBackgroundColor(calc_btn_bg); + cur_btn->SetBorderColor(calc_btn_bd); + cur_btn->SetTextColor(calc_btn_text); + cur_btn->SetFocus(); + btn_sizer->Add(cur_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 0); + cur_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { + deal_approximate_match_btn(); + }); + return btn_sizer; +} + +wxBoxSizer *ObjColorPanel::create_add_btn_sizer(wxWindow *parent) +{ + auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); + StateColor calc_btn_bg(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + StateColor calc_btn_bd(std::pair(wxColour(0, 174, 66), StateColor::Normal)); + StateColor calc_btn_text(std::pair(wxColour(255, 255, 254), StateColor::Normal)); + // create btn + m_quick_add_btn = new Button(parent, _L("Add")); + auto cur_btn = m_quick_add_btn; + cur_btn->SetFont(Label::Body_13); + cur_btn->SetMinSize(wxSize(FromDIP(60), FromDIP(20))); + cur_btn->SetCornerRadius(FromDIP(10)); + cur_btn->SetBackgroundColor(calc_btn_bg); + cur_btn->SetBorderColor(calc_btn_bd); + cur_btn->SetTextColor(calc_btn_text); + cur_btn->SetFocus(); + btn_sizer->Add(cur_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 0); + cur_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { + deal_add_btn(); + }); + return btn_sizer; +} + +wxBoxSizer *ObjColorPanel::create_reset_btn_sizer(wxWindow *parent) +{ + auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); + StateColor calc_btn_bg(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 174, 66), StateColor::Normal)); + StateColor calc_btn_bd(std::pair(wxColour(0, 174, 66), StateColor::Normal)); + StateColor calc_btn_text(std::pair(wxColour(255, 255, 254), StateColor::Normal)); + // create btn + m_quick_reset_btn = new Button(parent, _L("Reset")); + auto cur_btn = m_quick_reset_btn; + cur_btn->SetFont(Label::Body_13); + cur_btn->SetMinSize(wxSize(FromDIP(60), FromDIP(20))); + cur_btn->SetCornerRadius(FromDIP(10)); + cur_btn->SetBackgroundColor(calc_btn_bg); + cur_btn->SetBorderColor(calc_btn_bd); + cur_btn->SetTextColor(calc_btn_text); + cur_btn->SetFocus(); + btn_sizer->Add(cur_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 0); + cur_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { + deal_reset_btn(); + }); + return btn_sizer; +} + +wxBoxSizer *ObjColorPanel::create_extruder_icon_and_rgba_sizer(wxWindow *parent, int id, const wxColour &color) +{ + auto icon_sizer = new wxBoxSizer(wxHORIZONTAL); + wxButton *icon = new wxButton(parent, wxID_ANY, {}, wxDefaultPosition, ICON_SIZE, wxBORDER_NONE | wxBU_AUTODRAW); + icon->SetBitmap(*get_extruder_color_icon(color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), std::to_string(id + 1), FromDIP(16), FromDIP(16))); + icon->SetCanFocus(false); + m_extruder_icon_list.emplace_back(icon); + icon_sizer->Add(icon, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0); // wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM + icon_sizer->AddSpacer(FromDIP(10)); + + std::string message = get_color_str(color); + wxStaticText *rgba_title = new wxStaticText(parent, wxID_ANY, message.c_str()); + rgba_title->SetMinSize(wxSize(FromDIP(COLOR_LABEL_WIDTH), -1)); + rgba_title->SetMaxSize(wxSize(FromDIP(COLOR_LABEL_WIDTH), -1)); + icon_sizer->Add(rgba_title, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0); + return icon_sizer; +} + +std::string ObjColorPanel::get_color_str(const wxColour &color) { + std::string str = ("R:" + std::to_string(color.Red()) + + std::string(" G:") + std::to_string(color.Green()) + + std::string(" B:") + std::to_string(color.Blue()) + + std::string(" A:") + std::to_string(color.Alpha())); + return str; +} + +ComboBox *ObjColorPanel::CreateEditorCtrl(wxWindow *parent, int id) // wxRect labelRect,, const wxVariant &value +{ + std::vector icons = get_extruder_color_icons(); + const double em = Slic3r::GUI::wxGetApp().em_unit(); + bool thin_icon = false; + const int icon_width = lround((thin_icon ? 2 : 4.4) * em); + const int icon_height = lround(2 * em); + m_combox_icon_width = icon_width; + m_combox_icon_height = icon_height; + wxColour undefined_color(0,255,0,255); + icons.insert(icons.begin(), get_extruder_color_icon(undefined_color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), std::to_string(-1), icon_width, icon_height)); + if (icons.empty()) + return nullptr; + + ::ComboBox *c_editor = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(m_combox_width), -1), 0, nullptr, + wxCB_READONLY | CB_NO_DROP_ICON | CB_NO_TEXT); + c_editor->SetMinSize(wxSize(FromDIP(m_combox_width), -1)); + c_editor->SetMaxSize(wxSize(FromDIP(m_combox_width), -1)); + c_editor->GetDropDown().SetUseContentWidth(true); + for (size_t i = 0; i < icons.size(); i++) { + c_editor->Append(wxString::Format("%d", i), *icons[i]); + if (i == 0) { + c_editor->SetItemTooltip(i,undefined_color.GetAsString(wxC2S_HTML_SYNTAX)); + } else { + c_editor->SetItemTooltip(i, m_colours[i-1].GetAsString(wxC2S_HTML_SYNTAX)); + } + } + c_editor->SetSelection(0); + c_editor->SetName(wxString::Format("%d", id)); + c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { + auto *com_box = static_cast(evt.GetEventObject()); + int i = atoi(com_box->GetName().c_str()); + if (i < m_cluster_map_filaments.size()) { m_cluster_map_filaments[i] = com_box->GetSelection(); } + evt.StopPropagation(); + }); + return c_editor; +} + +void ObjColorPanel::deal_approximate_match_btn() +{ + auto calc_color_distance = [](wxColour c1, wxColour c2) { + float lab[2][3]; + RGB2Lab(c1.Red(), c1.Green(), c1.Blue(), &lab[0][0], &lab[0][1], &lab[0][2]); + RGB2Lab(c2.Red(), c2.Green(), c2.Blue(), &lab[1][0], &lab[1][1], &lab[1][2]); + + return DeltaE76(lab[0][0], lab[0][1], lab[0][2], lab[1][0], lab[1][1], lab[1][2]); + }; + m_warning_text->SetLabelText(""); + if (m_result_icon_list.size() == 0) { return; } + auto map_count = m_result_icon_list[0]->bitmap_combox->GetCount() -1; + if (map_count < 1) { return; } + for (size_t i = 0; i < m_cluster_colours.size(); i++) { + auto c = m_cluster_colours[i]; + std::vector color_dists; + color_dists.resize(map_count); + for (size_t j = 0; j < map_count; j++) { + auto tip_color = m_result_icon_list[0]->bitmap_combox->GetItemTooltip(j+1); + wxColour candidate_c(tip_color); + color_dists[j].distance = calc_color_distance(c, candidate_c); + color_dists[j].id = j + 1; + } + std::sort(color_dists.begin(), color_dists.end(), [](ColorDistValue &a, ColorDistValue& b) { + return a.distance < b.distance; + }); + auto new_index= color_dists[0].id; + m_result_icon_list[i]->bitmap_combox->SetSelection(new_index); + m_cluster_map_filaments[i] = new_index; + } +} + +void ObjColorPanel::show_sizer(wxSizer *sizer, bool show) +{ + wxSizerItemList items = sizer->GetChildren(); + for (wxSizerItemList::iterator it = items.begin(); it != items.end(); ++it) { + wxSizerItem *item = *it; + if (wxWindow *window = item->GetWindow()) { + window->Show(show); + } + if (wxSizer *son_sizer = item->GetSizer()) { + show_sizer(son_sizer, show); + } + } +} + +void ObjColorPanel::redraw_part_table() { + //show all and set -1 + deal_reset_btn(); + for (size_t i = 0; i < m_row_sizer_list.size(); i++) { + show_sizer(m_row_sizer_list[i], true); + } + if (m_cluster_colours.size() < m_row_sizer_list.size()) { // show part + for (size_t i = m_cluster_colours.size(); i < m_row_sizer_list.size(); i++) { + show_sizer(m_row_sizer_list[i], false); + //m_row_panel_list[i]->Show(false); // show_sizer(m_left_color_cluster_boxsizer_list[i],false); + // m_result_icon_list[i]->bitmap_combox->Show(false); + } + } else if (m_cluster_colours.size() > m_row_sizer_list.size()) { + for (size_t i = m_row_sizer_list.size(); i < m_cluster_colours.size(); i++) { + int id = i; + wxPanel *row_panel = new wxPanel(m_scrolledWindow); + row_panel->SetBackgroundColour((i+1) % 2 == 0 ? *wxWHITE : wxColour(238, 238, 238)); + auto row_sizer = new wxBoxSizer(wxHORIZONTAL); + row_panel->SetSizer(row_sizer); + + wxPanel *son_row_panel = new wxPanel(row_panel); + son_row_panel->SetMinSize(wxSize(FromDIP(PANEL_WIDTH), -1)); + son_row_panel->SetMaxSize(wxSize(FromDIP(PANEL_WIDTH), -1)); + son_row_panel->SetBackgroundColour((i + 1) % 2 == 0 ? *wxWHITE : wxColour(238, 238, 238)); + auto son_row_sizer = new wxGridSizer(1, 2, 1, 3); + son_row_panel->SetSizer(son_row_sizer); + + auto cluster_color_icon_sizer = create_color_icon_and_rgba_sizer(son_row_panel, id, m_cluster_colours[id]); + son_row_sizer->Add(cluster_color_icon_sizer, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, FromDIP(CONTENT_BORDER)); + // result_combox + create_result_button_sizer(son_row_panel, id); + son_row_sizer->Add(m_result_icon_list[id]->bitmap_combox, 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL, 0); + row_sizer->Add(son_row_panel, 0, wxALIGN_LEFT | wxALL, 0); + // extruder_icon + if (id < m_colours.size()) { + auto extruder_icon_sizer = create_extruder_icon_and_rgba_sizer(row_panel, id, m_colours[id]); + row_sizer->Add(extruder_icon_sizer, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, FromDIP(CONTENT_BORDER)); + } else { + row_sizer->Add(new wxStaticText(row_panel, wxID_ANY, ""), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, FromDIP(CONTENT_BORDER)); + } + m_row_sizer_list.emplace_back(son_row_sizer); + m_gridsizer->Add(row_panel, 0, wxALIGN_LEFT | wxALL, FromDIP(HEADER_BORDER)); + } + m_gridsizer->Layout(); + } + for (size_t i = 0; i < m_cluster_colours.size(); i++) { // update data + // m_color_cluster_icon_list//m_color_cluster_text_list + update_color_icon_and_rgba_sizer(i, m_cluster_colours[i]); + } + m_scrolledWindow->Refresh(); +} + +void ObjColorPanel::draw_table() +{ + auto row = std::max(m_cluster_colours.size(), m_colours.size()) + 1; + m_gridsizer = new wxGridSizer(row, 1, 3, 3); //(int rows, int cols, int vgap, int hgap ); + + m_color_cluster_icon_list.clear(); + m_extruder_icon_list.clear(); + for (size_t ii = 0; ii < row; ii++) { + wxPanel *row_panel = new wxPanel(m_scrolledWindow); + row_panel->SetBackgroundColour(ii % 2 == 0 ? *wxWHITE : wxColour(238, 238, 238)); + auto row_sizer = new wxBoxSizer(wxHORIZONTAL); + row_panel->SetSizer(row_sizer); + + wxPanel *son_row_panel = new wxPanel(row_panel); + son_row_panel->SetMinSize(wxSize(FromDIP(PANEL_WIDTH), -1)); + son_row_panel->SetMaxSize(wxSize(FromDIP(PANEL_WIDTH), -1)); + son_row_panel->SetBackgroundColour(ii % 2 == 0 ? *wxWHITE : wxColour(238, 238, 238)); + auto son_row_sizer = new wxGridSizer(1, 2, 1, 3); + son_row_panel->SetSizer(son_row_sizer); + if (ii == 0) { + wxStaticText *colors_left_title = new wxStaticText(son_row_panel, wxID_ANY, _L("Cluster colors")); + colors_left_title->SetFont(Label::Head_14); + son_row_sizer->Add(colors_left_title, 0, wxALIGN_LEFT | wxALL, FromDIP(HEADER_BORDER)); + + wxStaticText *colors_middle_title = new wxStaticText(son_row_panel, wxID_ANY, _L("Map Filament")); + colors_middle_title->SetFont(Label::Head_14); + son_row_sizer->Add(colors_middle_title, 0, wxALIGN_CENTER | wxALL, FromDIP(HEADER_BORDER)); + row_sizer->Add(son_row_panel, 0, wxALIGN_LEFT | wxALL, 0); + + wxStaticText *colors_right_title = new wxStaticText(row_panel, wxID_ANY, _L("Current filament colors")); + colors_right_title->SetFont(Label::Head_14); + row_sizer->Add(colors_right_title, 0, wxALIGN_LEFT | wxALL, FromDIP(HEADER_BORDER)); + } else { + int id = ii - 1; + if (id < m_cluster_colours.size()) { + auto cluster_color_icon_sizer = create_color_icon_and_rgba_sizer(son_row_panel, id, m_cluster_colours[id]); + son_row_sizer->Add(cluster_color_icon_sizer, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, FromDIP(CONTENT_BORDER)); + // result_combox + create_result_button_sizer(son_row_panel, id); + son_row_sizer->Add(m_result_icon_list[id]->bitmap_combox, 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL, FromDIP(CONTENT_BORDER)); + } + row_sizer->Add(son_row_panel, 0, wxALIGN_LEFT | wxALL, 0); + // extruder_icon + if (id < m_colours.size()) { + auto extruder_icon_sizer = create_extruder_icon_and_rgba_sizer(row_panel, id, m_colours[id]); + row_sizer->Add(extruder_icon_sizer, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, FromDIP(CONTENT_BORDER)); + } else { + row_sizer->Add(new wxStaticText(row_panel, wxID_ANY, ""), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, FromDIP(CONTENT_BORDER)); + } + } + if (ii>=1) { + m_row_sizer_list.emplace_back(son_row_sizer); + } + m_gridsizer->Add(row_panel, 0, wxALIGN_LEFT | wxALL, FromDIP(HEADER_BORDER)); + } + m_scrolledWindow->SetSizer(m_gridsizer); + int totalHeight = m_gridsizer->GetMinSize().y; + m_scrolledWindow->SetVirtualSize(MIN_OBJCOLOR_DIALOG_WIDTH, totalHeight); + if (totalHeight > FIX_SCROLL_HEIGTH) { + m_scrolledWindow->SetMinSize(wxSize(MIN_OBJCOLOR_DIALOG_WIDTH, FIX_SCROLL_HEIGTH)); + m_scrolledWindow->SetMaxSize(wxSize(MIN_OBJCOLOR_DIALOG_WIDTH, FIX_SCROLL_HEIGTH)); + } + else { + m_scrolledWindow->SetMinSize(wxSize(MIN_OBJCOLOR_DIALOG_WIDTH, totalHeight)); + } + m_scrolledWindow->EnableScrolling(false, true); + m_scrolledWindow->ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_DEFAULT);//wxSHOW_SB_ALWAYS + m_scrolledWindow->SetScrollRate(20, 20); +} + +void ObjColorPanel::deal_algo(char cluster_number, bool redraw_ui) +{ + if (m_last_cluster_number == cluster_number) { + return; + } + m_last_cluster_number = cluster_number; + QuantKMeans quant(10); + quant.apply(m_input_colors, m_cluster_colors_from_algo, m_cluster_labels_from_algo, (int)cluster_number); + m_cluster_colours.clear(); + m_cluster_colours.reserve(m_cluster_colors_from_algo.size()); + for (size_t i = 0; i < m_cluster_colors_from_algo.size(); i++) { + m_cluster_colours.emplace_back(convert_to_wxColour(m_cluster_colors_from_algo[i])); + } + if (m_cluster_colours.size() == 0) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",m_cluster_colours.size() = 0\n"; + return; + } + m_cluster_map_filaments.resize(m_cluster_colors_from_algo.size()); + m_color_cluster_num_by_algo = m_cluster_colors_from_algo.size(); + if (cluster_number == -1) { + m_color_num_recommend = m_color_cluster_num_by_algo; + } + //redraw ui + if (redraw_ui) { + redraw_part_table(); + } +} + +void ObjColorPanel::deal_add_btn() +{ + if (m_colours.size() > g_max_color) { return; } + std::vector new_icons; + auto new_color_size = m_cluster_colors_from_algo.size(); + new_icons.reserve(new_color_size); + m_new_add_colors.clear(); + m_new_add_colors.reserve(new_color_size); + int new_index = m_colours.size() + 1; + for (size_t i = 0; i < new_color_size; i++) { + if (m_colours.size() + new_icons.size() >= g_max_color) { + m_warning_text->SetLabelText(_L("Waring:The count of newly added and current extruders exceeds 16.")); + break; + } + wxColour cur_color = convert_to_wxColour(m_cluster_colors_from_algo[i]); + m_new_add_colors.emplace_back(cur_color); + new_icons.emplace_back(get_extruder_color_icon(cur_color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), + std::to_string(new_index), m_combox_icon_width, m_combox_icon_height)); + new_index++; + } + new_index = m_colours.size() + 1; + for (size_t i = 0; i < m_result_icon_list.size(); i++) { + auto item = m_result_icon_list[i]; + for (size_t k = 0; k < new_icons.size(); k++) { + item->bitmap_combox->Append(wxString::Format("%d", item->bitmap_combox->GetCount()), *new_icons[k]); + item->bitmap_combox->SetItemTooltip(item->bitmap_combox->GetCount() -1,m_new_add_colors[k].GetAsString(wxC2S_HTML_SYNTAX)); + } + item->bitmap_combox->SetSelection(new_index); + m_cluster_map_filaments[i] = new_index; + new_index++; + } + m_is_add_filament = true; +} + +void ObjColorPanel::deal_reset_btn() +{ + for (auto item : m_result_icon_list) { + // delete redundant bitmap + while (item->bitmap_combox->GetCount() > m_colours.size()+ 1) { + item->bitmap_combox->DeleteOneItem(item->bitmap_combox->GetCount() - 1); + } + item->bitmap_combox->SetSelection(0); + } + m_is_add_filament = false; + m_new_add_colors.clear(); + m_warning_text->SetLabelText(""); +} + +void ObjColorPanel::create_result_button_sizer(wxWindow *parent, int id) +{ + for (size_t i = m_result_icon_list.size(); i < id + 1; i++) { + m_result_icon_list.emplace_back(new ButtonState()); + } + m_result_icon_list[id]->bitmap_combox = CreateEditorCtrl(parent,id); +} + +wxBoxSizer *ObjColorPanel::create_color_icon_and_rgba_sizer(wxWindow *parent, int id, const wxColour& color) +{ + auto icon_sizer = new wxBoxSizer(wxHORIZONTAL); + wxButton *icon = new wxButton(parent, wxID_ANY, {}, wxDefaultPosition, ICON_SIZE, wxBORDER_NONE | wxBU_AUTODRAW); + icon->SetBitmap(*get_extruder_color_icon(color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), std::to_string(id + 1), FromDIP(16), FromDIP(16))); + icon->SetCanFocus(false); + m_color_cluster_icon_list.emplace_back(icon); + icon_sizer->Add(icon, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0); // wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM + icon_sizer->AddSpacer(FromDIP(10)); + + std::string message = get_color_str(color); + wxStaticText *rgba_title = new wxStaticText(parent, wxID_ANY, message.c_str()); + m_color_cluster_text_list.emplace_back(rgba_title); + rgba_title->SetMinSize(wxSize(FromDIP(COLOR_LABEL_WIDTH), -1)); + rgba_title->SetMaxSize(wxSize(FromDIP(COLOR_LABEL_WIDTH), -1)); + //rgba_title->SetFont(Label::Head_12); + icon_sizer->Add(rgba_title, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0); + return icon_sizer; +} + +void ObjColorPanel::update_color_icon_and_rgba_sizer(int id, const wxColour &color) +{ + if (id < m_color_cluster_text_list.size()) { + auto icon = m_color_cluster_icon_list[id]; + icon->SetBitmap(*get_extruder_color_icon(color.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), std::to_string(id + 1), FromDIP(16), FromDIP(16))); + std::string message = get_color_str(color); + m_color_cluster_text_list[id]->SetLabelText(message.c_str()); + } +} diff --git a/src/slic3r/GUI/ObjColorDialog.hpp b/src/slic3r/GUI/ObjColorDialog.hpp new file mode 100644 index 000000000..137bfe3e0 --- /dev/null +++ b/src/slic3r/GUI/ObjColorDialog.hpp @@ -0,0 +1,113 @@ +#ifndef _OBJ_COLOR_DIALOG_H_ +#define _OBJ_COLOR_DIALOG_H_ + +#include "GUI_Utils.hpp" +#include "libslic3r/Color.hpp" +#include +#include +#include +#include +#include +#include +class Button; +class Label; +class ComboBox; +struct ColorDistValue +{ + int id; + float distance; +}; +class ObjColorPanel : public wxPanel +{ +public: + // BBS + ObjColorPanel(wxWindow * parent, + std::vector & input_colors,bool is_single_color, + const std::vector & extruder_colours, + std::vector & filament_ids, + unsigned char & first_extruder_id); + void msw_rescale(); + bool is_ok(); + void update_filament_ids(); + struct ButtonState + { + ComboBox* bitmap_combox{nullptr}; + bool is_map{false};//int id{0}; + }; +private: + wxBoxSizer *create_approximate_match_btn_sizer(wxWindow *parent); + wxBoxSizer *create_add_btn_sizer(wxWindow *parent); + wxBoxSizer *create_reset_btn_sizer(wxWindow *parent); + wxBoxSizer *create_extruder_icon_and_rgba_sizer(wxWindow *parent, int id, const wxColour& color); + std::string get_color_str(const wxColour &color); + void create_result_button_sizer(wxWindow *parent, int id); + wxBoxSizer *create_color_icon_and_rgba_sizer(wxWindow *parent, int id, const wxColour& color); + void update_color_icon_and_rgba_sizer(int id, const wxColour &color); + ComboBox* CreateEditorCtrl(wxWindow *parent,int id); + void draw_table(); + void show_sizer(wxSizer *sizer, bool show); + void redraw_part_table(); + void deal_approximate_match_btn(); + void deal_add_btn(); + void deal_reset_btn(); + void deal_algo(char cluster_number,bool redraw_ui =false); +private: + //view ui + wxScrolledWindow * m_scrolledWindow = nullptr; + wxPanel * m_page_simple = nullptr; + wxBoxSizer * m_sizer = nullptr; + wxBoxSizer * m_sizer_simple = nullptr; + wxTextCtrl *m_color_cluster_num_by_user_ebox{nullptr}; + wxStaticText * m_warning_text{nullptr}; + Button * m_quick_approximate_match_btn{nullptr}; + Button * m_quick_add_btn{nullptr}; + Button * m_quick_reset_btn{nullptr}; + std::vector m_extruder_icon_list; + std::vector m_color_cluster_icon_list;//need modeify + std::vector m_color_cluster_text_list;//need modeify + std::vector m_row_sizer_list; // control show or not + std::vector m_result_icon_list; + int m_last_cluster_num{-1}; + const int m_combox_width{50}; + int m_combox_icon_width; + int m_combox_icon_height; + wxGridSizer* m_gridsizer = nullptr; + wxStaticText * m_test = nullptr; + //data + char m_last_cluster_number{-2}; + std::vector& m_input_colors; + int m_color_num_recommend{0}; + int m_color_cluster_num_by_algo{0}; + int m_input_colors_size{0}; + std::vector m_colours;//from project and show right + std::vector m_cluster_map_filaments;//show middle + std::vector m_cluster_colours;//from_algo and show left + bool m_can_add_filament{true}; + std::vector m_new_add_colors; + //algo result + std::vector m_cluster_colors_from_algo; + std::vector m_cluster_labels_from_algo; + //result + bool m_is_add_filament{false}; + unsigned char& m_first_extruder_id; + std::vector &m_filament_ids; +}; + +class ObjColorDialog : public Slic3r::GUI::DPIDialog +{ +public: + ObjColorDialog(wxWindow * parent, + std::vector& input_colors, bool is_single_color, + const std::vector & extruder_colours, + std::vector& filament_ids, + unsigned char & first_extruder_id); + wxBoxSizer* create_btn_sizer(long flags); + void on_dpi_changed(const wxRect &suggested_rect) override; +private: + ObjColorPanel* m_panel_ObjColor = nullptr; + std::unordered_map m_button_list; + std::vector& m_filament_ids; + unsigned char & m_first_extruder_id; +}; + +#endif // _WIPE_TOWER_DIALOG_H_ \ No newline at end of file diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 57ebbe6f2..97d22dbab 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -127,6 +127,7 @@ #include // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" +#include "ObjColorDialog.hpp" #include "libslic3r/CustomGCode.hpp" #include "libslic3r/Platform.hpp" @@ -182,6 +183,7 @@ wxDEFINE_EVENT(EVT_CREATE_FILAMENT, SimpleEvent); wxDEFINE_EVENT(EVT_MODIFY_FILAMENT, SimpleEvent); wxDEFINE_EVENT(EVT_ADD_FILAMENT, SimpleEvent); wxDEFINE_EVENT(EVT_DEL_FILAMENT, SimpleEvent); +wxDEFINE_EVENT(EVT_ADD_CUSTOM_FILAMENT, ColorEvent); bool Plater::has_illegal_filename_characters(const wxString& wxs_name) { std::string name = into_u8(wxs_name); @@ -1609,15 +1611,8 @@ void Sidebar::on_filaments_change(size_t num_filaments) void Sidebar::add_filament() { // BBS: limit filament choices to 16 if (p->combos_filament.size() >= 16) return; - - int filament_count = p->combos_filament.size() + 1; wxColour new_col = Plater::get_next_color_for_filament(); - std::string new_color = new_col.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); - wxGetApp().preset_bundle->set_num_filaments(filament_count, new_color); - wxGetApp().plater()->on_filaments_change(filament_count); - wxGetApp().get_tab(Preset::TYPE_PRINT)->update(); - wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); - auto_calc_flushing_volumes(filament_count - 1); + add_custom_filament(new_col); } void Sidebar::delete_filament() { @@ -1638,6 +1633,18 @@ void Sidebar::delete_filament() { wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); } +void Sidebar::add_custom_filament(wxColour new_col) { + if (p->combos_filament.size() >= 16) return; + + int filament_count = p->combos_filament.size() + 1; + std::string new_color = new_col.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); + wxGetApp().preset_bundle->set_num_filaments(filament_count, new_color); + wxGetApp().plater()->on_filaments_change(filament_count); + wxGetApp().get_tab(Preset::TYPE_PRINT)->update(); + wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); + auto_calc_flushing_volumes(filament_count - 1); +} + void Sidebar::on_bed_type_change(BedType bed_type) { // btDefault option is not included in global bed type setting @@ -2552,6 +2559,7 @@ struct Plater::priv void on_modify_filament(SimpleEvent &); void on_add_filament(SimpleEvent &); void on_delete_filament(SimpleEvent &); + void on_add_custom_filament(ColorEvent &); void on_object_select(SimpleEvent&); void on_plate_name_change(SimpleEvent &); @@ -2785,6 +2793,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->q->Bind(EVT_MODIFY_FILAMENT, &priv::on_modify_filament, this); this->q->Bind(EVT_ADD_FILAMENT, &priv::on_add_filament, this); this->q->Bind(EVT_DEL_FILAMENT, &priv::on_delete_filament, this); + this->q->Bind(EVT_ADD_CUSTOM_FILAMENT, &priv::on_add_custom_filament, this); view3D = new View3D(q, bed, &model, config, &background_process); //BBS: use partplater's gcode preview = new Preview(q, bed, &model, config, &background_process, partplate_list.get_current_slice_result(), [this]() { schedule_background_process(); }); @@ -3930,7 +3939,17 @@ std::vector Plater::priv::load_files(const std::vector& input_ std::vector project_presets; bool is_xxx; Semver file_version; - + + //ObjImportColorFn obj_color_fun=nullptr; + auto obj_color_fun = [this, &path](std::vector &input_colors, bool is_single_color, std::vector &filament_ids, + unsigned char &first_extruder_id) { + if (!boost::iends_with(path.string(), ".obj")) { return; } + const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); + ObjColorDialog color_dlg(nullptr, input_colors, is_single_color, extruder_colours, filament_ids, first_extruder_id); + if (color_dlg.ShowModal() != wxID_OK) { + filament_ids.clear(); + } + }; model = Slic3r::Model::read_from_file( path.string(), nullptr, nullptr, strategy, &plate_data, &project_presets, &is_xxx, &file_version, nullptr, [this, &dlg, real_filename, &progress_percent, &file_percent, INPUT_FILES_RATIO, total_files, i, &designer_model_id, &designer_country_code](int current, int total, bool &cancel, std::string &mode_id, std::string &code) @@ -3960,7 +3979,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (!isUtf8StepFile) Slic3r::GUI::show_info(nullptr, _L("Name of components inside step file is not UTF8 format!") + "\n\n" + _L("The name may show garbage characters!"), _L("Attention!")); - }); + }, + nullptr, 0, obj_color_fun); if (designer_model_id.empty() && boost::algorithm::iends_with(path.string(), ".stl")) { @@ -8039,6 +8059,11 @@ void Plater::priv::on_delete_filament(SimpleEvent &evt) { sidebar->delete_filament(); } +void Plater::priv::on_add_custom_filament(ColorEvent &evt) +{ + sidebar->add_custom_filament(evt.data); +} + void Plater::priv::enter_gizmos_stack() { assert(m_undo_redo_stack_active == &m_undo_redo_stack_main); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 2fdd66891..0c1d57231 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -101,6 +101,8 @@ wxDECLARE_EVENT(EVT_CREATE_FILAMENT, SimpleEvent); wxDECLARE_EVENT(EVT_MODIFY_FILAMENT, SimpleEvent); wxDECLARE_EVENT(EVT_ADD_FILAMENT, SimpleEvent); wxDECLARE_EVENT(EVT_DEL_FILAMENT, SimpleEvent); +using ColorEvent = Event; +wxDECLARE_EVENT(EVT_ADD_CUSTOM_FILAMENT, ColorEvent); const wxString DEFAULT_PROJECT_NAME = "Untitled"; class Sidebar : public wxPanel @@ -136,6 +138,7 @@ public: void on_filaments_change(size_t num_filaments); void add_filament(); void delete_filament(); + void add_custom_filament(wxColour new_col); // BBS void on_bed_type_change(BedType bed_type); void load_ams_list(std::string const & device, MachineObject* obj); diff --git a/src/slic3r/GUI/Widgets/ComboBox.hpp b/src/slic3r/GUI/Widgets/ComboBox.hpp index 74bebd052..d4b74f4df 100644 --- a/src/slic3r/GUI/Widgets/ComboBox.hpp +++ b/src/slic3r/GUI/Widgets/ComboBox.hpp @@ -66,6 +66,7 @@ public: wxBitmap GetItemBitmap(unsigned int n); void SetItemBitmap(unsigned int n, wxBitmap const &bitmap); bool is_drop_down(){return drop_down;} + void DeleteOneItem(unsigned int pos) { DoDeleteOneItem(pos); } protected: virtual int DoInsertItems(const wxArrayStringsAdapter &items, unsigned int pos, diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index b53573d01..b542723e1 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -598,10 +598,9 @@ wxBitmap *get_extruder_color_icon(std::string color, std::string label, int icon return bitmap; } - -void apply_extruder_selector(Slic3r::GUI::BitmapComboBox** ctrl, +void apply_extruder_selector(Slic3r::GUI::BitmapComboBox** ctrl, wxWindow* parent, - const std::string& first_item/* = ""*/, + const std::string& first_item/* = ""*/, wxPoint pos/* = wxDefaultPosition*/, wxSize size/* = wxDefaultSize*/, bool use_thin_icon/* = false*/) diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index e7e992e4b..767cb3e5b 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -57,7 +57,7 @@ wxBitmap create_menu_bitmap(const std::string& bmp_name); // BBS: support resize by fill border #if 1 -wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullptr, +wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullptr, const int px_cnt = 16, const bool grayscale = false, const std::string& new_color = std::string(), // color witch will used instead of orange const bool menu_bitmap = false, const bool resize = false, @@ -75,7 +75,6 @@ wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullp wxBitmap* get_default_extruder_color_icon(bool thin_icon = false); std::vector get_extruder_color_icons(bool thin_icon = false); wxBitmap * get_extruder_color_icon(std::string color, std::string label, int icon_width, int icon_height); - namespace Slic3r { namespace GUI { class BitmapComboBox;