diff --git a/.gitignore b/.gitignore index 9c59ebdab5..09abcc8dee 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ src/OrcaSlicer-doc/ resources/profiles/user/default *.code-workspace deps_src/build/ +test.js +/.cache/ +.clangd diff --git a/resources/images/canvas_drag.svg b/resources/images/canvas_drag.svg new file mode 100644 index 0000000000..821fa700de --- /dev/null +++ b/resources/images/canvas_drag.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/images/canvas_drag_active.svg b/resources/images/canvas_drag_active.svg new file mode 100644 index 0000000000..5aa863efee --- /dev/null +++ b/resources/images/canvas_drag_active.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/images/canvas_zoom_in.svg b/resources/images/canvas_zoom_in.svg new file mode 100644 index 0000000000..3fbf01ee09 --- /dev/null +++ b/resources/images/canvas_zoom_in.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/images/canvas_zoom_in_disable.svg b/resources/images/canvas_zoom_in_disable.svg new file mode 100644 index 0000000000..dc3388481f --- /dev/null +++ b/resources/images/canvas_zoom_in_disable.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/images/canvas_zoom_out.svg b/resources/images/canvas_zoom_out.svg new file mode 100644 index 0000000000..70d98c9c00 --- /dev/null +++ b/resources/images/canvas_zoom_out.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/images/canvas_zoom_out_disable.svg b/resources/images/canvas_zoom_out_disable.svg new file mode 100644 index 0000000000..be4f1d5a18 --- /dev/null +++ b/resources/images/canvas_zoom_out_disable.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/images/print_control_partskip.svg b/resources/images/print_control_partskip.svg new file mode 100644 index 0000000000..21fe0bd031 --- /dev/null +++ b/resources/images/print_control_partskip.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/images/print_control_partskip_disable.svg b/resources/images/print_control_partskip_disable.svg new file mode 100644 index 0000000000..79d8a90432 --- /dev/null +++ b/resources/images/print_control_partskip_disable.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/earcut/CHANGELOG.md b/src/earcut/CHANGELOG.md new file mode 100644 index 0000000000..41d9e6703b --- /dev/null +++ b/src/earcut/CHANGELOG.md @@ -0,0 +1,27 @@ +## Earcut.hpp changelog + +### master + + - Fixed a bunch of rare edge cases that led to bad triangulation (parity with Earcut v2.2.2) + - Removed use of deprecated `std::allocator::construct` + - Fixed a minor z-order hashing bug + - Improved visualization app, better docs + +### v0.12.4 + + - Fixed a crash in Crash in Earcut::findHoleBridge + - Added coverage checks + - Added macOS, MinGW builds + +### v0.12.3 + + - Fixed -Wunused-lambda-capture + +### v0.12.2 + + - Fixed potential division by zero + - Fixed -fsanitize=integer warning + +### v0.12.1 + + - Fixed cast precision warning diff --git a/src/earcut/CMakeLists.txt b/src/earcut/CMakeLists.txt new file mode 100644 index 0000000000..08c7bb1bed --- /dev/null +++ b/src/earcut/CMakeLists.txt @@ -0,0 +1,151 @@ +cmake_minimum_required(VERSION 3.2) +project(earcut_hpp LANGUAGES CXX C) + +option(EARCUT_BUILD_TESTS "Build the earcut test program" ON) +option(EARCUT_BUILD_BENCH "Build the earcut benchmark program" ON) +option(EARCUT_BUILD_VIZ "Build the earcut visualizer program" ON) +option(EARCUT_WARNING_IS_ERROR "Treat warnings as errors" OFF) + +if (NOT CMAKE_BUILD_TYPE AND NOT GENERATOR_IS_MULTI_CONFIG) + message(STATUS "No build type specified. Setting to 'Release'") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "The type of build." FORCE) +endif() + + +include(GNUInstallDirs) + +add_library(earcut_hpp INTERFACE) +add_library(earcut_hpp::earcut_hpp ALIAS earcut_hpp) + +target_include_directories(earcut_hpp INTERFACE + $ + $ +) + +set(CMAKE_CXX_STANDARD 11) + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" GREATER 3.7) + # Allow C++11 requirements to propagate when using recent CMake versions + target_compile_features(earcut_hpp INTERFACE cxx_std_11) +endif() + +file(GLOB FIXTURE_SOURCE_FILES test/fixtures/*.cpp test/fixtures/*.hpp) +source_group(fixtures FILES ${FIXTURE_SOURCE_FILES}) +add_library(fixtures OBJECT ${FIXTURE_SOURCE_FILES}) +target_compile_options(fixtures PRIVATE $<$:/Od>) + +# In CMake 3.12, use target_link_libraries(fixtures PUBLIC earcut_hpp libtess2). +# Since we support down to CMake 3.2, we need to manually propagate usage requirements of earcut_hpp +target_include_directories(fixtures PRIVATE "$") +target_compile_features(fixtures PRIVATE "$") + + +file(GLOB COMPARISON_SOURCE_FILES test/comparison/*.cpp test/comparison/*.hpp) +source_group(comparison FILES ${COMPARISON_SOURCE_FILES}) +# this is interface since there is no cpp files in the comparison directory +add_library(comparison INTERFACE) + + +file(GLOB LIBTESS2_SOURCE_FILES test/comparison/libtess2/*.c test/comparison/libtess2/*.h) +source_group(comparison/libtess2 FILES ${LIBTESS2_SOURCE_FILES}) +add_library(libtess2 ${LIBTESS2_SOURCE_FILES}) +target_compile_options(libtess2 PRIVATE + $<$:/wd4244 /wd4267> + $<$>:-w> +) + +add_library(common INTERFACE) +target_link_libraries(common INTERFACE libtess2 comparison) + +# optional: -march=native (builds with the optimizations available on the build machine (only for local use!)) +target_compile_options(common INTERFACE + $<$>:-pipe -Wall -Wextra -Wconversion -Wpedantic> +) + +if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang$" OR CMAKE_COMPILER_IS_GNUCXX) + if ("${CMAKE_CXX_FLAGS}" MATCHES "--coverage") + # We disable debug code for the coverage so it won't see assertion and other things only enabled for debugging + target_compile_definitions(common INTERFACE NDEBUG) + else() + # Here we enable the undefined behavior sanitizer for the tests, benchmarks and the viz + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("-fsanitize=undefined" HAVE_FLAG_SANITIZE_UNDEFINED) + if(HAVE_FLAG_SANITIZE_UNDEFINED) + target_compile_options(common INTERFACE $<$:-fsanitize=undefined>) + # TODO: Replace with target link option once we support CMake 3.13 + target_link_libraries(common INTERFACE $<$:-fsanitize=undefined>) + endif() + endif() +endif() + +if (EARCUT_WARNING_IS_ERROR) + target_compile_options(common INTERFACE + $<$:/WX> + $<$>:-Werror> + ) +endif() + +if (EARCUT_BUILD_TESTS) + enable_testing() + add_executable(tests test/tap.cpp test/tap.hpp test/test.cpp $) + target_link_libraries(tests PRIVATE earcut_hpp common) + add_test(NAME earcut_tests COMMAND tests) +endif() +if (EARCUT_BUILD_BENCH) + add_executable(bench test/bench.cpp $) + target_link_libraries(bench PRIVATE earcut_hpp common) +endif() +if (EARCUT_BUILD_VIZ) + add_executable(viz test/viz.cpp $) + + # Setup viz target + # OpenGL + # linux: xorg-dev libgl1-mesa-glx libgl1-mesa-dev + # windows: in the windows sdk + find_package(OpenGL REQUIRED) + + # GLFW3 + find_package(glfw3 QUIET) # try to use the system default + if (NOT glfw3_FOUND) + if(EXISTS "${PROJECT_SOURCE_DIR}/.gitmodules") + find_package(Git REQUIRED) + execute_process( + COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_QUIET + ERROR_QUIET + ) + endif() + + set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "Build the GLFW example programs" FORCE) + set(GLFW_BUILD_TESTS OFF CACHE BOOL "Build the GLFW test programs" FORCE) + set(GLFW_BUILD_DOCS OFF CACHE BOOL "Build the GLFW documentation" FORCE) + set(GLFW_INSTALL OFF CACHE BOOL "Generate installation target" FORCE) + add_subdirectory(glfw) + endif() + + target_compile_definitions(viz PRIVATE GL_SILENCE_DEPRECATION) + + # TODO: Using old variables for OpenGL package since they were added in CMake 3.8 + target_link_libraries(viz PRIVATE earcut_hpp common glfw ${OPENGL_LIBRARIES}) + target_include_directories(viz PRIVATE ${OPENGL_INCLUDE_DIR}) +endif() + +install( + DIRECTORY include/mapbox + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.hpp" +) + +install(TARGETS earcut_hpp EXPORT earcut_hpp-config) + +# Since there is two projects, we need to export into the parent directory +export( + TARGETS earcut_hpp + NAMESPACE earcut_hpp:: + FILE "${PROJECT_BINARY_DIR}/earcut_hpp-config.cmake" +) + +install(EXPORT earcut_hpp-config + DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/earcut_hpp" + NAMESPACE earcut_hpp:: +) diff --git a/src/earcut/LICENSE b/src/earcut/LICENSE new file mode 100644 index 0000000000..8bafb57730 --- /dev/null +++ b/src/earcut/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2015, Mapbox + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/src/earcut/README.md b/src/earcut/README.md new file mode 100644 index 0000000000..67f235b7ff --- /dev/null +++ b/src/earcut/README.md @@ -0,0 +1,131 @@ +## Earcut + +A C++ port of [earcut.js](https://github.com/mapbox/earcut), a fast, [header-only](https://github.com/mapbox/earcut.hpp/blob/master/include/mapbox/earcut.hpp) polygon triangulation library. + +[![Travis](https://img.shields.io/travis/com/mapbox/earcut.hpp.svg)](https://travis-ci.com/github/mapbox/earcut.hpp) +[![AppVeyor](https://ci.appveyor.com/api/projects/status/a1ysrqd69mqn7coo/branch/master?svg=true)](https://ci.appveyor.com/project/Mapbox/earcut-hpp-8wm4o/branch/master) +[![Coverage](https://img.shields.io/coveralls/github/mapbox/earcut.hpp.svg)](https://coveralls.io/github/mapbox/earcut.hpp) +[![Coverity Scan](https://img.shields.io/coverity/scan/14000.svg)](https://scan.coverity.com/projects/14000) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/mapbox/earcut.hpp.svg)](http://isitmaintained.com/project/mapbox/earcut.hpp "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/mapbox/earcut.hpp.svg)](http://isitmaintained.com/project/mapbox/earcut.hpp "Percentage of issues still open") +[![Mourner](https://img.shields.io/badge/simply-awesome-brightgreen.svg)](https://github.com/mourner/projects) + +The library implements a modified ear slicing algorithm, optimized by [z-order curve](http://en.wikipedia.org/wiki/Z-order_curve) hashing and extended to handle holes, twisted polygons, degeneracies and self-intersections in a way that doesn't _guarantee_ correctness of triangulation, but attempts to always produce acceptable results for practical data like geographical shapes. + +It's based on ideas from [FIST: Fast Industrial-Strength Triangulation of Polygons](http://www.cosy.sbg.ac.at/~held/projects/triang/triang.html) by Martin Held and [Triangulation by Ear Clipping](http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf) by David Eberly. + +## Usage + +```cpp +#include +``` +```cpp +// The number type to use for tessellation +using Coord = double; + +// The index type. Defaults to uint32_t, but you can also pass uint16_t if you know that your +// data won't have more than 65536 vertices. +using N = uint32_t; + +// Create array +using Point = std::array; +std::vector> polygon; + +// Fill polygon structure with actual data. Any winding order works. +// The first polyline defines the main polygon. +polygon.push_back({{100, 0}, {100, 100}, {0, 100}, {0, 0}}); +// Following polylines define holes. +polygon.push_back({{75, 25}, {75, 75}, {25, 75}, {25, 25}}); + +// Run tessellation +// Returns array of indices that refer to the vertices of the input polygon. +// e.g: the index 6 would refer to {25, 75} in this example. +// Three subsequent indices form a triangle. Output triangles are clockwise. +std::vector indices = mapbox::earcut(polygon); +``` + +Earcut can triangulate a simple, planar polygon of any winding order including holes. It will even return a robust, acceptable solution for non-simple poygons. Earcut works on a 2D plane. If you have three or more dimensions, you can project them onto a 2D surface before triangulation, or use a more suitable library for the task (e.g [CGAL](https://doc.cgal.org/latest/Triangulation_3/index.html)). + + +It is also possible to use your custom point type as input. There are default accessors defined for `std::tuple`, `std::pair`, and `std::array`. For a custom type (like Clipper's `IntPoint` type), do this: + +```cpp +// struct IntPoint { +// int64_t X, Y; +// }; + +namespace mapbox { +namespace util { + +template <> +struct nth<0, IntPoint> { + inline static auto get(const IntPoint &t) { + return t.X; + }; +}; +template <> +struct nth<1, IntPoint> { + inline static auto get(const IntPoint &t) { + return t.Y; + }; +}; + +} // namespace util +} // namespace mapbox +``` + +You can also use a custom container type for your polygon. Similar to std::vector, it has to meet the requirements of [Container](https://en.cppreference.com/w/cpp/named_req/Container), in particular `size()`, `empty()` and `operator[]`. + +

+ example triangulation +

+ +## Additional build instructions +In case you just want to use the earcut triangulation library; copy and include the header file [``](https://github.com/mapbox/earcut.hpp/blob/master/include/mapbox/earcut.hpp) in your project and follow the steps documented in the section [Usage](#usage). + +If you want to build the test, benchmark and visualization programs instead, follow these instructions: + +### Dependencies + +Before you continue, make sure to have the following tools and libraries installed: + * git ([Ubuntu](https://help.ubuntu.com/lts/serverguide/git.html)/[Windows/macOS](http://git-scm.com/downloads)) + * cmake 3.2+ ([Ubuntu](https://launchpad.net/~george-edison55/+archive/ubuntu/cmake-3.x)/[Windows/macOS](https://cmake.org/download/)) + * OpenGL SDK ([Ubuntu](http://packages.ubuntu.com/de/trusty/libgl1-mesa-dev)/[Windows](https://dev.windows.com/en-us/downloads/windows-10-sdk)/[macOS](https://developer.apple.com/opengl/)) + * Compiler such as [GCC 4.9+, Clang 3.4+](https://launchpad.net/~ubuntu-toolchain-r/+archive/ubuntu/test), [MSVC12+](https://www.visualstudio.com/) + +Note: On some operating systems such as Windows, manual steps are required to add cmake and [git](http://blog.countableset.ch/2012/06/07/adding-git-to-windows-7-path/) to your PATH environment variable. + +### Manual compilation + +```bash +git clone --recursive https://github.com/mapbox/earcut.hpp.git +cd earcut.hpp +mkdir build +cd build +cmake .. +make +# ./tests +# ./bench +# ./viz +``` + +### [Visual Studio](https://www.visualstudio.com/), [Eclipse](https://eclipse.org/), [XCode](https://developer.apple.com/xcode/), ... + +```batch +git clone --recursive https://github.com/mapbox/earcut.hpp.git +cd earcut.hpp +mkdir project +cd project +cmake .. -G "Visual Studio 14 2015" +::you can also generate projects for "Visual Studio 12 2013", "XCode", "Eclipse CDT4 - Unix Makefiles" +``` +After completion, open the generated project with your IDE. + + +### [CLion](https://www.jetbrains.com/clion/), [Visual Studio 2017+](https://www.visualstudio.com/) + +Import the project from https://github.com/mapbox/earcut.hpp.git and you should be good to go! + +## Status + +This is currently based on [earcut 2.2.4](https://github.com/mapbox/earcut#224-jul-5-2022). diff --git a/src/earcut/earcut.hpp b/src/earcut/earcut.hpp new file mode 100644 index 0000000000..fd3381484c --- /dev/null +++ b/src/earcut/earcut.hpp @@ -0,0 +1,814 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mapbox { + +namespace util { + +template struct nth { + inline static typename std::tuple_element::type + get(const T& t) { return std::get(t); }; +}; + +} + +namespace detail { + +template +class Earcut { +public: + std::vector indices; + std::size_t vertices = 0; + + template + void operator()(const Polygon& points); + +private: + struct Node { + Node(N index, double x_, double y_) : i(index), x(x_), y(y_) {} + Node(const Node&) = delete; + Node& operator=(const Node&) = delete; + Node(Node&&) = delete; + Node& operator=(Node&&) = delete; + + const N i; + const double x; + const double y; + + // previous and next vertice nodes in a polygon ring + Node* prev = nullptr; + Node* next = nullptr; + + // z-order curve value + int32_t z = 0; + + // previous and next nodes in z-order + Node* prevZ = nullptr; + Node* nextZ = nullptr; + + // indicates whether this is a steiner point + bool steiner = false; + }; + + template Node* linkedList(const Ring& points, const bool clockwise); + Node* filterPoints(Node* start, Node* end = nullptr); + void earcutLinked(Node* ear, int pass = 0); + bool isEar(Node* ear); + bool isEarHashed(Node* ear); + Node* cureLocalIntersections(Node* start); + void splitEarcut(Node* start); + template Node* eliminateHoles(const Polygon& points, Node* outerNode); + Node* eliminateHole(Node* hole, Node* outerNode); + Node* findHoleBridge(Node* hole, Node* outerNode); + bool sectorContainsSector(const Node* m, const Node* p); + void indexCurve(Node* start); + Node* sortLinked(Node* list); + int32_t zOrder(const double x_, const double y_); + Node* getLeftmost(Node* start); + bool pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const; + bool isValidDiagonal(Node* a, Node* b); + double area(const Node* p, const Node* q, const Node* r) const; + bool equals(const Node* p1, const Node* p2); + bool intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2); + bool onSegment(const Node* p, const Node* q, const Node* r); + int sign(double val); + bool intersectsPolygon(const Node* a, const Node* b); + bool locallyInside(const Node* a, const Node* b); + bool middleInside(const Node* a, const Node* b); + Node* splitPolygon(Node* a, Node* b); + template Node* insertNode(std::size_t i, const Point& p, Node* last); + void removeNode(Node* p); + + bool hashing; + double minX, maxX; + double minY, maxY; + double inv_size = 0; + + template > + class ObjectPool { + public: + ObjectPool() { } + ObjectPool(std::size_t blockSize_) { + reset(blockSize_); + } + ~ObjectPool() { + clear(); + } + template + T* construct(Args&&... args) { + if (currentIndex >= blockSize) { + currentBlock = alloc_traits::allocate(alloc, blockSize); + allocations.emplace_back(currentBlock); + currentIndex = 0; + } + T* object = ¤tBlock[currentIndex++]; + alloc_traits::construct(alloc, object, std::forward(args)...); + return object; + } + void reset(std::size_t newBlockSize) { + for (auto allocation : allocations) { + alloc_traits::deallocate(alloc, allocation, blockSize); + } + allocations.clear(); + blockSize = std::max(1, newBlockSize); + currentBlock = nullptr; + currentIndex = blockSize; + } + void clear() { reset(blockSize); } + private: + T* currentBlock = nullptr; + std::size_t currentIndex = 1; + std::size_t blockSize = 1; + std::vector allocations; + Alloc alloc; + typedef typename std::allocator_traits alloc_traits; + }; + ObjectPool nodes; +}; + +template template +void Earcut::operator()(const Polygon& points) { + // reset + indices.clear(); + vertices = 0; + + if (points.empty()) return; + + double x; + double y; + int threshold = 80; + std::size_t len = 0; + + for (size_t i = 0; threshold >= 0 && i < points.size(); i++) { + threshold -= static_cast(points[i].size()); + len += points[i].size(); + } + + //estimate size of nodes and indices + nodes.reset(len * 3 / 2); + indices.reserve(len + points[0].size()); + + Node* outerNode = linkedList(points[0], true); + if (!outerNode || outerNode->prev == outerNode->next) return; + + if (points.size() > 1) outerNode = eliminateHoles(points, outerNode); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + hashing = threshold < 0; + if (hashing) { + Node* p = outerNode->next; + minX = maxX = outerNode->x; + minY = maxY = outerNode->y; + do { + x = p->x; + y = p->y; + minX = std::min(minX, x); + minY = std::min(minY, y); + maxX = std::max(maxX, x); + maxY = std::max(maxY, y); + p = p->next; + } while (p != outerNode); + + // minX, minY and inv_size are later used to transform coords into integers for z-order calculation + inv_size = std::max(maxX - minX, maxY - minY); + inv_size = inv_size != .0 ? (32767. / inv_size) : .0; + } + + earcutLinked(outerNode); + + nodes.clear(); +} + +// create a circular doubly linked list from polygon points in the specified winding order +template template +typename Earcut::Node* +Earcut::linkedList(const Ring& points, const bool clockwise) { + using Point = typename Ring::value_type; + double sum = 0; + const std::size_t len = points.size(); + std::size_t i, j; + Node* last = nullptr; + + // calculate original winding order of a polygon ring + for (i = 0, j = len > 0 ? len - 1 : 0; i < len; j = i++) { + const auto& p1 = points[i]; + const auto& p2 = points[j]; + const double p20 = util::nth<0, Point>::get(p2); + const double p10 = util::nth<0, Point>::get(p1); + const double p11 = util::nth<1, Point>::get(p1); + const double p21 = util::nth<1, Point>::get(p2); + sum += (p20 - p10) * (p11 + p21); + } + + // link points into circular doubly-linked list in the specified winding order + if (clockwise == (sum > 0)) { + for (i = 0; i < len; i++) last = insertNode(vertices + i, points[i], last); + } else { + for (i = len; i-- > 0;) last = insertNode(vertices + i, points[i], last); + } + + if (last && equals(last, last->next)) { + removeNode(last); + last = last->next; + } + + vertices += len; + + return last; +} + +// eliminate colinear or duplicate points +template +typename Earcut::Node* +Earcut::filterPoints(Node* start, Node* end) { + if (!end) end = start; + + Node* p = start; + bool again; + do { + again = false; + + if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) { + removeNode(p); + p = end = p->prev; + + if (p == p->next) break; + again = true; + + } else { + p = p->next; + } + } while (again || p != end); + + return end; +} + +// main ear slicing loop which triangulates a polygon (given as a linked list) +template +void Earcut::earcutLinked(Node* ear, int pass) { + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && hashing) indexCurve(ear); + + Node* stop = ear; + Node* prev; + Node* next; + + // iterate through ears, slicing them one by one + while (ear->prev != ear->next) { + prev = ear->prev; + next = ear->next; + + if (hashing ? isEarHashed(ear) : isEar(ear)) { + // cut off the triangle + indices.emplace_back(prev->i); + indices.emplace_back(ear->i); + indices.emplace_back(next->i); + + removeNode(ear); + + // skipping the next vertice leads to less sliver triangles + ear = next->next; + stop = next->next; + + continue; + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear == stop) { + // try filtering points and slicing again + if (!pass) earcutLinked(filterPoints(ear), 1); + + // if this didn't work, try curing all small self-intersections locally + else if (pass == 1) { + ear = cureLocalIntersections(filterPoints(ear)); + earcutLinked(ear, 2); + + // as a last resort, try splitting the remaining polygon into two + } else if (pass == 2) splitEarcut(ear); + + break; + } + } +} + +// check whether a polygon node forms a valid ear with adjacent nodes +template +bool Earcut::isEar(Node* ear) { + const Node* a = ear->prev; + const Node* b = ear; + const Node* c = ear->next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + Node* p = ear->next->next; + + while (p != ear->prev) { + if (pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->next; + } + + return true; +} + +template +bool Earcut::isEarHashed(Node* ear) { + const Node* a = ear->prev; + const Node* b = ear; + const Node* c = ear->next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // triangle bbox; min & max are calculated like this for speed + const double minTX = std::min(a->x, std::min(b->x, c->x)); + const double minTY = std::min(a->y, std::min(b->y, c->y)); + const double maxTX = std::max(a->x, std::max(b->x, c->x)); + const double maxTY = std::max(a->y, std::max(b->y, c->y)); + + // z-order range for the current triangle bbox; + const int32_t minZ = zOrder(minTX, minTY); + const int32_t maxZ = zOrder(maxTX, maxTY); + + // first look for points inside the triangle in increasing z-order + Node* p = ear->nextZ; + + while (p && p->z <= maxZ) { + if (p != ear->prev && p != ear->next && + pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->nextZ; + } + + // then look for points in decreasing z-order + p = ear->prevZ; + + while (p && p->z >= minZ) { + if (p != ear->prev && p != ear->next && + pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->prevZ; + } + + return true; +} + +// go through all polygon nodes and cure small local self-intersections +template +typename Earcut::Node* +Earcut::cureLocalIntersections(Node* start) { + Node* p = start; + do { + Node* a = p->prev; + Node* b = p->next->next; + + // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2]) + if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) && locallyInside(b, a)) { + indices.emplace_back(a->i); + indices.emplace_back(p->i); + indices.emplace_back(b->i); + + // remove two nodes involved + removeNode(p); + removeNode(p->next); + + p = start = b; + } + p = p->next; + } while (p != start); + + return filterPoints(p); +} + +// try splitting polygon into two and triangulate them independently +template +void Earcut::splitEarcut(Node* start) { + // look for a valid diagonal that divides the polygon into two + Node* a = start; + do { + Node* b = a->next->next; + while (b != a->prev) { + if (a->i != b->i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + Node* c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a->next); + c = filterPoints(c, c->next); + + // run earcut on each half + earcutLinked(a); + earcutLinked(c); + return; + } + b = b->next; + } + a = a->next; + } while (a != start); +} + +// link every hole into the outer loop, producing a single-ring polygon without holes +template template +typename Earcut::Node* +Earcut::eliminateHoles(const Polygon& points, Node* outerNode) { + const size_t len = points.size(); + + std::vector queue; + for (size_t i = 1; i < len; i++) { + Node* list = linkedList(points[i], false); + if (list) { + if (list == list->next) list->steiner = true; + queue.push_back(getLeftmost(list)); + } + } + std::sort(queue.begin(), queue.end(), [](const Node* a, const Node* b) { + return a->x < b->x; + }); + + // process holes from left to right + for (size_t i = 0; i < queue.size(); i++) { + outerNode = eliminateHole(queue[i], outerNode); + } + + return outerNode; +} + +// find a bridge between vertices that connects hole with an outer ring and and link it +template +typename Earcut::Node* +Earcut::eliminateHole(Node* hole, Node* outerNode) { + Node* bridge = findHoleBridge(hole, outerNode); + if (!bridge) { + return outerNode; + } + + Node* bridgeReverse = splitPolygon(bridge, hole); + + // filter collinear points around the cuts + filterPoints(bridgeReverse, bridgeReverse->next); + + // Check if input node was removed by the filtering + return filterPoints(bridge, bridge->next); +} + +// David Eberly's algorithm for finding a bridge between hole and outer polygon +template +typename Earcut::Node* +Earcut::findHoleBridge(Node* hole, Node* outerNode) { + Node* p = outerNode; + double hx = hole->x; + double hy = hole->y; + double qx = -std::numeric_limits::infinity(); + Node* m = nullptr; + + // find a segment intersected by a ray from the hole's leftmost Vertex to the left; + // segment's endpoint with lesser x will be potential connection Vertex + do { + if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) { + double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y); + if (x <= hx && x > qx) { + qx = x; + m = p->x < p->next->x ? p : p->next; + if (x == hx) return m; // hole touches outer segment; pick leftmost endpoint + } + } + p = p->next; + } while (p != outerNode); + + if (!m) return 0; + + // look for points inside the triangle of hole Vertex, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the Vertex of the minimum angle with the ray as connection Vertex + + const Node* stop = m; + double tanMin = std::numeric_limits::infinity(); + double tanCur = 0; + + p = m; + double mx = m->x; + double my = m->y; + + do { + if (hx >= p->x && p->x >= mx && hx != p->x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p->x, p->y)) { + + tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential + + if (locallyInside(p, hole) && + (tanCur < tanMin || (tanCur == tanMin && (p->x > m->x || sectorContainsSector(m, p))))) { + m = p; + tanMin = tanCur; + } + } + + p = p->next; + } while (p != stop); + + return m; +} + +// whether sector in vertex m contains sector in vertex p in the same coordinates +template +bool Earcut::sectorContainsSector(const Node* m, const Node* p) { + return area(m->prev, m, p->prev) < 0 && area(p->next, m, m->next) < 0; +} + +// interlink polygon nodes in z-order +template +void Earcut::indexCurve(Node* start) { + assert(start); + Node* p = start; + + do { + p->z = p->z ? p->z : zOrder(p->x, p->y); + p->prevZ = p->prev; + p->nextZ = p->next; + p = p->next; + } while (p != start); + + p->prevZ->nextZ = nullptr; + p->prevZ = nullptr; + + sortLinked(p); +} + +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html +template +typename Earcut::Node* +Earcut::sortLinked(Node* list) { + assert(list); + Node* p; + Node* q; + Node* e; + Node* tail; + int i, numMerges, pSize, qSize; + int inSize = 1; + + for (;;) { + p = list; + list = nullptr; + tail = nullptr; + numMerges = 0; + + while (p) { + numMerges++; + q = p; + pSize = 0; + for (i = 0; i < inSize; i++) { + pSize++; + q = q->nextZ; + if (!q) break; + } + + qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize == 0) { + e = q; + q = q->nextZ; + qSize--; + } else if (qSize == 0 || !q) { + e = p; + p = p->nextZ; + pSize--; + } else if (p->z <= q->z) { + e = p; + p = p->nextZ; + pSize--; + } else { + e = q; + q = q->nextZ; + qSize--; + } + + if (tail) tail->nextZ = e; + else list = e; + + e->prevZ = tail; + tail = e; + } + + p = q; + } + + tail->nextZ = nullptr; + + if (numMerges <= 1) return list; + + inSize *= 2; + } +} + +// z-order of a Vertex given coords and size of the data bounding box +template +int32_t Earcut::zOrder(const double x_, const double y_) { + // coords are transformed into non-negative 15-bit integer range + int32_t x = static_cast((x_ - minX) * inv_size); + int32_t y = static_cast((y_ - minY) * inv_size); + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); +} + +// find the leftmost node of a polygon ring +template +typename Earcut::Node* +Earcut::getLeftmost(Node* start) { + Node* p = start; + Node* leftmost = start; + do { + if (p->x < leftmost->x || (p->x == leftmost->x && p->y < leftmost->y)) + leftmost = p; + p = p->next; + } while (p != start); + + return leftmost; +} + +// check if a point lies within a convex triangle +template +bool Earcut::pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const { + return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && + (ax - px) * (by - py) >= (bx - px) * (ay - py) && + (bx - px) * (cy - py) >= (cx - px) * (by - py); +} + +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) +template +bool Earcut::isValidDiagonal(Node* a, Node* b) { + return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) && // dones't intersect other edges + ((locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible + (area(a->prev, a, b->prev) != 0.0 || area(a, b->prev, b) != 0.0)) || // does not create opposite-facing sectors + (equals(a, b) && area(a->prev, a, a->next) > 0 && area(b->prev, b, b->next) > 0)); // special zero-length case +} + +// signed area of a triangle +template +double Earcut::area(const Node* p, const Node* q, const Node* r) const { + return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y); +} + +// check if two points are equal +template +bool Earcut::equals(const Node* p1, const Node* p2) { + return p1->x == p2->x && p1->y == p2->y; +} + +// check if two segments intersect +template +bool Earcut::intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2) { + int o1 = sign(area(p1, q1, p2)); + int o2 = sign(area(p1, q1, q2)); + int o3 = sign(area(p2, q2, p1)); + int o4 = sign(area(p2, q2, q1)); + + if (o1 != o2 && o3 != o4) return true; // general case + + if (o1 == 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 + if (o2 == 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 + if (o3 == 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 + if (o4 == 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 + + return false; +} + +// for collinear points p, q, r, check if point q lies on segment pr +template +bool Earcut::onSegment(const Node* p, const Node* q, const Node* r) { + return q->x <= std::max(p->x, r->x) && + q->x >= std::min(p->x, r->x) && + q->y <= std::max(p->y, r->y) && + q->y >= std::min(p->y, r->y); +} + +template +int Earcut::sign(double val) { + return (0.0 < val) - (val < 0.0); +} + +// check if a polygon diagonal intersects any polygon segments +template +bool Earcut::intersectsPolygon(const Node* a, const Node* b) { + const Node* p = a; + do { + if (p->i != a->i && p->next->i != a->i && p->i != b->i && p->next->i != b->i && + intersects(p, p->next, a, b)) return true; + p = p->next; + } while (p != a); + + return false; +} + +// check if a polygon diagonal is locally inside the polygon +template +bool Earcut::locallyInside(const Node* a, const Node* b) { + return area(a->prev, a, a->next) < 0 ? + area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0 : + area(a, b, a->prev) < 0 || area(a, a->next, b) < 0; +} + +// check if the middle Vertex of a polygon diagonal is inside the polygon +template +bool Earcut::middleInside(const Node* a, const Node* b) { + const Node* p = a; + bool inside = false; + double px = (a->x + b->x) / 2; + double py = (a->y + b->y) / 2; + do { + if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y && + (px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x)) + inside = !inside; + p = p->next; + } while (p != a); + + return inside; +} + +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits +// polygon into two; if one belongs to the outer ring and another to a hole, it merges it into a +// single ring +template +typename Earcut::Node* +Earcut::splitPolygon(Node* a, Node* b) { + Node* a2 = nodes.construct(a->i, a->x, a->y); + Node* b2 = nodes.construct(b->i, b->x, b->y); + Node* an = a->next; + Node* bp = b->prev; + + a->next = b; + b->prev = a; + + a2->next = an; + an->prev = a2; + + b2->next = a2; + a2->prev = b2; + + bp->next = b2; + b2->prev = bp; + + return b2; +} + +// create a node and util::optionally link it with previous one (in a circular doubly linked list) +template template +typename Earcut::Node* +Earcut::insertNode(std::size_t i, const Point& pt, Node* last) { + Node* p = nodes.construct(static_cast(i), util::nth<0, Point>::get(pt), util::nth<1, Point>::get(pt)); + + if (!last) { + p->prev = p; + p->next = p; + + } else { + assert(last); + p->next = last->next; + p->prev = last; + last->next->prev = p; + last->next = p; + } + return p; +} + +template +void Earcut::removeNode(Node* p) { + p->next->prev = p->prev; + p->prev->next = p->next; + + if (p->prevZ) p->prevZ->nextZ = p->nextZ; + if (p->nextZ) p->nextZ->prevZ = p->prevZ; +} +} + +template +std::vector earcut(const Polygon& poly) { + mapbox::detail::Earcut earcut; + earcut(poly); + return std::move(earcut.indices); +} +} diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 0e954f93b1..81d8d616da 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -373,6 +373,11 @@ set(SLIC3R_GUI_SOURCES GUI/SavePresetDialog.hpp GUI/SceneRaycaster.cpp GUI/SceneRaycaster.hpp + GUI/PartSkipCommon.hpp + GUI/PartSkipDialog.cpp + GUI/PartSkipDialog.hpp + GUI/SkipPartCanvas.cpp + GUI/SkipPartCanvas.hpp GUI/Search.cpp GUI/Search.hpp GUI/Selection.cpp diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index 8e2bb16905..ada83175d0 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -1860,6 +1860,17 @@ int MachineObject::command_control_fan_val(FanType fan_type, int val) } +int MachineObject::command_task_partskip(std::vector part_ids) +{ + BOOST_LOG_TRIVIAL(trace) << "command_task_partskip: "; + json j; + j["print"]["command"] = "skip_objects"; + j["print"]["obj_list"] = part_ids; + j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + + return this->publish_json(j.dump(), 1); +} + int MachineObject::command_task_abort() { BOOST_LOG_TRIVIAL(trace) << "command_task_abort: "; @@ -2730,7 +2741,7 @@ void MachineObject::reset() vt_tray.reset(); subtask_ = nullptr; - + m_partskip_ids.clear(); } void MachineObject::set_print_state(std::string status) @@ -2966,8 +2977,20 @@ int MachineObject::parse_json(std::string payload, bool key_field_only) print_json.load_compatible_settings(printer_type, ""); print_json.diff2all_base_reset(j_pre); } + + if (j_pre["print"].contains("s_obj")){ + if(j_pre["print"]["s_obj"].is_array()){ + m_partskip_ids.clear(); + for(auto it=j_pre["print"]["s_obj"].begin(); it!=j_pre["print"]["s_obj"].end(); it++){ + m_partskip_ids.push_back(it.value().get()); + } + } + } } } + if (j_pre["print"].contains("plate_idx") && m_plate_index == -1){ + m_plate_index = j_pre["print"]["plate_idx"].get(); + } } if (j_pre.contains("system")) { if (j_pre["system"].contains("command")) { diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index e3c90b884b..7f66c30f25 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -833,6 +833,9 @@ public: int xcam_filament_tangle_detect_count = 0; int ams_print_option_count = 0; + // part skip + std::vector m_partskip_ids; + //supported features bool is_support_chamber_edit{false}; bool is_support_extrusion_cali{false}; @@ -955,6 +958,7 @@ public: int command_control_fan_val(FanType fan_type, int val); int command_task_abort(); /* cancelled the job_id */ + int command_task_partskip(std::vector part_ids); int command_task_cancel(std::string job_id); int command_task_pause(); int command_task_resume(); diff --git a/src/slic3r/GUI/PartSkipCommon.hpp b/src/slic3r/GUI/PartSkipCommon.hpp new file mode 100644 index 0000000000..bd158edbf8 --- /dev/null +++ b/src/slic3r/GUI/PartSkipCommon.hpp @@ -0,0 +1,18 @@ +#ifndef PARTSKIPCOMMON_H +#define PARTSKIPCOMMON_H + + +namespace Slic3r { namespace GUI { + +enum PartState { + psUnCheck, + psChecked, + psSkipped +}; + + +typedef std::vector> PartsInfo; + +}} + +#endif // PARTSKIPCOMMON_H \ No newline at end of file diff --git a/src/slic3r/GUI/PartSkipDialog.cpp b/src/slic3r/GUI/PartSkipDialog.cpp new file mode 100644 index 0000000000..2f72abdb0f --- /dev/null +++ b/src/slic3r/GUI/PartSkipDialog.cpp @@ -0,0 +1,863 @@ +#include "GUI_Utils.hpp" +#include "GUI_App.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Widgets/CheckBox.hpp" +#include "Widgets/Label.hpp" + +#include "MsgDialog.hpp" +#include "Printer/PrinterFileSystem.h" +#include "PartSkipDialog.hpp" +#include "SkipPartCanvas.hpp" +#include "MediaPlayCtrl.h" + + +namespace Slic3r { namespace GUI { + +extern wxString hide_passwd(wxString url, std::vector const &passwords); +extern void refresh_agora_url(char const *device, char const *dev_ver, char const *channel, void *context, void (*callback)(void *context, char const *url)); + + +PartSkipDialog::PartSkipDialog(wxWindow* parent): DPIDialog(parent, wxID_ANY, _L("Skip Objects"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) +{ + std::time_t t = std::time(0); + std::stringstream buf; + buf << put_time(std::localtime(&t), "%a_%b_%d_%H_%M_%S/"); + m_timestamp = buf.str(); + + 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)); + SetBackgroundColour(*wxWHITE); + SetSizeHints( wxDefaultSize, wxDefaultSize ); + + m_sizer = new wxBoxSizer( wxVERTICAL ); + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); + m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA)); + m_sizer->Add(m_line_top, 0, wxEXPAND, FromDIP(5)); + m_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + + + m_simplebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); + m_book_first_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_book_third_panel = new wxPanel(m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + m_book_third_panel->SetBackgroundColour(*wxWHITE); + + m_dlg_sizer = new wxBoxSizer( wxVERTICAL ); + m_dlg_content_sizer = new wxBoxSizer( wxHORIZONTAL ); + m_canvas_sizer = new wxBoxSizer( wxVERTICAL ); + + // page 3 + wxGLAttributes canvasAttrs; + canvasAttrs.PlatformDefaults().Defaults().Stencil(8).EndList(); + m_canvas = new SkipPartCanvas(m_book_third_panel, canvasAttrs); + m_canvas->SetPosition(wxPoint(FromDIP(10), FromDIP(10))); + m_canvas->SetMinSize( wxSize( FromDIP(400),FromDIP(400) ) ); + + m_canvas_sizer->Add( m_canvas, 0, wxALL, FromDIP(10) ); + + m_canvas_btn_sizer = new wxBoxSizer( wxHORIZONTAL ); + m_canvas_btn_sizer->SetMinSize( wxSize( FromDIP(214),FromDIP(28) ) ); + + StateColor zoom_bg(std::pair(wxColour(255, 255, 255), StateColor::Disabled), std::pair(wxColour(206, 206, 206), StateColor::Pressed), + std::pair(wxColour(238, 238, 238), StateColor::Hovered), std::pair(wxColour(255, 255, 255), StateColor::Enabled), + std::pair(wxColour(255, 255, 255), StateColor::Normal)); + zoom_bg.setTakeFocusedAsHovered(false); + StateColor zoom_bd(std::pair(wxColour(144, 144, 144), StateColor::Disabled), std::pair(wxColour(38, 46, 48), StateColor::Enabled)); + StateColor zoom_text(std::pair(wxColour(144, 144, 144), StateColor::Disabled), std::pair(wxColour(38, 46, 48), StateColor::Enabled)); + + m_zoom_out_btn = new Button(m_book_third_panel, _L("")); + m_zoom_out_btn->SetIcon("canvas_zoom_out"); + m_zoom_out_btn->SetToolTip(_L("Zoom Out")); + m_zoom_out_btn->SetBackgroundColor(zoom_bg); + m_zoom_out_btn->SetBorderColor(wxColour(238, 238, 238)); + m_zoom_out_btn->SetCornerRadius(0); + m_zoom_out_btn->SetMinSize(wxSize(FromDIP(54), FromDIP(26))); + + m_canvas_btn_sizer->Add(m_zoom_out_btn, 0, wxEXPAND, 0); + + StateColor percent_bg(std::pair(wxColour(255, 255, 255), StateColor::Disabled), std::pair(wxColour(255, 255, 255), StateColor::Pressed), + std::pair(wxColour(255, 255, 255), StateColor::Hovered), std::pair(wxColour(255, 255, 255), StateColor::Enabled), + std::pair(wxColour(255, 255, 255), StateColor::Normal)); + m_percent_label = new Button(m_book_third_panel, _L("100 %")); + m_percent_label->SetBackgroundColor(percent_bg); + m_percent_label->SetBorderColor(wxColour(238, 238, 238)); + m_percent_label->SetMinSize(wxSize(FromDIP(54), FromDIP(26))); + m_percent_label->SetCornerRadius(0); + m_canvas_btn_sizer->Add( m_percent_label, 0, wxEXPAND, 0 ); + + m_zoom_in_btn = new Button(m_book_third_panel, _L("")); + m_zoom_in_btn->SetIcon("canvas_zoom_in"); + m_zoom_in_btn->SetToolTip(_L("Zoom In")); + m_zoom_in_btn->SetBackgroundColor(zoom_bg); + m_zoom_in_btn->SetBorderColor(wxColour(238, 238, 238)); + m_zoom_in_btn->SetMinSize(wxSize(FromDIP(54), FromDIP(26))); + m_zoom_in_btn->SetCornerRadius(0); + m_canvas_btn_sizer->Add(m_zoom_in_btn, 0, wxEXPAND, 0); + + m_switch_drag_btn = new Button(m_book_third_panel, _L("")); + m_switch_drag_btn->SetIcon("canvas_drag"); + m_switch_drag_btn->SetToolTip(_L("Drag")); + m_switch_drag_btn->SetBackgroundColor(*wxWHITE); + m_switch_drag_btn->SetBorderColor(wxColour(238, 238, 238)); + m_switch_drag_btn->SetMinSize(wxSize(FromDIP(54),FromDIP(26))); + m_switch_drag_btn->SetCornerRadius(0); + m_canvas_btn_sizer->Add( m_switch_drag_btn, 0, wxEXPAND, FromDIP(5) ); + + m_canvas_sizer->Add(m_canvas_btn_sizer, 0, wxALIGN_CENTER_HORIZONTAL, FromDIP(10)); + + m_dlg_content_sizer->Add( m_canvas_sizer, 0, wxALL, FromDIP(10) ); + + m_list_sizer = new wxBoxSizer( wxVERTICAL ); + m_list_sizer->SetMinSize( wxSize( FromDIP(267),FromDIP(422) ) ); + + auto all_checkbox_sizer = new wxBoxSizer(wxHORIZONTAL); + m_all_checkbox = new CheckBox(m_book_third_panel, wxID_ANY); + m_all_checkbox->SetValue(false); + m_all_checkbox->SetBackgroundColour(*wxWHITE); + m_all_label = new Label(m_book_third_panel, _L("Select All")); + m_all_label->Wrap(-1); + m_all_label->SetBackgroundColour(*wxWHITE); + + m_all_label->SetMinSize(wxSize(267, -1)); + m_all_label->SetMaxSize(wxSize(267, -1)); + all_checkbox_sizer->Add(m_all_checkbox, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0); + all_checkbox_sizer->Add(m_all_label, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0); + + m_list_sizer->Add(all_checkbox_sizer, 0, wxALL, FromDIP(5)); + + m_line = new wxPanel(m_book_third_panel, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(267), FromDIP(1)), wxTAB_TRAVERSAL); + m_line->SetBackgroundColour( wxColor(238, 238, 238) ); + + m_list_sizer->Add(m_line, 0, wxEXPAND, 0); + + m_list_view = new wxScrolledWindow( m_book_third_panel, wxID_ANY, wxDefaultPosition, wxSize( 267,-1 ), wxHSCROLL|wxVSCROLL ); + m_list_view->SetScrollRate( 5, 5 ); + m_list_view->SetMinSize( wxSize( FromDIP(267),FromDIP(410) ) ); + m_list_view->SetMaxSize(wxSize(FromDIP(267), FromDIP(410))); + m_list_view->SetBackgroundColour(*wxWHITE); + + m_scroll_sizer = new wxBoxSizer(wxVERTICAL); + m_list_view->SetSizer(m_scroll_sizer); + m_list_view->Layout(); + + m_list_sizer->Add(m_list_view, 0, wxEXPAND, 0); + + m_dlg_content_sizer->Add( m_list_sizer, 0, wxALL, FromDIP(15) ); + + + m_dlg_sizer->Add( m_dlg_content_sizer, 0, wxEXPAND, FromDIP(5) ); + + m_dlg_btn_sizer = new wxBoxSizer( wxHORIZONTAL ); + m_dlg_btn_sizer->SetMinSize(wxSize(FromDIP(720), FromDIP(54))); + + m_cnt_label = new Label(m_book_third_panel, _L("cnt 1")); + m_cnt_label->Wrap( -1 ); + m_cnt_label->SetBackgroundColour(*wxWHITE); + m_cnt_label->SetForegroundColour(wxColour(0, 174, 66)); + + m_cnt_label->SetFont(Label::Head_15); + m_dlg_btn_sizer->Add(0, 0, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + m_dlg_btn_sizer->Add( m_cnt_label, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(0) ); + + m_tot_label = new Label(m_book_third_panel, _L("tot 15")); + m_tot_label->Wrap( -1 ); + m_tot_label->SetBackgroundColour(*wxWHITE); + + m_dlg_btn_sizer->Add(m_tot_label, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + m_dlg_btn_sizer->Add(0, 0, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + + StateColor btn_bg_green(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 177, 66), StateColor::Normal)); + + m_apply_btn = new Button(m_book_third_panel, wxString(_L("Skip"))); + m_apply_btn->SetBackgroundColor(btn_bg_green); + m_apply_btn->SetTextColor(*wxWHITE); + // m_apply_btn->SetBorderColor(wxColour(38, 46, 48)); + m_apply_btn->SetFont(Label::Body_14); + m_apply_btn->SetSize(wxSize(-1, FromDIP(32))); + m_apply_btn->SetMinSize(wxSize(FromDIP(80), FromDIP(32))); + m_apply_btn->SetCornerRadius(FromDIP(16)); + m_dlg_btn_sizer->Add(m_apply_btn, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(15)); + + m_dlg_sizer->Add( m_dlg_btn_sizer, 0, wxEXPAND, FromDIP(5) ); + + m_book_third_panel->SetSizer( m_dlg_sizer ); + m_book_third_panel->Layout(); + m_dlg_sizer->Fit( m_book_third_panel ); + + // page 2 + m_book_second_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_book_second_panel->SetBackgroundColour(*wxWHITE); + m_book_second_sizer = new wxBoxSizer( wxVERTICAL ); + m_book_second_btn_sizer = new wxBoxSizer( wxHORIZONTAL ); + + m_retry_label = new Label( m_book_second_panel, _L("Load Skipping Objects Information Failed. \nPlease try again.")); + m_retry_label->Wrap( -1 ); + m_retry_label->SetBackgroundColour(*wxWHITE); + m_book_second_sizer->Add(0, 0, 1, wxEXPAND, 0); + m_book_second_sizer->Add(m_retry_label, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_CENTER_HORIZONTAL, 0); + m_book_second_sizer->Add(0, 0, 1, wxEXPAND, 0); + + m_second_retry_btn = new Button(m_book_second_panel, _L("Retry")); + m_second_retry_btn->SetBackgroundColor(btn_bg_green); + // m_second_retry_btn->SetBorderColor(wxColour(38, 46, 48)); + m_second_retry_btn->SetTextColor(*wxWHITE); + m_second_retry_btn->SetFont(Label::Body_14); + m_second_retry_btn->SetSize(wxSize(FromDIP(80), FromDIP(32))); + m_second_retry_btn->SetMinSize(wxSize(FromDIP(80), FromDIP(32))); + m_second_retry_btn->SetCornerRadius(FromDIP(16)); + m_second_retry_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnRetryButton, this); + m_book_second_btn_sizer->Add(m_second_retry_btn, 0, wxALL, FromDIP(24)); + + m_book_second_sizer->Add(m_book_second_btn_sizer, 0, wxALIGN_RIGHT, 0); + m_book_second_panel->SetSizer( m_book_second_sizer ); + m_book_second_panel->Layout(); + m_book_second_sizer->Fit( m_book_second_panel ); + + // page 1 + m_book_first_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_book_first_sizer = new wxBoxSizer( wxVERTICAL ); + m_book_first_sizer->SetMinSize(wxSize(FromDIP(720), FromDIP(500))); + + // auto m_loading_sizer = new wxBoxSizer(wxHORIZONTAL); + // std::vector list{"ams_rfid_1", "ams_rfid_2", "ams_rfid_3", "ams_rfid_4"}; + // m_loading_icon = new AnimaIcon(m_book_first_panel, wxID_ANY, list, "refresh_printer", 100); + + m_loading_label = new Label( m_book_first_panel, wxString(_L("Skipping Objects Information Loading ..."))); + m_loading_label->Wrap( -1 ); + m_loading_label->SetBackgroundColour(*wxWHITE); + + // m_loading_sizer->Add(0, 0, 1, wxEXPAND, 0); + // m_loading_sizer->Add(m_loading_icon, 0, wxALL, FromDIP(5)); + // m_loading_sizer->Add(m_loading_label, 0, wxALL, FromDIP(5)); + // m_loading_sizer->Add(0, 0, 1, wxEXPAND, 0); + + m_book_first_sizer->Add(0, 0, 1, wxEXPAND, 0); + m_book_first_sizer->Add( m_loading_label, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 0); + m_book_first_sizer->Add(0, 0, 1, wxEXPAND, 0); + + m_book_first_panel->SetSizer( m_book_first_sizer ); + m_book_first_panel->Layout(); + m_book_first_sizer->Fit( m_book_first_panel ); + + m_simplebook->AddPage( m_book_first_panel, _("loading page"), false ); + m_simplebook->AddPage( m_book_second_panel, _("retry page"), false ); + m_simplebook->AddPage( m_book_third_panel, _("dialog page"), false ); + m_sizer->Add( m_simplebook, 1, wxEXPAND | wxALL, 5 ); + + + SetSizer( m_sizer ); + m_zoom_in_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomIn, this); + m_zoom_out_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomOut, this); + m_switch_drag_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnSwitchDrag, this); + m_canvas->Bind(EVT_ZOOM_PERCENT, &PartSkipDialog::OnZoomPercent, this); + m_canvas->Bind(EVT_CANVAS_PART, &PartSkipDialog::UpdatePartsStateFromCanvas, this); + + m_apply_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnApplyDialog, this); + m_all_checkbox->Bind(wxEVT_TOGGLEBUTTON, &PartSkipDialog::OnAllCheckbox, this); + + Fit(); + Layout(); + CentreOnParent(); +} + +PartSkipDialog::~PartSkipDialog() {} + +void PartSkipDialog::on_dpi_changed(const wxRect& suggested_rect) { Fit(); } + + +std::string PartSkipDialog::create_tmp_path(){ + boost::filesystem::path parent_path(temporary_dir()); + + std::stringstream buf; + buf << "/bamboo_task/"; + buf << m_timestamp; + if (m_obj) { + buf << m_obj->dev_id << "_"; + buf << m_obj->job_id_ << "/"; + } else { + buf << 1 << "_" << 1 << "/"; + } + std::string tmp_path = (parent_path / buf.str() ).string(); + + if (!std::filesystem::exists(tmp_path + "Metadata/") && !fs::create_directories(tmp_path + "Metadata/")) { + wxMessageBox("create file failed."); + } + return tmp_path; +} + + +bool PartSkipDialog::is_local_file_existed(const std::vector &local_paths) +{ + for (auto path : local_paths) { + if (!std::filesystem::exists(path)) { + return false; + } + } + return true; +} + +void PartSkipDialog::DownloadPartsFile() +{ + m_tmp_path = create_tmp_path(); //wxGetApp().app_config->get("download_path"); + + m_local_paths.clear(); + m_target_paths.clear(); + + int plate_idx = m_obj ? m_obj->m_plate_index : 1; + + m_local_paths.push_back(m_tmp_path + "Metadata/pick_" + std::to_string(plate_idx) + ".png"); + m_local_paths.push_back(m_tmp_path + "Metadata/model_settings.config"); + m_local_paths.push_back(m_tmp_path + "Metadata/slice_info.config"); + + m_target_paths.push_back("Metadata/pick_" + std::to_string(plate_idx) + ".png"); + m_target_paths.push_back("Metadata/model_settings.config"); + m_target_paths.push_back("Metadata/slice_info.config"); + + if (!is_local_file_existed(m_local_paths)) { + m_file_sys = boost::make_shared(); + m_file_sys->Attached(); + m_file_sys->Bind(EVT_STATUS_CHANGED, &PartSkipDialog::OnFileSystemEvent, this); + m_file_sys->Bind(EVT_RAMDOWNLOAD, &PartSkipDialog::OnFileSystemResult, this); + m_file_sys->Start(); + } else { + m_file_sys->SendExistedFile(); + } +} +void PartSkipDialog::fetchUrl(boost::weak_ptr wfs) +{ + boost::shared_ptr fs(wfs.lock()); + if (!fs) return; + + DeviceManager *dm = GUI::wxGetApp().getDeviceManager(); + MachineObject *obj = dm->get_selected_machine(); + + if (obj == nullptr) { + fs->SetUrl("0"); + return; + } + std::string dev_ver = obj->get_ota_version(); + std::string dev_id = obj->dev_id; + int remote_proto = obj->get_file_remote(); + + NetworkAgent *agent = wxGetApp().getAgent(); + std::string agent_version = agent ? agent->get_version() : ""; + + if (agent) { + switch (m_url_state) { + case URL_TCP: { + std::string devIP = obj->dev_ip; + std::string accessCode = obj->get_access_code(); + std::string tcp_url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode; + CallAfter([=] { + boost::shared_ptr fs(wfs.lock()); + if (!fs) return; + if (boost::algorithm::starts_with(tcp_url, "bambu:///")) { + fs->SetUrl(tcp_url); + } else { + fs->SetUrl("3"); + } + }); + break; + } + case URL_TUTK: { + std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""}; + agent->get_camera_url(obj->dev_id + "|" + dev_ver + "|" + protocols[1], [this, wfs, m = dev_id, v = agent->get_version(), dv = dev_ver](std::string url) { + if (boost::algorithm::starts_with(url, "bambu:///")) { + url += "&device=" + m; + url += "&net_ver=" + v; + url += "&dev_ver=" + dv; + url += "&refresh_url=" + boost::lexical_cast(&refresh_agora_url); + url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); + url += "&cli_ver=" + std::string(SLIC3R_VERSION); + } + BOOST_LOG_TRIVIAL(info) << "SendToPrinter::fetchUrl: camera_url: " << hide_passwd(url, {"?uid=", "authkey=", "passwd="}); + std::cout << "SendToPrinter::fetchUrl: camera_url: " << hide_passwd(url, {"?uid=", "authkey=", "passwd="}); + CallAfter([=] { + boost::shared_ptr fs(wfs.lock()); + if (!fs) return; + if (boost::algorithm::starts_with(url, "bambu:///")) { + fs->SetUrl(url); + } else { + fs->SetUrl("3"); + } + }); + }); + break; + } + default: break; + } + } +} + +void PartSkipDialog::OnFileSystemEvent(wxCommandEvent &e) +{ + e.Skip(); + auto wfs = boost::weak_ptr(m_file_sys); + boost::shared_ptr fs(wfs.lock()); + if (!fs) return; + + wxString msg; + int status = e.GetInt(); + int extra = e.GetExtraLong(); + + switch (status) { + case PrinterFileSystem::Initializing: + case PrinterFileSystem::Connecting: break; + case PrinterFileSystem::ListSyncing: { + m_file_sys->GetPickImages(m_local_paths, m_target_paths); + break; + } + case PrinterFileSystem::Failed: { + m_file_sys->SendConnectFail(); + break; + } + case PrinterFileSystem::Reconnecting: break; + } + if (e.GetInt() == PrinterFileSystem::Initializing) { + CallAfter([=] { + boost::shared_ptr fs(wfs.lock()); + if (!fs) return; + fetchUrl(boost::weak_ptr(fs)); + }); + } +} +void PartSkipDialog::OnFileSystemResult(wxCommandEvent &event){ + int result = event.GetInt(); + if (result == 0) { + InitDialogUI(); + SetSimplebookPage(2); + }else{ + switch (m_url_state) { + case URL_TCP: { + m_url_state = URL_TUTK; + break; + } + case URL_TUTK: { + m_url_state = URL_TCP; + break; + } + } + SetSimplebookPage(1); + } + // wxMilliSleep(1000); + // m_loading_icon->Stop(); + Refresh(); +} + +void PartSkipDialog::InitSchedule(MachineObject *obj){ + m_obj = obj; + SetSimplebookPage(0); + // m_loading_icon->Play(); + DownloadPartsFile(); +} + +void PartSkipDialog::OnRetryButton(wxCommandEvent &event) { + event.Skip(); + InitSchedule(m_obj); + Refresh(); +} + + +bool PartSkipDialog::is_drag_mode(){ + return m_is_drag == true; +} + +PartsInfo PartSkipDialog::GetPartsInfo(){ + PartsInfo parts_info; + for (auto [part_id, part_state] : this->m_parts_state) + { + parts_info.push_back(std::pair(part_id, part_state)); + } + return parts_info; +} + +void PartSkipDialog::OnZoomIn(wxCommandEvent &event){ + m_canvas->ZoomIn(20); + UpdateZoomPercent(); +} + +void PartSkipDialog::OnZoomOut(wxCommandEvent &event){ + m_canvas->ZoomOut(20); + UpdateZoomPercent(); +} + +void PartSkipDialog::OnSwitchDrag(wxCommandEvent& event){ + if (this->is_drag_mode()) { + m_is_drag = false; + m_switch_drag_btn->SetBackgroundColor(*wxWHITE); + m_switch_drag_btn->SetIcon("canvas_drag"); + } else { + m_is_drag = true; + m_switch_drag_btn->SetBackgroundColor(wxColour(0, 174, 66)); + m_switch_drag_btn->SetIcon("canvas_drag_active"); + } + m_canvas->SwitchDrag(m_is_drag); +} + +void PartSkipDialog::OnZoomPercent(wxCommandEvent &event) { + m_zoom_percent = event.GetInt(); + if (m_zoom_percent >= 1000) { + m_zoom_percent = 1000; + m_zoom_in_btn->Enable(false); + m_zoom_in_btn->SetIcon("canvas_zoom_in_disable"); + } else if (m_zoom_percent <= 100) { + m_zoom_percent = 100; + m_zoom_out_btn->Enable(false); + m_zoom_out_btn->SetIcon("canvas_zoom_out_disable"); + } else { + m_zoom_in_btn->Enable(true); + m_zoom_out_btn->Enable(true); + m_zoom_in_btn->SetIcon("canvas_zoom_in"); + m_zoom_out_btn->SetIcon("canvas_zoom_out"); + } + + UpdateZoomPercent(); +} + +void PartSkipDialog::UpdatePartsStateFromCanvas(wxCommandEvent &event) { + int part_id = event.GetExtraLong(); + PartState part_state = PartState(event.GetInt()); + + m_parts_state[part_id] = part_state; + if(part_state == psUnCheck){ + m_all_checkbox->SetValue(false); + } + if(IsAllChecked()){ + m_all_checkbox->SetValue(true); + } + UpdateDialogUI(); +} + +void PartSkipDialog::UpdateZoomPercent() { + m_percent_label->SetLabel(wxString::Format(_L("%d%%"), m_zoom_percent)); +} + +void PartSkipDialog::UpdateCountLabel() { + int check_cnt = 0; + int tot_cnt = 0; + for (auto [part_id, part_state] : m_parts_state) { + if (part_state == PartState::psChecked) check_cnt++; + if (part_state != PartState::psSkipped) tot_cnt++; + } + m_cnt_label->SetLabel(wxString::Format(_L("%d"), check_cnt)); + m_tot_label->SetLabel(wxString::Format(_L("/%d Selected"), tot_cnt)); +} + +bool PartSkipDialog::Show(bool show) +{ + if (show) { + wxGetApp().UpdateDlgDarkUI(this); + CentreOnParent(); + + Layout(); + Fit(); + } + return DPIDialog::Show(show); +} + +void PartSkipDialog::InitDialogUI() { + m_print_lock = true; + m_scroll_sizer->Clear(true); + m_all_checkbox->SetValue(false); + m_parts_state.clear(); + m_parts_name.clear(); + + string pick_img = m_local_paths[0]; + string model_settings = m_local_paths[1]; + + m_switch_drag_btn->SetIcon("canvas_drag"); + m_switch_drag_btn->SetBackgroundColor(*wxWHITE); + m_is_drag = false; + m_canvas->SwitchDrag(false); + m_canvas->SetZoomPercent(100); + m_canvas->SetOffset(wxPoint(0, 0)); + m_canvas->LoadPickImage(pick_img); + ModelSettingHelper helper(model_settings); + + if (helper.Parse()) { + auto parse_result = helper.GetResults(); + for (const auto& part : parse_result) { + m_parts_state[part.identify_id] = part.state; + m_parts_name[part.identify_id] = part.name; + } + if (m_obj) { + std::vector partskip_ids = m_obj->m_partskip_ids; + for (auto part_id : partskip_ids) { + m_parts_state[part_id] = PartState::psSkipped; + } + } + + for(const auto& [part_id, part_state] : m_parts_state) { + auto line_sizer = new wxBoxSizer(wxHORIZONTAL); + auto checkbox = new CheckBox(m_list_view); + auto label = new Label(m_list_view, _L("file 0")); + + checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, part_id=part_id](wxCommandEvent& event) { + m_parts_state[part_id] = event.IsChecked() ? PartState::psChecked : PartState::psUnCheck; + if(!event.IsChecked()){ + m_all_checkbox->SetValue(false); + }else if(IsAllChecked()){ + m_all_checkbox->SetValue(true); + } + m_canvas->UpdatePartsInfo(GetPartsInfo()); + UpdateCountLabel(); + event.Skip(); + }, checkbox->GetId()); + + if (part_state == PartState::psChecked) { + checkbox->SetValue(true); + checkbox->Enable(true); + } else if(part_state == PartState::psUnCheck) { + checkbox->SetValue(false); + checkbox->Enable(true); + } else if (part_state == PartState::psSkipped) { + checkbox->SetValue(true); + checkbox->Enable(false); + } + label->SetLabel(wxString::FromUTF8(m_parts_name[part_id])); + label->SetBackgroundColour(*wxWHITE); + label->SetForegroundColour(wxColor(107,107,107)); + label->Wrap(-1); + checkbox->SetBackgroundColour(*wxWHITE); + + line_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0); + line_sizer->Add(label, 1, wxALL | wxALIGN_CENTER_VERTICAL, 0); + m_scroll_sizer->Add(line_sizer, 0, wxALL, FromDIP(5)); + } + m_canvas->UpdatePartsInfo(GetPartsInfo()); + } + + m_scroll_sizer->Layout(); + UpdateCountLabel(); + Refresh(); + m_print_lock = false; +} + +void PartSkipDialog::UpdatePartsStateFromPrinter(MachineObject *obj) { + if (m_print_lock) return; + m_obj = obj; + if (m_obj) { + std::vector partskip_ids = m_obj->m_partskip_ids; + for(auto part_id : partskip_ids) { + m_parts_state[part_id] = PartState::psSkipped; + } + m_canvas->UpdatePartsInfo(GetPartsInfo()); + UpdateDialogUI(); + } +} + + +void PartSkipDialog::UpdateDialogUI(){ + if(m_parts_state.size() != m_scroll_sizer->GetItemCount()){ + BOOST_LOG_TRIVIAL(warning) << "m_parts_state and m_scroll_sizer mismatch."; + return; + } + + for (auto it = m_parts_state.begin(); it != m_parts_state.end(); ++it) { + int idx = std::distance(m_parts_state.begin(), it); + auto part_id = it->first; + auto part_state = it->second; + + wxSizerItem *item = m_scroll_sizer->GetItem(idx); + if (item && item->IsSizer()) { + wxSizer* sizer = item->GetSizer(); + auto check_item = sizer->GetItem((size_t)0); + + if (check_item && check_item->IsWindow()) + { + wxWindow *window = check_item->GetWindow(); + CheckBox *checkbox = dynamic_cast(window); + if (part_state == PartState::psChecked) { + checkbox->SetValue(true); + } else if (part_state == PartState::psUnCheck) { + checkbox->SetValue(false); + } else { + checkbox->SetValue(true); + checkbox->Enable(false); + } + } + } + } + + UpdateCountLabel(); + Refresh(); +} + +void PartSkipDialog::SetSimplebookPage(int page) { + m_simplebook->SetSelection(page); +} + +bool PartSkipDialog::IsAllChecked() { + for (auto& [part_id, part_state] : m_parts_state) { + if (part_state == PartState::psUnCheck) + return false; + } + return true; +} + +void PartSkipDialog::OnAllCheckbox(wxCommandEvent &event) +{ + if (m_all_checkbox->GetValue()) { + for (auto& [part_id, part_state] : m_parts_state) { + if (part_state == PartState::psUnCheck) + part_state = PartState::psChecked; + } + } + else{ + for (auto& [part_id, part_state] : m_parts_state) { + if (part_state == PartState::psChecked) + part_state = PartState::psUnCheck; + } + } + m_canvas->UpdatePartsInfo(GetPartsInfo()); + UpdateDialogUI(); + event.Skip(); +} + + +void PartSkipDialog::OnApplyDialog(wxCommandEvent &event) +{ + event.Skip(); + m_partskip_ids.clear(); + for (const auto& [part_id, part_state] : m_parts_state) { + if (part_state == PartState::psChecked) { + m_partskip_ids.push_back(part_id); + } + } + PartSkipConfirmDialog confirm_dialog(this); + confirm_dialog.SetMsgLabel(wxString::Format(_L("Skipping %d objects."), m_partskip_ids.size())); + + if(confirm_dialog.ShowModal() == wxID_OK){ + if (m_obj) { + BOOST_LOG_TRIVIAL(info) << "monitor: skipping "<< m_partskip_ids.size() <<" parts dev_id =" << m_obj->dev_id; + + bool all_skipped = true; + for (auto [part_id, part_state] : m_parts_state) { + if (part_state == PartState::psUnCheck) all_skipped = false; + } + + if (all_skipped) { + m_obj->command_task_abort(); + } else { + m_obj->command_task_partskip(m_partskip_ids); + } + EndModal(wxID_OK); + } else { + BOOST_LOG_TRIVIAL(warning) << "machine object is null."; + } + } +} + + +PartSkipConfirmDialog::PartSkipConfirmDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _L("Skip Objects"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) +{ + 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)); + SetBackgroundColour(*wxWHITE); + SetSizeHints(wxDefaultSize, wxDefaultSize); + + wxBoxSizer* m_sizer; + m_sizer = new wxBoxSizer( wxVERTICAL ); + + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); + m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA)); + m_sizer->Add(m_line_top, 0, wxEXPAND, 0); + m_sizer->Add(0, 0, 0, wxALL, FromDIP(15)); + + m_msg_label = new Label( this, wxString(_L("Skipping objects."))); + m_msg_label->Wrap( -1 ); + m_msg_label->SetBackgroundColour(*wxWHITE); + + m_sizer->Add(m_msg_label, 0, wxLEFT, FromDIP(15)); + + auto m_tip_label = new Label(this, wxString(_("This action cannot be undone. Continue?"))); + m_tip_label->Wrap(-1); + m_tip_label->SetBackgroundColour(*wxWHITE); + m_tip_label->SetForegroundColour(wxColor(92,92,92)); + m_sizer->Add(m_tip_label, 0, wxLEFT | wxTOP, FromDIP(15)); + + wxBoxSizer* m_button_sizer; + m_button_sizer = new wxBoxSizer(wxHORIZONTAL); + m_button_sizer->SetMinSize(wxSize(FromDIP(480), FromDIP(54))); + m_button_sizer->Add(0, 0, 1, wxEXPAND, 0); + + + StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + StateColor btn_bg_green(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), + std::pair(wxColour(0, 177, 66), StateColor::Normal)); + + m_apply_button = new Button(this, wxString(_L("Continue"))); + m_apply_button->SetBackgroundColor(btn_bg_green); + m_apply_button->SetTextColor(*wxWHITE); + // m_apply_button->SetBorderColor(wxColour(38, 46, 48)); + m_apply_button->SetFont(Label::Body_14); + m_apply_button->SetSize(wxSize(FromDIP(80), FromDIP(32))); + m_apply_button->SetMinSize(wxSize(FromDIP(80), FromDIP(32))); + m_apply_button->SetCornerRadius(FromDIP(16)); + m_apply_button->Bind(wxEVT_BUTTON, [this](auto& e){ + EndModal(wxID_OK); + e.Skip(); + }); + + m_button_sizer->Add(m_apply_button, 0, wxALL, FromDIP(15)); + m_sizer->Add(m_button_sizer, 1, wxEXPAND, FromDIP(5)); + m_sizer->Fit( this ); + + SetSizer( m_sizer ); + Layout(); + Fit(); +} + +PartSkipConfirmDialog::~PartSkipConfirmDialog() +{ +} + +bool PartSkipConfirmDialog::Show(bool show) +{ + if (show) { + wxGetApp().UpdateDlgDarkUI(this); + CentreOnParent(); + + Layout(); + Fit(); + } + return DPIDialog::Show(show); +} + +void PartSkipConfirmDialog::on_dpi_changed(const wxRect& suggested_rect) { Fit(); } + + +Button* PartSkipConfirmDialog::GetConfirmButton() +{ + return m_apply_button; +} + +void PartSkipConfirmDialog::SetMsgLabel(wxString msg){ + m_msg_label->SetLabel(msg); +} + + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PartSkipDialog.hpp b/src/slic3r/GUI/PartSkipDialog.hpp new file mode 100644 index 0000000000..cd8fca3be7 --- /dev/null +++ b/src/slic3r/GUI/PartSkipDialog.hpp @@ -0,0 +1,153 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Widgets/Label.hpp" +#include "Widgets/CheckBox.hpp" +#include "Widgets/Button.hpp" +#include "Widgets/AnimaController.hpp" +#include "DeviceManager.hpp" +#include "PartSkipCommon.hpp" +#include "Printer/PrinterFileSystem.h" +#include "I18N.hpp" +#include "GUI_Utils.hpp" + + +namespace Slic3r { namespace GUI { + +class SkipPartCanvas; + +enum URL_STATE { + URL_TCP, + URL_TUTK, +}; + +class PartSkipConfirmDialog : public DPIDialog +{ +private: +protected: + Label *m_msg_label; + Button *m_apply_button; + +public: + PartSkipConfirmDialog(wxWindow *parent); + ~PartSkipConfirmDialog(); + + void on_dpi_changed(const wxRect &suggested_rect); + Button* GetConfirmButton(); + void SetMsgLabel(wxString msg); + bool Show(bool show); +}; + +class PartSkipDialog : public DPIDialog +{ +public: + PartSkipDialog(wxWindow *parent); + ~PartSkipDialog(); + void on_dpi_changed(const wxRect &suggested_rect); + bool Show(bool show); + + void UpdatePartsStateFromPrinter(MachineObject *obj_); + void SetSimplebookPage(int page); + void InitSchedule(MachineObject *obj_); + void InitDialogUI(); + + + MachineObject *m_obj{nullptr}; + + wxSimplebook* m_simplebook; + wxPanel* m_book_third_panel; + wxPanel* m_book_second_panel; + wxPanel* m_book_first_panel; + + SkipPartCanvas* m_canvas; + Button* m_zoom_in_btn; + Button* m_zoom_out_btn; + Button* m_switch_drag_btn; + CheckBox* m_all_checkbox; + Button* m_percent_label; + Label * m_all_label; + wxPanel* m_line; + wxScrolledWindow* m_list_view; + + Label* m_cnt_label; + Label* m_tot_label; + + Button* m_apply_btn; + + Label* m_loading_label; + Label* m_retry_label; + + wxBoxSizer* m_sizer; + wxBoxSizer* m_dlg_sizer; + wxBoxSizer* m_dlg_content_sizer; + wxBoxSizer* m_dlg_btn_sizer; + wxBoxSizer* m_canvas_sizer; + wxBoxSizer* m_canvas_btn_sizer; + wxBoxSizer* m_list_sizer; + wxBoxSizer* m_scroll_sizer; + wxBoxSizer* m_book_first_sizer; + wxBoxSizer* m_book_second_sizer; + wxBoxSizer* m_book_second_btn_sizer; + Button* m_second_retry_btn; + AnimaIcon* m_loading_icon; + +private: + int m_zoom_percent{100}; + bool m_is_drag{false}; + bool m_print_lock{true}; + + std::map m_parts_state; + std::map m_parts_name; + std::vector m_partskip_ids; + + enum URL_STATE m_url_state = URL_STATE::URL_TCP; + + PartsInfo GetPartsInfo(); + bool is_drag_mode(); + + boost::shared_ptr m_file_sys; + bool m_file_sys_result{false}; + std::string m_timestamp; + std::string m_tmp_path; + std::vector m_local_paths; + std::vector m_target_paths; + std::string create_tmp_path(); + + bool is_local_file_existed(const std::vector &local_paths); + + void DownloadPartsFile(); + void OnFileSystemEvent(wxCommandEvent &event); + void OnFileSystemResult(wxCommandEvent &event); + void fetchUrl(boost::weak_ptr wfs); + + + void OnZoomIn(wxCommandEvent &event); + void OnZoomOut(wxCommandEvent &event); + void OnSwitchDrag(wxCommandEvent &event); + void OnZoomPercent(wxCommandEvent &event); + void UpdatePartsStateFromCanvas(wxCommandEvent &event); + + void UpdateZoomPercent(); + void UpdateCountLabel(); + void UpdateDialogUI(); + bool IsAllChecked(); + + void OnRetryButton(wxCommandEvent &event); + void OnAllCheckbox(wxCommandEvent &event); + void OnApplyDialog(wxCommandEvent &event); +}; + +}} \ No newline at end of file diff --git a/src/slic3r/GUI/Printer/PrinterFileSystem.cpp b/src/slic3r/GUI/Printer/PrinterFileSystem.cpp index 28ac337ae7..4385cbfb52 100644 --- a/src/slic3r/GUI/Printer/PrinterFileSystem.cpp +++ b/src/slic3r/GUI/Printer/PrinterFileSystem.cpp @@ -39,6 +39,7 @@ wxDEFINE_EVENT(EVT_FILE_CHANGED, wxCommandEvent); wxDEFINE_EVENT(EVT_SELECT_CHANGED, wxCommandEvent); wxDEFINE_EVENT(EVT_THUMBNAIL, wxCommandEvent); wxDEFINE_EVENT(EVT_DOWNLOAD, wxCommandEvent); +wxDEFINE_EVENT(EVT_RAMDOWNLOAD, wxCommandEvent); wxDEFINE_EVENT(EVT_FILE_CALLBACK, wxCommandEvent); @@ -248,6 +249,131 @@ struct PrinterFileSystem::Download : Progress boost::uuids::detail::md5 boost_md5; }; + +void PrinterFileSystem::GetPickImages(const std::vector &local_paths, const std::vector &targetpaths) +{ + + GetPickImage(1, local_paths[0], targetpaths[0]); + GetPickImage(2, local_paths[1], targetpaths[1]); + GetPickImage(3, local_paths[2], targetpaths[2]); + +} + +void PrinterFileSystem::GetPickImage(int id, const std::string &local_path, const std::string &targetpath) +{ + json j; + + j["sequence_id"] = id; + j["version"] = 1; + j["peer_host"] = "studio"; + j["command"] = "get_project_file"; + j["file_rel_path"] = targetpath; + + std::string param = j.dump(); + + DownloadRamFile(16, local_path, param); +} + + +void PrinterFileSystem::DownloadRamFile(int index, const std::string &local_path, const std::string & param) +{ + std::shared_ptr download(new Download); + download->local_path = local_path; + + json req; + req["path"] = "mem:/" + std::to_string(index); + req["offset"] = 0; + req["mem_dl_param_size"] = param.size(); + + m_download_seq = SendRequest( + FILE_DOWNLOAD, req, + [download](json const &resp, Progress &prog, unsigned char const *data) -> int { + size_t size = resp.value("size", 0); + prog.size = resp["offset"]; + prog.total = resp["total"]; + + if (resp.contains("mem_dl_param_size")) { + size_t s = resp["mem_dl_param_size"].get(); + std::string json_str(reinterpret_cast(data), s); + // OutputDebugStringA(json_str.c_str()); + // OutputDebugStringA("\n"); + json mem_dl_json = json::parse(json_str); + // download->mem_dl_param_size = size; + if (!mem_dl_json.contains("result") || mem_dl_json["result"] == 1 ) { + wxLogWarning("Download failed: result = 1"); + return ERROR_JSON; + } + return CONTINUE; + } + + if (prog.size == 0 ) { + download->ofs.open(download->local_path, std::ios::binary); + if (!download->ofs) { + download->error = last_system_error(); + wxLogWarning("DownloadImageFromRam open error: %s\n", wxString::FromUTF8(download->error)); + return FILE_OPEN_ERR; + } + } + + download->ofs.write(reinterpret_cast(data), size); + if (!download->ofs) { + download->error = last_system_error(); + wxLogWarning("DownloadImageFromRam write error: %s\n", wxString::FromUTF8(download->error)); + return FILE_READ_WRITE_ERR; + } + + download->boost_md5.process_bytes(data, size); + + prog.size += size; + download->total = prog.total; + download->size = prog.size; + + if (prog.size < prog.total) { + return 0; + } + download->ofs.close(); + + std::string md5 = resp["file_md5"]; + boost::uuids::detail::md5::digest_type digest; + download->boost_md5.get_digest(digest); + for (int i = 0; i < 4; ++i) digest[i] = boost::endian::endian_reverse(digest[i]); + std::string str_md5; + const auto char_digest = reinterpret_cast(&digest[0]); + boost::algorithm::hex(char_digest, char_digest + sizeof(digest), std::back_inserter(str_md5)); + if (!boost::iequals(str_md5, md5)) { + wxLogWarning("DownloadImageFromRam checksum error: %s != %s\n", str_md5, md5); + boost::system::error_code ec; + boost::filesystem::rename(download->local_path, download->local_path + ".tmp", ec); + return FILE_CHECK_ERR; + } + return SUCCESS; + }, + + [this, download](int result, Progress const &data) { + //OutputDebugStringA(std::to_string(result).c_str()); + //OutputDebugStringA("\n"); + if (result == CONTINUE) { return; } + std::string msg; + if (result == SUCCESS) { + wxLogMessage("DownloadImageFromRam finished: %s", download->local_path); + msg = "SUCCESS"; + SendChangedEvent(EVT_RAMDOWNLOAD, result, result ? download->error : download->local_path); + } else if (result != CONTINUE) { + wxLogWarning("DownloadImageFromRam failed: %s", download->error); + msg = "ERROR"; + SendChangedEvent(EVT_RAMDOWNLOAD, result, result ? download->error : download->local_path); + } + },param); +} + +void PrinterFileSystem::SendExistedFile(){ + SendChangedEvent(EVT_RAMDOWNLOAD, SUCCESS); +} +void PrinterFileSystem::SendConnectFail(){ + SendChangedEvent(EVT_RAMDOWNLOAD, ERROR_PIPE); +} + + void PrinterFileSystem::DownloadFiles(size_t index, std::string const &path) { if (index == (size_t) -1) { @@ -281,6 +407,10 @@ void PrinterFileSystem::DownloadFiles(size_t index, std::string const &path) DownloadNextFile(); } + + + + void PrinterFileSystem::DownloadCheckFiles(std::string const &path) { for (size_t i = 0; i < m_file_list.size(); ++i) { @@ -706,7 +836,7 @@ void PrinterFileSystem::DownloadNextFile() file.download.reset(), file.flags &= ~FF_DOWNLOAD; else // FAILED file.download.reset(); - if (&file_index.first == &m_file_list) + if (&file_index.first == &m_file_list) SendChangedEvent(EVT_DOWNLOAD, download->index, result ? download->error : file.local_path, result); } } @@ -1030,7 +1160,7 @@ void PrinterFileSystem::DumpLog(void * thiz, int, tchar const *msg) static_cast(thiz)->Bambu_FreeLogMsg(msg); } -boost::uint32_t PrinterFileSystem::SendRequest(int type, json const &req, callback_t2 const &callback) +boost::uint32_t PrinterFileSystem::SendRequest(int type, json const &req, callback_t2 const &callback,const std::string& param) { if (m_session.tunnel == nullptr) { Retry(); @@ -1044,6 +1174,13 @@ boost::uint32_t PrinterFileSystem::SendRequest(int type, json const &req, callba root["req"] = req; std::ostringstream oss; oss << root; + + if (!param.empty()) { + oss << "\n\n"; + oss << param; + } + // OutputDebugStringA(oss.str().c_str()); + // OutputDebugStringA("\n"); auto msg = oss.str(); boost::unique_lock l(m_mutex); m_messages.push_back(msg); diff --git a/src/slic3r/GUI/Printer/PrinterFileSystem.h b/src/slic3r/GUI/Printer/PrinterFileSystem.h index 5a40e063d6..a94a5e3e96 100644 --- a/src/slic3r/GUI/Printer/PrinterFileSystem.h +++ b/src/slic3r/GUI/Printer/PrinterFileSystem.h @@ -22,6 +22,7 @@ wxDECLARE_EVENT(EVT_FILE_CHANGED, wxCommandEvent); wxDECLARE_EVENT(EVT_SELECT_CHANGED, wxCommandEvent); wxDECLARE_EVENT(EVT_THUMBNAIL, wxCommandEvent); wxDECLARE_EVENT(EVT_DOWNLOAD, wxCommandEvent); +wxDECLARE_EVENT(EVT_RAMDOWNLOAD, wxCommandEvent); class PrinterFileSystem : public wxEvtHandler, public boost::enable_shared_from_this, BambuLib { @@ -138,6 +139,17 @@ public: void DownloadFiles(size_t index, std::string const &path); + void GetPickImage(int id, const std::string &local_path, const std::string &path); + + void GetPickImages(const std::vector &local_paths, const std::vector &targetpaths); + + + void DownloadRamFile(int index, const std::string &local_path, const std::string ¶m); + + void SendExistedFile(); + + void SendConnectFail(); + void DownloadCheckFiles(std::string const &path); bool DownloadCheckFile(size_t index); @@ -218,8 +230,7 @@ private: typedef std::function callback_t2; - template - boost::uint32_t SendRequest(int type, json const& req, Translator const& translator, Callback const& callback) + template boost::uint32_t SendRequest(int type, json const &req, Translator const &translator, Callback const &callback, const std::string ¶m = "") { auto c = [translator, callback, this](int result, json const &resp, unsigned char const *data) -> int { @@ -236,7 +247,7 @@ private: PostCallback(callback, result, t); return result; }; - return SendRequest(type, req, c); + return SendRequest(type, req, c, param); } template using Applier = std::function; @@ -266,7 +277,7 @@ private: InstallNotify(type, c); } - boost::uint32_t SendRequest(int type, json const &req, callback_t2 const &callback); + boost::uint32_t SendRequest(int type, json const &req, callback_t2 const &callback, const std::string ¶m = ""); void InstallNotify(int type, callback_t2 const &callback); diff --git a/src/slic3r/GUI/SkipPartCanvas.cpp b/src/slic3r/GUI/SkipPartCanvas.cpp new file mode 100644 index 0000000000..e9218aa714 --- /dev/null +++ b/src/slic3r/GUI/SkipPartCanvas.cpp @@ -0,0 +1,628 @@ +#include +#include "SkipPartCanvas.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +wxDEFINE_EVENT(EVT_ZOOM_PERCENT, wxCommandEvent); +wxDEFINE_EVENT(EVT_CANVAS_PART, wxCommandEvent); + +namespace Slic3r { +namespace GUI { + +SkipPartCanvas::SkipPartCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs) + : wxGLCanvas(parent, dispAttrs) { + context_ = new wxGLContext(this); + this->Bind(wxEVT_PAINT, &SkipPartCanvas::OnPaint, this); + this->Bind(wxEVT_MOUSEWHEEL, &SkipPartCanvas::OnMouseWheel, this); + this->Bind(wxEVT_LEFT_DOWN, &SkipPartCanvas::OnMouseLeftDown, this); + this->Bind(wxEVT_LEFT_DCLICK, &SkipPartCanvas::OnMouseLeftDown, this); + this->Bind(wxEVT_LEFT_UP, &SkipPartCanvas::OnMouseLeftUp, this); + this->Bind(wxEVT_RIGHT_DOWN, &SkipPartCanvas::OnMouseRightDown, this); + this->Bind(wxEVT_RIGHT_UP, &SkipPartCanvas::OnMouseRightUp, this); + this->Bind(wxEVT_SIZE, &SkipPartCanvas::OnSize, this); + this->Bind(wxEVT_MOTION, &SkipPartCanvas::OnMouseMotion, this); +} + +void SkipPartCanvas::LoadPickImage(const std::string & path) +{ + parts_state_.clear(); + int preffered_w{FromDIP(400)}, preffered_h{FromDIP(400)}; + cv::Mat src_image = cv::imread(path, cv::IMREAD_UNCHANGED); + float zoom_x{static_cast(preffered_w) / src_image.cols}; + float zoom_y{static_cast(preffered_h) / src_image.rows}; + float image_scale{0}; + if (abs(zoom_x - 1) > abs(zoom_y - 1)) + image_scale = zoom_x; + else + image_scale = zoom_y; + cv::resize(src_image, pick_image_, cv::Size(), image_scale, image_scale); + std::vector channels; + cv::split(pick_image_, channels); // channels[3] 是 Alpha + cv::Mat alpha = channels[3]; + cv::Mat mask; + cv::threshold(alpha, mask, 0, 255, cv::THRESH_BINARY); + std::vector> pick_counters; + cv::findContours(mask, pick_counters, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_KCOS); + std::vector> polygon; + for (const auto& counter : pick_counters) { + cv::Point center_pos{0,0}; + for (const auto& pt : counter) { + center_pos += pt; + } + center_pos = center_pos / static_cast(counter.size()); + auto id = GetIdAtImagePt(wxPoint(center_pos.x, center_pos.y)); + if (id > 0) { + polygon.clear(); + polygon.emplace_back(std::vector{}); + for (const auto& pt : counter) { + polygon[0].push_back(FloatPoint{pt.x * 1.0f, pt.y * 1.0f}); + } + std::vector indices = mapbox::earcut(polygon); + std::vector final_counter{}; + for (size_t i = 0; i < indices.size(); i += 3) { + FloatPoint a = polygon[0][indices[i]]; + FloatPoint b = polygon[0][indices[i+1]]; + FloatPoint c = polygon[0][indices[i+2]]; + final_counter.push_back(FloatPoint{a[0], a[1]}); + final_counter.push_back(FloatPoint{b[0], b[1]}); + final_counter.push_back(FloatPoint{c[0], c[1]}); + } + parts_triangles_.emplace(id, final_counter); + pick_parts_.emplace(id, counter); + if (parts_state_.find(id) == parts_state_.end()) + parts_state_.emplace(id, psUnCheck); + } + } +} + +void SkipPartCanvas::ZoomIn(const int zoom_percent) +{ + SetZoomPercent(zoom_percent_ + zoom_percent); + Refresh(); +} + +void SkipPartCanvas::ZoomOut(const int zoom_percent) +{ + SetZoomPercent(zoom_percent_ - zoom_percent); + Refresh(); +} + +void SkipPartCanvas::SwitchDrag(const bool drag_on) +{ + fixed_draging_ = drag_on; + AutoSetCursor(); +} + + +void SkipPartCanvas::UpdatePartsInfo(const PartsInfo& parts) +{ + for (auto const& part : parts) { + if (auto res = parts_state_.find(part.first); res != parts_state_.end()) + res->second = part.second; + } + Refresh(); +} + +void DrawRoundedRect(float x, float y, float width, float height, float radius, const ColorRGB& color, int segments = 16) +{ + glColor3f(color.r(), color.g(), color.b()); + + // 1. Draw center rectangle + glBegin(GL_QUADS); + glVertex2f(x + radius, y + radius); + glVertex2f(x + width - radius, y + radius); + glVertex2f(x + width - radius, y + height - radius); + glVertex2f(x + radius, y + height - radius); + glEnd(); + + // 2. Draw side rectangles (excluding corners) + glBegin(GL_QUADS); + // Left + glVertex2f(x, y + radius); + glVertex2f(x + radius, y + radius); + glVertex2f(x + radius, y + height - radius); + glVertex2f(x, y + height - radius); + + // Right + glVertex2f(x + width - radius, y + radius); + glVertex2f(x + width, y + radius); + glVertex2f(x + width, y + height - radius); + glVertex2f(x + width - radius, y + height - radius); + + // Top + glVertex2f(x + radius, y + height - radius); + glVertex2f(x + width - radius, y + height - radius); + glVertex2f(x + width - radius, y + height); + glVertex2f(x + radius, y + height); + + // Bottom + glVertex2f(x + radius, y); + glVertex2f(x + width - radius, y); + glVertex2f(x + width - radius, y + radius); + glVertex2f(x + radius, y + radius); + glEnd(); + + // 3. Draw corners + auto drawCorner = [&](float cx, float cy, float startAngle) { + glBegin(GL_TRIANGLE_FAN); + glVertex2f(cx, cy); + for (int i = 0; i <= segments; ++i) { + float angle = startAngle + (M_PI * 0.5f) * (float)i / segments; + glVertex2f(cx + cosf(angle) * radius, cy + sinf(angle) * radius); + } + glEnd(); + }; + + drawCorner(x + radius, y + radius, M_PI); // bottom-left + drawCorner(x + width - radius, y + radius, 1.5f * M_PI); // bottom-right + drawCorner(x + width - radius, y + height - radius, 0.0f); // top-right + drawCorner(x + radius, y + height - radius, 0.5f * M_PI); // top-left +} + + +void SkipPartCanvas::Render() +{ + constexpr float border_w = 3.f; + constexpr int uncheckd_stencil =1; + constexpr int checkd_stencil = 2; + constexpr int skipped_stencil = 3; + + SetCurrent(*context_); + glPushAttrib(GL_ALL_ATTRIB_BITS); + + int w, h; + GetClientSize(&w, &h); + glViewport(0, 0, w, h); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + auto view_rect = ViewPtToImagePt(wxPoint(w, h)); + glOrtho(offset_.x, view_rect.x, view_rect.y, offset_.y, -1, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glClearColor(parent_color_.r(), parent_color_.g(), parent_color_.b(), 1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + float rx = offset_.x; + float ry = offset_.y; + float rw = view_rect.x - offset_.x; + float rh = view_rect.y - offset_.y; + float radius = std::min(rw, rh) * 0.05f; + + DrawRoundedRect(rx, ry, rw, rh, radius, ColorRGB{0.9f, 0.9f, 0.9f}); + + glEnable(GL_BLEND); + glEnable(GL_MULTISAMPLE); + glEnable(GL_LINE_SMOOTH); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glEnable(GL_STENCIL_TEST); + + auto draw_shape = [this, border_w](const int stencil, const PartState part_type, const ColorRGB& rgb) { + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glStencilFunc(GL_ALWAYS, stencil, 0xFF); + glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); + + for (const auto& contour : parts_triangles_) { + auto part_info = parts_state_.find(contour.first); + if (part_info == parts_state_.end() || part_info->second != part_type) + continue; + glColor3f(1, 1, 1); + glBegin(GL_TRIANGLES); + for (size_t i = 0; i < contour.second.size(); i += 3) { + glVertex2f(contour.second[i][0], contour.second[i][1]); + glVertex2f(contour.second[i+1][0], contour.second[i+1][1]); + glVertex2f(contour.second[i+2][0], contour.second[i+2][1]); + } + glEnd(); + } + + for (const auto& contour : pick_parts_) { + if (contour.first != this->hover_id_) continue; + auto part_info = parts_state_.find(contour.first); + if (part_info == parts_state_.end() || part_info->second != part_type) + continue; + + glColor3f(rgb.r(), rgb.g(), rgb.b()); + glLineWidth(border_w); + glBegin(GL_LINE_LOOP); + for (const auto& pt : contour.second) { + glVertex2f(pt.x, pt.y); + } + glEnd(); + } + }; + // draw unchecked shapes + // stencil1 => unchecked + draw_shape(uncheckd_stencil, psUnCheck, ColorRGB{0, 174 / 255.f, 66 / 255.f}); + + // draw checked shapes + // stencil2 => checked + draw_shape(checkd_stencil, psChecked, ColorRGB{208 / 255.f, 27 / 255.f, 66 / 255.f}); + + // draw skipped shapes + // stencil3 => skipped + draw_shape(skipped_stencil, psSkipped, ColorRGB{95 / 255.f, 95 / 255.f, 95 / 255.f}); + + auto draw_mask = [this, view_rect, border_w, w, h](const int stencil, const PartState part_type, + const ColorRGB& background, const ColorRGB& line, const ColorRGB& bound) { + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glStencilFunc(GL_EQUAL, stencil, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // Don't change stencil + glColor3f(background.r(), background.g(), background.b()); + glBegin(GL_POLYGON); + glVertex2f(offset_.x, offset_.y); + glVertex2f(offset_.x, view_rect.y); + glVertex2f(view_rect.x, view_rect.y); + glVertex2f(view_rect.x, offset_.y); + glEnd(); + // draw main color + glColor3f(line.r(), line.g(), line.b()); + // re-draw shape bound + for (const auto& contour : pick_parts_) { + auto part_info = parts_state_.find(contour.first); + if (part_info == parts_state_.end() || part_info->second != part_type) + continue; + glColor3f(bound.r(), bound.g(), bound.b()); + glLineWidth(border_w); + glBegin(GL_LINE_LOOP); + for (const auto& pt : contour.second) { + glVertex2f(pt.x, pt.y); + } + glEnd(); + } + }; + + draw_mask(checkd_stencil, psChecked, ColorRGB{239 / 255.f, 175 / 255.f, 175 / 255.f}, + ColorRGB{225 / 255.f, 71 / 255.f, 71 / 255.f}, ColorRGB{208 / 255.f, 27 / 255.f, 27 / 255.f}); + + draw_mask(skipped_stencil, psSkipped, ColorRGB{159 / 255.f, 159 / 255.f, 159 / 255.f}, + ColorRGB{95 / 255.f, 95 / 255.f, 95 / 255.f}, ColorRGB{95 / 255.f, 95 / 255.f, 95 / 255.f}); + + glDisable(GL_STENCIL_TEST); + + glPopAttrib(); + glFlush(); +} + +void SkipPartCanvas::DebugLogLine(std::string str) +{ + //if (!log_ctrl) + // return; + //log_ctrl->AppendText(str + "\n"); +} + +void SkipPartCanvas::SendSelectEvent(int id, PartState state) { + wxCommandEvent evt(EVT_CANVAS_PART); + evt.SetExtraLong(id); + evt.SetInt(static_cast(state)); + wxPostEvent(this, evt); +} +void SkipPartCanvas::SendZoomEvent(int zoom_percent) { + wxCommandEvent evt(EVT_ZOOM_PERCENT); + evt.SetInt(zoom_percent_); + wxPostEvent(this, evt); +} + +inline double SkipPartCanvas::Zoom() const +{ + return zoom_percent_ / 100.0f; +} + +inline wxPoint SkipPartCanvas::ViewPtToImagePt(const wxPoint& view_pt) const +{ + return wxPoint(view_pt.x / Zoom(), view_pt.y / Zoom()) + offset_; +} + +uint32_t SkipPartCanvas::GetIdAtImagePt(const wxPoint& image_pt) const +{ + if (image_pt.x >= 0 && image_pt.x < pick_image_.cols + && image_pt.y >= 0 && image_pt.y < pick_image_.rows) { + // at(row, col)=>at(y, x) + cv::Vec4b bgr = pick_image_.at(image_pt.y, image_pt.x); + SkipIdHelper helper{bgr[2], bgr[1], bgr[0]}; + helper.reverse(); + return helper.value; + } else { + return 0; + } +} + +inline uint32_t SkipPartCanvas::GetIdAtViewPt(const wxPoint& view_pt) const +{ + wxPoint pt_at_image = ViewPtToImagePt(view_pt); + return GetIdAtImagePt(pt_at_image); +} + +void SkipPartCanvas::SetZoomPercent(const int value) +{ + zoom_percent_ = std::clamp(value, 100, 1000); + std::ostringstream oss; + oss << "zoom to " << zoom_percent_; + DebugLogLine(oss.str()); + + SendZoomEvent(zoom_percent_); +} + +void SkipPartCanvas::SetOffset(const wxPoint& value) +{ + int w, h; + GetClientSize(&w, &h); + int max_w = static_cast(w * (1 - 1 / Zoom())) >= 0 ? static_cast(w * (1 - 1 / Zoom())) : 0; + int max_h = static_cast(w * (1 - 1 / Zoom())) >= 0 ? static_cast(h * (1 - 1 / Zoom())) : 0; + offset_.x = std::clamp(value.x, 0, max_w); + offset_.y = std::clamp(value.y, 0, max_h); +} + +void SkipPartCanvas::AutoSetCursor() +{ + if(is_draging_ || fixed_draging_) + SetCursor(wxCursor(wxCURSOR_HAND)); + else + SetCursor(wxCursor(wxCURSOR_NONE)); +} + +void SkipPartCanvas::StartDrag(const wxPoint& mouse_pt) +{ + drag_start_pt_ = mouse_pt; + drag_start_offset_ = offset_; + is_draging_ = true; + AutoSetCursor(); +} + +void SkipPartCanvas::ProcessDrag(const wxPoint& mouse_pt) +{ + wxPoint drag_offset = mouse_pt - drag_start_pt_; + SetOffset(- wxPoint(drag_offset.x / Zoom(), drag_offset.y / Zoom()) + drag_start_offset_); + Refresh(); +} + +void SkipPartCanvas::ProcessHover(const wxPoint& mouse_pt) +{ + auto id_at_mouse = GetIdAtViewPt(mouse_pt); + int new_hover_id { -1 }; + auto part_state = parts_state_.find(id_at_mouse); + if (part_state != parts_state_.end() && part_state->second == psUnCheck) { + new_hover_id = id_at_mouse; + }; + if (new_hover_id != this->hover_id_) { + this->hover_id_ = new_hover_id; + Refresh(); + } +} + +void SkipPartCanvas::EndDrag() +{ + is_draging_ = false; + AutoSetCursor(); +} + + void SkipPartCanvas::OnPaint(wxPaintEvent &event) + { + wxPaintDC dc(this); + if (!IsShown()) return; + + SetCurrent(*context_); + + Render(); + SwapBuffers(); + } + +void SkipPartCanvas::OnSize(wxSizeEvent& event) +{ + event.Skip(); +} + +void SkipPartCanvas::OnMouseLeftDown(wxMouseEvent& event) +{ + DebugLogLine("OnMouseLeftDown"); + if (!event.LeftIsDown()) { + event.Skip(); + DebugLogLine("skip----OnMouseLeftDown"); + return; + } + if (fixed_draging_) + StartDrag(wxPoint(event.GetX(), event.GetY())); + left_down_ = true; +} + +void SkipPartCanvas::OnMouseLeftUp(wxMouseEvent& event) +{ + DebugLogLine("OnMouseLeftUp"); + if (event.LeftIsDown() || !left_down_) { + event.Skip(); + DebugLogLine("skip----OnMouseLeftUp"); + return; + } + auto id_at_mouse = GetIdAtViewPt(wxPoint(event.GetX(), event.GetY())); + auto part_state = parts_state_.find(id_at_mouse); + if (part_state != parts_state_.end() && part_state->second != psSkipped) { + if (part_state->second == psUnCheck) + part_state = parts_state_.insert_or_assign(part_state->first, psChecked).first; + else + part_state = parts_state_.insert_or_assign(part_state->first, psUnCheck).first; + // if (select_callback_) + // select_callback_(part_state->first, part_state->second); + SendSelectEvent(part_state->first, part_state->second); + } + left_down_ = false; + if (fixed_draging_) + EndDrag(); + else { + Refresh(); + } +} + +void SkipPartCanvas::OnMouseRightDown(wxMouseEvent& event) +{ + DebugLogLine("OnMouseRightDown"); + if (!event.RightIsDown()) { + event.Skip(); + DebugLogLine("skip----OnMouseRightDown"); + return; + } + StartDrag(wxPoint(event.GetX(), event.GetY())); +} + +void SkipPartCanvas::OnMouseRightUp(wxMouseEvent& event) +{ + DebugLogLine("OnMouseRightUp"); + if (event.RightIsDown() || !is_draging_) { + event.Skip(); + DebugLogLine("skip----OnMouseRightUp"); + return; + } + EndDrag(); +} + +void SkipPartCanvas::OnMouseMotion(wxMouseEvent& event) +{ + ProcessHover(wxPoint(event.GetX(), event.GetY())); + if (!event.RightIsDown() && !(event.LeftIsDown() && fixed_draging_)) { + event.Skip(); + return; + } + ProcessDrag(wxPoint(event.GetX(), event.GetY())); +} + +void SkipPartCanvas::OnMouseWheel(wxMouseEvent& event) +{ + wxPoint view_mouse = wxPoint(event.GetX(), event.GetY()); + auto pre_image_pos = ViewPtToImagePt(view_mouse); + SetZoomPercent(zoom_percent_ + 10 * (event.GetWheelRotation() / 120.0)); + auto now_image_pos = ViewPtToImagePt(view_mouse); + SetOffset(offset_ - (now_image_pos - pre_image_pos)); + Refresh(); +} + +// Base class with error messages management + +void _BBS_3MF_Base::add_error(const std::string &error) const +{ + boost::unique_lock l(mutex); + m_errors.push_back(error); +} +void _BBS_3MF_Base::clear_errors() { m_errors.clear(); } + +void _BBS_3MF_Base::log_errors() +{ + for (const std::string &error : m_errors) BOOST_LOG_TRIVIAL(error) << error; +} + + +ModelSettingHelper::ModelSettingHelper(const std::string &path) : path_(path) {} + +bool ModelSettingHelper::Parse() +{ + boost::nowide::fstream fs(path_); + if (!fs) { + add_error("Failed to open file\n"); + return false; + } + XML_Parser parser = XML_ParserCreate(nullptr); + if (!parser) { + add_error("Unable to create parser"); + return false; + } + XML_SetUserData(parser, this); + XML_SetElementHandler(parser, ModelSettingHelper::StartElementHandler, ModelSettingHelper::EndElementHandler); + + try { + char buffer[4000]; + while (fs.read(buffer, sizeof(buffer)) || fs.gcount() > 0) { + auto ret = XML_Parse(parser, buffer, static_cast(fs.gcount()), fs.eof()); + if (ret != XML_STATUS_OK) { + add_error("return value of XML_Parse doesn't match XM_STATUS_OK"); + XML_ParserFree(parser); + return false; + } + } + } + catch (std::exception& e) { + add_error(std::string("exception:") + e.what()); + XML_ParserFree(parser); + return false; + } + XML_ParserFree(parser); + return true; +} + +std::vector ModelSettingHelper::GetResults() { return context_.objects; } + + +void XMLCALL ModelSettingHelper::StartElementHandler(void *userData, const XML_Char *name, const XML_Char **atts) +{ + ModelSettingHelper* self = static_cast(userData); + if (strcmp(name, "object") == 0) { + for (int i = 0; atts[i]; i += 2) { + if (strcmp(atts[i], "id") == 0) { + self->context_.current_object_id = atoi(atts[i+1]); + } + } + } else if ((self->context_.current_object_id != -1) + && (strcmp(name, "metadata") == 0) + && (self->context_.node_name.top() == "object")) { + const XML_Char *key{nullptr}, *value{nullptr}; + for (int i = 0; atts[i]; i += 2) { + if (strcmp(atts[i], "key") == 0) key = atts[i+1]; + if (strcmp(atts[i], "value") == 0) value = atts[i+1]; + } + if (key && value && (strcmp(key, "name") == 0)) { + self->context_.temp_object.id = self->context_.current_object_id; + self->context_.temp_object.name = std::string(value); + } + } else if ((strcmp(name, "metadata") == 0) + && (self->context_.node_name.top() == "model_instance")) { + const XML_Char *key{nullptr}, *value{nullptr}; + for (int i = 0; atts[i]; i += 2) { + if (strcmp(atts[i], "key") == 0) key = atts[i+1]; + if (strcmp(atts[i], "value") == 0) value = atts[i+1]; + } + if (key && value && (strcmp(key, "object_id") == 0)) { + self->context_.current_instance_object_id = atoi(value); + } else if (key && value && (strcmp(key, "identify_id") == 0)) { + self->context_.current_instance_identify_id = atoi(value); + } + } + self->context_.node_name.push(std::string(name)); +} + +void XMLCALL ModelSettingHelper::EndElementHandler(void *userData, const XML_Char *name) +{ + ModelSettingHelper* self = static_cast(userData); + if (strcmp(name, "object") == 0) { + self->context_.objects.push_back(self->context_.temp_object); + self->context_.current_object_id = -1; + } + else if ((strcmp(name, "model_instance") == 0) + && (self->context_.current_instance_identify_id != -1) + && (self->context_.current_instance_object_id != -1)) { + for (auto& obj : self->context_.objects) { + if (obj.id == self->context_.current_instance_object_id) { + obj.identify_id = self->context_.current_instance_identify_id; + break; + } + } + self->context_.current_instance_identify_id = -1; + self->context_.current_instance_object_id = -1; + } else if (strcmp(name, "object") == 0) { + self->context_.current_object_id = -1; + } + self->context_.node_name.pop(); +} + +void ModelSettingHelper::DataHandler(const XML_Char *s, int len) +{ + // do nothing +} +} +} \ No newline at end of file diff --git a/src/slic3r/GUI/SkipPartCanvas.hpp b/src/slic3r/GUI/SkipPartCanvas.hpp new file mode 100644 index 0000000000..b4a6ad3d2c --- /dev/null +++ b/src/slic3r/GUI/SkipPartCanvas.hpp @@ -0,0 +1,163 @@ +#ifndef SKIPPARTCANVAS_H +#define SKIPPARTCANVAS_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "PartSkipCommon.hpp" + +wxDECLARE_EVENT(EVT_ZOOM_PERCENT, wxCommandEvent); +wxDECLARE_EVENT(EVT_CANVAS_PART, wxCommandEvent); + +namespace Slic3r { +namespace GUI { + + +using Coord = float; +using FloatPoint = std::array; + + +struct ObjectInfo { + int id{-1}; + std::string name{""}; + int identify_id{-1}; + PartState state{psUnCheck}; +}; + +class SkipPartCanvas : public wxGLCanvas +{ + union SkipIdHelper + { + uint32_t value = 0; + + struct + { + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t _padding; + }; + + uint8_t bytes[4]; + + SkipIdHelper() = default; + + SkipIdHelper(uint8_t red, uint8_t green, uint8_t blue) + : r(red), g(green), b(blue), _padding(0) {} + + SkipIdHelper(uint32_t val): value(val){} + void reverse() { + uint8_t tmp{r}; + r = b; + b = tmp; + } + }; +public: + SkipPartCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs); + ~SkipPartCanvas() = default; + + void SetParentBackground(const ColorRGB& color) { + parent_color_ = color; + } + + void LoadPickImage(const std::string& path); + void ZoomIn(const int zoom_percent); + void ZoomOut(const int zoom_percent); + void SwitchDrag(const bool drag_on); + void UpdatePartsInfo(const PartsInfo& parts); + void SetZoomPercent(const int value); + void SetOffset(const wxPoint &value); + + wxTextCtrl* log_ctrl; +protected: + void OnPaint(wxPaintEvent& event); + void OnSize(wxSizeEvent& event); + void OnMouseLeftDown(wxMouseEvent& event); + void OnMouseLeftUp(wxMouseEvent& event); + void OnMouseRightDown(wxMouseEvent& event); + void OnMouseRightUp(wxMouseEvent& event); + void OnMouseMotion(wxMouseEvent& event); + void OnMouseWheel(wxMouseEvent& event); +private: + wxGLContext* context_; + cv::Mat pick_image_; + std::unordered_map> parts_triangles_; + std::unordered_map> pick_parts_; + std::unordered_map parts_state_; + bool gl_inited_{false}; + int zoom_percent_{100}; + wxPoint offset_{0,0}; + wxPoint drag_start_offset_{0,0}; + wxPoint drag_start_pt_{0,0}; + bool is_draging_{false}; + bool fixed_draging_{false}; + bool left_down_{false}; + ColorRGB parent_color_ = ColorRGB(); + int hover_id_{-1}; + + void SendSelectEvent(int id, PartState state); + void SendZoomEvent(int zoom_percent); + + inline double Zoom() const; + inline wxPoint ViewPtToImagePt(const wxPoint& view_pt) const; + uint32_t GetIdAtImagePt(const wxPoint& image_pt) const; + inline uint32_t GetIdAtViewPt(const wxPoint& view_pt) const; + + void ProcessHover(const wxPoint& mouse_pt); + void AutoSetCursor(); + void StartDrag(const wxPoint& mouse_pt); + void ProcessDrag(const wxPoint& mouse_pt); + void EndDrag(); + + void Render(); + + void DebugLogLine(std::string str); +}; + +class _BBS_3MF_Base +{ + mutable boost::mutex mutex; + mutable std::vector m_errors; + +protected: + void add_error(const std::string& error) const; + void clear_errors(); + +public: + void log_errors(); +}; + +class ModelSettingHelper : public _BBS_3MF_Base { + struct ParseContext{ + std::vector objects; + ObjectInfo temp_object; + + std::stack node_name; + int current_object_id{-1}; + int current_instance_identify_id{-1}; + int current_instance_object_id{-1}; + }; + +public: + ModelSettingHelper(const std::string& path); + + + bool Parse(); + std::vector GetResults(); +private: + std::string path_; + ParseContext context_; + + void static XMLCALL StartElementHandler(void* userData, const XML_Char* name, const XML_Char** atts); + void static XMLCALL EndElementHandler(void* userData, const XML_Char* name); + void DataHandler(const XML_Char* s, int len); +}; + +} +} +#endif //SKIPPARTCANVAS_H \ No newline at end of file diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index 88aca02cb1..25138c0168 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -278,6 +278,20 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) bSizer_task_btn->Add(FromDIP(10), 0, 0); + StateColor white_bg(std::pair(wxColour(255, 255, 255), StateColor::Disabled), std::pair(wxColour(255, 255, 255), StateColor::Pressed), + std::pair(wxColour(255, 255, 255), StateColor::Hovered), std::pair(wxColour(255, 255, 255), StateColor::Enabled), + std::pair(wxColour(255, 255, 255), StateColor::Normal)); + m_button_partskip = new Button(m_panel_progress, ""); + m_button_partskip->Enable(false); + m_button_partskip->SetBackgroundColor(white_bg); + m_button_partskip->SetIcon("print_control_partskip_disable"); + m_button_partskip->SetBorderColor(*wxWHITE); + m_button_partskip->SetFont(Label::Body_12); + m_button_partskip->SetCornerRadius(0); + m_button_partskip->SetToolTip(_L("Parts Skip")); + m_button_partskip->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { m_button_partskip->SetIcon("print_control_partskip"); }); + m_button_partskip->Bind(wxEVT_LEAVE_WINDOW, [this](auto &e) { m_button_partskip->SetIcon("print_control_partskip"); }); + m_button_pause_resume = new ScalableButton(m_panel_progress, wxID_ANY, "print_control_pause", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER,true); m_button_pause_resume->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { @@ -314,6 +328,8 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) m_sizer_progressbar->Add(m_gauge_progress, 1, wxALIGN_CENTER_VERTICAL, 0); m_sizer_progressbar->Add(0, 0, 0, wxEXPAND|wxLEFT, FromDIP(18)); + m_sizer_progressbar->Add(m_button_partskip, 0, wxALL, FromDIP(5)); + m_sizer_progressbar->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(18)); m_sizer_progressbar->Add(m_button_pause_resume, 0, wxALL, FromDIP(5)); m_sizer_progressbar->Add(0, 0, 0, wxEXPAND|wxLEFT, FromDIP(18)); m_sizer_progressbar->Add(m_button_abort, 0, wxALL, FromDIP(5)); @@ -638,6 +654,22 @@ void PrintingTaskPanel::reset_printing_value() this->set_plate_index(-1); } +void PrintingTaskPanel::update_machine_object(MachineObject* obj){ + if(obj) m_obj = obj; +} + +void PrintingTaskPanel::enable_partskip_button(bool enable) +{ + if (!enable) { + m_button_partskip->Enable(false); + m_button_partskip->SetLabel(""); + m_button_partskip->SetIcon("print_control_partskip_disable"); + }else if(m_obj && m_obj->is_support_brtc){ + m_button_partskip->Enable(true); + m_button_partskip->SetIcon("print_control_partskip"); + } +} + void PrintingTaskPanel::enable_pause_resume_button(bool enable, std::string type) { if (!enable) { @@ -1731,6 +1763,7 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co m_switch_cham_fan->SetValue(false); /* set default enable state */ + m_project_task_panel->enable_partskip_button(false); m_project_task_panel->enable_pause_resume_button(false, "resume_disable"); m_project_task_panel->enable_abort_button(false); @@ -1751,6 +1784,7 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co // Connect Events m_project_task_panel->get_bitmap_thumbnail()->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::refresh_thumbnail_webrequest), NULL, this); + m_project_task_panel->get_partskip_button()->Connect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_subtask_partskip), NULL, this); m_project_task_panel->get_pause_resume_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this); m_project_task_panel->get_abort_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this); m_project_task_panel->get_market_scoring_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this); @@ -1810,6 +1844,7 @@ StatusPanel::~StatusPanel() { // Disconnect Events m_project_task_panel->get_bitmap_thumbnail()->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::refresh_thumbnail_webrequest), NULL, this); + m_project_task_panel->get_partskip_button()->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_subtask_partskip), NULL, this); m_project_task_panel->get_pause_resume_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this); m_project_task_panel->get_abort_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this); m_project_task_panel->get_market_scoring_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this); @@ -1940,6 +1975,17 @@ void StatusPanel::on_market_retry(wxCommandEvent &event) } } +void StatusPanel::on_subtask_partskip(wxCommandEvent &event) +{ + if (m_partskip_dlg == nullptr) { + m_partskip_dlg = new PartSkipDialog(this->GetParent()); + } + + auto dm = GUI::wxGetApp().getDeviceManager(); + m_partskip_dlg->InitSchedule(dm->get_selected_machine()); + m_partskip_dlg->ShowModal(); +} + void StatusPanel::on_subtask_pause_resume(wxCommandEvent &event) { if (obj) { @@ -3089,9 +3135,12 @@ void StatusPanel::update_subtask(MachineObject *obj) || obj->is_in_calibration()) { reset_printing_values(); } else if (obj->is_in_printing() || obj->print_status == "FINISH") { + update_partskip_subtask(obj); + if (obj->is_in_prepare() || obj->print_status == "SLICING") { m_project_task_panel->market_scoring_hide(); m_project_task_panel->get_request_failed_panel()->Hide(); + m_project_task_panel->enable_partskip_button(false); m_project_task_panel->enable_abort_button(false); m_project_task_panel->enable_pause_resume_button(false, "pause_disable"); wxString prepare_text; @@ -3133,7 +3182,7 @@ void StatusPanel::update_subtask(MachineObject *obj) } else { m_project_task_panel->enable_pause_resume_button(true, "pause"); } - + m_project_task_panel->enable_partskip_button(true); // update printing stage m_project_task_panel->update_left_time(obj->mc_left_time); if (obj->subtask_) { @@ -3150,6 +3199,7 @@ void StatusPanel::update_subtask(MachineObject *obj) if (obj->is_printing_finished()) { obj->update_model_task(); m_project_task_panel->enable_abort_button(false); + m_project_task_panel->enable_partskip_button(false); m_project_task_panel->enable_pause_resume_button(false, "resume_disable"); // is makeworld subtask if (wxGetApp().has_model_mall() && obj->is_makeworld_subtask()) { @@ -3217,6 +3267,26 @@ void StatusPanel::update_subtask(MachineObject *obj) Layout(); } +void StatusPanel::update_partskip_subtask(MachineObject *obj){ + if (!obj) return; + if (!obj->subtask_) return; + + m_project_task_panel->update_machine_object(obj); + + auto partskip_button = m_project_task_panel->get_partskip_button(); + if (partskip_button) { + int part_cnt = obj->m_partskip_ids.size(); + if (part_cnt > 0) + partskip_button->SetLabel(wxString::Format(_L("(%d)"), part_cnt)); + else + partskip_button->SetLabel(""); + } + + if(m_partskip_dlg && m_partskip_dlg->IsShown()) { + m_partskip_dlg->UpdatePartsStateFromPrinter(obj); + } +} + void StatusPanel::update_cloud_subtask(MachineObject *obj) { if (!obj) return; @@ -3281,6 +3351,7 @@ void StatusPanel::update_sdcard_subtask(MachineObject *obj) void StatusPanel::reset_printing_values() { + m_project_task_panel->enable_partskip_button(false); m_project_task_panel->enable_pause_resume_button(false, "pause_disable"); m_project_task_panel->enable_abort_button(false); m_project_task_panel->reset_printing_value(); diff --git a/src/slic3r/GUI/StatusPanel.hpp b/src/slic3r/GUI/StatusPanel.hpp index 287858de9a..6afc194cd8 100644 --- a/src/slic3r/GUI/StatusPanel.hpp +++ b/src/slic3r/GUI/StatusPanel.hpp @@ -32,6 +32,7 @@ #include "Widgets/AMSControl.hpp" #include "Widgets/FanControl.hpp" #include "HMS.hpp" +#include "PartSkipDialog.hpp" class StepIndicator; @@ -160,7 +161,7 @@ public: private: - MachineObject* m_obj; + MachineObject* m_obj{nullptr}; ScalableBitmap m_thumbnail_placeholder; wxBitmap m_thumbnail_bmp_display; ScalableBitmap m_bitmap_use_time; @@ -192,6 +193,7 @@ private: wxStaticBitmap* m_bitmap_static_use_weight; ScalableButton* m_button_pause_resume; ScalableButton* m_button_abort; + Button* m_button_partskip; Button* m_button_market_scoring; Button* m_button_clean; Button * m_button_market_retry; @@ -217,6 +219,7 @@ public: void msw_rescale(); public: + void enable_partskip_button(bool enable); void enable_pause_resume_button(bool enable, std::string type); void enable_abort_button(bool enable); void update_subtask_name(wxString name); @@ -232,10 +235,12 @@ public: void set_plate_index(int plate_idx = -1); void market_scoring_show(); void market_scoring_hide(); + void update_machine_object(MachineObject* obj); public: ScalableButton* get_abort_button() {return m_button_abort;}; ScalableButton* get_pause_resume_button() {return m_button_pause_resume;}; + Button* get_partskip_button() { return m_button_partskip; }; Button* get_market_scoring_button() {return m_button_market_scoring;}; Button * get_market_retry_buttom() { return m_button_market_retry; }; Button* get_clean_button() {return m_button_clean;}; @@ -328,6 +333,7 @@ protected: wxStaticText * m_staticText_progress_left; wxStaticText * m_staticText_layers; Button * m_button_report; + Button * m_button_partskip; ScalableButton *m_button_pause_resume; ScalableButton *m_button_abort; Button * m_button_clean; @@ -402,6 +408,7 @@ protected: PrintingTaskPanel * m_project_task_panel; // Virtual event handlers, override them in your derived class + virtual void on_subtask_partskip(wxCommandEvent &event) { event.Skip(); } virtual void on_subtask_pause_resume(wxCommandEvent &event) { event.Skip(); } virtual void on_subtask_abort(wxCommandEvent &event) { event.Skip(); } virtual void on_lamp_switch(wxCommandEvent &event) { event.Skip(); } @@ -484,6 +491,7 @@ protected: FanControlPopup* m_fan_control_popup{nullptr}; ExtrusionCalibration *m_extrusion_cali_dlg{nullptr}; + PartSkipDialog *m_partskip_dlg{nullptr}; wxString m_request_url; bool m_start_loading_thumbnail = false; @@ -525,6 +533,7 @@ protected: void on_market_scoring(wxCommandEvent &event); void on_market_retry(wxCommandEvent &event); + void on_subtask_partskip(wxCommandEvent &event); void on_subtask_pause_resume(wxCommandEvent &event); void on_subtask_abort(wxCommandEvent &event); void on_print_error_clean(wxCommandEvent &event); @@ -602,6 +611,7 @@ protected: void update_basic_print_data(bool def = false); void update_model_info(); void update_subtask(MachineObject* obj); + void update_partskip_subtask(MachineObject *obj); void update_cloud_subtask(MachineObject *obj); void update_sdcard_subtask(MachineObject *obj); void update_temp_ctrl(MachineObject *obj);