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/deps_src/CMakeLists.txt b/deps_src/CMakeLists.txt index f9e8c8fc3b..5c4e62b99f 100644 --- a/deps_src/CMakeLists.txt +++ b/deps_src/CMakeLists.txt @@ -6,6 +6,7 @@ project(deps_src) # Header-only libraries (INTERFACE) add_subdirectory(agg) add_subdirectory(ankerl) +add_subdirectory(earcut) add_subdirectory(fast_float) add_subdirectory(nanosvg) add_subdirectory(nlohmann) diff --git a/deps_src/earcut/CHANGELOG.md b/deps_src/earcut/CHANGELOG.md new file mode 100644 index 0000000000..41d9e6703b --- /dev/null +++ b/deps_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/deps_src/earcut/CMakeLists.txt b/deps_src/earcut/CMakeLists.txt new file mode 100644 index 0000000000..533241e92a --- /dev/null +++ b/deps_src/earcut/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.13) +project(earcut) + +add_library(earcut INTERFACE) + +target_include_directories(earcut SYSTEM + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_sources(earcut INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/earcut.hpp +) diff --git a/deps_src/earcut/LICENSE b/deps_src/earcut/LICENSE new file mode 100644 index 0000000000..8bafb57730 --- /dev/null +++ b/deps_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/deps_src/earcut/README.md b/deps_src/earcut/README.md new file mode 100644 index 0000000000..67f235b7ff --- /dev/null +++ b/deps_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/deps_src/earcut/earcut.hpp b/deps_src/earcut/earcut.hpp new file mode 100644 index 0000000000..fd3381484c --- /dev/null +++ b/deps_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/doc/print_settings/support/support_settings_support.md b/doc/print_settings/support/support_settings_support.md index c9df62c6b1..e17fd793a6 100644 --- a/doc/print_settings/support/support_settings_support.md +++ b/doc/print_settings/support/support_settings_support.md @@ -20,7 +20,7 @@ Support structures are used in 3D printing to provide stability to overhangs and - [Initial layer density](#initial-layer-density) - [Initial layer expansion](#initial-layer-expansion) - [On build plate only](#on-build-plate-only) -- [Remove small overhangs](#remove-small-overhangs) +- [Ignore small overhangs](#ignore-small-overhangs) ## Type @@ -95,6 +95,6 @@ Expand the first raft or support layer to improve bed plate adhesion. Don't create support on model surface, only on build plate. -## Remove small overhangs +## Ignore small overhangs -Remove small overhangs that possibly need no supports. +With this setting small overhangs that possibly need no supports will be ignored from support generation. diff --git a/localization/i18n/OrcaSlicer.pot b/localization/i18n/OrcaSlicer.pot index 876a024b51..66db02866c 100644 --- a/localization/i18n/OrcaSlicer.pot +++ b/localization/i18n/OrcaSlicer.pot @@ -12802,10 +12802,10 @@ msgid "" "etc." msgstr "" -msgid "Remove small overhangs" +msgid "Ignore small overhangs" msgstr "" -msgid "Remove small overhangs that possibly need no supports." +msgid "Ignore small overhangs that possibly don't require support." msgstr "" msgid "Top Z distance" diff --git a/localization/i18n/list.txt b/localization/i18n/list.txt index 0236e89a41..52046c7c88 100644 --- a/localization/i18n/list.txt +++ b/localization/i18n/list.txt @@ -234,6 +234,10 @@ src/slic3r/Utils/Flashforge.cpp src/slic3r/GUI/Jobs/OAuthJob.cpp src/slic3r/GUI/BackgroundSlicingProcess.cpp src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp +src/slic3r/GUI/PartSkipDialog.cpp +src/slic3r/GUI/PartSkipDialog.hpp +src/slic3r/GUI/SkipPartCanvas.cpp +src/slic3r/GUI/SkipPartCanvas.hpp src/slic3r/GUI/FilamentBitmapUtils.cpp src/slic3r/GUI/FilamentBitmapUtils.hpp src/slic3r/GUI/FilamentPickerDialog.cpp 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/partskip_retry.svg b/resources/images/partskip_retry.svg new file mode 100644 index 0000000000..ff104e9951 --- /dev/null +++ b/resources/images/partskip_retry.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + 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/resources/images/print_control_partskip_hover.svg b/resources/images/print_control_partskip_hover.svg new file mode 100644 index 0000000000..b41ffcc516 --- /dev/null +++ b/resources/images/print_control_partskip_hover.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index bf9266b4dc..bcd970d824 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -5415,9 +5415,9 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(false)); def = this->add("support_remove_small_overhang", coBool); - def->label = L("Remove small overhangs"); + def->label = L("Ignore small overhangs"); def->category = L("Support"); - def->tooltip = L("Remove small overhangs that possibly need no supports."); + def->tooltip = L("Ignore small overhangs that possibly don't require support."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index f398c88ed2..71e7991a55 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -405,6 +405,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 b4680d3229..fb4c9085ae 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -1336,6 +1336,17 @@ int MachineObject::command_go_home() return this->is_in_printing() ? this->publish_gcode("G28 X\n") : this->publish_gcode("G28 \n"); } +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, 1); +} + int MachineObject::command_task_abort() { BOOST_LOG_TRIVIAL(trace) << "command_task_abort: "; @@ -2313,6 +2324,7 @@ void MachineObject::reset() } } subtask_ = nullptr; + m_partskip_ids.clear(); } void MachineObject::set_print_state(std::string status) @@ -2567,6 +2579,29 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ 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 + if (j_pre["print"]["plate_idx"].is_number()) + { + m_plate_index = j_pre["print"]["plate_idx"].get(); + } + else if (j_pre["print"]["plate_idx"].is_string()) + { + try + { + m_plate_index = std::stoi(j_pre["print"]["plate_idx"].get()); + } + catch (...) { BOOST_LOG_TRIVIAL(error) << "parse_json: failed to convert plate_idx to int"; } } } } @@ -4407,6 +4442,7 @@ void MachineObject::update_slice_info(std::string project_id, std::string profil if (plate_idx >= 0) { plate_index = plate_idx; + this->m_plate_index = plate_idx; } else { std::string subtask_json; @@ -4469,8 +4505,7 @@ void MachineObject::update_slice_info(std::string project_id, std::string profil BOOST_LOG_TRIVIAL(error) << "task_info: get subtask id failed!"; } } - - this->m_plate_index = plate_index; + // this->m_plate_index = plate_index; }); } } @@ -4880,6 +4915,7 @@ void MachineObject::parse_new_info(json print) is_support_airprinting_detection = get_flag_bits(fun, 45); m_fan->SetSupportCoolingFilter(get_flag_bits(fun, 46)); is_support_ext_change_assist = get_flag_bits(fun, 48); + is_support_partskip = get_flag_bits(fun, 49); } /*aux*/ diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index 14489f7065..1752dd047c 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -554,6 +554,9 @@ public: time_t xcam_prompt_sound_hold_start = 0; time_t xcam_filament_tangle_detect_hold_start = 0; + // part skip + std::vector m_partskip_ids; + /*target from Studio-SwitchBoard, default to INVALID_NOZZLE_ID if no switching control from PC*/ int targ_nozzle_id_from_pc = INVALID_EXTRUDER_ID; @@ -593,6 +596,7 @@ public: bool m_support_mqtt_homing { false };// fun[32] bool is_support_brtc{false}; // fun[31], support tcp and upload protocol bool is_support_ext_change_assist{false}; + bool is_support_partskip{false}; // refine printer function options bool is_support_spaghetti_detection{false}; @@ -689,6 +693,7 @@ public: 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..0956bf9fe1 --- /dev/null +++ b/src/slic3r/GUI/PartSkipDialog.cpp @@ -0,0 +1,1049 @@ +#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 + +#include "Widgets/CheckBox.hpp" +#include "Widgets/Label.hpp" + +#include "MsgDialog.hpp" +#include "Printer/PrinterFileSystem.h" +#include "PartSkipDialog.hpp" +#include "SkipPartCanvas.hpp" +#include "MediaPlayCtrl.h" + +#include "DeviceCore/DevManager.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)); + +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)); + +static 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)); + +static StateColor zoom_bd(std::pair(wxColour(144, 144, 144), StateColor::Disabled), std::pair(wxColour(38, 46, 48), StateColor::Enabled)); +static StateColor zoom_text(std::pair(wxColour(144, 144, 144), StateColor::Disabled), std::pair(wxColour(38, 46, 48), StateColor::Enabled)); + +static 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)); + +static StateColor btn_bg_gray(std::pair(wxColour(194, 194, 194), StateColor::Pressed), + std::pair(wxColour(194, 194, 194), StateColor::Hovered), + std::pair(wxColour(194, 194, 194), StateColor::Normal)); + +static StateColor btn_bg_green(std::pair(wxColour(0, 137, 123), StateColor::Pressed), + std::pair(wxColour(38, 166, 154), StateColor::Hovered), + std::pair(wxColour(0, 150, 136), StateColor::Normal)); + +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); + + m_sizer = new wxBoxSizer(wxVERTICAL); + + m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); + m_line_top->SetMinSize(wxSize(-1, 1)); + m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA)); + m_sizer->Add(m_line_top, 0, wxEXPAND | wxTOP, FromDIP(0)); + + m_simplebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); + m_simplebook->SetMinSize(wxSize(FromDIP(720), FromDIP(535))); + m_simplebook->SetMaxSize(wxSize(FromDIP(720), FromDIP(535))); + 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->SetMinSize(wxSize(FromDIP(400), FromDIP(400))); + m_canvas->SetMaxSize(wxSize(FromDIP(400), FromDIP(400))); + + m_canvas_btn_sizer = new wxBoxSizer(wxHORIZONTAL); + m_canvas_btn_sizer->SetMinSize(wxSize(-1, FromDIP(28))); + + zoom_bg.setTakeFocusedAsHovered(false); + + m_zoom_out_btn = new Button(m_book_third_panel, wxEmptyString); + 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(56), FromDIP(28))); + + 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(56), FromDIP(28))); + m_percent_label->SetCornerRadius(0); + + m_zoom_in_btn = new Button(m_book_third_panel, wxEmptyString); + 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(56), FromDIP(28))); + m_zoom_in_btn->SetCornerRadius(0); + + m_switch_drag_btn = new Button(m_book_third_panel, wxEmptyString); + 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(56), FromDIP(28))); + m_switch_drag_btn->SetCornerRadius(0); + + m_canvas_btn_sizer->Add(m_zoom_out_btn, 0, wxEXPAND | wxLEFT, FromDIP(105)); + m_canvas_btn_sizer->Add(m_percent_label, 0, wxEXPAND, 0); + m_canvas_btn_sizer->Add(m_zoom_in_btn, 0, wxEXPAND, 0); + m_canvas_btn_sizer->Add(m_switch_drag_btn, 0, wxEXPAND | wxRIGHT, FromDIP(88)); + + 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->SetMinSize(wxSize(FromDIP(18), FromDIP(18))); + 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->SetMinSize(wxSize(-1, FromDIP(18))); + 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, wxALIGN_CENTER_VERTICAL, 0); + all_checkbox_sizer->Add(m_all_label, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(8)); + + m_line = new wxPanel(m_book_third_panel, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(267), FromDIP(1)), wxTAB_TRAVERSAL); + m_line->SetMinSize(wxSize(FromDIP(267), 1)); + m_line->SetMaxSize(wxSize(FromDIP(267), 1)); + m_line->SetBackgroundColour(wxColor(238, 238, 238)); + + 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(378))); + m_list_view->SetMaxSize(wxSize(FromDIP(267), FromDIP(378))); + m_list_view->SetBackgroundColour(*wxWHITE); + + m_scroll_sizer = new wxBoxSizer(wxVERTICAL); + m_list_view->SetSizer(m_scroll_sizer); + m_list_view->Layout(); + + m_dlg_btn_sizer = new wxBoxSizer(wxHORIZONTAL); + m_dlg_btn_sizer->SetMinSize(wxSize(-1, FromDIP(54))); + + m_cnt_label = new Label(m_book_third_panel, wxEmptyString); + m_cnt_label->Wrap(-1); + m_cnt_label->SetBackgroundColour(*wxWHITE); + m_cnt_label->SetForegroundColour(wxColour(0, 150, 136)); + m_cnt_label->SetFont(Label::Head_16); + m_cnt_label->SetSize(wxSize(-1, FromDIP(20))); + m_cnt_label->SetMaxSize(wxSize(-1, FromDIP(20))); + + m_tot_label = new Label(m_book_third_panel, wxEmptyString); + m_tot_label->Wrap(-1); + m_tot_label->SetBackgroundColour(*wxWHITE); + m_tot_label->SetMinSize(wxSize(FromDIP(200), FromDIP(20))); + + m_apply_btn = new Button(m_book_third_panel, _L("Skip")); + m_apply_btn->SetBackgroundColor(btn_bg_gray); + m_apply_btn->SetTextColor(wxColour("#FFFFFE")); + // m_apply_btn->SetBorderColor(wxColour(38, 46, 48)); + m_apply_btn->SetFont(Label::Body_14); + m_apply_btn->SetSize(wxSize(FromDIP(80), FromDIP(32))); + m_apply_btn->SetMinSize(wxSize(FromDIP(80), FromDIP(32))); + m_apply_btn->SetCornerRadius(FromDIP(16)); + m_apply_btn->SetToolTip(wxEmptyString); + + m_canvas_sizer->Add(m_canvas, 0, wxLEFT | wxTOP | wxEXPAND, FromDIP(17)); + m_canvas_sizer->Add(m_canvas_btn_sizer, 0, wxTOP, FromDIP(8)); + m_list_sizer->Add(0, 0, 1, wxEXPAND, 0); + + m_list_sizer->Add(0, 0, 0, wxTOP, FromDIP(27)); + m_list_sizer->Add(all_checkbox_sizer, 0, wxLEFT, FromDIP(0)); + m_list_sizer->Add(m_line, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(12)); + m_list_sizer->Add(m_list_view, 0, wxEXPAND, 0); + m_list_sizer->Add(0, 0, 1, wxEXPAND, 0); + + m_dlg_content_sizer->Add(m_canvas_sizer, 0, wxEXPAND, FromDIP(0)); + m_dlg_content_sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(22)); + m_dlg_content_sizer->Add(m_list_sizer, 0, wxRIGHT | wxEXPAND, FromDIP(14)); + + m_dlg_btn_sizer->Add(0, 0, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(21)); + m_dlg_btn_sizer->Add(m_cnt_label, 0, wxALIGN_CENTER_VERTICAL, FromDIP(0)); +#ifdef __WXMSW__ + m_dlg_btn_sizer->Add(m_tot_label, 0, wxLEFT | wxTOP | wxALIGN_CENTER_VERTICAL, FromDIP(2)); +#else + m_dlg_btn_sizer->Add(m_tot_label, 0, wxALIGN_CENTER_VERTICAL, 0); +#endif + m_dlg_btn_sizer->Add(0, 0, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, FromDIP(0)); + m_dlg_btn_sizer->Add(m_apply_btn, 0, wxALIGN_CENTER_VERTICAL, FromDIP(0)); + m_dlg_btn_sizer->Add(0, 0, 0, wxLEFT | wxEXPAND | wxALIGN_CENTER_VERTICAL, FromDIP(24)); + + m_dlg_placeholder = new wxPanel(m_book_third_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + m_dlg_placeholder->SetMinSize(wxSize(-1, FromDIP(15))); + m_dlg_placeholder->SetMaxSize(wxSize(-1, FromDIP(15))); + m_dlg_placeholder->SetBackgroundColour(*wxWHITE); + + m_dlg_sizer->Add(m_dlg_content_sizer, 1, wxEXPAND, FromDIP(0)); + // m_dlg_sizer->Add( 0, 0, 1, wxEXPAND, FromDIP(0)); + m_dlg_sizer->Add(m_dlg_btn_sizer, 0, wxEXPAND, FromDIP(0)); + m_dlg_sizer->Add(m_dlg_placeholder, 0, 0, wxEXPAND, 0); + + 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_bitmap = new wxStaticBitmap(m_book_second_panel, -1, create_scaled_bitmap("partskip_retry", m_book_second_panel, 200), wxDefaultPosition, wxDefaultSize); + m_retry_label = new Label(m_book_second_panel, _L("Load skipping objects information failed. Please 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_bitmap, 0, wxALIGN_CENTER_HORIZONTAL, 0); + m_book_second_sizer->Add(m_retry_label, 0, 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_icon->SetMinSize(wxSize(FromDIP(25), FromDIP(25))); + + m_loading_label = new Label(m_book_first_panel, _L("Loading ...")); + m_loading_label->Wrap(-1); + m_loading_label->SetBackgroundColour(*wxWHITE); + + m_loading_sizer->Add(m_loading_icon, 0, wxALIGN_CENTER_VERTICAL, FromDIP(0)); + m_loading_sizer->Add(m_loading_label, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(5)); + + m_book_first_sizer->Add(0, 0, 1, wxEXPAND, 0); + m_book_first_sizer->Add(m_loading_sizer, 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); + + Layout(); + Fit(); + CentreOnParent(); +} + +PartSkipDialog::~PartSkipDialog() {} + +void PartSkipDialog::on_dpi_changed(const wxRect &suggested_rect) +{ + m_canvas->LoadPickImage(m_local_paths[0]); + + m_loading_icon->SetMinSize(wxSize(FromDIP(25), FromDIP(25))); + m_retry_bitmap->SetBitmap(create_scaled_bitmap("partskip_retry", m_book_second_panel, 200)); + + m_percent_label->SetMinSize(wxSize(FromDIP(56), FromDIP(28))); + m_percent_label->SetMaxSize(wxSize(FromDIP(56), FromDIP(28))); + m_zoom_out_btn->SetMinSize(wxSize(FromDIP(56), FromDIP(28))); + m_zoom_out_btn->SetMaxSize(wxSize(FromDIP(56), FromDIP(28))); + m_zoom_in_btn->SetMinSize(wxSize(FromDIP(56), FromDIP(28))); + m_zoom_in_btn->SetMaxSize(wxSize(FromDIP(56), FromDIP(28))); + m_switch_drag_btn->SetMinSize(wxSize(FromDIP(56), FromDIP(28))); + m_switch_drag_btn->SetMaxSize(wxSize(FromDIP(56), FromDIP(28))); + m_percent_label->Rescale(); + m_zoom_out_btn->Rescale(); + m_zoom_in_btn->Rescale(); + m_switch_drag_btn->Rescale(); + + m_cnt_label->SetMaxSize(wxSize(-1, FromDIP(20))); + + m_tot_label->SetMinSize(wxSize(FromDIP(200), FromDIP(20))); + m_tot_label->SetLabel(m_tot_label->GetLabel()); + m_tot_label->Refresh(); + + m_line_top->SetMinSize(wxSize(-1, 1)); + m_line->SetMinSize(wxSize(FromDIP(267), 1)); + m_line->SetMaxSize(wxSize(FromDIP(267), 1)); + + m_apply_btn->SetMinSize(wxSize(FromDIP(80), FromDIP(32))); + m_apply_btn->SetCornerRadius(FromDIP(16)); + m_apply_btn->Rescale(); + + m_dlg_placeholder->SetMinSize(wxSize(-1, FromDIP(15))); + m_dlg_placeholder->SetMaxSize(wxSize(-1, FromDIP(15))); + + // m_second_retry_btn->SetSize(wxSize(-1, FromDIP(32))); + m_second_retry_btn->SetMinSize(wxSize(FromDIP(80), FromDIP(32))); + m_second_retry_btn->SetCornerRadius(FromDIP(16)); + m_second_retry_btn->Rescale(); + + m_all_checkbox->SetMinSize(wxSize(FromDIP(18), FromDIP(18))); + m_all_checkbox->Rescale(); + + for (auto it = m_scroll_sizer->GetChildren().begin(); it != m_scroll_sizer->GetChildren().end(); ++it) { + wxSizerItem *item = *it; + 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); + checkbox->SetMinSize(wxSize(FromDIP(18), FromDIP(18))); + checkbox->Rescale(); + } + } + } + m_canvas_btn_sizer->SetMinSize(wxSize(-1, FromDIP(28))); + m_canvas_btn_sizer->Layout(); + m_canvas_sizer->Layout(); + m_dlg_content_sizer->Layout(); + m_dlg_sizer->Layout(); + + Fit(); + Layout(); +} + +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) { + const auto& dev_id = m_obj->get_dev_id(); + buf << dev_id.substr(0, 3) << "_"; + buf << dev_id.substr(dev_id.length() - 3, 3) << "_"; + 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() +{ + BOOST_LOG_TRIVIAL(info) << "part skip: create temp path begin."; + m_tmp_path = create_tmp_path(); // wxGetApp().app_config->get("download_path"); + BOOST_LOG_TRIVIAL(info) << "part skip: create temp path end."; + + m_local_paths.clear(); + m_target_paths.clear(); + + m_plate_idx = m_obj ? m_obj->m_plate_index : -1; + if (m_plate_idx < 0) { + m_plate_idx = 1; + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "part skip: printer plate index is invalid."; + } + + m_local_paths.push_back(m_tmp_path + "Metadata/pick_" + std::to_string(m_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(m_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)) { + if (!m_file_sys) { + 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(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "part skip: print file system start."; + } else { + m_file_sys->Retry(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "part skip: print file system retry."; + } + } else { + m_file_sys->SendExistedFile(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "part skip: local parts info file is existed."; + } +} +// actor +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->get_dev_id(); + // int remote_proto = obj->get_file_remote(); + + NetworkAgent *agent = wxGetApp().getAgent(); + std::string agent_version = agent ? agent->get_version() : ""; + + auto url_state = m_url_state; + if (obj->is_lan_mode_printer()) { url_state = URL_TCP; } + + if (agent) { + switch (url_state) { + case URL_TCP: { + std::string devIP = obj->get_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->get_dev_id() + "|" + dev_ver + "|" + protocols[3], [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); + } + 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; + } + } +} +// controller +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->Stop(); + if (m_url_state == URL_TCP) { + m_url_state = URL_TUTK; + m_file_sys->SetUrl("3"); + m_file_sys->Retry(); + BOOST_LOG_TRIVIAL(info) << "part skip: print file system connnect failed first."; + } else { + m_file_sys->SendConnectFail(); + BOOST_LOG_TRIVIAL(info) << "part skip: print file system connnect failed second."; + } + 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)); + BOOST_LOG_TRIVIAL(info) << "part skip: fetch url, get parts info files from printer."; + }); + } +} + +// reseter: [TCP -> TUTK(TCP)] -> [TCP -> TUTK(TCP)] +void PartSkipDialog::OnFileSystemResult(wxCommandEvent &event) +{ + int result = event.GetInt(); + m_loading_icon->Stop(); + if (result == 0) { + InitDialogUI(); + SetSimplebookPage(2); + m_file_sys->Stop(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "part skip: on file system result success."; + } else { + m_url_state = URL_TCP; + SetSimplebookPage(1); + m_file_sys->Stop(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "part skip: on file system result failed."; + } +} + +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, 150, 136)); + 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); } + + UpdateApplyButtonStatus(); + 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_cnt_label->Fit(); + m_tot_label->SetLabel(wxString::Format(_L("/%d Selected"), tot_cnt)); + m_tot_label->Fit(); + m_dlg_btn_sizer->Layout(); +} + +bool PartSkipDialog::Show(bool show) +{ + if (show) { + wxGetApp().UpdateDlgDarkUI(this); + CentreOnParent(); + + Layout(); + Fit(); + } + return DPIDialog::Show(show); +} + +void PartSkipDialog::InitDialogUI() +{ + m_print_lock = true; + is_model_support_partskip = false; + BOOST_LOG_TRIVIAL(info) << "part skip: lock parts info from printer."; + 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 slice_info = m_local_paths[2]; + + 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)); + + BOOST_LOG_TRIVIAL(info) << "part skip: load canvas pick image begin."; + m_canvas->LoadPickImage(pick_img); + BOOST_LOG_TRIVIAL(info) << "part skip: load canvas pick image end."; + ModelSettingHelper helper(slice_info); + + if (helper.Parse()) { + is_model_support_partskip = helper.GetLabelObjectEnabled(m_plate_idx); + auto parse_result = helper.GetPlateObjects(m_plate_idx); + 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, wxEmptyString); + + 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(); + UpdateApplyButtonStatus(); + 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(StateColor::darkModeColorFor(*wxWHITE)); + label->SetForegroundColour(StateColor::darkModeColorFor(wxColor(107, 107, 107))); + label->Wrap(-1); + label->SetMinSize(wxSize(-1, FromDIP(18))); + checkbox->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE)); + checkbox->SetMinSize(wxSize(FromDIP(18), FromDIP(18))); + checkbox->SetMaxSize(wxSize(FromDIP(18), FromDIP(18))); + + line_sizer->Add(checkbox, 0, wxALIGN_CENTER_VERTICAL, 0); + line_sizer->Add(label, 1, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(8)); + m_scroll_sizer->Add(line_sizer, 0, wxBOTTOM | wxEXPAND, FromDIP(12)); + } + m_canvas->UpdatePartsInfo(GetPartsInfo()); + BOOST_LOG_TRIVIAL(info) << "part skip: update canvas parts info."; + } + + m_scroll_sizer->Layout(); + m_list_view->FitInside(); + UpdateApplyButtonStatus(); + UpdateCountLabel(); + Refresh(); + m_print_lock = false; + BOOST_LOG_TRIVIAL(info) << "part skip: unlock parts info from printer."; +} + +void PartSkipDialog::UpdatePartsStateFromPrinter(MachineObject *obj) +{ + if (m_print_lock) { + BOOST_LOG_TRIVIAL(info) << "part skip: parts info from printer is locked."; + return; + } + m_obj = obj; + if (m_obj) { + bool update_flag = false; + std::vector partskip_ids = m_obj->m_partskip_ids; + for (auto part_id : partskip_ids) { + if (m_parts_state[part_id] != PartState::psSkipped) { + m_parts_state[part_id] = PartState::psSkipped; + update_flag = true; + } + } + if (update_flag) { + m_canvas->UpdatePartsInfo(GetPartsInfo()); + UpdateDialogUI(); + } + } +} + +void PartSkipDialog::UpdateDialogUI() +{ + if (m_parts_state.size() != m_scroll_sizer->GetItemCount()) { + BOOST_LOG_TRIVIAL(warning) << "part skip: 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_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; +} + +bool PartSkipDialog::IsAllCancled() +{ + for (auto &[part_id, part_state] : m_parts_state) { + if (part_state == PartState::psChecked) 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; + } + } + UpdateApplyButtonStatus(); + m_canvas->UpdatePartsInfo(GetPartsInfo()); + UpdateDialogUI(); + event.Skip(); +} + +void PartSkipDialog::UpdateApplyButtonStatus() +{ + if (IsAllCancled()) { + m_apply_btn->SetBackgroundColor(btn_bg_gray); + m_apply_btn->SetToolTip(_L("Nothing selected")); + m_enable_apply_btn = false; + } else if (m_parts_state.size() > 64) { + m_apply_btn->SetBackgroundColor(btn_bg_gray); + m_apply_btn->SetToolTip(_L("Over 64 objects in single plate")); + m_enable_apply_btn = false; + } else if (!is_model_support_partskip) { + m_apply_btn->SetBackgroundColor(btn_bg_gray); + m_apply_btn->SetToolTip(_L("The current print job cannot be skipped")); + m_enable_apply_btn = false; + } else { + m_apply_btn->SetBackgroundColor(btn_bg_green); + m_apply_btn->SetToolTip(wxEmptyString); + m_enable_apply_btn = true; + } +} + +void PartSkipDialog::OnApplyDialog(wxCommandEvent &event) +{ + event.Skip(); + + if (!m_enable_apply_btn) return; + + 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); } + } + + bool all_skipped = true; + for (auto [part_id, part_state] : m_parts_state) { + if (part_state == PartState::psUnCheck) all_skipped = false; + } + + PartSkipConfirmDialog confirm_dialog(this); + if (all_skipped) { + confirm_dialog.SetMsgLabel(_L("Skipping all objects.")); + confirm_dialog.SetTipLabel(_L("The printing job will be stopped. Continue?")); + } else { + confirm_dialog.SetMsgLabel(wxString::Format(_L("Skipping %d objects."), m_partskip_ids.size())); + confirm_dialog.SetTipLabel(_L("This action cannot be undone. Continue?")); + } + + if (confirm_dialog.ShowModal() == wxID_OK) { + if (m_obj) { + BOOST_LOG_TRIVIAL(info) << "part skip: skipping " << m_partskip_ids.size() << " objects."; + + if (all_skipped) { + m_obj->command_task_abort(); + BOOST_LOG_TRIVIAL(info) << "part skip: command skip all parts, abort task."; + } else if (m_partskip_ids.size() > 0) { + m_obj->command_task_partskip(m_partskip_ids); + BOOST_LOG_TRIVIAL(info) << "part skip: command skip " << m_partskip_ids.size() << " parts."; + } + EndModal(wxID_OK); + } else { + BOOST_LOG_TRIVIAL(warning) << "part skip: machine object is null."; + } + } +} + +int PartSkipDialog::GetAllSkippedPartsNum() +{ + int skipped_cnt = 0; + for (auto &[part_id, part_state] : m_parts_state) { + if (part_state == PartState::psSkipped || part_state == PartState::psChecked) skipped_cnt++; + } + return skipped_cnt; +} + +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); + SetMinSize(wxSize(FromDIP(480), FromDIP(215))); + 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->SetMinSize(wxSize(FromDIP(480), 1)); + m_line_top->SetMaxSize(wxSize(FromDIP(480), 1)); + 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, _L("Skipping objects.")); + m_msg_label->Wrap(-1); + m_msg_label->SetBackgroundColour(*wxWHITE); + + m_tip_label = new Label(this, _L("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_msg_label, 0, wxLEFT, FromDIP(29)); + m_sizer->Add(0, 0, 0, wxTOP, FromDIP(9)); + m_sizer->Add(m_tip_label, 0, wxLEFT, FromDIP(29)); + + 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, _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, wxRIGHT | wxBOTTOM, FromDIP(24)); + 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) +{ + m_apply_button->SetMinSize(wxSize(FromDIP(80), FromDIP(32))); + m_apply_button->SetCornerRadius(FromDIP(16)); + m_apply_button->Rescale(); + Layout(); + Fit(); +} + +Button *PartSkipConfirmDialog::GetConfirmButton() { return m_apply_button; } + +void PartSkipConfirmDialog::SetMsgLabel(wxString msg) { m_msg_label->SetLabel(msg); } + +void PartSkipConfirmDialog::SetTipLabel(wxString msg) { m_tip_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..4f0b5bf07f --- /dev/null +++ b/src/slic3r/GUI/PartSkipDialog.hpp @@ -0,0 +1,163 @@ +#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; + Label *m_tip_label; + Button *m_apply_button; + +public: + PartSkipConfirmDialog(wxWindow *parent); + ~PartSkipConfirmDialog(); + + void on_dpi_changed(const wxRect &suggested_rect); + Button *GetConfirmButton(); + void SetMsgLabel(wxString msg); + void SetTipLabel(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(); + int GetAllSkippedPartsNum(); + + 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; + wxPanel *m_line_top; + wxScrolledWindow *m_list_view; + + wxPanel *m_dlg_placeholder; + Label *m_cnt_label; + Label *m_tot_label; + + Button *m_apply_btn; + + Label *m_loading_label; + Label *m_retry_label; + ScalableBitmap *m_retry_icon; + wxStaticBitmap *m_retry_bitmap; + + 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_plate_idx{-1}; + int m_zoom_percent{100}; + bool m_is_drag{false}; + bool m_print_lock{true}; + bool m_enable_apply_btn{false}; + bool is_model_support_partskip{false}; + + 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(); + void UpdateApplyButtonStatus(); + bool IsAllChecked(); + bool IsAllCancled(); + + void OnRetryButton(wxCommandEvent &event); + void OnAllCheckbox(wxCommandEvent &event); + void OnApplyDialog(wxCommandEvent &event); +}; + +}} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/Printer/PrinterFileSystem.cpp b/src/slic3r/GUI/Printer/PrinterFileSystem.cpp index e831ef81ce..8ecbe87391 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_MEDIA_ABILITY_CHANGED, wxCommandEvent); wxDEFINE_EVENT(EVT_UPLOADING, wxCommandEvent); wxDEFINE_EVENT(EVT_UPLOAD_CHANGED, wxCommandEvent); @@ -271,6 +272,149 @@ struct PrinterFileSystem::Upload : Progress boost::filesystem::ifstream ifs; }; + +void PrinterFileSystem::GetPickImages(const std::vector &local_paths, const std::vector &targetpaths) +{ + m_download_states.clear(); + + 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; + } + if(mem_dl_json.contains("size") && mem_dl_json["size"] == 0 ) + return FILE_SIZE_ERR; + + 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) { + if (std::filesystem::exists(download->local_path)) { + m_download_states.emplace_back(true); + BOOST_LOG_TRIVIAL(info) <<"DownloadImageFromRam finished: " << download->local_path << "result = " << result; + }else{ + m_download_states.emplace_back(false); + BOOST_LOG_TRIVIAL(warning) <<"DownloadImageFromRam finished, but file not exist: " << download->local_path << "result = " << result; + } + } else if (result != CONTINUE) { + m_download_states.emplace_back(false); + BOOST_LOG_TRIVIAL(warning) << "DownloadImageFromRam failed: " << download->error << "result = " << result; + } + + if(m_download_states.size() == 3){ + if(m_download_states[0] && m_download_states[1] && m_download_states[2]){ + SendChangedEvent(EVT_RAMDOWNLOAD, SUCCESS); + }else{ + // FILE_NO_EXIST is not really error_code + SendChangedEvent(EVT_RAMDOWNLOAD, FILE_NO_EXIST); + } + }else{ + BOOST_LOG_TRIVIAL(warning) << "m_download_states current size is : " << m_download_states.size(); + } + },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) { @@ -304,6 +448,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) { @@ -1280,7 +1428,7 @@ void PrinterFileSystem::CancelUploadTask(bool send_cancel_req) } } -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(); @@ -1294,6 +1442,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 f8147b501f..03a630e72f 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); wxDECLARE_EVENT(EVT_MEDIA_ABILITY_CHANGED, wxCommandEvent); wxDECLARE_EVENT(EVT_UPLOADING, wxCommandEvent); wxDECLARE_EVENT(EVT_UPLOAD_CHANGED, wxCommandEvent); @@ -183,6 +184,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); @@ -275,7 +287,7 @@ private: typedef std::function callback_t3; - 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 { @@ -292,7 +304,7 @@ private: PostCallback(callback, result, t); return result; }; - return SendRequest(type, req, c); + return SendRequest(type, req, c, param); } template using Applier = std::function; @@ -322,7 +334,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); @@ -365,6 +377,8 @@ private: size_t m_lock_end = 0; int m_task_flags = 0; + std::vector m_download_states; + private: struct Session { diff --git a/src/slic3r/GUI/SkipPartCanvas.cpp b/src/slic3r/GUI/SkipPartCanvas.cpp new file mode 100644 index 0000000000..98bc3bdbe4 --- /dev/null +++ b/src/slic3r/GUI/SkipPartCanvas.cpp @@ -0,0 +1,697 @@ +#include +#include "SkipPartCanvas.hpp" + +#include +#include +#include +#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) +{ + if(!std::filesystem::exists(path)) return; + + auto ParseShapeId = [](cv::Mat image, const std::vector> &contours, const std::vector &hierarchy, int root_idx) -> uint32_t { + cv::Mat mask = cv::Mat::zeros(image.size(), CV_8UC1); + + cv::drawContours(mask, contours, root_idx, 255, cv::FILLED); + + int child = hierarchy[root_idx][2]; + while (child != -1) { + cv::drawContours(mask, contours, child, 0, cv::FILLED); + child = hierarchy[child][0]; + } + std::vector pixels; + for (int y = 0; y < image.rows; ++y) { + for (int x = 0; x < image.cols; ++x) { + if (mask.at(y, x)) { pixels.push_back(image.at(y, x)); } + } + } + + std::map> colorCount( + [](const cv::Vec3b &a, const cv::Vec3b &b) { return std::lexicographical_compare(a.val, a.val + 3, b.val, b.val + 3); }); + + for (auto &c : pixels) colorCount[c]++; + + cv::Vec3b main_color; + int max_count = 0; + int total_count = 0; + for (const auto &kv : colorCount) { + if (kv.second > max_count) { + max_count = kv.second; + main_color = kv.first; + } + total_count += kv.second; + } + + SkipIdHelper helper{main_color[2], main_color[1], main_color[0]}; + helper.reverse(); + return (max_count * 2 > total_count) ? helper.value : 0; + }; + + parts_state_.clear(); + parts_triangles_.clear(); + pick_parts_.clear(); + int preffered_w{FromDIP(400)}, preffered_h{FromDIP(400)}; + cv::Mat src_image = cv::imread(path, cv::IMREAD_UNCHANGED); + cv::cvtColor(src_image, src_image, cv::COLOR_BGRA2BGR); // remove alpha + 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; + image_view_scale_ = 1 / image_scale; + pick_image_ = src_image; + std::vector channels; + cv::Mat gray; // convert to gray + cv::cvtColor(pick_image_, gray, cv::COLOR_BGR2GRAY); + cv::Mat mask; // convery to binary + cv::threshold(gray, mask, 0, 255, cv::THRESH_BINARY); + std::vector> pick_counters; + std::vector hierarchy; + cv::findContours(mask, pick_counters, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_TC89_KCOS); + auto compute_depth = [&](int idx) { + int depth = 0; + while (hierarchy[idx][3] != -1) { + depth++; + idx = hierarchy[idx][3]; + } + return depth; + }; + for (int i = 0; i < pick_counters.size(); ++i) { + int depth = compute_depth(i); + int parent = hierarchy[i][3]; + if (parent != -1) continue; + + auto id = ParseShapeId(pick_image_, pick_counters, hierarchy, i); + if (id > 0) { + std::vector flat_points; + std::vector> polygon; + + // part body + { + polygon.emplace_back(); + for (const auto &pt : pick_counters[i]) { + FloatPoint fp{pt.x * 1.0f, pt.y * 1.0f}; + polygon.back().push_back(fp); + flat_points.push_back(fp); + } + int child = hierarchy[i][2]; + while (child != -1) { + polygon.emplace_back(); + for (const auto &pt : pick_counters[child]) { + FloatPoint fp{pt.x * 1.0f, pt.y * 1.0f}; + polygon.back().push_back(fp); + flat_points.push_back(fp); + } + child = hierarchy[child][0]; + } + std::vector indices = mapbox::earcut(polygon); + std::vector final_counter; + for (size_t j = 0; j < indices.size(); j += 3) { + final_counter.push_back(flat_points[indices[j]]); + final_counter.push_back(flat_points[indices[j + 1]]); + final_counter.push_back(flat_points[indices[j + 2]]); + } + + parts_triangles_[id].emplace_back(final_counter); + } + // part outlines + { + pick_parts_[id].emplace_back(pick_counters[i]); + int child = hierarchy[i][2]; + while (child != -1) { + pick_parts_[id].emplace_back(pick_counters[child]); + child = hierarchy[child][0]; + } + } + 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); +#if defined(__APPLE__) + double scale = GetDPIScaleFactor(); + glViewport(0, 0, w * scale, h * scale); +#else + glViewport(0, 0, w, h); +#endif + 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); + for (const auto &contour_item : contour.second) { + glBegin(GL_TRIANGLES); + for (size_t i = 0; i < contour_item.size(); i += 3) { + glVertex2f(contour_item[i][0], contour_item[i][1]); + glVertex2f(contour_item[i + 1][0], contour_item[i + 1][1]); + glVertex2f(contour_item[i + 2][0], contour_item[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); + for (const auto &contour_item : contour.second) { + glBegin(GL_LINE_LOOP); + for (const auto &pt : contour_item) { 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); + for (const auto &contour_item : contour.second) { + glBegin(GL_LINE_LOOP); + for (const auto &pt : contour_item) { 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 * image_view_scale_ / Zoom(), view_pt.y * image_view_scale_ / 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::Vec3b 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_) * image_view_scale_; + 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; +} + +void XMLCALL ModelSettingHelper::StartElementHandler(void *userData, const XML_Char *name, const XML_Char **atts) +{ + ModelSettingHelper *self = static_cast(userData); + if (strcmp(name, "plate") == 0) { + self->context_.current_plate = PlateInfo(); // start a new plate + self->context_.in_plate = true; + } else if (strcmp(name, "metadata") == 0 && self->context_.in_plate) { + std::string key, value; + 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 == "index") { self->context_.current_plate.index = std::stoi(value); } + if (key == "label_object_enabled") { self->context_.current_plate.label_object_enabled = value == "true"; } + } else if (strcmp(name, "object") == 0 && self->context_.in_plate) { + ObjectInfo obj; + for (int i = 0; atts[i]; i += 2) { + if (strcmp(atts[i], "identify_id") == 0) obj.identify_id = atoi(atts[i + 1]); + if (strcmp(atts[i], "name") == 0) obj.name = atts[i + 1]; + } + self->context_.current_plate.objects.push_back(obj); + } +} + +void XMLCALL ModelSettingHelper::EndElementHandler(void *userData, const XML_Char *name) +{ + ModelSettingHelper *self = static_cast(userData); + if (strcmp(name, "plate") == 0 && self->context_.in_plate) { + self->context_.plates.push_back(self->context_.current_plate); + self->context_.current_plate = PlateInfo(); // reset + self->context_.in_plate = false; + } +} + +std::vector ModelSettingHelper::GetPlateObjects(int plate_idx) { + for (const auto &plate : context_.plates) { + if (plate.index == plate_idx) { + return plate.objects; + } + } + return std::vector(); +} + +bool ModelSettingHelper::GetLabelObjectEnabled(int plate_idx) +{ + for (const auto &plate : context_.plates) { + if (plate.index == plate_idx) { return plate.label_object_enabled; } + } + return false; +} + +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..a9c8f1fec7 --- /dev/null +++ b/src/slic3r/GUI/SkipPartCanvas.hpp @@ -0,0 +1,169 @@ +#ifndef SKIPPARTCANVAS_H +#define SKIPPARTCANVAS_H +#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 { + 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 < uint32_t, std::vector>> parts_triangles_; + std::unordered_map < uint32_t, std::vector>> 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}; + double image_view_scale_{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(); +}; + +struct PlateInfo +{ + int index{-1}; + std::vector objects; + bool label_object_enabled = false; +}; + +class ModelSettingHelper : public _BBS_3MF_Base +{ + struct ParseContext + { + std::vector plates; + PlateInfo current_plate; + ObjectInfo temp_object; + bool in_plate = false; + }; + +public: + ModelSettingHelper(const std::string &path); + + bool Parse(); + std::vector GetPlateObjects(int plate_idx); + bool GetLabelObjectEnabled(int plate_idx); + +private: + std::string path_; + ParseContext context_; + + static void XMLCALL StartElementHandler(void *userData, const XML_Char *name, const XML_Char **atts); + static void 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 2236d8305f..688882f969 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -584,12 +584,6 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) bSizer_task_name->Add(task_name_panel, 0, wxEXPAND, FromDIP(5)); - - /* wxFlexGridSizer *fgSizer_task = new wxFlexGridSizer(2, 2, 0, 0); - fgSizer_task->AddGrowableCol(0); - fgSizer_task->SetFlexibleDirection(wxVERTICAL); - fgSizer_task->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);*/ - m_printing_stage_value = new wxStaticText(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT | wxST_ELLIPSIZE_END); m_printing_stage_value->Wrap(-1); m_printing_stage_value->SetMaxSize(wxSize(FromDIP(800),-1)); @@ -612,24 +606,34 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) m_staticText_profile_value->SetForegroundColour(0x6B6B6B); + auto progress_lr_panel = new wxPanel(parent, wxID_ANY); + progress_lr_panel->SetBackgroundColour(*wxWHITE); - auto m_panel_progress = new wxPanel(parent, wxID_ANY); - m_panel_progress->SetBackgroundColour(*wxWHITE); - auto m_sizer_progressbar = new wxBoxSizer(wxHORIZONTAL); - m_gauge_progress = new ProgressBar(m_panel_progress, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize); + m_gauge_progress = new ProgressBar(progress_lr_panel, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize); m_gauge_progress->SetValue(0); m_gauge_progress->SetHeight(PROGRESSBAR_HEIGHT); - m_gauge_progress->SetMaxSize(wxSize(FromDIP(600), -1)); - m_panel_progress->SetSizer(m_sizer_progressbar); - m_panel_progress->Layout(); - m_panel_progress->SetSize(wxSize(-1, FromDIP(24))); - m_panel_progress->SetMaxSize(wxSize(-1, FromDIP(24))); wxBoxSizer *bSizer_task_btn = new wxBoxSizer(wxHORIZONTAL); bSizer_task_btn->Add(FromDIP(10), 0, 0); - m_button_pause_resume = new ScalableButton(m_panel_progress, wxID_ANY, "print_control_pause", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER,true); + 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(progress_lr_panel, wxEmptyString, "print_control_partskip_disable", 0, 20, wxID_ANY); + m_button_partskip->Enable(false); + m_button_partskip->Hide(); + 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_hover"); }); + m_button_partskip->Bind(wxEVT_LEAVE_WINDOW, [this](auto &e) { m_button_partskip->SetIcon("print_control_partskip"); }); + + m_button_pause_resume = new ScalableButton(progress_lr_panel, wxID_ANY, "print_control_pause", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER,true); m_button_pause_resume->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { if (m_button_pause_resume->GetToolTipText() == _L("Pause")) { @@ -652,7 +656,7 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) } }); - m_button_abort = new ScalableButton(m_panel_progress, wxID_ANY, "print_control_stop", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true); + m_button_abort = new ScalableButton(progress_lr_panel, wxID_ANY, "print_control_stop", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true); m_button_abort->SetToolTip(_L("Stop")); m_button_abort->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { @@ -663,19 +667,14 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) m_button_abort->SetBitmap_("print_control_stop"); } ); - 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_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)); - wxBoxSizer *bSizer_buttons = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer *bSizer_text = new wxBoxSizer(wxHORIZONTAL); - wxPanel* penel_bottons = new wxPanel(parent); - wxPanel* penel_text = new wxPanel(penel_bottons); + wxBoxSizer *bSizer_finish_time = new wxBoxSizer(wxHORIZONTAL); + wxPanel* penel_text = new wxPanel(progress_lr_panel); + wxPanel* penel_finish_time = new wxPanel(progress_lr_panel); penel_text->SetBackgroundColour(*wxWHITE); - penel_bottons->SetBackgroundColour(*wxWHITE); + penel_finish_time->SetBackgroundColour(*wxWHITE); wxBoxSizer *sizer_percent = new wxBoxSizer(wxVERTICAL); sizer_percent->Add(0, 0, 1, wxEXPAND, 0); @@ -708,55 +707,65 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) m_staticText_progress_left->SetFont(wxFont(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("HarmonyOS Sans SC"))); m_staticText_progress_left->SetForegroundColour(wxColour(146, 146, 146)); - // Orca: display the end time of the print - m_staticText_progress_end = new wxStaticText(penel_text, wxID_ANY, L("N/A"), wxDefaultPosition, wxDefaultSize, 0); - m_staticText_progress_end->Wrap(-1); - m_staticText_progress_end->SetFont( - wxFont(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("HarmonyOS Sans SC"))); - m_staticText_progress_end->SetForegroundColour(wxColour(146, 146, 146)); - - //fgSizer_task->Add(bSizer_buttons, 0, wxEXPAND, 0); - //fgSizer_task->Add(0, 0, 0, wxEXPAND, FromDIP(5)); - - wxPanel* panel_button_block = new wxPanel(penel_bottons, wxID_ANY); - panel_button_block->SetMinSize(wxSize(TASK_BUTTON_SIZE.x * 2 + FromDIP(5) * 4, -1)); - panel_button_block->SetMinSize(wxSize(TASK_BUTTON_SIZE.x * 2 + FromDIP(5) * 4, -1)); - panel_button_block->SetSize(wxSize(TASK_BUTTON_SIZE.x * 2 + FromDIP(5) * 2, -1)); - panel_button_block->SetBackgroundColour(*wxWHITE); - m_staticText_layers = new wxStaticText(penel_text, wxID_ANY, _L("Layer: N/A")); m_staticText_layers->SetFont(wxFont(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("HarmonyOS Sans SC"))); m_staticText_layers->SetForegroundColour(wxColour(146, 146, 146)); m_staticText_layers->Hide(); - //bSizer_text->Add(m_staticText_progress_percent, 0, wxALL, 0); bSizer_text->Add(sizer_percent, 0, wxEXPAND, 0); bSizer_text->Add(sizer_percent_icon, 0, wxEXPAND, 0); bSizer_text->Add(0, 0, 1, wxEXPAND, 0); - bSizer_text->Add(m_staticText_layers, 0, wxALIGN_CENTER | wxALL, 0); + bSizer_text->Add(m_staticText_layers, 0, wxALIGN_CENTER_VERTICAL | wxALL, 0); bSizer_text->Add(0, 0, 0, wxLEFT, FromDIP(20)); - bSizer_text->Add(m_staticText_progress_left, 0, wxALIGN_CENTER | wxALL, 0); - // Orca: display the end time of the print - bSizer_text->Add(0, 0, 0, wxLEFT, FromDIP(8)); - bSizer_text->Add(m_staticText_progress_end, 0, wxALIGN_CENTER | wxALL, 0); + bSizer_text->Add(m_staticText_progress_left, 0, wxALIGN_CENTER_VERTICAL | wxALL, 0); - penel_text->SetMaxSize(wxSize(FromDIP(600), -1)); + // penel_text->SetMaxSize(wxSize(FromDIP(600), -1)); penel_text->SetSizer(bSizer_text); penel_text->Layout(); - bSizer_buttons->Add(penel_text, 1, wxEXPAND | wxALL, 0); - bSizer_buttons->Add(panel_button_block, 0, wxALIGN_CENTER | wxALL, 0); + // Orca: display the end time of the print + m_staticText_progress_end = new wxStaticText(penel_finish_time, wxID_ANY, L("N/A"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText_progress_end->Wrap(-1); + m_staticText_progress_end->SetFont( + wxFont(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("HarmonyOS Sans SC"))); + m_staticText_progress_end->SetForegroundColour(wxColour(146, 146, 146)); + bSizer_finish_time->Add(0, 0, 1, wxEXPAND, 0); + bSizer_finish_time->Add(m_staticText_progress_end, 0, wxLEFT | wxEXPAND, 0); + // penel_finish_time->SetMaxSize(wxSize(FromDIP(600), -1)); + penel_finish_time->SetSizer(bSizer_finish_time); + penel_finish_time->Layout(); - penel_bottons->SetSizer(bSizer_buttons); - penel_bottons->Layout(); + auto progress_lr_sizer = new wxBoxSizer(wxHORIZONTAL); + auto progress_left_sizer = new wxBoxSizer(wxVERTICAL); + auto progress_right_sizer = new wxBoxSizer(wxHORIZONTAL); + + progress_left_sizer->Add(penel_text, 0, wxEXPAND | wxALL, 0); + progress_left_sizer->Add(m_gauge_progress, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(10)); + progress_left_sizer->Add(penel_finish_time, 0, wxEXPAND |wxALL, 0); + // progress_left_sizer->SetMaxSize(wxSize(FromDIP(600), -1)); + + progress_right_sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(18)); + progress_right_sizer->Add(m_button_partskip, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(0));//5 + progress_right_sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(18)); + progress_right_sizer->Add(m_button_pause_resume, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(0)); + progress_right_sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(18)); + progress_right_sizer->Add(m_button_abort, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(0)); + progress_right_sizer->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(18)); + + progress_lr_sizer->Add(progress_left_sizer, 1, wxEXPAND | wxALL, 0); + progress_lr_sizer->Add(progress_right_sizer, 0, wxEXPAND | wxALL , 0); + + progress_lr_panel->SetSizer(progress_lr_sizer); + progress_lr_panel->SetMaxSize(wxSize(FromDIP(720), -1)); + + progress_lr_panel->Layout(); + progress_lr_panel->Fit(); bSizer_subtask_info->Add(0, 0, 0, wxEXPAND | wxTOP, FromDIP(14)); bSizer_subtask_info->Add(bSizer_task_name, 0, wxEXPAND|wxRIGHT, FromDIP(18)); bSizer_subtask_info->Add(m_staticText_profile_value, 0, wxEXPAND | wxTOP, FromDIP(5)); bSizer_subtask_info->Add(m_printing_stage_value, 0, wxEXPAND | wxTOP, FromDIP(5)); - bSizer_subtask_info->Add(penel_bottons, 0, wxEXPAND | wxTOP, FromDIP(10)); - bSizer_subtask_info->Add(m_panel_progress, 0, wxEXPAND|wxRIGHT, FromDIP(25)); - + bSizer_subtask_info->Add(progress_lr_panel, 0, wxEXPAND | wxTOP, FromDIP(5)); m_printing_sizer = new wxBoxSizer(wxHORIZONTAL); m_printing_sizer->SetMinSize(wxSize(PAGE_MIN_WIDTH, -1)); @@ -764,7 +773,6 @@ void PrintingTaskPanel::create_panel(wxWindow* parent) m_printing_sizer->Add(FromDIP(8), 0, 0, wxEXPAND, 0); m_printing_sizer->Add(bSizer_subtask_info, 1, wxALL | wxEXPAND, 0); - m_staticline = new wxPanel( parent, wxID_ANY); m_staticline->SetBackgroundColour(wxColour(238,238,238)); m_staticline->Layout(); @@ -994,6 +1002,24 @@ void PrintingTaskPanel::reset_printing_value() this->set_plate_index(-1); } +void PrintingTaskPanel::enable_partskip_button(MachineObject* obj, bool enable) +{ + int stage = 0; + bool in_calibration_mode = false; + if( obj && (obj->print_type == "system" || CalibUtils::get_calib_mode_by_name(obj->subtask_name, stage) != CalibMode::Calib_None)){ + in_calibration_mode = true; + } + + if (!enable || in_calibration_mode) { + m_button_partskip->Enable(false); + m_button_partskip->SetLabel(""); + m_button_partskip->SetIcon("print_control_partskip_disable"); + }else if(obj && 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) { @@ -2248,6 +2274,7 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co //m_switch_fan->SetValue(false); /* set default enable state */ + m_project_task_panel->enable_partskip_button(nullptr, false); m_project_task_panel->enable_pause_resume_button(false, "resume_disable"); m_project_task_panel->enable_abort_button(false); @@ -2278,6 +2305,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); @@ -2338,6 +2366,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); @@ -2472,6 +2501,35 @@ void StatusPanel::on_market_retry(wxCommandEvent &event) } } +void StatusPanel::update_partskip_button(MachineObject *obj) { + if (!obj) return; + + auto partskip_button = m_project_task_panel->get_partskip_button(); + if( obj->is_support_partskip ){ + partskip_button->Show(); + }else{ + partskip_button->Hide(); + } + BOOST_LOG_TRIVIAL(info) << "part skip: is_support_partskip: "<< obj->is_support_partskip; +} + +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()); + BOOST_LOG_TRIVIAL(info) << "part skip: initial part skip dialog."; + if(m_partskip_dlg->ShowModal() == wxID_OK){ + int cnt = m_partskip_dlg->GetAllSkippedPartsNum(); + m_project_task_panel->set_part_skipped_count(cnt); + m_project_task_panel->set_part_skipped_dirty(5); + BOOST_LOG_TRIVIAL(info) << "part skip: prepare to filter printer dirty data."; + } +} + void StatusPanel::on_subtask_pause_resume(wxCommandEvent &event) { if (obj) { @@ -3609,13 +3667,17 @@ void StatusPanel::update_subtask(MachineObject *obj) m_project_task_panel->show_layers_num(obj->is_support_layer_num); update_model_info(); + update_partskip_button(obj); if (obj->is_system_printing() || 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(nullptr, false); m_project_task_panel->enable_abort_button(false); m_project_task_panel->enable_pause_resume_button(false, "pause_disable"); wxString prepare_text; @@ -3657,7 +3719,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(obj, true); // update printing stage m_project_task_panel->update_left_time(obj->mc_left_time); if (obj->subtask_) { @@ -3674,6 +3736,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(nullptr, false); m_project_task_panel->enable_pause_resume_button(false, "resume_disable"); // is makeworld subtask if (wxGetApp().has_model_mall() && obj->is_makeworld_subtask()) { @@ -3741,6 +3804,32 @@ void StatusPanel::update_subtask(MachineObject *obj) Layout(); } +void StatusPanel::update_partskip_subtask(MachineObject *obj){ + if (!obj) return; + if (!obj->subtask_) return; + + auto partskip_button = m_project_task_panel->get_partskip_button(); + if (partskip_button) { + int part_cnt = 0; + if(m_project_task_panel->get_part_skipped_dirty() > 0){ + m_project_task_panel->set_part_skipped_dirty(m_project_task_panel->get_part_skipped_dirty() - 1); + part_cnt = m_project_task_panel->get_part_skipped_count(); + BOOST_LOG_TRIVIAL(info) << "part skip: stop recv printer dirty data."; + }else{ + part_cnt = obj->m_partskip_ids.size(); + BOOST_LOG_TRIVIAL(info) << "part skip: recv printer normal data."; + } + 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; @@ -3807,6 +3896,7 @@ void StatusPanel::update_sdcard_subtask(MachineObject *obj) void StatusPanel::reset_printing_values() { + m_project_task_panel->enable_partskip_button(nullptr, 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 08cf77f7aa..a5550c43fa 100644 --- a/src/slic3r/GUI/StatusPanel.hpp +++ b/src/slic3r/GUI/StatusPanel.hpp @@ -33,6 +33,7 @@ #include "Widgets/FilamentLoad.hpp" #include "Widgets/FanControl.hpp" #include "HMS.hpp" +#include "PartSkipDialog.hpp" #include "DeviceErrorDialog.hpp" class StepIndicator; @@ -260,7 +261,7 @@ public: private: - MachineObject* m_obj; + MachineObject* m_obj{nullptr}; ScalableBitmap m_thumbnail_placeholder; wxBitmap m_thumbnail_bmp_display; ScalableBitmap m_bitmap_use_time; @@ -292,6 +293,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; @@ -303,6 +305,10 @@ private: std::vector m_score_star; bool m_star_count_dirty = false; + // partskip button + int m_part_skipped_count{ 0 }; + int m_part_skipped_dirty{ 0 }; + ProgressBar* m_gauge_progress; Label* m_error_text; PrintingTaskType m_type; @@ -317,6 +323,7 @@ public: void msw_rescale(); public: + void enable_partskip_button(MachineObject* obj, bool enable); void enable_pause_resume_button(bool enable, std::string type); void enable_abort_button(bool enable); void update_subtask_name(wxString name); @@ -337,6 +344,7 @@ public: 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;}; @@ -347,6 +355,10 @@ public: std::vector &get_score_star() { return m_score_star; } bool get_star_count_dirty() { return m_star_count_dirty; } void set_star_count_dirty(bool dirty) { m_star_count_dirty = dirty; } + int get_part_skipped_count() { return m_part_skipped_count; } + void set_part_skipped_count(int count) { m_part_skipped_count = count; } + int get_part_skipped_dirty() { return m_part_skipped_dirty; } + void set_part_skipped_dirty(int dirty) { m_part_skipped_dirty = dirty; } void set_has_reted_text(bool has_rated); void paint(wxPaintEvent&); }; @@ -432,6 +444,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; @@ -526,6 +539,7 @@ protected: StaticBox* m_filament_load_box; // 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(); } @@ -612,6 +626,7 @@ protected: FanControlPopupNew* 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; @@ -652,6 +667,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); @@ -728,6 +744,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); @@ -747,6 +764,9 @@ protected: void update_camera_state(MachineObject* obj); bool show_vcamera = false; + // partskip button + void update_partskip_button(MachineObject* obj); + public: void update_error_message(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9f118dbc5f..a65010168f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2529,7 +2529,7 @@ void TabPrint::build() optgroup->append_single_option_line("raft_first_layer_expansion", "support_settings_support#initial-layer-expansion"); optgroup->append_single_option_line("support_on_build_plate_only", "support_settings_support#on-build-plate-only"); optgroup->append_single_option_line("support_critical_regions_only", "support_settings_support#support-critical-regions-only"); - optgroup->append_single_option_line("support_remove_small_overhang", "support_settings_support#remove-small-overhangs"); + optgroup->append_single_option_line("support_remove_small_overhang", "support_settings_support#ignore-small-overhangs"); //optgroup->append_single_option_line("enforce_support_layers", "support_settings_support"); optgroup = page->new_optgroup(L("Raft"), L"param_raft"); diff --git a/src/slic3r/GUI/Widgets/AnimaController.cpp b/src/slic3r/GUI/Widgets/AnimaController.cpp index 10f0061dbf..4cd110342f 100644 --- a/src/slic3r/GUI/Widgets/AnimaController.cpp +++ b/src/slic3r/GUI/Widgets/AnimaController.cpp @@ -9,8 +9,9 @@ AnimaIcon::AnimaIcon(wxWindow *parent, wxWindowID id, std::vector img_list, std::string img_enable, int ivt) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize), m_ivt(ivt) { + auto sizer = new wxBoxSizer(wxHORIZONTAL); SetBackgroundColour((wxColour(255, 255, 255))); - m_size = 20; + m_size = 25; //add ScalableBitmap for (const auto &filename : img_list) m_images.emplace_back(create_scaled_bitmap(filename, this, m_size)); @@ -47,12 +48,13 @@ AnimaIcon::AnimaIcon(wxWindow *parent, wxWindowID id, std::vector i SetCursor(wxCursor(wxCURSOR_ARROW)); e.Skip(); }); - + sizer->Add(m_bitmap, 0, wxALIGN_CENTER, 0); + SetSizer(sizer); SetSize(wxSize(FromDIP(m_size), FromDIP(m_size))); SetMaxSize(wxSize(FromDIP(m_size), FromDIP(m_size))); SetMinSize(wxSize(FromDIP(m_size), FromDIP(m_size))); - Refresh(); - + Layout(); + Fit(); Play(); } @@ -73,5 +75,3 @@ void AnimaIcon::Enable() { if (m_bitmap) { m_bitmap->SetBitmap(m_image_enable); } } - - diff --git a/src/slic3r/GUI/Widgets/ProgressDialog.cpp b/src/slic3r/GUI/Widgets/ProgressDialog.cpp index 1db69da11f..0ace737d8d 100644 --- a/src/slic3r/GUI/Widgets/ProgressDialog.cpp +++ b/src/slic3r/GUI/Widgets/ProgressDialog.cpp @@ -813,7 +813,7 @@ void ProgressDialog::DoSetSize(int x, int y, int width, int height, int sizeFlag // m_block_right->SetPosition(wxPoint(PROGRESSDIALOG_GAUGE_SIZE.x - 2, 0)); //} #endif - wxWindow::DoSetSize(x, y, width, height, sizeFlags); + wxDialog::DoSetSize(x, y, width, height, sizeFlags); } void ProgressDialog::DisableOtherWindows()