ENH: support parts skipping function
Jira: STUDIO-12687 Change-Id: I244cb611954590bd5e741f0d2701f359426a33a2 (cherry picked from commit e7e90e0f8ca0106a51d18d83efa0de56b332ddc0)
This commit is contained in:
27
src/earcut/CHANGELOG.md
Normal file
27
src/earcut/CHANGELOG.md
Normal file
@@ -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
|
||||
151
src/earcut/CMakeLists.txt
Normal file
151
src/earcut/CMakeLists.txt
Normal file
@@ -0,0 +1,151 @@
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
project(earcut_hpp LANGUAGES CXX C)
|
||||
|
||||
option(EARCUT_BUILD_TESTS "Build the earcut test program" ON)
|
||||
option(EARCUT_BUILD_BENCH "Build the earcut benchmark program" ON)
|
||||
option(EARCUT_BUILD_VIZ "Build the earcut visualizer program" ON)
|
||||
option(EARCUT_WARNING_IS_ERROR "Treat warnings as errors" OFF)
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE AND NOT GENERATOR_IS_MULTI_CONFIG)
|
||||
message(STATUS "No build type specified. Setting to 'Release'")
|
||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "The type of build." FORCE)
|
||||
endif()
|
||||
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
add_library(earcut_hpp INTERFACE)
|
||||
add_library(earcut_hpp::earcut_hpp ALIAS earcut_hpp)
|
||||
|
||||
target_include_directories(earcut_hpp INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" GREATER 3.7)
|
||||
# Allow C++11 requirements to propagate when using recent CMake versions
|
||||
target_compile_features(earcut_hpp INTERFACE cxx_std_11)
|
||||
endif()
|
||||
|
||||
file(GLOB FIXTURE_SOURCE_FILES test/fixtures/*.cpp test/fixtures/*.hpp)
|
||||
source_group(fixtures FILES ${FIXTURE_SOURCE_FILES})
|
||||
add_library(fixtures OBJECT ${FIXTURE_SOURCE_FILES})
|
||||
target_compile_options(fixtures PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/Od>)
|
||||
|
||||
# In CMake 3.12, use target_link_libraries(fixtures PUBLIC earcut_hpp libtess2).
|
||||
# Since we support down to CMake 3.2, we need to manually propagate usage requirements of earcut_hpp
|
||||
target_include_directories(fixtures PRIVATE "$<TARGET_PROPERTY:earcut_hpp,INTERFACE_INCLUDE_DIRECTORIES>")
|
||||
target_compile_features(fixtures PRIVATE "$<TARGET_PROPERTY:earcut_hpp,INTERFACE_COMPILE_FEATURES>")
|
||||
|
||||
|
||||
file(GLOB COMPARISON_SOURCE_FILES test/comparison/*.cpp test/comparison/*.hpp)
|
||||
source_group(comparison FILES ${COMPARISON_SOURCE_FILES})
|
||||
# this is interface since there is no cpp files in the comparison directory
|
||||
add_library(comparison INTERFACE)
|
||||
|
||||
|
||||
file(GLOB LIBTESS2_SOURCE_FILES test/comparison/libtess2/*.c test/comparison/libtess2/*.h)
|
||||
source_group(comparison/libtess2 FILES ${LIBTESS2_SOURCE_FILES})
|
||||
add_library(libtess2 ${LIBTESS2_SOURCE_FILES})
|
||||
target_compile_options(libtess2 PRIVATE
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/wd4244 /wd4267>
|
||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-w>
|
||||
)
|
||||
|
||||
add_library(common INTERFACE)
|
||||
target_link_libraries(common INTERFACE libtess2 comparison)
|
||||
|
||||
# optional: -march=native (builds with the optimizations available on the build machine (only for local use!))
|
||||
target_compile_options(common INTERFACE
|
||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pipe -Wall -Wextra -Wconversion -Wpedantic>
|
||||
)
|
||||
|
||||
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang$" OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
if ("${CMAKE_CXX_FLAGS}" MATCHES "--coverage")
|
||||
# We disable debug code for the coverage so it won't see assertion and other things only enabled for debugging
|
||||
target_compile_definitions(common INTERFACE NDEBUG)
|
||||
else()
|
||||
# Here we enable the undefined behavior sanitizer for the tests, benchmarks and the viz
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag("-fsanitize=undefined" HAVE_FLAG_SANITIZE_UNDEFINED)
|
||||
if(HAVE_FLAG_SANITIZE_UNDEFINED)
|
||||
target_compile_options(common INTERFACE $<$<CONFIG:Debug>:-fsanitize=undefined>)
|
||||
# TODO: Replace with target link option once we support CMake 3.13
|
||||
target_link_libraries(common INTERFACE $<$<CONFIG:Debug>:-fsanitize=undefined>)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (EARCUT_WARNING_IS_ERROR)
|
||||
target_compile_options(common INTERFACE
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/WX>
|
||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Werror>
|
||||
)
|
||||
endif()
|
||||
|
||||
if (EARCUT_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_executable(tests test/tap.cpp test/tap.hpp test/test.cpp $<TARGET_OBJECTS:fixtures>)
|
||||
target_link_libraries(tests PRIVATE earcut_hpp common)
|
||||
add_test(NAME earcut_tests COMMAND tests)
|
||||
endif()
|
||||
if (EARCUT_BUILD_BENCH)
|
||||
add_executable(bench test/bench.cpp $<TARGET_OBJECTS:fixtures>)
|
||||
target_link_libraries(bench PRIVATE earcut_hpp common)
|
||||
endif()
|
||||
if (EARCUT_BUILD_VIZ)
|
||||
add_executable(viz test/viz.cpp $<TARGET_OBJECTS:fixtures>)
|
||||
|
||||
# Setup viz target
|
||||
# OpenGL
|
||||
# linux: xorg-dev libgl1-mesa-glx libgl1-mesa-dev
|
||||
# windows: in the windows sdk
|
||||
find_package(OpenGL REQUIRED)
|
||||
|
||||
# GLFW3
|
||||
find_package(glfw3 QUIET) # try to use the system default
|
||||
if (NOT glfw3_FOUND)
|
||||
if(EXISTS "${PROJECT_SOURCE_DIR}/.gitmodules")
|
||||
find_package(Git REQUIRED)
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_QUIET
|
||||
ERROR_QUIET
|
||||
)
|
||||
endif()
|
||||
|
||||
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "Build the GLFW example programs" FORCE)
|
||||
set(GLFW_BUILD_TESTS OFF CACHE BOOL "Build the GLFW test programs" FORCE)
|
||||
set(GLFW_BUILD_DOCS OFF CACHE BOOL "Build the GLFW documentation" FORCE)
|
||||
set(GLFW_INSTALL OFF CACHE BOOL "Generate installation target" FORCE)
|
||||
add_subdirectory(glfw)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(viz PRIVATE GL_SILENCE_DEPRECATION)
|
||||
|
||||
# TODO: Using old variables for OpenGL package since they were added in CMake 3.8
|
||||
target_link_libraries(viz PRIVATE earcut_hpp common glfw ${OPENGL_LIBRARIES})
|
||||
target_include_directories(viz PRIVATE ${OPENGL_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
install(
|
||||
DIRECTORY include/mapbox
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.hpp"
|
||||
)
|
||||
|
||||
install(TARGETS earcut_hpp EXPORT earcut_hpp-config)
|
||||
|
||||
# Since there is two projects, we need to export into the parent directory
|
||||
export(
|
||||
TARGETS earcut_hpp
|
||||
NAMESPACE earcut_hpp::
|
||||
FILE "${PROJECT_BINARY_DIR}/earcut_hpp-config.cmake"
|
||||
)
|
||||
|
||||
install(EXPORT earcut_hpp-config
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/earcut_hpp"
|
||||
NAMESPACE earcut_hpp::
|
||||
)
|
||||
15
src/earcut/LICENSE
Normal file
15
src/earcut/LICENSE
Normal file
@@ -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.
|
||||
131
src/earcut/README.md
Normal file
131
src/earcut/README.md
Normal file
@@ -0,0 +1,131 @@
|
||||
## Earcut
|
||||
|
||||
A C++ port of [earcut.js](https://github.com/mapbox/earcut), a fast, [header-only](https://github.com/mapbox/earcut.hpp/blob/master/include/mapbox/earcut.hpp) polygon triangulation library.
|
||||
|
||||
[](https://travis-ci.com/github/mapbox/earcut.hpp)
|
||||
[](https://ci.appveyor.com/project/Mapbox/earcut-hpp-8wm4o/branch/master)
|
||||
[](https://coveralls.io/github/mapbox/earcut.hpp)
|
||||
[](https://scan.coverity.com/projects/14000)
|
||||
[](http://isitmaintained.com/project/mapbox/earcut.hpp "Average time to resolve an issue")
|
||||
[](http://isitmaintained.com/project/mapbox/earcut.hpp "Percentage of issues still open")
|
||||
[](https://github.com/mourner/projects)
|
||||
|
||||
The library implements a modified ear slicing algorithm, optimized by [z-order curve](http://en.wikipedia.org/wiki/Z-order_curve) hashing and extended to handle holes, twisted polygons, degeneracies and self-intersections in a way that doesn't _guarantee_ correctness of triangulation, but attempts to always produce acceptable results for practical data like geographical shapes.
|
||||
|
||||
It's based on ideas from [FIST: Fast Industrial-Strength Triangulation of Polygons](http://www.cosy.sbg.ac.at/~held/projects/triang/triang.html) by Martin Held and [Triangulation by Ear Clipping](http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf) by David Eberly.
|
||||
|
||||
## Usage
|
||||
|
||||
```cpp
|
||||
#include <earcut.hpp>
|
||||
```
|
||||
```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<Coord, 2>;
|
||||
std::vector<std::vector<Point>> 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<N> indices = mapbox::earcut<N>(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<T>, it has to meet the requirements of [Container](https://en.cppreference.com/w/cpp/named_req/Container), in particular `size()`, `empty()` and `operator[]`.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://camo.githubusercontent.com/01836f8ba21af844c93d8d3145f4e9976025a696/68747470733a2f2f692e696d6775722e636f6d2f67314e704c54712e706e67" alt="example triangulation"/>
|
||||
</p>
|
||||
|
||||
## Additional build instructions
|
||||
In case you just want to use the earcut triangulation library; copy and include the header file [`<earcut.hpp>`](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).
|
||||
814
src/earcut/earcut.hpp
Normal file
814
src/earcut/earcut.hpp
Normal file
@@ -0,0 +1,814 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace mapbox {
|
||||
|
||||
namespace util {
|
||||
|
||||
template <std::size_t I, typename T> struct nth {
|
||||
inline static typename std::tuple_element<I, T>::type
|
||||
get(const T& t) { return std::get<I>(t); };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename N = uint32_t>
|
||||
class Earcut {
|
||||
public:
|
||||
std::vector<N> indices;
|
||||
std::size_t vertices = 0;
|
||||
|
||||
template <typename Polygon>
|
||||
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 <typename Ring> 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 <typename Polygon> 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 <typename Point> 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 <typename T, typename Alloc = std::allocator<T>>
|
||||
class ObjectPool {
|
||||
public:
|
||||
ObjectPool() { }
|
||||
ObjectPool(std::size_t blockSize_) {
|
||||
reset(blockSize_);
|
||||
}
|
||||
~ObjectPool() {
|
||||
clear();
|
||||
}
|
||||
template <typename... Args>
|
||||
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>(args)...);
|
||||
return object;
|
||||
}
|
||||
void reset(std::size_t newBlockSize) {
|
||||
for (auto allocation : allocations) {
|
||||
alloc_traits::deallocate(alloc, allocation, blockSize);
|
||||
}
|
||||
allocations.clear();
|
||||
blockSize = std::max<std::size_t>(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<T*> allocations;
|
||||
Alloc alloc;
|
||||
typedef typename std::allocator_traits<Alloc> alloc_traits;
|
||||
};
|
||||
ObjectPool<Node> nodes;
|
||||
};
|
||||
|
||||
template <typename N> template <typename Polygon>
|
||||
void Earcut<N>::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<int>(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<double>(minX, x);
|
||||
minY = std::min<double>(minY, y);
|
||||
maxX = std::max<double>(maxX, x);
|
||||
maxY = std::max<double>(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<double>(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 <typename N> template <typename Ring>
|
||||
typename Earcut<N>::Node*
|
||||
Earcut<N>::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 N>
|
||||
typename Earcut<N>::Node*
|
||||
Earcut<N>::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 <typename N>
|
||||
void Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::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<double>(a->x, std::min<double>(b->x, c->x));
|
||||
const double minTY = std::min<double>(a->y, std::min<double>(b->y, c->y));
|
||||
const double maxTX = std::max<double>(a->x, std::max<double>(b->x, c->x));
|
||||
const double maxTY = std::max<double>(a->y, std::max<double>(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 N>
|
||||
typename Earcut<N>::Node*
|
||||
Earcut<N>::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 <typename N>
|
||||
void Earcut<N>::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 <typename N> template <typename Polygon>
|
||||
typename Earcut<N>::Node*
|
||||
Earcut<N>::eliminateHoles(const Polygon& points, Node* outerNode) {
|
||||
const size_t len = points.size();
|
||||
|
||||
std::vector<Node*> 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 N>
|
||||
typename Earcut<N>::Node*
|
||||
Earcut<N>::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 N>
|
||||
typename Earcut<N>::Node*
|
||||
Earcut<N>::findHoleBridge(Node* hole, Node* outerNode) {
|
||||
Node* p = outerNode;
|
||||
double hx = hole->x;
|
||||
double hy = hole->y;
|
||||
double qx = -std::numeric_limits<double>::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<double>::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 <typename N>
|
||||
bool Earcut<N>::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 <typename N>
|
||||
void Earcut<N>::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 N>
|
||||
typename Earcut<N>::Node*
|
||||
Earcut<N>::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 <typename N>
|
||||
int32_t Earcut<N>::zOrder(const double x_, const double y_) {
|
||||
// coords are transformed into non-negative 15-bit integer range
|
||||
int32_t x = static_cast<int32_t>((x_ - minX) * inv_size);
|
||||
int32_t y = static_cast<int32_t>((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 N>
|
||||
typename Earcut<N>::Node*
|
||||
Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::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 <typename N>
|
||||
double Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::equals(const Node* p1, const Node* p2) {
|
||||
return p1->x == p2->x && p1->y == p2->y;
|
||||
}
|
||||
|
||||
// check if two segments intersect
|
||||
template <typename N>
|
||||
bool Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::onSegment(const Node* p, const Node* q, const Node* r) {
|
||||
return q->x <= std::max<double>(p->x, r->x) &&
|
||||
q->x >= std::min<double>(p->x, r->x) &&
|
||||
q->y <= std::max<double>(p->y, r->y) &&
|
||||
q->y >= std::min<double>(p->y, r->y);
|
||||
}
|
||||
|
||||
template <typename N>
|
||||
int Earcut<N>::sign(double val) {
|
||||
return (0.0 < val) - (val < 0.0);
|
||||
}
|
||||
|
||||
// check if a polygon diagonal intersects any polygon segments
|
||||
template <typename N>
|
||||
bool Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::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 <typename N>
|
||||
bool Earcut<N>::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 N>
|
||||
typename Earcut<N>::Node*
|
||||
Earcut<N>::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 <typename N> template <typename Point>
|
||||
typename Earcut<N>::Node*
|
||||
Earcut<N>::insertNode(std::size_t i, const Point& pt, Node* last) {
|
||||
Node* p = nodes.construct(static_cast<N>(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 <typename N>
|
||||
void Earcut<N>::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 <typename N = uint32_t, typename Polygon>
|
||||
std::vector<N> earcut(const Polygon& poly) {
|
||||
mapbox::detail::Earcut<N> earcut;
|
||||
earcut(poly);
|
||||
return std::move(earcut.indices);
|
||||
}
|
||||
}
|
||||
@@ -373,6 +373,11 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/SavePresetDialog.hpp
|
||||
GUI/SceneRaycaster.cpp
|
||||
GUI/SceneRaycaster.hpp
|
||||
GUI/PartSkipCommon.hpp
|
||||
GUI/PartSkipDialog.cpp
|
||||
GUI/PartSkipDialog.hpp
|
||||
GUI/SkipPartCanvas.cpp
|
||||
GUI/SkipPartCanvas.hpp
|
||||
GUI/Search.cpp
|
||||
GUI/Search.hpp
|
||||
GUI/Selection.cpp
|
||||
|
||||
@@ -1860,6 +1860,17 @@ int MachineObject::command_control_fan_val(FanType fan_type, int val)
|
||||
}
|
||||
|
||||
|
||||
int MachineObject::command_task_partskip(std::vector<int> part_ids)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "command_task_partskip: ";
|
||||
json j;
|
||||
j["print"]["command"] = "skip_objects";
|
||||
j["print"]["obj_list"] = part_ids;
|
||||
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
|
||||
|
||||
return this->publish_json(j.dump(), 1);
|
||||
}
|
||||
|
||||
int MachineObject::command_task_abort()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "command_task_abort: ";
|
||||
@@ -2730,7 +2741,7 @@ void MachineObject::reset()
|
||||
vt_tray.reset();
|
||||
|
||||
subtask_ = nullptr;
|
||||
|
||||
m_partskip_ids.clear();
|
||||
}
|
||||
|
||||
void MachineObject::set_print_state(std::string status)
|
||||
@@ -2966,8 +2977,20 @@ int MachineObject::parse_json(std::string payload, bool key_field_only)
|
||||
print_json.load_compatible_settings(printer_type, "");
|
||||
print_json.diff2all_base_reset(j_pre);
|
||||
}
|
||||
|
||||
if (j_pre["print"].contains("s_obj")){
|
||||
if(j_pre["print"]["s_obj"].is_array()){
|
||||
m_partskip_ids.clear();
|
||||
for(auto it=j_pre["print"]["s_obj"].begin(); it!=j_pre["print"]["s_obj"].end(); it++){
|
||||
m_partskip_ids.push_back(it.value().get<int>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (j_pre["print"].contains("plate_idx") && m_plate_index == -1){
|
||||
m_plate_index = j_pre["print"]["plate_idx"].get<int>();
|
||||
}
|
||||
}
|
||||
if (j_pre.contains("system")) {
|
||||
if (j_pre["system"].contains("command")) {
|
||||
|
||||
@@ -833,6 +833,9 @@ public:
|
||||
int xcam_filament_tangle_detect_count = 0;
|
||||
int ams_print_option_count = 0;
|
||||
|
||||
// part skip
|
||||
std::vector<int> m_partskip_ids;
|
||||
|
||||
//supported features
|
||||
bool is_support_chamber_edit{false};
|
||||
bool is_support_extrusion_cali{false};
|
||||
@@ -955,6 +958,7 @@ public:
|
||||
int command_control_fan_val(FanType fan_type, int val);
|
||||
int command_task_abort();
|
||||
/* cancelled the job_id */
|
||||
int command_task_partskip(std::vector<int> part_ids);
|
||||
int command_task_cancel(std::string job_id);
|
||||
int command_task_pause();
|
||||
int command_task_resume();
|
||||
|
||||
18
src/slic3r/GUI/PartSkipCommon.hpp
Normal file
18
src/slic3r/GUI/PartSkipCommon.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef PARTSKIPCOMMON_H
|
||||
#define PARTSKIPCOMMON_H
|
||||
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
enum PartState {
|
||||
psUnCheck,
|
||||
psChecked,
|
||||
psSkipped
|
||||
};
|
||||
|
||||
|
||||
typedef std::vector<std::pair<int, PartState>> PartsInfo;
|
||||
|
||||
}}
|
||||
|
||||
#endif // PARTSKIPCOMMON_H
|
||||
863
src/slic3r/GUI/PartSkipDialog.cpp
Normal file
863
src/slic3r/GUI/PartSkipDialog.cpp
Normal file
@@ -0,0 +1,863 @@
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include <wx/panel.h>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/webrequest.h>
|
||||
#include <wx/control.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/display.h>
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/sstream.h>
|
||||
#include <wx/zstream.h>
|
||||
#include <wx/window.h>
|
||||
#include <wx/dcgraph.h>
|
||||
#include <wx/glcanvas.h>
|
||||
#include <wx/utils.h>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
|
||||
#include "MsgDialog.hpp"
|
||||
#include "Printer/PrinterFileSystem.h"
|
||||
#include "PartSkipDialog.hpp"
|
||||
#include "SkipPartCanvas.hpp"
|
||||
#include "MediaPlayCtrl.h"
|
||||
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
extern wxString hide_passwd(wxString url, std::vector<wxString> const &passwords);
|
||||
extern void refresh_agora_url(char const *device, char const *dev_ver, char const *channel, void *context, void (*callback)(void *context, char const *url));
|
||||
|
||||
|
||||
PartSkipDialog::PartSkipDialog(wxWindow* parent): DPIDialog(parent, wxID_ANY, _L("Skip Objects"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
|
||||
{
|
||||
std::time_t t = std::time(0);
|
||||
std::stringstream buf;
|
||||
buf << put_time(std::localtime(&t), "%a_%b_%d_%H_%M_%S/");
|
||||
m_timestamp = buf.str();
|
||||
|
||||
std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % Slic3r::resources_dir()).str();
|
||||
SetIcon(wxIcon(Slic3r::encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
SetSizeHints( wxDefaultSize, wxDefaultSize );
|
||||
|
||||
m_sizer = new wxBoxSizer( wxVERTICAL );
|
||||
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
|
||||
m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA));
|
||||
m_sizer->Add(m_line_top, 0, wxEXPAND, FromDIP(5));
|
||||
m_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
|
||||
|
||||
|
||||
m_simplebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_book_first_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
|
||||
m_book_third_panel = new wxPanel(m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
m_book_third_panel->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
m_dlg_sizer = new wxBoxSizer( wxVERTICAL );
|
||||
m_dlg_content_sizer = new wxBoxSizer( wxHORIZONTAL );
|
||||
m_canvas_sizer = new wxBoxSizer( wxVERTICAL );
|
||||
|
||||
// page 3
|
||||
wxGLAttributes canvasAttrs;
|
||||
canvasAttrs.PlatformDefaults().Defaults().Stencil(8).EndList();
|
||||
m_canvas = new SkipPartCanvas(m_book_third_panel, canvasAttrs);
|
||||
m_canvas->SetPosition(wxPoint(FromDIP(10), FromDIP(10)));
|
||||
m_canvas->SetMinSize( wxSize( FromDIP(400),FromDIP(400) ) );
|
||||
|
||||
m_canvas_sizer->Add( m_canvas, 0, wxALL, FromDIP(10) );
|
||||
|
||||
m_canvas_btn_sizer = new wxBoxSizer( wxHORIZONTAL );
|
||||
m_canvas_btn_sizer->SetMinSize( wxSize( FromDIP(214),FromDIP(28) ) );
|
||||
|
||||
StateColor zoom_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled), std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Enabled),
|
||||
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
|
||||
zoom_bg.setTakeFocusedAsHovered(false);
|
||||
StateColor zoom_bd(std::pair<wxColour, int>(wxColour(144, 144, 144), StateColor::Disabled), std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
|
||||
StateColor zoom_text(std::pair<wxColour, int>(wxColour(144, 144, 144), StateColor::Disabled), std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
|
||||
|
||||
m_zoom_out_btn = new Button(m_book_third_panel, _L(""));
|
||||
m_zoom_out_btn->SetIcon("canvas_zoom_out");
|
||||
m_zoom_out_btn->SetToolTip(_L("Zoom Out"));
|
||||
m_zoom_out_btn->SetBackgroundColor(zoom_bg);
|
||||
m_zoom_out_btn->SetBorderColor(wxColour(238, 238, 238));
|
||||
m_zoom_out_btn->SetCornerRadius(0);
|
||||
m_zoom_out_btn->SetMinSize(wxSize(FromDIP(54), FromDIP(26)));
|
||||
|
||||
m_canvas_btn_sizer->Add(m_zoom_out_btn, 0, wxEXPAND, 0);
|
||||
|
||||
StateColor percent_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Hovered), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Enabled),
|
||||
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
|
||||
m_percent_label = new Button(m_book_third_panel, _L("100 %"));
|
||||
m_percent_label->SetBackgroundColor(percent_bg);
|
||||
m_percent_label->SetBorderColor(wxColour(238, 238, 238));
|
||||
m_percent_label->SetMinSize(wxSize(FromDIP(54), FromDIP(26)));
|
||||
m_percent_label->SetCornerRadius(0);
|
||||
m_canvas_btn_sizer->Add( m_percent_label, 0, wxEXPAND, 0 );
|
||||
|
||||
m_zoom_in_btn = new Button(m_book_third_panel, _L(""));
|
||||
m_zoom_in_btn->SetIcon("canvas_zoom_in");
|
||||
m_zoom_in_btn->SetToolTip(_L("Zoom In"));
|
||||
m_zoom_in_btn->SetBackgroundColor(zoom_bg);
|
||||
m_zoom_in_btn->SetBorderColor(wxColour(238, 238, 238));
|
||||
m_zoom_in_btn->SetMinSize(wxSize(FromDIP(54), FromDIP(26)));
|
||||
m_zoom_in_btn->SetCornerRadius(0);
|
||||
m_canvas_btn_sizer->Add(m_zoom_in_btn, 0, wxEXPAND, 0);
|
||||
|
||||
m_switch_drag_btn = new Button(m_book_third_panel, _L(""));
|
||||
m_switch_drag_btn->SetIcon("canvas_drag");
|
||||
m_switch_drag_btn->SetToolTip(_L("Drag"));
|
||||
m_switch_drag_btn->SetBackgroundColor(*wxWHITE);
|
||||
m_switch_drag_btn->SetBorderColor(wxColour(238, 238, 238));
|
||||
m_switch_drag_btn->SetMinSize(wxSize(FromDIP(54),FromDIP(26)));
|
||||
m_switch_drag_btn->SetCornerRadius(0);
|
||||
m_canvas_btn_sizer->Add( m_switch_drag_btn, 0, wxEXPAND, FromDIP(5) );
|
||||
|
||||
m_canvas_sizer->Add(m_canvas_btn_sizer, 0, wxALIGN_CENTER_HORIZONTAL, FromDIP(10));
|
||||
|
||||
m_dlg_content_sizer->Add( m_canvas_sizer, 0, wxALL, FromDIP(10) );
|
||||
|
||||
m_list_sizer = new wxBoxSizer( wxVERTICAL );
|
||||
m_list_sizer->SetMinSize( wxSize( FromDIP(267),FromDIP(422) ) );
|
||||
|
||||
auto all_checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_all_checkbox = new CheckBox(m_book_third_panel, wxID_ANY);
|
||||
m_all_checkbox->SetValue(false);
|
||||
m_all_checkbox->SetBackgroundColour(*wxWHITE);
|
||||
m_all_label = new Label(m_book_third_panel, _L("Select All"));
|
||||
m_all_label->Wrap(-1);
|
||||
m_all_label->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
m_all_label->SetMinSize(wxSize(267, -1));
|
||||
m_all_label->SetMaxSize(wxSize(267, -1));
|
||||
all_checkbox_sizer->Add(m_all_checkbox, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0);
|
||||
all_checkbox_sizer->Add(m_all_label, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0);
|
||||
|
||||
m_list_sizer->Add(all_checkbox_sizer, 0, wxALL, FromDIP(5));
|
||||
|
||||
m_line = new wxPanel(m_book_third_panel, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(267), FromDIP(1)), wxTAB_TRAVERSAL);
|
||||
m_line->SetBackgroundColour( wxColor(238, 238, 238) );
|
||||
|
||||
m_list_sizer->Add(m_line, 0, wxEXPAND, 0);
|
||||
|
||||
m_list_view = new wxScrolledWindow( m_book_third_panel, wxID_ANY, wxDefaultPosition, wxSize( 267,-1 ), wxHSCROLL|wxVSCROLL );
|
||||
m_list_view->SetScrollRate( 5, 5 );
|
||||
m_list_view->SetMinSize( wxSize( FromDIP(267),FromDIP(410) ) );
|
||||
m_list_view->SetMaxSize(wxSize(FromDIP(267), FromDIP(410)));
|
||||
m_list_view->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
m_scroll_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_list_view->SetSizer(m_scroll_sizer);
|
||||
m_list_view->Layout();
|
||||
|
||||
m_list_sizer->Add(m_list_view, 0, wxEXPAND, 0);
|
||||
|
||||
m_dlg_content_sizer->Add( m_list_sizer, 0, wxALL, FromDIP(15) );
|
||||
|
||||
|
||||
m_dlg_sizer->Add( m_dlg_content_sizer, 0, wxEXPAND, FromDIP(5) );
|
||||
|
||||
m_dlg_btn_sizer = new wxBoxSizer( wxHORIZONTAL );
|
||||
m_dlg_btn_sizer->SetMinSize(wxSize(FromDIP(720), FromDIP(54)));
|
||||
|
||||
m_cnt_label = new Label(m_book_third_panel, _L("cnt 1"));
|
||||
m_cnt_label->Wrap( -1 );
|
||||
m_cnt_label->SetBackgroundColour(*wxWHITE);
|
||||
m_cnt_label->SetForegroundColour(wxColour(0, 174, 66));
|
||||
|
||||
m_cnt_label->SetFont(Label::Head_15);
|
||||
m_dlg_btn_sizer->Add(0, 0, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(5));
|
||||
m_dlg_btn_sizer->Add( m_cnt_label, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(0) );
|
||||
|
||||
m_tot_label = new Label(m_book_third_panel, _L("tot 15"));
|
||||
m_tot_label->Wrap( -1 );
|
||||
m_tot_label->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
m_dlg_btn_sizer->Add(m_tot_label, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
|
||||
m_dlg_btn_sizer->Add(0, 0, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, FromDIP(5));
|
||||
|
||||
StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed), std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
|
||||
|
||||
|
||||
StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed), std::pair<wxColour, int>(wxColour(61, 203, 115), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(wxColour(0, 177, 66), StateColor::Normal));
|
||||
|
||||
m_apply_btn = new Button(m_book_third_panel, wxString(_L("Skip")));
|
||||
m_apply_btn->SetBackgroundColor(btn_bg_green);
|
||||
m_apply_btn->SetTextColor(*wxWHITE);
|
||||
// m_apply_btn->SetBorderColor(wxColour(38, 46, 48));
|
||||
m_apply_btn->SetFont(Label::Body_14);
|
||||
m_apply_btn->SetSize(wxSize(-1, FromDIP(32)));
|
||||
m_apply_btn->SetMinSize(wxSize(FromDIP(80), FromDIP(32)));
|
||||
m_apply_btn->SetCornerRadius(FromDIP(16));
|
||||
m_dlg_btn_sizer->Add(m_apply_btn, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(15));
|
||||
|
||||
m_dlg_sizer->Add( m_dlg_btn_sizer, 0, wxEXPAND, FromDIP(5) );
|
||||
|
||||
m_book_third_panel->SetSizer( m_dlg_sizer );
|
||||
m_book_third_panel->Layout();
|
||||
m_dlg_sizer->Fit( m_book_third_panel );
|
||||
|
||||
// page 2
|
||||
m_book_second_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
|
||||
m_book_second_panel->SetBackgroundColour(*wxWHITE);
|
||||
m_book_second_sizer = new wxBoxSizer( wxVERTICAL );
|
||||
m_book_second_btn_sizer = new wxBoxSizer( wxHORIZONTAL );
|
||||
|
||||
m_retry_label = new Label( m_book_second_panel, _L("Load Skipping Objects Information Failed. \nPlease try again."));
|
||||
m_retry_label->Wrap( -1 );
|
||||
m_retry_label->SetBackgroundColour(*wxWHITE);
|
||||
m_book_second_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
m_book_second_sizer->Add(m_retry_label, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_CENTER_HORIZONTAL, 0);
|
||||
m_book_second_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
|
||||
m_second_retry_btn = new Button(m_book_second_panel, _L("Retry"));
|
||||
m_second_retry_btn->SetBackgroundColor(btn_bg_green);
|
||||
// m_second_retry_btn->SetBorderColor(wxColour(38, 46, 48));
|
||||
m_second_retry_btn->SetTextColor(*wxWHITE);
|
||||
m_second_retry_btn->SetFont(Label::Body_14);
|
||||
m_second_retry_btn->SetSize(wxSize(FromDIP(80), FromDIP(32)));
|
||||
m_second_retry_btn->SetMinSize(wxSize(FromDIP(80), FromDIP(32)));
|
||||
m_second_retry_btn->SetCornerRadius(FromDIP(16));
|
||||
m_second_retry_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnRetryButton, this);
|
||||
m_book_second_btn_sizer->Add(m_second_retry_btn, 0, wxALL, FromDIP(24));
|
||||
|
||||
m_book_second_sizer->Add(m_book_second_btn_sizer, 0, wxALIGN_RIGHT, 0);
|
||||
m_book_second_panel->SetSizer( m_book_second_sizer );
|
||||
m_book_second_panel->Layout();
|
||||
m_book_second_sizer->Fit( m_book_second_panel );
|
||||
|
||||
// page 1
|
||||
m_book_first_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
|
||||
m_book_first_sizer = new wxBoxSizer( wxVERTICAL );
|
||||
m_book_first_sizer->SetMinSize(wxSize(FromDIP(720), FromDIP(500)));
|
||||
|
||||
// auto m_loading_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
// std::vector<std::string> list{"ams_rfid_1", "ams_rfid_2", "ams_rfid_3", "ams_rfid_4"};
|
||||
// m_loading_icon = new AnimaIcon(m_book_first_panel, wxID_ANY, list, "refresh_printer", 100);
|
||||
|
||||
m_loading_label = new Label( m_book_first_panel, wxString(_L("Skipping Objects Information Loading ...")));
|
||||
m_loading_label->Wrap( -1 );
|
||||
m_loading_label->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
// m_loading_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
// m_loading_sizer->Add(m_loading_icon, 0, wxALL, FromDIP(5));
|
||||
// m_loading_sizer->Add(m_loading_label, 0, wxALL, FromDIP(5));
|
||||
// m_loading_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
|
||||
m_book_first_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
m_book_first_sizer->Add( m_loading_label, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 0);
|
||||
m_book_first_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
|
||||
m_book_first_panel->SetSizer( m_book_first_sizer );
|
||||
m_book_first_panel->Layout();
|
||||
m_book_first_sizer->Fit( m_book_first_panel );
|
||||
|
||||
m_simplebook->AddPage( m_book_first_panel, _("loading page"), false );
|
||||
m_simplebook->AddPage( m_book_second_panel, _("retry page"), false );
|
||||
m_simplebook->AddPage( m_book_third_panel, _("dialog page"), false );
|
||||
m_sizer->Add( m_simplebook, 1, wxEXPAND | wxALL, 5 );
|
||||
|
||||
|
||||
SetSizer( m_sizer );
|
||||
m_zoom_in_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomIn, this);
|
||||
m_zoom_out_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomOut, this);
|
||||
m_switch_drag_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnSwitchDrag, this);
|
||||
m_canvas->Bind(EVT_ZOOM_PERCENT, &PartSkipDialog::OnZoomPercent, this);
|
||||
m_canvas->Bind(EVT_CANVAS_PART, &PartSkipDialog::UpdatePartsStateFromCanvas, this);
|
||||
|
||||
m_apply_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnApplyDialog, this);
|
||||
m_all_checkbox->Bind(wxEVT_TOGGLEBUTTON, &PartSkipDialog::OnAllCheckbox, this);
|
||||
|
||||
Fit();
|
||||
Layout();
|
||||
CentreOnParent();
|
||||
}
|
||||
|
||||
PartSkipDialog::~PartSkipDialog() {}
|
||||
|
||||
void PartSkipDialog::on_dpi_changed(const wxRect& suggested_rect) { Fit(); }
|
||||
|
||||
|
||||
std::string PartSkipDialog::create_tmp_path(){
|
||||
boost::filesystem::path parent_path(temporary_dir());
|
||||
|
||||
std::stringstream buf;
|
||||
buf << "/bamboo_task/";
|
||||
buf << m_timestamp;
|
||||
if (m_obj) {
|
||||
buf << m_obj->dev_id << "_";
|
||||
buf << m_obj->job_id_ << "/";
|
||||
} else {
|
||||
buf << 1 << "_" << 1 << "/";
|
||||
}
|
||||
std::string tmp_path = (parent_path / buf.str() ).string();
|
||||
|
||||
if (!std::filesystem::exists(tmp_path + "Metadata/") && !fs::create_directories(tmp_path + "Metadata/")) {
|
||||
wxMessageBox("create file failed.");
|
||||
}
|
||||
return tmp_path;
|
||||
}
|
||||
|
||||
|
||||
bool PartSkipDialog::is_local_file_existed(const std::vector<string> &local_paths)
|
||||
{
|
||||
for (auto path : local_paths) {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PartSkipDialog::DownloadPartsFile()
|
||||
{
|
||||
m_tmp_path = create_tmp_path(); //wxGetApp().app_config->get("download_path");
|
||||
|
||||
m_local_paths.clear();
|
||||
m_target_paths.clear();
|
||||
|
||||
int plate_idx = m_obj ? m_obj->m_plate_index : 1;
|
||||
|
||||
m_local_paths.push_back(m_tmp_path + "Metadata/pick_" + std::to_string(plate_idx) + ".png");
|
||||
m_local_paths.push_back(m_tmp_path + "Metadata/model_settings.config");
|
||||
m_local_paths.push_back(m_tmp_path + "Metadata/slice_info.config");
|
||||
|
||||
m_target_paths.push_back("Metadata/pick_" + std::to_string(plate_idx) + ".png");
|
||||
m_target_paths.push_back("Metadata/model_settings.config");
|
||||
m_target_paths.push_back("Metadata/slice_info.config");
|
||||
|
||||
if (!is_local_file_existed(m_local_paths)) {
|
||||
m_file_sys = boost::make_shared<PrinterFileSystem>();
|
||||
m_file_sys->Attached();
|
||||
m_file_sys->Bind(EVT_STATUS_CHANGED, &PartSkipDialog::OnFileSystemEvent, this);
|
||||
m_file_sys->Bind(EVT_RAMDOWNLOAD, &PartSkipDialog::OnFileSystemResult, this);
|
||||
m_file_sys->Start();
|
||||
} else {
|
||||
m_file_sys->SendExistedFile();
|
||||
}
|
||||
}
|
||||
void PartSkipDialog::fetchUrl(boost::weak_ptr<PrinterFileSystem> wfs)
|
||||
{
|
||||
boost::shared_ptr fs(wfs.lock());
|
||||
if (!fs) return;
|
||||
|
||||
DeviceManager *dm = GUI::wxGetApp().getDeviceManager();
|
||||
MachineObject *obj = dm->get_selected_machine();
|
||||
|
||||
if (obj == nullptr) {
|
||||
fs->SetUrl("0");
|
||||
return;
|
||||
}
|
||||
std::string dev_ver = obj->get_ota_version();
|
||||
std::string dev_id = obj->dev_id;
|
||||
int remote_proto = obj->get_file_remote();
|
||||
|
||||
NetworkAgent *agent = wxGetApp().getAgent();
|
||||
std::string agent_version = agent ? agent->get_version() : "";
|
||||
|
||||
if (agent) {
|
||||
switch (m_url_state) {
|
||||
case URL_TCP: {
|
||||
std::string devIP = obj->dev_ip;
|
||||
std::string accessCode = obj->get_access_code();
|
||||
std::string tcp_url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode;
|
||||
CallAfter([=] {
|
||||
boost::shared_ptr fs(wfs.lock());
|
||||
if (!fs) return;
|
||||
if (boost::algorithm::starts_with(tcp_url, "bambu:///")) {
|
||||
fs->SetUrl(tcp_url);
|
||||
} else {
|
||||
fs->SetUrl("3");
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case URL_TUTK: {
|
||||
std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""};
|
||||
agent->get_camera_url(obj->dev_id + "|" + dev_ver + "|" + protocols[1], [this, wfs, m = dev_id, v = agent->get_version(), dv = dev_ver](std::string url) {
|
||||
if (boost::algorithm::starts_with(url, "bambu:///")) {
|
||||
url += "&device=" + m;
|
||||
url += "&net_ver=" + v;
|
||||
url += "&dev_ver=" + dv;
|
||||
url += "&refresh_url=" + boost::lexical_cast<std::string>(&refresh_agora_url);
|
||||
url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid");
|
||||
url += "&cli_ver=" + std::string(SLIC3R_VERSION);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "SendToPrinter::fetchUrl: camera_url: " << hide_passwd(url, {"?uid=", "authkey=", "passwd="});
|
||||
std::cout << "SendToPrinter::fetchUrl: camera_url: " << hide_passwd(url, {"?uid=", "authkey=", "passwd="});
|
||||
CallAfter([=] {
|
||||
boost::shared_ptr fs(wfs.lock());
|
||||
if (!fs) return;
|
||||
if (boost::algorithm::starts_with(url, "bambu:///")) {
|
||||
fs->SetUrl(url);
|
||||
} else {
|
||||
fs->SetUrl("3");
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PartSkipDialog::OnFileSystemEvent(wxCommandEvent &e)
|
||||
{
|
||||
e.Skip();
|
||||
auto wfs = boost::weak_ptr(m_file_sys);
|
||||
boost::shared_ptr fs(wfs.lock());
|
||||
if (!fs) return;
|
||||
|
||||
wxString msg;
|
||||
int status = e.GetInt();
|
||||
int extra = e.GetExtraLong();
|
||||
|
||||
switch (status) {
|
||||
case PrinterFileSystem::Initializing:
|
||||
case PrinterFileSystem::Connecting: break;
|
||||
case PrinterFileSystem::ListSyncing: {
|
||||
m_file_sys->GetPickImages(m_local_paths, m_target_paths);
|
||||
break;
|
||||
}
|
||||
case PrinterFileSystem::Failed: {
|
||||
m_file_sys->SendConnectFail();
|
||||
break;
|
||||
}
|
||||
case PrinterFileSystem::Reconnecting: break;
|
||||
}
|
||||
if (e.GetInt() == PrinterFileSystem::Initializing) {
|
||||
CallAfter([=] {
|
||||
boost::shared_ptr fs(wfs.lock());
|
||||
if (!fs) return;
|
||||
fetchUrl(boost::weak_ptr(fs));
|
||||
});
|
||||
}
|
||||
}
|
||||
void PartSkipDialog::OnFileSystemResult(wxCommandEvent &event){
|
||||
int result = event.GetInt();
|
||||
if (result == 0) {
|
||||
InitDialogUI();
|
||||
SetSimplebookPage(2);
|
||||
}else{
|
||||
switch (m_url_state) {
|
||||
case URL_TCP: {
|
||||
m_url_state = URL_TUTK;
|
||||
break;
|
||||
}
|
||||
case URL_TUTK: {
|
||||
m_url_state = URL_TCP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
SetSimplebookPage(1);
|
||||
}
|
||||
// wxMilliSleep(1000);
|
||||
// m_loading_icon->Stop();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void PartSkipDialog::InitSchedule(MachineObject *obj){
|
||||
m_obj = obj;
|
||||
SetSimplebookPage(0);
|
||||
// m_loading_icon->Play();
|
||||
DownloadPartsFile();
|
||||
}
|
||||
|
||||
void PartSkipDialog::OnRetryButton(wxCommandEvent &event) {
|
||||
event.Skip();
|
||||
InitSchedule(m_obj);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
|
||||
bool PartSkipDialog::is_drag_mode(){
|
||||
return m_is_drag == true;
|
||||
}
|
||||
|
||||
PartsInfo PartSkipDialog::GetPartsInfo(){
|
||||
PartsInfo parts_info;
|
||||
for (auto [part_id, part_state] : this->m_parts_state)
|
||||
{
|
||||
parts_info.push_back(std::pair<int, PartState>(part_id, part_state));
|
||||
}
|
||||
return parts_info;
|
||||
}
|
||||
|
||||
void PartSkipDialog::OnZoomIn(wxCommandEvent &event){
|
||||
m_canvas->ZoomIn(20);
|
||||
UpdateZoomPercent();
|
||||
}
|
||||
|
||||
void PartSkipDialog::OnZoomOut(wxCommandEvent &event){
|
||||
m_canvas->ZoomOut(20);
|
||||
UpdateZoomPercent();
|
||||
}
|
||||
|
||||
void PartSkipDialog::OnSwitchDrag(wxCommandEvent& event){
|
||||
if (this->is_drag_mode()) {
|
||||
m_is_drag = false;
|
||||
m_switch_drag_btn->SetBackgroundColor(*wxWHITE);
|
||||
m_switch_drag_btn->SetIcon("canvas_drag");
|
||||
} else {
|
||||
m_is_drag = true;
|
||||
m_switch_drag_btn->SetBackgroundColor(wxColour(0, 174, 66));
|
||||
m_switch_drag_btn->SetIcon("canvas_drag_active");
|
||||
}
|
||||
m_canvas->SwitchDrag(m_is_drag);
|
||||
}
|
||||
|
||||
void PartSkipDialog::OnZoomPercent(wxCommandEvent &event) {
|
||||
m_zoom_percent = event.GetInt();
|
||||
if (m_zoom_percent >= 1000) {
|
||||
m_zoom_percent = 1000;
|
||||
m_zoom_in_btn->Enable(false);
|
||||
m_zoom_in_btn->SetIcon("canvas_zoom_in_disable");
|
||||
} else if (m_zoom_percent <= 100) {
|
||||
m_zoom_percent = 100;
|
||||
m_zoom_out_btn->Enable(false);
|
||||
m_zoom_out_btn->SetIcon("canvas_zoom_out_disable");
|
||||
} else {
|
||||
m_zoom_in_btn->Enable(true);
|
||||
m_zoom_out_btn->Enable(true);
|
||||
m_zoom_in_btn->SetIcon("canvas_zoom_in");
|
||||
m_zoom_out_btn->SetIcon("canvas_zoom_out");
|
||||
}
|
||||
|
||||
UpdateZoomPercent();
|
||||
}
|
||||
|
||||
void PartSkipDialog::UpdatePartsStateFromCanvas(wxCommandEvent &event) {
|
||||
int part_id = event.GetExtraLong();
|
||||
PartState part_state = PartState(event.GetInt());
|
||||
|
||||
m_parts_state[part_id] = part_state;
|
||||
if(part_state == psUnCheck){
|
||||
m_all_checkbox->SetValue(false);
|
||||
}
|
||||
if(IsAllChecked()){
|
||||
m_all_checkbox->SetValue(true);
|
||||
}
|
||||
UpdateDialogUI();
|
||||
}
|
||||
|
||||
void PartSkipDialog::UpdateZoomPercent() {
|
||||
m_percent_label->SetLabel(wxString::Format(_L("%d%%"), m_zoom_percent));
|
||||
}
|
||||
|
||||
void PartSkipDialog::UpdateCountLabel() {
|
||||
int check_cnt = 0;
|
||||
int tot_cnt = 0;
|
||||
for (auto [part_id, part_state] : m_parts_state) {
|
||||
if (part_state == PartState::psChecked) check_cnt++;
|
||||
if (part_state != PartState::psSkipped) tot_cnt++;
|
||||
}
|
||||
m_cnt_label->SetLabel(wxString::Format(_L("%d"), check_cnt));
|
||||
m_tot_label->SetLabel(wxString::Format(_L("/%d Selected"), tot_cnt));
|
||||
}
|
||||
|
||||
bool PartSkipDialog::Show(bool show)
|
||||
{
|
||||
if (show) {
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
CentreOnParent();
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
return DPIDialog::Show(show);
|
||||
}
|
||||
|
||||
void PartSkipDialog::InitDialogUI() {
|
||||
m_print_lock = true;
|
||||
m_scroll_sizer->Clear(true);
|
||||
m_all_checkbox->SetValue(false);
|
||||
m_parts_state.clear();
|
||||
m_parts_name.clear();
|
||||
|
||||
string pick_img = m_local_paths[0];
|
||||
string model_settings = m_local_paths[1];
|
||||
|
||||
m_switch_drag_btn->SetIcon("canvas_drag");
|
||||
m_switch_drag_btn->SetBackgroundColor(*wxWHITE);
|
||||
m_is_drag = false;
|
||||
m_canvas->SwitchDrag(false);
|
||||
m_canvas->SetZoomPercent(100);
|
||||
m_canvas->SetOffset(wxPoint(0, 0));
|
||||
m_canvas->LoadPickImage(pick_img);
|
||||
ModelSettingHelper helper(model_settings);
|
||||
|
||||
if (helper.Parse()) {
|
||||
auto parse_result = helper.GetResults();
|
||||
for (const auto& part : parse_result) {
|
||||
m_parts_state[part.identify_id] = part.state;
|
||||
m_parts_name[part.identify_id] = part.name;
|
||||
}
|
||||
if (m_obj) {
|
||||
std::vector<int> partskip_ids = m_obj->m_partskip_ids;
|
||||
for (auto part_id : partskip_ids) {
|
||||
m_parts_state[part_id] = PartState::psSkipped;
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& [part_id, part_state] : m_parts_state) {
|
||||
auto line_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto checkbox = new CheckBox(m_list_view);
|
||||
auto label = new Label(m_list_view, _L("file 0"));
|
||||
|
||||
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, part_id=part_id](wxCommandEvent& event) {
|
||||
m_parts_state[part_id] = event.IsChecked() ? PartState::psChecked : PartState::psUnCheck;
|
||||
if(!event.IsChecked()){
|
||||
m_all_checkbox->SetValue(false);
|
||||
}else if(IsAllChecked()){
|
||||
m_all_checkbox->SetValue(true);
|
||||
}
|
||||
m_canvas->UpdatePartsInfo(GetPartsInfo());
|
||||
UpdateCountLabel();
|
||||
event.Skip();
|
||||
}, checkbox->GetId());
|
||||
|
||||
if (part_state == PartState::psChecked) {
|
||||
checkbox->SetValue(true);
|
||||
checkbox->Enable(true);
|
||||
} else if(part_state == PartState::psUnCheck) {
|
||||
checkbox->SetValue(false);
|
||||
checkbox->Enable(true);
|
||||
} else if (part_state == PartState::psSkipped) {
|
||||
checkbox->SetValue(true);
|
||||
checkbox->Enable(false);
|
||||
}
|
||||
label->SetLabel(wxString::FromUTF8(m_parts_name[part_id]));
|
||||
label->SetBackgroundColour(*wxWHITE);
|
||||
label->SetForegroundColour(wxColor(107,107,107));
|
||||
label->Wrap(-1);
|
||||
checkbox->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
line_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0);
|
||||
line_sizer->Add(label, 1, wxALL | wxALIGN_CENTER_VERTICAL, 0);
|
||||
m_scroll_sizer->Add(line_sizer, 0, wxALL, FromDIP(5));
|
||||
}
|
||||
m_canvas->UpdatePartsInfo(GetPartsInfo());
|
||||
}
|
||||
|
||||
m_scroll_sizer->Layout();
|
||||
UpdateCountLabel();
|
||||
Refresh();
|
||||
m_print_lock = false;
|
||||
}
|
||||
|
||||
void PartSkipDialog::UpdatePartsStateFromPrinter(MachineObject *obj) {
|
||||
if (m_print_lock) return;
|
||||
m_obj = obj;
|
||||
if (m_obj) {
|
||||
std::vector<int> partskip_ids = m_obj->m_partskip_ids;
|
||||
for(auto part_id : partskip_ids) {
|
||||
m_parts_state[part_id] = PartState::psSkipped;
|
||||
}
|
||||
m_canvas->UpdatePartsInfo(GetPartsInfo());
|
||||
UpdateDialogUI();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PartSkipDialog::UpdateDialogUI(){
|
||||
if(m_parts_state.size() != m_scroll_sizer->GetItemCount()){
|
||||
BOOST_LOG_TRIVIAL(warning) << "m_parts_state and m_scroll_sizer mismatch.";
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = m_parts_state.begin(); it != m_parts_state.end(); ++it) {
|
||||
int idx = std::distance(m_parts_state.begin(), it);
|
||||
auto part_id = it->first;
|
||||
auto part_state = it->second;
|
||||
|
||||
wxSizerItem *item = m_scroll_sizer->GetItem(idx);
|
||||
if (item && item->IsSizer()) {
|
||||
wxSizer* sizer = item->GetSizer();
|
||||
auto check_item = sizer->GetItem((size_t)0);
|
||||
|
||||
if (check_item && check_item->IsWindow())
|
||||
{
|
||||
wxWindow *window = check_item->GetWindow();
|
||||
CheckBox *checkbox = dynamic_cast<CheckBox *>(window);
|
||||
if (part_state == PartState::psChecked) {
|
||||
checkbox->SetValue(true);
|
||||
} else if (part_state == PartState::psUnCheck) {
|
||||
checkbox->SetValue(false);
|
||||
} else {
|
||||
checkbox->SetValue(true);
|
||||
checkbox->Enable(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCountLabel();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void PartSkipDialog::SetSimplebookPage(int page) {
|
||||
m_simplebook->SetSelection(page);
|
||||
}
|
||||
|
||||
bool PartSkipDialog::IsAllChecked() {
|
||||
for (auto& [part_id, part_state] : m_parts_state) {
|
||||
if (part_state == PartState::psUnCheck)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PartSkipDialog::OnAllCheckbox(wxCommandEvent &event)
|
||||
{
|
||||
if (m_all_checkbox->GetValue()) {
|
||||
for (auto& [part_id, part_state] : m_parts_state) {
|
||||
if (part_state == PartState::psUnCheck)
|
||||
part_state = PartState::psChecked;
|
||||
}
|
||||
}
|
||||
else{
|
||||
for (auto& [part_id, part_state] : m_parts_state) {
|
||||
if (part_state == PartState::psChecked)
|
||||
part_state = PartState::psUnCheck;
|
||||
}
|
||||
}
|
||||
m_canvas->UpdatePartsInfo(GetPartsInfo());
|
||||
UpdateDialogUI();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
|
||||
void PartSkipDialog::OnApplyDialog(wxCommandEvent &event)
|
||||
{
|
||||
event.Skip();
|
||||
m_partskip_ids.clear();
|
||||
for (const auto& [part_id, part_state] : m_parts_state) {
|
||||
if (part_state == PartState::psChecked) {
|
||||
m_partskip_ids.push_back(part_id);
|
||||
}
|
||||
}
|
||||
PartSkipConfirmDialog confirm_dialog(this);
|
||||
confirm_dialog.SetMsgLabel(wxString::Format(_L("Skipping %d objects."), m_partskip_ids.size()));
|
||||
|
||||
if(confirm_dialog.ShowModal() == wxID_OK){
|
||||
if (m_obj) {
|
||||
BOOST_LOG_TRIVIAL(info) << "monitor: skipping "<< m_partskip_ids.size() <<" parts dev_id =" << m_obj->dev_id;
|
||||
|
||||
bool all_skipped = true;
|
||||
for (auto [part_id, part_state] : m_parts_state) {
|
||||
if (part_state == PartState::psUnCheck) all_skipped = false;
|
||||
}
|
||||
|
||||
if (all_skipped) {
|
||||
m_obj->command_task_abort();
|
||||
} else {
|
||||
m_obj->command_task_partskip(m_partskip_ids);
|
||||
}
|
||||
EndModal(wxID_OK);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << "machine object is null.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PartSkipConfirmDialog::PartSkipConfirmDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _L("Skip Objects"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
|
||||
{
|
||||
std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % Slic3r::resources_dir()).str();
|
||||
SetIcon(wxIcon(Slic3r::encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
SetSizeHints(wxDefaultSize, wxDefaultSize);
|
||||
|
||||
wxBoxSizer* m_sizer;
|
||||
m_sizer = new wxBoxSizer( wxVERTICAL );
|
||||
|
||||
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
|
||||
m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA));
|
||||
m_sizer->Add(m_line_top, 0, wxEXPAND, 0);
|
||||
m_sizer->Add(0, 0, 0, wxALL, FromDIP(15));
|
||||
|
||||
m_msg_label = new Label( this, wxString(_L("Skipping objects.")));
|
||||
m_msg_label->Wrap( -1 );
|
||||
m_msg_label->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
m_sizer->Add(m_msg_label, 0, wxLEFT, FromDIP(15));
|
||||
|
||||
auto m_tip_label = new Label(this, wxString(_("This action cannot be undone. Continue?")));
|
||||
m_tip_label->Wrap(-1);
|
||||
m_tip_label->SetBackgroundColour(*wxWHITE);
|
||||
m_tip_label->SetForegroundColour(wxColor(92,92,92));
|
||||
m_sizer->Add(m_tip_label, 0, wxLEFT | wxTOP, FromDIP(15));
|
||||
|
||||
wxBoxSizer* m_button_sizer;
|
||||
m_button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_button_sizer->SetMinSize(wxSize(FromDIP(480), FromDIP(54)));
|
||||
m_button_sizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
|
||||
|
||||
StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed), std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
|
||||
|
||||
StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed), std::pair<wxColour, int>(wxColour(61, 203, 115), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(wxColour(0, 177, 66), StateColor::Normal));
|
||||
|
||||
m_apply_button = new Button(this, wxString(_L("Continue")));
|
||||
m_apply_button->SetBackgroundColor(btn_bg_green);
|
||||
m_apply_button->SetTextColor(*wxWHITE);
|
||||
// m_apply_button->SetBorderColor(wxColour(38, 46, 48));
|
||||
m_apply_button->SetFont(Label::Body_14);
|
||||
m_apply_button->SetSize(wxSize(FromDIP(80), FromDIP(32)));
|
||||
m_apply_button->SetMinSize(wxSize(FromDIP(80), FromDIP(32)));
|
||||
m_apply_button->SetCornerRadius(FromDIP(16));
|
||||
m_apply_button->Bind(wxEVT_BUTTON, [this](auto& e){
|
||||
EndModal(wxID_OK);
|
||||
e.Skip();
|
||||
});
|
||||
|
||||
m_button_sizer->Add(m_apply_button, 0, wxALL, FromDIP(15));
|
||||
m_sizer->Add(m_button_sizer, 1, wxEXPAND, FromDIP(5));
|
||||
m_sizer->Fit( this );
|
||||
|
||||
SetSizer( m_sizer );
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
PartSkipConfirmDialog::~PartSkipConfirmDialog()
|
||||
{
|
||||
}
|
||||
|
||||
bool PartSkipConfirmDialog::Show(bool show)
|
||||
{
|
||||
if (show) {
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
CentreOnParent();
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
return DPIDialog::Show(show);
|
||||
}
|
||||
|
||||
void PartSkipConfirmDialog::on_dpi_changed(const wxRect& suggested_rect) { Fit(); }
|
||||
|
||||
|
||||
Button* PartSkipConfirmDialog::GetConfirmButton()
|
||||
{
|
||||
return m_apply_button;
|
||||
}
|
||||
|
||||
void PartSkipConfirmDialog::SetMsgLabel(wxString msg){
|
||||
m_msg_label->SetLabel(msg);
|
||||
}
|
||||
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
153
src/slic3r/GUI/PartSkipDialog.hpp
Normal file
153
src/slic3r/GUI/PartSkipDialog.hpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#include <wx/panel.h>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/webrequest.h>
|
||||
#include <wx/control.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/display.h>
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/sstream.h>
|
||||
#include <wx/zstream.h>
|
||||
#include <wx/window.h>
|
||||
#include <wx/dcgraph.h>
|
||||
#include <wx/simplebook.h>
|
||||
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/Button.hpp"
|
||||
#include "Widgets/AnimaController.hpp"
|
||||
#include "DeviceManager.hpp"
|
||||
#include "PartSkipCommon.hpp"
|
||||
#include "Printer/PrinterFileSystem.h"
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class SkipPartCanvas;
|
||||
|
||||
enum URL_STATE {
|
||||
URL_TCP,
|
||||
URL_TUTK,
|
||||
};
|
||||
|
||||
class PartSkipConfirmDialog : public DPIDialog
|
||||
{
|
||||
private:
|
||||
protected:
|
||||
Label *m_msg_label;
|
||||
Button *m_apply_button;
|
||||
|
||||
public:
|
||||
PartSkipConfirmDialog(wxWindow *parent);
|
||||
~PartSkipConfirmDialog();
|
||||
|
||||
void on_dpi_changed(const wxRect &suggested_rect);
|
||||
Button* GetConfirmButton();
|
||||
void SetMsgLabel(wxString msg);
|
||||
bool Show(bool show);
|
||||
};
|
||||
|
||||
class PartSkipDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
PartSkipDialog(wxWindow *parent);
|
||||
~PartSkipDialog();
|
||||
void on_dpi_changed(const wxRect &suggested_rect);
|
||||
bool Show(bool show);
|
||||
|
||||
void UpdatePartsStateFromPrinter(MachineObject *obj_);
|
||||
void SetSimplebookPage(int page);
|
||||
void InitSchedule(MachineObject *obj_);
|
||||
void InitDialogUI();
|
||||
|
||||
|
||||
MachineObject *m_obj{nullptr};
|
||||
|
||||
wxSimplebook* m_simplebook;
|
||||
wxPanel* m_book_third_panel;
|
||||
wxPanel* m_book_second_panel;
|
||||
wxPanel* m_book_first_panel;
|
||||
|
||||
SkipPartCanvas* m_canvas;
|
||||
Button* m_zoom_in_btn;
|
||||
Button* m_zoom_out_btn;
|
||||
Button* m_switch_drag_btn;
|
||||
CheckBox* m_all_checkbox;
|
||||
Button* m_percent_label;
|
||||
Label * m_all_label;
|
||||
wxPanel* m_line;
|
||||
wxScrolledWindow* m_list_view;
|
||||
|
||||
Label* m_cnt_label;
|
||||
Label* m_tot_label;
|
||||
|
||||
Button* m_apply_btn;
|
||||
|
||||
Label* m_loading_label;
|
||||
Label* m_retry_label;
|
||||
|
||||
wxBoxSizer* m_sizer;
|
||||
wxBoxSizer* m_dlg_sizer;
|
||||
wxBoxSizer* m_dlg_content_sizer;
|
||||
wxBoxSizer* m_dlg_btn_sizer;
|
||||
wxBoxSizer* m_canvas_sizer;
|
||||
wxBoxSizer* m_canvas_btn_sizer;
|
||||
wxBoxSizer* m_list_sizer;
|
||||
wxBoxSizer* m_scroll_sizer;
|
||||
wxBoxSizer* m_book_first_sizer;
|
||||
wxBoxSizer* m_book_second_sizer;
|
||||
wxBoxSizer* m_book_second_btn_sizer;
|
||||
Button* m_second_retry_btn;
|
||||
AnimaIcon* m_loading_icon;
|
||||
|
||||
private:
|
||||
int m_zoom_percent{100};
|
||||
bool m_is_drag{false};
|
||||
bool m_print_lock{true};
|
||||
|
||||
std::map<uint32_t, PartState> m_parts_state;
|
||||
std::map<uint32_t, std::string> m_parts_name;
|
||||
std::vector<int> m_partskip_ids;
|
||||
|
||||
enum URL_STATE m_url_state = URL_STATE::URL_TCP;
|
||||
|
||||
PartsInfo GetPartsInfo();
|
||||
bool is_drag_mode();
|
||||
|
||||
boost::shared_ptr<PrinterFileSystem> m_file_sys;
|
||||
bool m_file_sys_result{false};
|
||||
std::string m_timestamp;
|
||||
std::string m_tmp_path;
|
||||
std::vector<string> m_local_paths;
|
||||
std::vector<string> m_target_paths;
|
||||
std::string create_tmp_path();
|
||||
|
||||
bool is_local_file_existed(const std::vector<string> &local_paths);
|
||||
|
||||
void DownloadPartsFile();
|
||||
void OnFileSystemEvent(wxCommandEvent &event);
|
||||
void OnFileSystemResult(wxCommandEvent &event);
|
||||
void fetchUrl(boost::weak_ptr<PrinterFileSystem> wfs);
|
||||
|
||||
|
||||
void OnZoomIn(wxCommandEvent &event);
|
||||
void OnZoomOut(wxCommandEvent &event);
|
||||
void OnSwitchDrag(wxCommandEvent &event);
|
||||
void OnZoomPercent(wxCommandEvent &event);
|
||||
void UpdatePartsStateFromCanvas(wxCommandEvent &event);
|
||||
|
||||
void UpdateZoomPercent();
|
||||
void UpdateCountLabel();
|
||||
void UpdateDialogUI();
|
||||
bool IsAllChecked();
|
||||
|
||||
void OnRetryButton(wxCommandEvent &event);
|
||||
void OnAllCheckbox(wxCommandEvent &event);
|
||||
void OnApplyDialog(wxCommandEvent &event);
|
||||
};
|
||||
|
||||
}}
|
||||
@@ -39,6 +39,7 @@ wxDEFINE_EVENT(EVT_FILE_CHANGED, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_SELECT_CHANGED, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_THUMBNAIL, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_DOWNLOAD, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_RAMDOWNLOAD, wxCommandEvent);
|
||||
|
||||
wxDEFINE_EVENT(EVT_FILE_CALLBACK, wxCommandEvent);
|
||||
|
||||
@@ -248,6 +249,131 @@ struct PrinterFileSystem::Download : Progress
|
||||
boost::uuids::detail::md5 boost_md5;
|
||||
};
|
||||
|
||||
|
||||
void PrinterFileSystem::GetPickImages(const std::vector<std::string> &local_paths, const std::vector<std::string> &targetpaths)
|
||||
{
|
||||
|
||||
GetPickImage(1, local_paths[0], targetpaths[0]);
|
||||
GetPickImage(2, local_paths[1], targetpaths[1]);
|
||||
GetPickImage(3, local_paths[2], targetpaths[2]);
|
||||
|
||||
}
|
||||
|
||||
void PrinterFileSystem::GetPickImage(int id, const std::string &local_path, const std::string &targetpath)
|
||||
{
|
||||
json j;
|
||||
|
||||
j["sequence_id"] = id;
|
||||
j["version"] = 1;
|
||||
j["peer_host"] = "studio";
|
||||
j["command"] = "get_project_file";
|
||||
j["file_rel_path"] = targetpath;
|
||||
|
||||
std::string param = j.dump();
|
||||
|
||||
DownloadRamFile(16, local_path, param);
|
||||
}
|
||||
|
||||
|
||||
void PrinterFileSystem::DownloadRamFile(int index, const std::string &local_path, const std::string & param)
|
||||
{
|
||||
std::shared_ptr<Download> 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<Progress>(
|
||||
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<size_t>();
|
||||
std::string json_str(reinterpret_cast<const char *>(data), s);
|
||||
// OutputDebugStringA(json_str.c_str());
|
||||
// OutputDebugStringA("\n");
|
||||
json mem_dl_json = json::parse(json_str);
|
||||
// download->mem_dl_param_size = size;
|
||||
if (!mem_dl_json.contains("result") || mem_dl_json["result"] == 1 ) {
|
||||
wxLogWarning("Download failed: result = 1");
|
||||
return ERROR_JSON;
|
||||
}
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
if (prog.size == 0 ) {
|
||||
download->ofs.open(download->local_path, std::ios::binary);
|
||||
if (!download->ofs) {
|
||||
download->error = last_system_error();
|
||||
wxLogWarning("DownloadImageFromRam open error: %s\n", wxString::FromUTF8(download->error));
|
||||
return FILE_OPEN_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
download->ofs.write(reinterpret_cast<const char *>(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<const char *>(&digest[0]);
|
||||
boost::algorithm::hex(char_digest, char_digest + sizeof(digest), std::back_inserter(str_md5));
|
||||
if (!boost::iequals(str_md5, md5)) {
|
||||
wxLogWarning("DownloadImageFromRam checksum error: %s != %s\n", str_md5, md5);
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::rename(download->local_path, download->local_path + ".tmp", ec);
|
||||
return FILE_CHECK_ERR;
|
||||
}
|
||||
return SUCCESS;
|
||||
},
|
||||
|
||||
[this, download](int result, Progress const &data) {
|
||||
//OutputDebugStringA(std::to_string(result).c_str());
|
||||
//OutputDebugStringA("\n");
|
||||
if (result == CONTINUE) { return; }
|
||||
std::string msg;
|
||||
if (result == SUCCESS) {
|
||||
wxLogMessage("DownloadImageFromRam finished: %s", download->local_path);
|
||||
msg = "SUCCESS";
|
||||
SendChangedEvent(EVT_RAMDOWNLOAD, result, result ? download->error : download->local_path);
|
||||
} else if (result != CONTINUE) {
|
||||
wxLogWarning("DownloadImageFromRam failed: %s", download->error);
|
||||
msg = "ERROR";
|
||||
SendChangedEvent(EVT_RAMDOWNLOAD, result, result ? download->error : download->local_path);
|
||||
}
|
||||
},param);
|
||||
}
|
||||
|
||||
void PrinterFileSystem::SendExistedFile(){
|
||||
SendChangedEvent(EVT_RAMDOWNLOAD, SUCCESS);
|
||||
}
|
||||
void PrinterFileSystem::SendConnectFail(){
|
||||
SendChangedEvent(EVT_RAMDOWNLOAD, ERROR_PIPE);
|
||||
}
|
||||
|
||||
|
||||
void PrinterFileSystem::DownloadFiles(size_t index, std::string const &path)
|
||||
{
|
||||
if (index == (size_t) -1) {
|
||||
@@ -281,6 +407,10 @@ void PrinterFileSystem::DownloadFiles(size_t index, std::string const &path)
|
||||
DownloadNextFile();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void PrinterFileSystem::DownloadCheckFiles(std::string const &path)
|
||||
{
|
||||
for (size_t i = 0; i < m_file_list.size(); ++i) {
|
||||
@@ -706,7 +836,7 @@ void PrinterFileSystem::DownloadNextFile()
|
||||
file.download.reset(), file.flags &= ~FF_DOWNLOAD;
|
||||
else // FAILED
|
||||
file.download.reset();
|
||||
if (&file_index.first == &m_file_list)
|
||||
if (&file_index.first == &m_file_list)
|
||||
SendChangedEvent(EVT_DOWNLOAD, download->index, result ? download->error : file.local_path, result);
|
||||
}
|
||||
}
|
||||
@@ -1030,7 +1160,7 @@ void PrinterFileSystem::DumpLog(void * thiz, int, tchar const *msg)
|
||||
static_cast<PrinterFileSystem*>(thiz)->Bambu_FreeLogMsg(msg);
|
||||
}
|
||||
|
||||
boost::uint32_t PrinterFileSystem::SendRequest(int type, json const &req, callback_t2 const &callback)
|
||||
boost::uint32_t PrinterFileSystem::SendRequest(int type, json const &req, callback_t2 const &callback,const std::string& param)
|
||||
{
|
||||
if (m_session.tunnel == nullptr) {
|
||||
Retry();
|
||||
@@ -1044,6 +1174,13 @@ boost::uint32_t PrinterFileSystem::SendRequest(int type, json const &req, callba
|
||||
root["req"] = req;
|
||||
std::ostringstream oss;
|
||||
oss << root;
|
||||
|
||||
if (!param.empty()) {
|
||||
oss << "\n\n";
|
||||
oss << param;
|
||||
}
|
||||
// OutputDebugStringA(oss.str().c_str());
|
||||
// OutputDebugStringA("\n");
|
||||
auto msg = oss.str();
|
||||
boost::unique_lock l(m_mutex);
|
||||
m_messages.push_back(msg);
|
||||
|
||||
@@ -22,6 +22,7 @@ wxDECLARE_EVENT(EVT_FILE_CHANGED, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_SELECT_CHANGED, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_THUMBNAIL, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_DOWNLOAD, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_RAMDOWNLOAD, wxCommandEvent);
|
||||
|
||||
class PrinterFileSystem : public wxEvtHandler, public boost::enable_shared_from_this<PrinterFileSystem>, BambuLib
|
||||
{
|
||||
@@ -138,6 +139,17 @@ public:
|
||||
|
||||
void DownloadFiles(size_t index, std::string const &path);
|
||||
|
||||
void GetPickImage(int id, const std::string &local_path, const std::string &path);
|
||||
|
||||
void GetPickImages(const std::vector<std::string> &local_paths, const std::vector<std::string> &targetpaths);
|
||||
|
||||
|
||||
void DownloadRamFile(int index, const std::string &local_path, const std::string ¶m);
|
||||
|
||||
void SendExistedFile();
|
||||
|
||||
void SendConnectFail();
|
||||
|
||||
void DownloadCheckFiles(std::string const &path);
|
||||
|
||||
bool DownloadCheckFile(size_t index);
|
||||
@@ -218,8 +230,7 @@ private:
|
||||
|
||||
typedef std::function<int(int, json const &resp, unsigned char const *data)> callback_t2;
|
||||
|
||||
template <typename T>
|
||||
boost::uint32_t SendRequest(int type, json const& req, Translator<T> const& translator, Callback<T> const& callback)
|
||||
template<typename T> boost::uint32_t SendRequest(int type, json const &req, Translator<T> const &translator, Callback<T> const &callback, const std::string ¶m = "")
|
||||
{
|
||||
auto c = [translator, callback, this](int result, json const &resp, unsigned char const *data) -> int
|
||||
{
|
||||
@@ -236,7 +247,7 @@ private:
|
||||
PostCallback<T>(callback, result, t);
|
||||
return result;
|
||||
};
|
||||
return SendRequest(type, req, c);
|
||||
return SendRequest(type, req, c, param);
|
||||
}
|
||||
|
||||
template<typename T> using Applier = std::function<void(T const &)>;
|
||||
@@ -266,7 +277,7 @@ private:
|
||||
InstallNotify(type, c);
|
||||
}
|
||||
|
||||
boost::uint32_t SendRequest(int type, json const &req, callback_t2 const &callback);
|
||||
boost::uint32_t SendRequest(int type, json const &req, callback_t2 const &callback, const std::string ¶m = "");
|
||||
|
||||
void InstallNotify(int type, callback_t2 const &callback);
|
||||
|
||||
|
||||
628
src/slic3r/GUI/SkipPartCanvas.cpp
Normal file
628
src/slic3r/GUI/SkipPartCanvas.cpp
Normal file
@@ -0,0 +1,628 @@
|
||||
#include <GL/glew.h>
|
||||
#include "SkipPartCanvas.hpp"
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <expat.h>
|
||||
#include <earcut/earcut.hpp>
|
||||
#include <libslic3r/Color.hpp>
|
||||
|
||||
wxDEFINE_EVENT(EVT_ZOOM_PERCENT, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_CANVAS_PART, wxCommandEvent);
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
SkipPartCanvas::SkipPartCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs)
|
||||
: wxGLCanvas(parent, dispAttrs) {
|
||||
context_ = new wxGLContext(this);
|
||||
this->Bind(wxEVT_PAINT, &SkipPartCanvas::OnPaint, this);
|
||||
this->Bind(wxEVT_MOUSEWHEEL, &SkipPartCanvas::OnMouseWheel, this);
|
||||
this->Bind(wxEVT_LEFT_DOWN, &SkipPartCanvas::OnMouseLeftDown, this);
|
||||
this->Bind(wxEVT_LEFT_DCLICK, &SkipPartCanvas::OnMouseLeftDown, this);
|
||||
this->Bind(wxEVT_LEFT_UP, &SkipPartCanvas::OnMouseLeftUp, this);
|
||||
this->Bind(wxEVT_RIGHT_DOWN, &SkipPartCanvas::OnMouseRightDown, this);
|
||||
this->Bind(wxEVT_RIGHT_UP, &SkipPartCanvas::OnMouseRightUp, this);
|
||||
this->Bind(wxEVT_SIZE, &SkipPartCanvas::OnSize, this);
|
||||
this->Bind(wxEVT_MOTION, &SkipPartCanvas::OnMouseMotion, this);
|
||||
}
|
||||
|
||||
void SkipPartCanvas::LoadPickImage(const std::string & path)
|
||||
{
|
||||
parts_state_.clear();
|
||||
int preffered_w{FromDIP(400)}, preffered_h{FromDIP(400)};
|
||||
cv::Mat src_image = cv::imread(path, cv::IMREAD_UNCHANGED);
|
||||
float zoom_x{static_cast<float>(preffered_w) / src_image.cols};
|
||||
float zoom_y{static_cast<float>(preffered_h) / src_image.rows};
|
||||
float image_scale{0};
|
||||
if (abs(zoom_x - 1) > abs(zoom_y - 1))
|
||||
image_scale = zoom_x;
|
||||
else
|
||||
image_scale = zoom_y;
|
||||
cv::resize(src_image, pick_image_, cv::Size(), image_scale, image_scale);
|
||||
std::vector<cv::Mat> channels;
|
||||
cv::split(pick_image_, channels); // channels[3] 是 Alpha
|
||||
cv::Mat alpha = channels[3];
|
||||
cv::Mat mask;
|
||||
cv::threshold(alpha, mask, 0, 255, cv::THRESH_BINARY);
|
||||
std::vector<std::vector<cv::Point>> pick_counters;
|
||||
cv::findContours(mask, pick_counters, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_KCOS);
|
||||
std::vector<std::vector<FloatPoint>> polygon;
|
||||
for (const auto& counter : pick_counters) {
|
||||
cv::Point center_pos{0,0};
|
||||
for (const auto& pt : counter) {
|
||||
center_pos += pt;
|
||||
}
|
||||
center_pos = center_pos / static_cast<int>(counter.size());
|
||||
auto id = GetIdAtImagePt(wxPoint(center_pos.x, center_pos.y));
|
||||
if (id > 0) {
|
||||
polygon.clear();
|
||||
polygon.emplace_back(std::vector<FloatPoint>{});
|
||||
for (const auto& pt : counter) {
|
||||
polygon[0].push_back(FloatPoint{pt.x * 1.0f, pt.y * 1.0f});
|
||||
}
|
||||
std::vector<uint32_t> indices = mapbox::earcut<uint32_t>(polygon);
|
||||
std::vector<FloatPoint> final_counter{};
|
||||
for (size_t i = 0; i < indices.size(); i += 3) {
|
||||
FloatPoint a = polygon[0][indices[i]];
|
||||
FloatPoint b = polygon[0][indices[i+1]];
|
||||
FloatPoint c = polygon[0][indices[i+2]];
|
||||
final_counter.push_back(FloatPoint{a[0], a[1]});
|
||||
final_counter.push_back(FloatPoint{b[0], b[1]});
|
||||
final_counter.push_back(FloatPoint{c[0], c[1]});
|
||||
}
|
||||
parts_triangles_.emplace(id, final_counter);
|
||||
pick_parts_.emplace(id, counter);
|
||||
if (parts_state_.find(id) == parts_state_.end())
|
||||
parts_state_.emplace(id, psUnCheck);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkipPartCanvas::ZoomIn(const int zoom_percent)
|
||||
{
|
||||
SetZoomPercent(zoom_percent_ + zoom_percent);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void SkipPartCanvas::ZoomOut(const int zoom_percent)
|
||||
{
|
||||
SetZoomPercent(zoom_percent_ - zoom_percent);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void SkipPartCanvas::SwitchDrag(const bool drag_on)
|
||||
{
|
||||
fixed_draging_ = drag_on;
|
||||
AutoSetCursor();
|
||||
}
|
||||
|
||||
|
||||
void SkipPartCanvas::UpdatePartsInfo(const PartsInfo& parts)
|
||||
{
|
||||
for (auto const& part : parts) {
|
||||
if (auto res = parts_state_.find(part.first); res != parts_state_.end())
|
||||
res->second = part.second;
|
||||
}
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void DrawRoundedRect(float x, float y, float width, float height, float radius, const ColorRGB& color, int segments = 16)
|
||||
{
|
||||
glColor3f(color.r(), color.g(), color.b());
|
||||
|
||||
// 1. Draw center rectangle
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(x + radius, y + radius);
|
||||
glVertex2f(x + width - radius, y + radius);
|
||||
glVertex2f(x + width - radius, y + height - radius);
|
||||
glVertex2f(x + radius, y + height - radius);
|
||||
glEnd();
|
||||
|
||||
// 2. Draw side rectangles (excluding corners)
|
||||
glBegin(GL_QUADS);
|
||||
// Left
|
||||
glVertex2f(x, y + radius);
|
||||
glVertex2f(x + radius, y + radius);
|
||||
glVertex2f(x + radius, y + height - radius);
|
||||
glVertex2f(x, y + height - radius);
|
||||
|
||||
// Right
|
||||
glVertex2f(x + width - radius, y + radius);
|
||||
glVertex2f(x + width, y + radius);
|
||||
glVertex2f(x + width, y + height - radius);
|
||||
glVertex2f(x + width - radius, y + height - radius);
|
||||
|
||||
// Top
|
||||
glVertex2f(x + radius, y + height - radius);
|
||||
glVertex2f(x + width - radius, y + height - radius);
|
||||
glVertex2f(x + width - radius, y + height);
|
||||
glVertex2f(x + radius, y + height);
|
||||
|
||||
// Bottom
|
||||
glVertex2f(x + radius, y);
|
||||
glVertex2f(x + width - radius, y);
|
||||
glVertex2f(x + width - radius, y + radius);
|
||||
glVertex2f(x + radius, y + radius);
|
||||
glEnd();
|
||||
|
||||
// 3. Draw corners
|
||||
auto drawCorner = [&](float cx, float cy, float startAngle) {
|
||||
glBegin(GL_TRIANGLE_FAN);
|
||||
glVertex2f(cx, cy);
|
||||
for (int i = 0; i <= segments; ++i) {
|
||||
float angle = startAngle + (M_PI * 0.5f) * (float)i / segments;
|
||||
glVertex2f(cx + cosf(angle) * radius, cy + sinf(angle) * radius);
|
||||
}
|
||||
glEnd();
|
||||
};
|
||||
|
||||
drawCorner(x + radius, y + radius, M_PI); // bottom-left
|
||||
drawCorner(x + width - radius, y + radius, 1.5f * M_PI); // bottom-right
|
||||
drawCorner(x + width - radius, y + height - radius, 0.0f); // top-right
|
||||
drawCorner(x + radius, y + height - radius, 0.5f * M_PI); // top-left
|
||||
}
|
||||
|
||||
|
||||
void SkipPartCanvas::Render()
|
||||
{
|
||||
constexpr float border_w = 3.f;
|
||||
constexpr int uncheckd_stencil =1;
|
||||
constexpr int checkd_stencil = 2;
|
||||
constexpr int skipped_stencil = 3;
|
||||
|
||||
SetCurrent(*context_);
|
||||
glPushAttrib(GL_ALL_ATTRIB_BITS);
|
||||
|
||||
int w, h;
|
||||
GetClientSize(&w, &h);
|
||||
glViewport(0, 0, w, h);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
auto view_rect = ViewPtToImagePt(wxPoint(w, h));
|
||||
glOrtho(offset_.x, view_rect.x, view_rect.y, offset_.y, -1, 1);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
glClearColor(parent_color_.r(), parent_color_.g(), parent_color_.b(), 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
float rx = offset_.x;
|
||||
float ry = offset_.y;
|
||||
float rw = view_rect.x - offset_.x;
|
||||
float rh = view_rect.y - offset_.y;
|
||||
float radius = std::min(rw, rh) * 0.05f;
|
||||
|
||||
DrawRoundedRect(rx, ry, rw, rh, radius, ColorRGB{0.9f, 0.9f, 0.9f});
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
|
||||
auto draw_shape = [this, border_w](const int stencil, const PartState part_type, const ColorRGB& rgb) {
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
glStencilFunc(GL_ALWAYS, stencil, 0xFF);
|
||||
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
|
||||
|
||||
for (const auto& contour : parts_triangles_) {
|
||||
auto part_info = parts_state_.find(contour.first);
|
||||
if (part_info == parts_state_.end() || part_info->second != part_type)
|
||||
continue;
|
||||
glColor3f(1, 1, 1);
|
||||
glBegin(GL_TRIANGLES);
|
||||
for (size_t i = 0; i < contour.second.size(); i += 3) {
|
||||
glVertex2f(contour.second[i][0], contour.second[i][1]);
|
||||
glVertex2f(contour.second[i+1][0], contour.second[i+1][1]);
|
||||
glVertex2f(contour.second[i+2][0], contour.second[i+2][1]);
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
|
||||
for (const auto& contour : pick_parts_) {
|
||||
if (contour.first != this->hover_id_) continue;
|
||||
auto part_info = parts_state_.find(contour.first);
|
||||
if (part_info == parts_state_.end() || part_info->second != part_type)
|
||||
continue;
|
||||
|
||||
glColor3f(rgb.r(), rgb.g(), rgb.b());
|
||||
glLineWidth(border_w);
|
||||
glBegin(GL_LINE_LOOP);
|
||||
for (const auto& pt : contour.second) {
|
||||
glVertex2f(pt.x, pt.y);
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
};
|
||||
// draw unchecked shapes
|
||||
// stencil1 => unchecked
|
||||
draw_shape(uncheckd_stencil, psUnCheck, ColorRGB{0, 174 / 255.f, 66 / 255.f});
|
||||
|
||||
// draw checked shapes
|
||||
// stencil2 => checked
|
||||
draw_shape(checkd_stencil, psChecked, ColorRGB{208 / 255.f, 27 / 255.f, 66 / 255.f});
|
||||
|
||||
// draw skipped shapes
|
||||
// stencil3 => skipped
|
||||
draw_shape(skipped_stencil, psSkipped, ColorRGB{95 / 255.f, 95 / 255.f, 95 / 255.f});
|
||||
|
||||
auto draw_mask = [this, view_rect, border_w, w, h](const int stencil, const PartState part_type,
|
||||
const ColorRGB& background, const ColorRGB& line, const ColorRGB& bound) {
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
glStencilFunc(GL_EQUAL, stencil, 0xFF);
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // Don't change stencil
|
||||
glColor3f(background.r(), background.g(), background.b());
|
||||
glBegin(GL_POLYGON);
|
||||
glVertex2f(offset_.x, offset_.y);
|
||||
glVertex2f(offset_.x, view_rect.y);
|
||||
glVertex2f(view_rect.x, view_rect.y);
|
||||
glVertex2f(view_rect.x, offset_.y);
|
||||
glEnd();
|
||||
// draw main color
|
||||
glColor3f(line.r(), line.g(), line.b());
|
||||
// re-draw shape bound
|
||||
for (const auto& contour : pick_parts_) {
|
||||
auto part_info = parts_state_.find(contour.first);
|
||||
if (part_info == parts_state_.end() || part_info->second != part_type)
|
||||
continue;
|
||||
glColor3f(bound.r(), bound.g(), bound.b());
|
||||
glLineWidth(border_w);
|
||||
glBegin(GL_LINE_LOOP);
|
||||
for (const auto& pt : contour.second) {
|
||||
glVertex2f(pt.x, pt.y);
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
};
|
||||
|
||||
draw_mask(checkd_stencil, psChecked, ColorRGB{239 / 255.f, 175 / 255.f, 175 / 255.f},
|
||||
ColorRGB{225 / 255.f, 71 / 255.f, 71 / 255.f}, ColorRGB{208 / 255.f, 27 / 255.f, 27 / 255.f});
|
||||
|
||||
draw_mask(skipped_stencil, psSkipped, ColorRGB{159 / 255.f, 159 / 255.f, 159 / 255.f},
|
||||
ColorRGB{95 / 255.f, 95 / 255.f, 95 / 255.f}, ColorRGB{95 / 255.f, 95 / 255.f, 95 / 255.f});
|
||||
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
glPopAttrib();
|
||||
glFlush();
|
||||
}
|
||||
|
||||
void SkipPartCanvas::DebugLogLine(std::string str)
|
||||
{
|
||||
//if (!log_ctrl)
|
||||
// return;
|
||||
//log_ctrl->AppendText(str + "\n");
|
||||
}
|
||||
|
||||
void SkipPartCanvas::SendSelectEvent(int id, PartState state) {
|
||||
wxCommandEvent evt(EVT_CANVAS_PART);
|
||||
evt.SetExtraLong(id);
|
||||
evt.SetInt(static_cast<int>(state));
|
||||
wxPostEvent(this, evt);
|
||||
}
|
||||
void SkipPartCanvas::SendZoomEvent(int zoom_percent) {
|
||||
wxCommandEvent evt(EVT_ZOOM_PERCENT);
|
||||
evt.SetInt(zoom_percent_);
|
||||
wxPostEvent(this, evt);
|
||||
}
|
||||
|
||||
inline double SkipPartCanvas::Zoom() const
|
||||
{
|
||||
return zoom_percent_ / 100.0f;
|
||||
}
|
||||
|
||||
inline wxPoint SkipPartCanvas::ViewPtToImagePt(const wxPoint& view_pt) const
|
||||
{
|
||||
return wxPoint(view_pt.x / Zoom(), view_pt.y / Zoom()) + offset_;
|
||||
}
|
||||
|
||||
uint32_t SkipPartCanvas::GetIdAtImagePt(const wxPoint& image_pt) const
|
||||
{
|
||||
if (image_pt.x >= 0 && image_pt.x < pick_image_.cols
|
||||
&& image_pt.y >= 0 && image_pt.y < pick_image_.rows) {
|
||||
// at(row, col)=>at(y, x)
|
||||
cv::Vec4b bgr = pick_image_.at<cv::Vec4b>(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<int>(w * (1 - 1 / Zoom())) >= 0 ? static_cast<int>(w * (1 - 1 / Zoom())) : 0;
|
||||
int max_h = static_cast<int>(w * (1 - 1 / Zoom())) >= 0 ? static_cast<int>(h * (1 - 1 / Zoom())) : 0;
|
||||
offset_.x = std::clamp(value.x, 0, max_w);
|
||||
offset_.y = std::clamp(value.y, 0, max_h);
|
||||
}
|
||||
|
||||
void SkipPartCanvas::AutoSetCursor()
|
||||
{
|
||||
if(is_draging_ || fixed_draging_)
|
||||
SetCursor(wxCursor(wxCURSOR_HAND));
|
||||
else
|
||||
SetCursor(wxCursor(wxCURSOR_NONE));
|
||||
}
|
||||
|
||||
void SkipPartCanvas::StartDrag(const wxPoint& mouse_pt)
|
||||
{
|
||||
drag_start_pt_ = mouse_pt;
|
||||
drag_start_offset_ = offset_;
|
||||
is_draging_ = true;
|
||||
AutoSetCursor();
|
||||
}
|
||||
|
||||
void SkipPartCanvas::ProcessDrag(const wxPoint& mouse_pt)
|
||||
{
|
||||
wxPoint drag_offset = mouse_pt - drag_start_pt_;
|
||||
SetOffset(- wxPoint(drag_offset.x / Zoom(), drag_offset.y / Zoom()) + drag_start_offset_);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void SkipPartCanvas::ProcessHover(const wxPoint& mouse_pt)
|
||||
{
|
||||
auto id_at_mouse = GetIdAtViewPt(mouse_pt);
|
||||
int new_hover_id { -1 };
|
||||
auto part_state = parts_state_.find(id_at_mouse);
|
||||
if (part_state != parts_state_.end() && part_state->second == psUnCheck) {
|
||||
new_hover_id = id_at_mouse;
|
||||
};
|
||||
if (new_hover_id != this->hover_id_) {
|
||||
this->hover_id_ = new_hover_id;
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void SkipPartCanvas::EndDrag()
|
||||
{
|
||||
is_draging_ = false;
|
||||
AutoSetCursor();
|
||||
}
|
||||
|
||||
void SkipPartCanvas::OnPaint(wxPaintEvent &event)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
if (!IsShown()) return;
|
||||
|
||||
SetCurrent(*context_);
|
||||
|
||||
Render();
|
||||
SwapBuffers();
|
||||
}
|
||||
|
||||
void SkipPartCanvas::OnSize(wxSizeEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void SkipPartCanvas::OnMouseLeftDown(wxMouseEvent& event)
|
||||
{
|
||||
DebugLogLine("OnMouseLeftDown");
|
||||
if (!event.LeftIsDown()) {
|
||||
event.Skip();
|
||||
DebugLogLine("skip----OnMouseLeftDown");
|
||||
return;
|
||||
}
|
||||
if (fixed_draging_)
|
||||
StartDrag(wxPoint(event.GetX(), event.GetY()));
|
||||
left_down_ = true;
|
||||
}
|
||||
|
||||
void SkipPartCanvas::OnMouseLeftUp(wxMouseEvent& event)
|
||||
{
|
||||
DebugLogLine("OnMouseLeftUp");
|
||||
if (event.LeftIsDown() || !left_down_) {
|
||||
event.Skip();
|
||||
DebugLogLine("skip----OnMouseLeftUp");
|
||||
return;
|
||||
}
|
||||
auto id_at_mouse = GetIdAtViewPt(wxPoint(event.GetX(), event.GetY()));
|
||||
auto part_state = parts_state_.find(id_at_mouse);
|
||||
if (part_state != parts_state_.end() && part_state->second != psSkipped) {
|
||||
if (part_state->second == psUnCheck)
|
||||
part_state = parts_state_.insert_or_assign(part_state->first, psChecked).first;
|
||||
else
|
||||
part_state = parts_state_.insert_or_assign(part_state->first, psUnCheck).first;
|
||||
// if (select_callback_)
|
||||
// select_callback_(part_state->first, part_state->second);
|
||||
SendSelectEvent(part_state->first, part_state->second);
|
||||
}
|
||||
left_down_ = false;
|
||||
if (fixed_draging_)
|
||||
EndDrag();
|
||||
else {
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void SkipPartCanvas::OnMouseRightDown(wxMouseEvent& event)
|
||||
{
|
||||
DebugLogLine("OnMouseRightDown");
|
||||
if (!event.RightIsDown()) {
|
||||
event.Skip();
|
||||
DebugLogLine("skip----OnMouseRightDown");
|
||||
return;
|
||||
}
|
||||
StartDrag(wxPoint(event.GetX(), event.GetY()));
|
||||
}
|
||||
|
||||
void SkipPartCanvas::OnMouseRightUp(wxMouseEvent& event)
|
||||
{
|
||||
DebugLogLine("OnMouseRightUp");
|
||||
if (event.RightIsDown() || !is_draging_) {
|
||||
event.Skip();
|
||||
DebugLogLine("skip----OnMouseRightUp");
|
||||
return;
|
||||
}
|
||||
EndDrag();
|
||||
}
|
||||
|
||||
void SkipPartCanvas::OnMouseMotion(wxMouseEvent& event)
|
||||
{
|
||||
ProcessHover(wxPoint(event.GetX(), event.GetY()));
|
||||
if (!event.RightIsDown() && !(event.LeftIsDown() && fixed_draging_)) {
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
ProcessDrag(wxPoint(event.GetX(), event.GetY()));
|
||||
}
|
||||
|
||||
void SkipPartCanvas::OnMouseWheel(wxMouseEvent& event)
|
||||
{
|
||||
wxPoint view_mouse = wxPoint(event.GetX(), event.GetY());
|
||||
auto pre_image_pos = ViewPtToImagePt(view_mouse);
|
||||
SetZoomPercent(zoom_percent_ + 10 * (event.GetWheelRotation() / 120.0));
|
||||
auto now_image_pos = ViewPtToImagePt(view_mouse);
|
||||
SetOffset(offset_ - (now_image_pos - pre_image_pos));
|
||||
Refresh();
|
||||
}
|
||||
|
||||
// Base class with error messages management
|
||||
|
||||
void _BBS_3MF_Base::add_error(const std::string &error) const
|
||||
{
|
||||
boost::unique_lock l(mutex);
|
||||
m_errors.push_back(error);
|
||||
}
|
||||
void _BBS_3MF_Base::clear_errors() { m_errors.clear(); }
|
||||
|
||||
void _BBS_3MF_Base::log_errors()
|
||||
{
|
||||
for (const std::string &error : m_errors) BOOST_LOG_TRIVIAL(error) << error;
|
||||
}
|
||||
|
||||
|
||||
ModelSettingHelper::ModelSettingHelper(const std::string &path) : path_(path) {}
|
||||
|
||||
bool ModelSettingHelper::Parse()
|
||||
{
|
||||
boost::nowide::fstream fs(path_);
|
||||
if (!fs) {
|
||||
add_error("Failed to open file\n");
|
||||
return false;
|
||||
}
|
||||
XML_Parser parser = XML_ParserCreate(nullptr);
|
||||
if (!parser) {
|
||||
add_error("Unable to create parser");
|
||||
return false;
|
||||
}
|
||||
XML_SetUserData(parser, this);
|
||||
XML_SetElementHandler(parser, ModelSettingHelper::StartElementHandler, ModelSettingHelper::EndElementHandler);
|
||||
|
||||
try {
|
||||
char buffer[4000];
|
||||
while (fs.read(buffer, sizeof(buffer)) || fs.gcount() > 0) {
|
||||
auto ret = XML_Parse(parser, buffer, static_cast<int>(fs.gcount()), fs.eof());
|
||||
if (ret != XML_STATUS_OK) {
|
||||
add_error("return value of XML_Parse doesn't match XM_STATUS_OK");
|
||||
XML_ParserFree(parser);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
add_error(std::string("exception:") + e.what());
|
||||
XML_ParserFree(parser);
|
||||
return false;
|
||||
}
|
||||
XML_ParserFree(parser);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<ObjectInfo> ModelSettingHelper::GetResults() { return context_.objects; }
|
||||
|
||||
|
||||
void XMLCALL ModelSettingHelper::StartElementHandler(void *userData, const XML_Char *name, const XML_Char **atts)
|
||||
{
|
||||
ModelSettingHelper* self = static_cast<ModelSettingHelper*>(userData);
|
||||
if (strcmp(name, "object") == 0) {
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if (strcmp(atts[i], "id") == 0) {
|
||||
self->context_.current_object_id = atoi(atts[i+1]);
|
||||
}
|
||||
}
|
||||
} else if ((self->context_.current_object_id != -1)
|
||||
&& (strcmp(name, "metadata") == 0)
|
||||
&& (self->context_.node_name.top() == "object")) {
|
||||
const XML_Char *key{nullptr}, *value{nullptr};
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if (strcmp(atts[i], "key") == 0) key = atts[i+1];
|
||||
if (strcmp(atts[i], "value") == 0) value = atts[i+1];
|
||||
}
|
||||
if (key && value && (strcmp(key, "name") == 0)) {
|
||||
self->context_.temp_object.id = self->context_.current_object_id;
|
||||
self->context_.temp_object.name = std::string(value);
|
||||
}
|
||||
} else if ((strcmp(name, "metadata") == 0)
|
||||
&& (self->context_.node_name.top() == "model_instance")) {
|
||||
const XML_Char *key{nullptr}, *value{nullptr};
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if (strcmp(atts[i], "key") == 0) key = atts[i+1];
|
||||
if (strcmp(atts[i], "value") == 0) value = atts[i+1];
|
||||
}
|
||||
if (key && value && (strcmp(key, "object_id") == 0)) {
|
||||
self->context_.current_instance_object_id = atoi(value);
|
||||
} else if (key && value && (strcmp(key, "identify_id") == 0)) {
|
||||
self->context_.current_instance_identify_id = atoi(value);
|
||||
}
|
||||
}
|
||||
self->context_.node_name.push(std::string(name));
|
||||
}
|
||||
|
||||
void XMLCALL ModelSettingHelper::EndElementHandler(void *userData, const XML_Char *name)
|
||||
{
|
||||
ModelSettingHelper* self = static_cast<ModelSettingHelper*>(userData);
|
||||
if (strcmp(name, "object") == 0) {
|
||||
self->context_.objects.push_back(self->context_.temp_object);
|
||||
self->context_.current_object_id = -1;
|
||||
}
|
||||
else if ((strcmp(name, "model_instance") == 0)
|
||||
&& (self->context_.current_instance_identify_id != -1)
|
||||
&& (self->context_.current_instance_object_id != -1)) {
|
||||
for (auto& obj : self->context_.objects) {
|
||||
if (obj.id == self->context_.current_instance_object_id) {
|
||||
obj.identify_id = self->context_.current_instance_identify_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
self->context_.current_instance_identify_id = -1;
|
||||
self->context_.current_instance_object_id = -1;
|
||||
} else if (strcmp(name, "object") == 0) {
|
||||
self->context_.current_object_id = -1;
|
||||
}
|
||||
self->context_.node_name.pop();
|
||||
}
|
||||
|
||||
void ModelSettingHelper::DataHandler(const XML_Char *s, int len)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
163
src/slic3r/GUI/SkipPartCanvas.hpp
Normal file
163
src/slic3r/GUI/SkipPartCanvas.hpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#ifndef SKIPPARTCANVAS_H
|
||||
#define SKIPPARTCANVAS_H
|
||||
#include <wx/wx.h>
|
||||
#include <wx/glcanvas.h>
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <wx/textctrl.h>
|
||||
#include <vector>
|
||||
#include <stack>
|
||||
#include <expat.h>
|
||||
#include <libslic3r/Color.hpp>
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#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<Coord, 2>;
|
||||
|
||||
|
||||
struct ObjectInfo {
|
||||
int id{-1};
|
||||
std::string name{""};
|
||||
int identify_id{-1};
|
||||
PartState state{psUnCheck};
|
||||
};
|
||||
|
||||
class SkipPartCanvas : public wxGLCanvas
|
||||
{
|
||||
union SkipIdHelper
|
||||
{
|
||||
uint32_t value = 0;
|
||||
|
||||
struct
|
||||
{
|
||||
uint8_t b;
|
||||
uint8_t g;
|
||||
uint8_t r;
|
||||
uint8_t _padding;
|
||||
};
|
||||
|
||||
uint8_t bytes[4];
|
||||
|
||||
SkipIdHelper() = default;
|
||||
|
||||
SkipIdHelper(uint8_t red, uint8_t green, uint8_t blue)
|
||||
: r(red), g(green), b(blue), _padding(0) {}
|
||||
|
||||
SkipIdHelper(uint32_t val): value(val){}
|
||||
void reverse() {
|
||||
uint8_t tmp{r};
|
||||
r = b;
|
||||
b = tmp;
|
||||
}
|
||||
};
|
||||
public:
|
||||
SkipPartCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs);
|
||||
~SkipPartCanvas() = default;
|
||||
|
||||
void SetParentBackground(const ColorRGB& color) {
|
||||
parent_color_ = color;
|
||||
}
|
||||
|
||||
void LoadPickImage(const std::string& path);
|
||||
void ZoomIn(const int zoom_percent);
|
||||
void ZoomOut(const int zoom_percent);
|
||||
void SwitchDrag(const bool drag_on);
|
||||
void UpdatePartsInfo(const PartsInfo& parts);
|
||||
void SetZoomPercent(const int value);
|
||||
void SetOffset(const wxPoint &value);
|
||||
|
||||
wxTextCtrl* log_ctrl;
|
||||
protected:
|
||||
void OnPaint(wxPaintEvent& event);
|
||||
void OnSize(wxSizeEvent& event);
|
||||
void OnMouseLeftDown(wxMouseEvent& event);
|
||||
void OnMouseLeftUp(wxMouseEvent& event);
|
||||
void OnMouseRightDown(wxMouseEvent& event);
|
||||
void OnMouseRightUp(wxMouseEvent& event);
|
||||
void OnMouseMotion(wxMouseEvent& event);
|
||||
void OnMouseWheel(wxMouseEvent& event);
|
||||
private:
|
||||
wxGLContext* context_;
|
||||
cv::Mat pick_image_;
|
||||
std::unordered_map<uint32_t, std::vector<FloatPoint>> parts_triangles_;
|
||||
std::unordered_map<uint32_t, std::vector<cv::Point>> pick_parts_;
|
||||
std::unordered_map<uint32_t, PartState> parts_state_;
|
||||
bool gl_inited_{false};
|
||||
int zoom_percent_{100};
|
||||
wxPoint offset_{0,0};
|
||||
wxPoint drag_start_offset_{0,0};
|
||||
wxPoint drag_start_pt_{0,0};
|
||||
bool is_draging_{false};
|
||||
bool fixed_draging_{false};
|
||||
bool left_down_{false};
|
||||
ColorRGB parent_color_ = ColorRGB();
|
||||
int hover_id_{-1};
|
||||
|
||||
void SendSelectEvent(int id, PartState state);
|
||||
void SendZoomEvent(int zoom_percent);
|
||||
|
||||
inline double Zoom() const;
|
||||
inline wxPoint ViewPtToImagePt(const wxPoint& view_pt) const;
|
||||
uint32_t GetIdAtImagePt(const wxPoint& image_pt) const;
|
||||
inline uint32_t GetIdAtViewPt(const wxPoint& view_pt) const;
|
||||
|
||||
void ProcessHover(const wxPoint& mouse_pt);
|
||||
void AutoSetCursor();
|
||||
void StartDrag(const wxPoint& mouse_pt);
|
||||
void ProcessDrag(const wxPoint& mouse_pt);
|
||||
void EndDrag();
|
||||
|
||||
void Render();
|
||||
|
||||
void DebugLogLine(std::string str);
|
||||
};
|
||||
|
||||
class _BBS_3MF_Base
|
||||
{
|
||||
mutable boost::mutex mutex;
|
||||
mutable std::vector<std::string> m_errors;
|
||||
|
||||
protected:
|
||||
void add_error(const std::string& error) const;
|
||||
void clear_errors();
|
||||
|
||||
public:
|
||||
void log_errors();
|
||||
};
|
||||
|
||||
class ModelSettingHelper : public _BBS_3MF_Base {
|
||||
struct ParseContext{
|
||||
std::vector<ObjectInfo> objects;
|
||||
ObjectInfo temp_object;
|
||||
|
||||
std::stack<std::string> node_name;
|
||||
int current_object_id{-1};
|
||||
int current_instance_identify_id{-1};
|
||||
int current_instance_object_id{-1};
|
||||
};
|
||||
|
||||
public:
|
||||
ModelSettingHelper(const std::string& path);
|
||||
|
||||
|
||||
bool Parse();
|
||||
std::vector<ObjectInfo> GetResults();
|
||||
private:
|
||||
std::string path_;
|
||||
ParseContext context_;
|
||||
|
||||
void static XMLCALL StartElementHandler(void* userData, const XML_Char* name, const XML_Char** atts);
|
||||
void static XMLCALL EndElementHandler(void* userData, const XML_Char* name);
|
||||
void DataHandler(const XML_Char* s, int len);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
#endif //SKIPPARTCANVAS_H
|
||||
@@ -278,6 +278,20 @@ void PrintingTaskPanel::create_panel(wxWindow* parent)
|
||||
|
||||
bSizer_task_btn->Add(FromDIP(10), 0, 0);
|
||||
|
||||
StateColor white_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Hovered), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Enabled),
|
||||
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
|
||||
m_button_partskip = new Button(m_panel_progress, "");
|
||||
m_button_partskip->Enable(false);
|
||||
m_button_partskip->SetBackgroundColor(white_bg);
|
||||
m_button_partskip->SetIcon("print_control_partskip_disable");
|
||||
m_button_partskip->SetBorderColor(*wxWHITE);
|
||||
m_button_partskip->SetFont(Label::Body_12);
|
||||
m_button_partskip->SetCornerRadius(0);
|
||||
m_button_partskip->SetToolTip(_L("Parts Skip"));
|
||||
m_button_partskip->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { m_button_partskip->SetIcon("print_control_partskip"); });
|
||||
m_button_partskip->Bind(wxEVT_LEAVE_WINDOW, [this](auto &e) { m_button_partskip->SetIcon("print_control_partskip"); });
|
||||
|
||||
m_button_pause_resume = new ScalableButton(m_panel_progress, wxID_ANY, "print_control_pause", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER,true);
|
||||
|
||||
m_button_pause_resume->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) {
|
||||
@@ -314,6 +328,8 @@ void PrintingTaskPanel::create_panel(wxWindow* parent)
|
||||
|
||||
m_sizer_progressbar->Add(m_gauge_progress, 1, wxALIGN_CENTER_VERTICAL, 0);
|
||||
m_sizer_progressbar->Add(0, 0, 0, wxEXPAND|wxLEFT, FromDIP(18));
|
||||
m_sizer_progressbar->Add(m_button_partskip, 0, wxALL, FromDIP(5));
|
||||
m_sizer_progressbar->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(18));
|
||||
m_sizer_progressbar->Add(m_button_pause_resume, 0, wxALL, FromDIP(5));
|
||||
m_sizer_progressbar->Add(0, 0, 0, wxEXPAND|wxLEFT, FromDIP(18));
|
||||
m_sizer_progressbar->Add(m_button_abort, 0, wxALL, FromDIP(5));
|
||||
@@ -638,6 +654,22 @@ void PrintingTaskPanel::reset_printing_value()
|
||||
this->set_plate_index(-1);
|
||||
}
|
||||
|
||||
void PrintingTaskPanel::update_machine_object(MachineObject* obj){
|
||||
if(obj) m_obj = obj;
|
||||
}
|
||||
|
||||
void PrintingTaskPanel::enable_partskip_button(bool enable)
|
||||
{
|
||||
if (!enable) {
|
||||
m_button_partskip->Enable(false);
|
||||
m_button_partskip->SetLabel("");
|
||||
m_button_partskip->SetIcon("print_control_partskip_disable");
|
||||
}else if(m_obj && m_obj->is_support_brtc){
|
||||
m_button_partskip->Enable(true);
|
||||
m_button_partskip->SetIcon("print_control_partskip");
|
||||
}
|
||||
}
|
||||
|
||||
void PrintingTaskPanel::enable_pause_resume_button(bool enable, std::string type)
|
||||
{
|
||||
if (!enable) {
|
||||
@@ -1731,6 +1763,7 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co
|
||||
m_switch_cham_fan->SetValue(false);
|
||||
|
||||
/* set default enable state */
|
||||
m_project_task_panel->enable_partskip_button(false);
|
||||
m_project_task_panel->enable_pause_resume_button(false, "resume_disable");
|
||||
m_project_task_panel->enable_abort_button(false);
|
||||
|
||||
@@ -1751,6 +1784,7 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co
|
||||
|
||||
// Connect Events
|
||||
m_project_task_panel->get_bitmap_thumbnail()->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::refresh_thumbnail_webrequest), NULL, this);
|
||||
m_project_task_panel->get_partskip_button()->Connect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_subtask_partskip), NULL, this);
|
||||
m_project_task_panel->get_pause_resume_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
|
||||
m_project_task_panel->get_abort_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
|
||||
m_project_task_panel->get_market_scoring_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
|
||||
@@ -1810,6 +1844,7 @@ StatusPanel::~StatusPanel()
|
||||
{
|
||||
// Disconnect Events
|
||||
m_project_task_panel->get_bitmap_thumbnail()->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::refresh_thumbnail_webrequest), NULL, this);
|
||||
m_project_task_panel->get_partskip_button()->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_subtask_partskip), NULL, this);
|
||||
m_project_task_panel->get_pause_resume_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
|
||||
m_project_task_panel->get_abort_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
|
||||
m_project_task_panel->get_market_scoring_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
|
||||
@@ -1940,6 +1975,17 @@ void StatusPanel::on_market_retry(wxCommandEvent &event)
|
||||
}
|
||||
}
|
||||
|
||||
void StatusPanel::on_subtask_partskip(wxCommandEvent &event)
|
||||
{
|
||||
if (m_partskip_dlg == nullptr) {
|
||||
m_partskip_dlg = new PartSkipDialog(this->GetParent());
|
||||
}
|
||||
|
||||
auto dm = GUI::wxGetApp().getDeviceManager();
|
||||
m_partskip_dlg->InitSchedule(dm->get_selected_machine());
|
||||
m_partskip_dlg->ShowModal();
|
||||
}
|
||||
|
||||
void StatusPanel::on_subtask_pause_resume(wxCommandEvent &event)
|
||||
{
|
||||
if (obj) {
|
||||
@@ -3089,9 +3135,12 @@ void StatusPanel::update_subtask(MachineObject *obj)
|
||||
|| obj->is_in_calibration()) {
|
||||
reset_printing_values();
|
||||
} else if (obj->is_in_printing() || obj->print_status == "FINISH") {
|
||||
update_partskip_subtask(obj);
|
||||
|
||||
if (obj->is_in_prepare() || obj->print_status == "SLICING") {
|
||||
m_project_task_panel->market_scoring_hide();
|
||||
m_project_task_panel->get_request_failed_panel()->Hide();
|
||||
m_project_task_panel->enable_partskip_button(false);
|
||||
m_project_task_panel->enable_abort_button(false);
|
||||
m_project_task_panel->enable_pause_resume_button(false, "pause_disable");
|
||||
wxString prepare_text;
|
||||
@@ -3133,7 +3182,7 @@ void StatusPanel::update_subtask(MachineObject *obj)
|
||||
} else {
|
||||
m_project_task_panel->enable_pause_resume_button(true, "pause");
|
||||
}
|
||||
|
||||
m_project_task_panel->enable_partskip_button(true);
|
||||
// update printing stage
|
||||
m_project_task_panel->update_left_time(obj->mc_left_time);
|
||||
if (obj->subtask_) {
|
||||
@@ -3150,6 +3199,7 @@ void StatusPanel::update_subtask(MachineObject *obj)
|
||||
if (obj->is_printing_finished()) {
|
||||
obj->update_model_task();
|
||||
m_project_task_panel->enable_abort_button(false);
|
||||
m_project_task_panel->enable_partskip_button(false);
|
||||
m_project_task_panel->enable_pause_resume_button(false, "resume_disable");
|
||||
// is makeworld subtask
|
||||
if (wxGetApp().has_model_mall() && obj->is_makeworld_subtask()) {
|
||||
@@ -3217,6 +3267,26 @@ void StatusPanel::update_subtask(MachineObject *obj)
|
||||
Layout();
|
||||
}
|
||||
|
||||
void StatusPanel::update_partskip_subtask(MachineObject *obj){
|
||||
if (!obj) return;
|
||||
if (!obj->subtask_) return;
|
||||
|
||||
m_project_task_panel->update_machine_object(obj);
|
||||
|
||||
auto partskip_button = m_project_task_panel->get_partskip_button();
|
||||
if (partskip_button) {
|
||||
int part_cnt = obj->m_partskip_ids.size();
|
||||
if (part_cnt > 0)
|
||||
partskip_button->SetLabel(wxString::Format(_L("(%d)"), part_cnt));
|
||||
else
|
||||
partskip_button->SetLabel("");
|
||||
}
|
||||
|
||||
if(m_partskip_dlg && m_partskip_dlg->IsShown()) {
|
||||
m_partskip_dlg->UpdatePartsStateFromPrinter(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void StatusPanel::update_cloud_subtask(MachineObject *obj)
|
||||
{
|
||||
if (!obj) return;
|
||||
@@ -3281,6 +3351,7 @@ void StatusPanel::update_sdcard_subtask(MachineObject *obj)
|
||||
|
||||
void StatusPanel::reset_printing_values()
|
||||
{
|
||||
m_project_task_panel->enable_partskip_button(false);
|
||||
m_project_task_panel->enable_pause_resume_button(false, "pause_disable");
|
||||
m_project_task_panel->enable_abort_button(false);
|
||||
m_project_task_panel->reset_printing_value();
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "Widgets/AMSControl.hpp"
|
||||
#include "Widgets/FanControl.hpp"
|
||||
#include "HMS.hpp"
|
||||
#include "PartSkipDialog.hpp"
|
||||
|
||||
class StepIndicator;
|
||||
|
||||
@@ -160,7 +161,7 @@ public:
|
||||
|
||||
|
||||
private:
|
||||
MachineObject* m_obj;
|
||||
MachineObject* m_obj{nullptr};
|
||||
ScalableBitmap m_thumbnail_placeholder;
|
||||
wxBitmap m_thumbnail_bmp_display;
|
||||
ScalableBitmap m_bitmap_use_time;
|
||||
@@ -192,6 +193,7 @@ private:
|
||||
wxStaticBitmap* m_bitmap_static_use_weight;
|
||||
ScalableButton* m_button_pause_resume;
|
||||
ScalableButton* m_button_abort;
|
||||
Button* m_button_partskip;
|
||||
Button* m_button_market_scoring;
|
||||
Button* m_button_clean;
|
||||
Button * m_button_market_retry;
|
||||
@@ -217,6 +219,7 @@ public:
|
||||
void msw_rescale();
|
||||
|
||||
public:
|
||||
void enable_partskip_button(bool enable);
|
||||
void enable_pause_resume_button(bool enable, std::string type);
|
||||
void enable_abort_button(bool enable);
|
||||
void update_subtask_name(wxString name);
|
||||
@@ -232,10 +235,12 @@ public:
|
||||
void set_plate_index(int plate_idx = -1);
|
||||
void market_scoring_show();
|
||||
void market_scoring_hide();
|
||||
void update_machine_object(MachineObject* obj);
|
||||
|
||||
public:
|
||||
ScalableButton* get_abort_button() {return m_button_abort;};
|
||||
ScalableButton* get_pause_resume_button() {return m_button_pause_resume;};
|
||||
Button* get_partskip_button() { return m_button_partskip; };
|
||||
Button* get_market_scoring_button() {return m_button_market_scoring;};
|
||||
Button * get_market_retry_buttom() { return m_button_market_retry; };
|
||||
Button* get_clean_button() {return m_button_clean;};
|
||||
@@ -328,6 +333,7 @@ protected:
|
||||
wxStaticText * m_staticText_progress_left;
|
||||
wxStaticText * m_staticText_layers;
|
||||
Button * m_button_report;
|
||||
Button * m_button_partskip;
|
||||
ScalableButton *m_button_pause_resume;
|
||||
ScalableButton *m_button_abort;
|
||||
Button * m_button_clean;
|
||||
@@ -402,6 +408,7 @@ protected:
|
||||
PrintingTaskPanel * m_project_task_panel;
|
||||
|
||||
// Virtual event handlers, override them in your derived class
|
||||
virtual void on_subtask_partskip(wxCommandEvent &event) { event.Skip(); }
|
||||
virtual void on_subtask_pause_resume(wxCommandEvent &event) { event.Skip(); }
|
||||
virtual void on_subtask_abort(wxCommandEvent &event) { event.Skip(); }
|
||||
virtual void on_lamp_switch(wxCommandEvent &event) { event.Skip(); }
|
||||
@@ -484,6 +491,7 @@ protected:
|
||||
FanControlPopup* m_fan_control_popup{nullptr};
|
||||
|
||||
ExtrusionCalibration *m_extrusion_cali_dlg{nullptr};
|
||||
PartSkipDialog *m_partskip_dlg{nullptr};
|
||||
|
||||
wxString m_request_url;
|
||||
bool m_start_loading_thumbnail = false;
|
||||
@@ -525,6 +533,7 @@ protected:
|
||||
|
||||
void on_market_scoring(wxCommandEvent &event);
|
||||
void on_market_retry(wxCommandEvent &event);
|
||||
void on_subtask_partskip(wxCommandEvent &event);
|
||||
void on_subtask_pause_resume(wxCommandEvent &event);
|
||||
void on_subtask_abort(wxCommandEvent &event);
|
||||
void on_print_error_clean(wxCommandEvent &event);
|
||||
@@ -602,6 +611,7 @@ protected:
|
||||
void update_basic_print_data(bool def = false);
|
||||
void update_model_info();
|
||||
void update_subtask(MachineObject* obj);
|
||||
void update_partskip_subtask(MachineObject *obj);
|
||||
void update_cloud_subtask(MachineObject *obj);
|
||||
void update_sdcard_subtask(MachineObject *obj);
|
||||
void update_temp_ctrl(MachineObject *obj);
|
||||
|
||||
Reference in New Issue
Block a user