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.
+
+[](https://travis-ci.com/github/mapbox/earcut.hpp)
+[](https://ci.appveyor.com/project/Mapbox/earcut-hpp-8wm4o/branch/master)
+[](https://coveralls.io/github/mapbox/earcut.hpp)
+[](https://scan.coverity.com/projects/14000)
+[](http://isitmaintained.com/project/mapbox/earcut.hpp "Average time to resolve an issue")
+[](http://isitmaintained.com/project/mapbox/earcut.hpp "Percentage of issues still open")
+[](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[]`.
+
+
+
+
+
+## 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