mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-01-31 00:48:41 +03:00
update src
This commit is contained in:
@@ -504,7 +504,7 @@ find_package(cereal REQUIRED)
|
||||
set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/i18n")
|
||||
set(QDT_L18N_DIR "${CMAKE_CURRENT_SOURCE_DIR}/qdt/i18n")
|
||||
add_custom_target(gettext_make_pot
|
||||
COMMAND xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost
|
||||
COMMAND xgettext --keyword=L --no-wrap --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost
|
||||
-f "${QDT_L18N_DIR}/list.txt"
|
||||
-o "${QDT_L18N_DIR}/QIDIStudio.pot"
|
||||
COMMAND hintsToPot ${SLIC3R_RESOURCES_DIR} ${QDT_L18N_DIR}
|
||||
@@ -521,7 +521,7 @@ foreach(po_file ${QDT_L10N_PO_FILES})
|
||||
SET(po_new_file "${po_dir}/QIDIStudio_.po")
|
||||
add_custom_command(
|
||||
TARGET gettext_merge_po_with_pot PRE_BUILD
|
||||
COMMAND msgmerge -N -o ${po_file} ${po_file} "${QDT_L18N_DIR}/QIDIStudio.pot"
|
||||
COMMAND msgmerge --no-wrap -N -o ${po_file} ${po_file} "${QDT_L18N_DIR}/QIDIStudio.pot"
|
||||
DEPENDS ${po_file}
|
||||
)
|
||||
endforeach()
|
||||
|
||||
@@ -25,6 +25,7 @@ add_subdirectory(libslic3r)
|
||||
|
||||
if (SLIC3R_GUI)
|
||||
add_subdirectory(imgui)
|
||||
add_subdirectory(imguizmo)
|
||||
add_subdirectory(hidapi)
|
||||
include_directories(hidapi/include)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,6 @@
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <wchar.h>
|
||||
|
||||
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
extern "C"
|
||||
{
|
||||
@@ -18,22 +15,16 @@ extern "C"
|
||||
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 0;
|
||||
}
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
#include <GL/GL.h>
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
class OpenGLVersionCheck
|
||||
{
|
||||
@@ -42,42 +33,45 @@ public:
|
||||
std::string glsl_version;
|
||||
std::string vendor;
|
||||
std::string renderer;
|
||||
|
||||
HINSTANCE hOpenGL = nullptr;
|
||||
bool success = false;
|
||||
|
||||
bool load_opengl_dll()
|
||||
{
|
||||
MSG msg = {0};
|
||||
WNDCLASS wc = {0};
|
||||
MSG msg = { 0 };
|
||||
WNDCLASS wc = { 0 };
|
||||
wc.lpfnWndProc = OpenGLVersionCheck::supports_opengl2_wndproc;
|
||||
wc.hInstance = (HINSTANCE)GetModuleHandle(nullptr);
|
||||
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
|
||||
wc.lpszClassName = L"QIDIStudio_opengl_version_check";
|
||||
wc.lpszClassName = L"BambuStudio_opengl_version_check";
|
||||
wc.style = CS_OWNDC;
|
||||
if (RegisterClass(&wc)) {
|
||||
HWND hwnd = CreateWindowW(wc.lpszClassName, L"QIDIStudio_opengl_version_check", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, 0, 0, wc.hInstance, (LPVOID)this);
|
||||
HWND hwnd = CreateWindowW(wc.lpszClassName, L"BambuStudio_opengl_version_check", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, 0, 0, wc.hInstance, (LPVOID)this);
|
||||
if (hwnd) {
|
||||
message_pump_exit = false;
|
||||
while (GetMessage(&msg, NULL, 0, 0 ) > 0 && ! message_pump_exit)
|
||||
while (GetMessage(&msg, NULL, 0, 0) > 0 && !message_pump_exit)
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
return this->success;
|
||||
}
|
||||
|
||||
void unload_opengl_dll()
|
||||
bool unload_opengl_dll()
|
||||
{
|
||||
if (this->hOpenGL) {
|
||||
BOOL released = FreeLibrary(this->hOpenGL);
|
||||
if (released)
|
||||
printf("System OpenGL library released\n");
|
||||
if (this->hOpenGL != nullptr) {
|
||||
if (::FreeLibrary(this->hOpenGL) != FALSE) {
|
||||
if (::GetModuleHandle(L"opengl32.dll") == nullptr) {
|
||||
printf("System OpenGL library successfully released\n");
|
||||
this->hOpenGL = nullptr;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
printf("System OpenGL library released but not removed\n");
|
||||
}
|
||||
else
|
||||
printf("System OpenGL library NOT released\n");
|
||||
this->hOpenGL = nullptr;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
|
||||
{
|
||||
// printf("is_version_greater_or_equal_to, version: %s\n", version.c_str());
|
||||
@@ -85,10 +79,8 @@ public:
|
||||
boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on);
|
||||
if (tokens.empty())
|
||||
return false;
|
||||
|
||||
std::vector<std::string> numbers;
|
||||
boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on);
|
||||
|
||||
unsigned int gl_major = 0;
|
||||
unsigned int gl_minor = 0;
|
||||
if (numbers.size() > 0)
|
||||
@@ -103,10 +95,8 @@ public:
|
||||
else
|
||||
return gl_minor >= minor;
|
||||
}
|
||||
|
||||
protected:
|
||||
static bool message_pump_exit;
|
||||
|
||||
void check(HWND hWnd)
|
||||
{
|
||||
hOpenGL = LoadLibraryExW(L"opengl32.dll", nullptr, 0);
|
||||
@@ -114,22 +104,18 @@ protected:
|
||||
printf("Failed loading the system opengl32.dll\n");
|
||||
return;
|
||||
}
|
||||
|
||||
typedef HGLRC (WINAPI *Func_wglCreateContext)(HDC);
|
||||
typedef BOOL (WINAPI *Func_wglMakeCurrent )(HDC, HGLRC);
|
||||
typedef BOOL (WINAPI *Func_wglDeleteContext)(HGLRC);
|
||||
typedef GLubyte* (WINAPI *Func_glGetString )(GLenum);
|
||||
|
||||
typedef HGLRC(WINAPI* Func_wglCreateContext)(HDC);
|
||||
typedef BOOL(WINAPI* Func_wglMakeCurrent)(HDC, HGLRC);
|
||||
typedef BOOL(WINAPI* Func_wglDeleteContext)(HGLRC);
|
||||
typedef GLubyte* (WINAPI* Func_glGetString)(GLenum);
|
||||
Func_wglCreateContext wglCreateContext = (Func_wglCreateContext)GetProcAddress(hOpenGL, "wglCreateContext");
|
||||
Func_wglMakeCurrent wglMakeCurrent = (Func_wglMakeCurrent) GetProcAddress(hOpenGL, "wglMakeCurrent");
|
||||
Func_wglMakeCurrent wglMakeCurrent = (Func_wglMakeCurrent)GetProcAddress(hOpenGL, "wglMakeCurrent");
|
||||
Func_wglDeleteContext wglDeleteContext = (Func_wglDeleteContext)GetProcAddress(hOpenGL, "wglDeleteContext");
|
||||
Func_glGetString glGetString = (Func_glGetString) GetProcAddress(hOpenGL, "glGetString");
|
||||
|
||||
Func_glGetString glGetString = (Func_glGetString)GetProcAddress(hOpenGL, "glGetString");
|
||||
if (wglCreateContext == nullptr || wglMakeCurrent == nullptr || wglDeleteContext == nullptr || glGetString == nullptr) {
|
||||
printf("Failed loading the system opengl32.dll: The library is invalid.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
PIXELFORMATDESCRIPTOR pfd =
|
||||
{
|
||||
sizeof(PIXELFORMATDESCRIPTOR),
|
||||
@@ -149,7 +135,6 @@ protected:
|
||||
0,
|
||||
0, 0, 0
|
||||
};
|
||||
|
||||
HDC ourWindowHandleToDeviceContext = ::GetDC(hWnd);
|
||||
// Gdi32.dll
|
||||
int letWindowsChooseThisPixelFormat = ::ChoosePixelFormat(ourWindowHandleToDeviceContext, &pfd);
|
||||
@@ -159,7 +144,7 @@ protected:
|
||||
HGLRC glcontext = wglCreateContext(ourWindowHandleToDeviceContext);
|
||||
wglMakeCurrent(ourWindowHandleToDeviceContext, glcontext);
|
||||
// Opengl32.dll
|
||||
const char *data = (const char*)glGetString(GL_VERSION);
|
||||
const char* data = (const char*)glGetString(GL_VERSION);
|
||||
if (data != nullptr)
|
||||
this->version = data;
|
||||
// printf("check -version: %s\n", version.c_str());
|
||||
@@ -177,15 +162,14 @@ protected:
|
||||
::ReleaseDC(hWnd, ourWindowHandleToDeviceContext);
|
||||
this->success = true;
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK supports_opengl2_wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
switch(message)
|
||||
switch (message)
|
||||
{
|
||||
case WM_CREATE:
|
||||
{
|
||||
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
|
||||
OpenGLVersionCheck *ogl_data = reinterpret_cast<OpenGLVersionCheck*>(pCreate->lpCreateParams);
|
||||
CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
|
||||
OpenGLVersionCheck* ogl_data = reinterpret_cast<OpenGLVersionCheck*>(pCreate->lpCreateParams);
|
||||
ogl_data->check(hWnd);
|
||||
DestroyWindow(hWnd);
|
||||
return 0;
|
||||
@@ -198,43 +182,37 @@ protected:
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool OpenGLVersionCheck::message_pump_exit = false;
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
extern "C" {
|
||||
typedef int (__stdcall *Slic3rMainFunc)(int argc, wchar_t **argv);
|
||||
typedef int(__stdcall* Slic3rMainFunc)(int argc, wchar_t** argv);
|
||||
Slic3rMainFunc qidistu_main = nullptr;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
#ifdef SLIC3R_WRAPPER_NOCONSOLE
|
||||
int APIENTRY wWinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, PWSTR /* lpCmdLine */, int /* nCmdShow */)
|
||||
{
|
||||
int argc;
|
||||
wchar_t **argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
|
||||
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
|
||||
#else
|
||||
int wmain(int argc, wchar_t **argv)
|
||||
{
|
||||
int wmain(int argc, wchar_t** argv)
|
||||
{
|
||||
#endif
|
||||
// Allow the asserts to open message box, such message box allows to ignore the assert and continue with the application.
|
||||
// Without this call, the seemingly same message box is being opened by the abort() function, but that is too late and
|
||||
// the application will be killed even if "Ignore" button is pressed.
|
||||
_set_error_mode(_OUT_TO_MSGBOX);
|
||||
|
||||
std::vector<wchar_t*> argv_extended;
|
||||
argv_extended.emplace_back(argv[0]);
|
||||
|
||||
#ifdef SLIC3R_WRAPPER_GCODEVIEWER
|
||||
wchar_t gcodeviewer_param[] = L"--gcodeviewer";
|
||||
argv_extended.emplace_back(gcodeviewer_param);
|
||||
#endif /* SLIC3R_WRAPPER_GCODEVIEWER */
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
// Here one may push some additional parameters based on the wrapper type.
|
||||
bool force_mesa = false;
|
||||
#endif /* SLIC3R_GUI */
|
||||
for (int i = 1; i < argc; ++ i) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
#ifdef SLIC3R_GUI
|
||||
if (wcscmp(argv[i], L"--sw-renderer") == 0)
|
||||
force_mesa = true;
|
||||
@@ -244,16 +222,14 @@ int wmain(int argc, wchar_t **argv)
|
||||
argv_extended.emplace_back(argv[i]);
|
||||
}
|
||||
argv_extended.emplace_back(nullptr);
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
OpenGLVersionCheck opengl_version_check;
|
||||
bool load_mesa =
|
||||
// Forced from the command line.
|
||||
force_mesa ||
|
||||
// Try to load the default OpenGL driver and test its context version.
|
||||
! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0);
|
||||
!opengl_version_check.load_opengl_dll() || !opengl_version_check.is_version_greater_or_equal_to(3, 2);
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
wchar_t path_to_exe[MAX_PATH + 1] = { 0 };
|
||||
::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH);
|
||||
wchar_t drive[_MAX_DRIVE];
|
||||
@@ -262,35 +238,38 @@ int wmain(int argc, wchar_t **argv)
|
||||
wchar_t ext[_MAX_EXT];
|
||||
_wsplitpath(path_to_exe, drive, dir, fname, ext);
|
||||
_wmakepath(path_to_exe, drive, dir, nullptr, nullptr);
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
// https://wiki.qt.io/Cross_compiling_Mesa_for_Windows
|
||||
// http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/
|
||||
// https://wiki.qt.io/Cross_compiling_Mesa_for_Windows
|
||||
// http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/
|
||||
if (load_mesa) {
|
||||
opengl_version_check.unload_opengl_dll();
|
||||
bool res = opengl_version_check.unload_opengl_dll();
|
||||
if (!res) {
|
||||
MessageBox(nullptr, L"Error:QIDIStudio was unable to automatically switch to MESA OpenGL library.",
|
||||
L"QIDIStudio Error", MB_OK);
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
wchar_t path_to_mesa[MAX_PATH + 1] = { 0 };
|
||||
wcscpy(path_to_mesa, path_to_exe);
|
||||
wcscat(path_to_mesa, L"mesa\\opengl32.dll");
|
||||
printf("Loading MESA OpenGL library: %S\n", path_to_mesa);
|
||||
HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0);
|
||||
if (hInstance_OpenGL == nullptr) {
|
||||
if (hInstance_OpenGL == nullptr)
|
||||
printf("MESA OpenGL library was not loaded\n");
|
||||
} else
|
||||
else
|
||||
printf("MESA OpenGL library was loaded sucessfully\n");
|
||||
}
|
||||
}
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
|
||||
wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 };
|
||||
wcscpy(path_to_slic3r, path_to_exe);
|
||||
wcscat(path_to_slic3r, L"QIDIStudio.dll");
|
||||
// printf("Loading Slic3r library: %S\n", path_to_slic3r);
|
||||
// printf("Loading Slic3r library: %S\n", path_to_slic3r);
|
||||
HINSTANCE hInstance_Slic3r = LoadLibraryExW(path_to_slic3r, nullptr, 0);
|
||||
if (hInstance_Slic3r == nullptr) {
|
||||
printf("QIDIStudio.dll was not loaded, error=%d\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// resolve function address here
|
||||
qidistu_main = (Slic3rMainFunc)GetProcAddress(hInstance_Slic3r,
|
||||
#ifdef _WIN64
|
||||
@@ -301,10 +280,10 @@ int wmain(int argc, wchar_t **argv)
|
||||
#endif
|
||||
);
|
||||
if (qidistu_main == nullptr) {
|
||||
printf("could not locate the function qidistu_main in QIDIStudio.dll\n");
|
||||
printf("could not locate the function bambustu_main in QIDIStudio.dll\n");
|
||||
return -1;
|
||||
}
|
||||
// argc minus the trailing nullptr of the argv
|
||||
return qidistu_main((int)argv_extended.size() - 1, argv_extended.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +230,16 @@ struct indexed_triangle_set
|
||||
size_t memsize() const {
|
||||
return sizeof(*this) + (sizeof(stl_triangle_vertex_indices) + sizeof(FaceProperty)) * indices.size() + sizeof(stl_vertex) * vertices.size();
|
||||
}
|
||||
|
||||
void add_indice(int f0, int f1, int f2, bool check_pts_size = false)
|
||||
{
|
||||
if (f0 < 0 || f1 < 0 || f2 < 0) { return; }
|
||||
if (check_pts_size) {
|
||||
auto pts_size = vertices.size();
|
||||
if (f0 < pts_size && f1 < pts_size && f2 < pts_size) { indices.emplace_back(f0, f1, f2); }
|
||||
} else {
|
||||
indices.emplace_back(f0, f1, f2);
|
||||
}
|
||||
}
|
||||
std::vector<stl_triangle_vertex_indices> indices;
|
||||
std::vector<stl_vertex> vertices;
|
||||
std::vector<FaceProperty> properties;
|
||||
|
||||
@@ -138,7 +138,7 @@ static FILE *stl_open_count_facets(stl_file *stl, const char *file, unsigned int
|
||||
rewind(fp);
|
||||
|
||||
// Get the header.
|
||||
int i = 0;
|
||||
unsigned int i = 0;
|
||||
for (; i < custom_header_length && (stl->stats.header[i] = getc(fp)) != '\n'; ++ i) ;
|
||||
stl->stats.header[i] = '\0'; // Lose the '\n'
|
||||
stl->stats.header[custom_header_length] = '\0';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
option(SLIC3R_ENC_CHECK "Verify encoding of source files" 1)
|
||||
option(SLIC3R_ENC_CHECK "Verify encoding of source files" 0)
|
||||
|
||||
if (IS_CROSS_COMPILE)
|
||||
# Force disable due to cross compilation. This fact is already printed on cli for users
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,16 +10,10 @@
|
||||
#ifndef CLIPPER_ENGINE_H
|
||||
#define CLIPPER_ENGINE_H
|
||||
|
||||
constexpr auto CLIPPER2_VERSION = "1.0.6";
|
||||
|
||||
#include <cstdlib>
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include <queue>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include "clipper.core.h"
|
||||
#include <memory>
|
||||
|
||||
namespace Clipper2Lib {
|
||||
|
||||
@@ -29,15 +23,16 @@ namespace Clipper2Lib {
|
||||
struct Vertex;
|
||||
struct LocalMinima;
|
||||
struct OutRec;
|
||||
struct Joiner;
|
||||
struct HorzSegment;
|
||||
|
||||
//Note: all clipping operations except for Difference are commutative.
|
||||
enum class ClipType { None, Intersection, Union, Difference, Xor };
|
||||
enum class ClipType { NoClip, Intersection, Union, Difference, Xor };
|
||||
|
||||
enum class PathType { Subject, Clip };
|
||||
enum class JoinWith { NoJoin, Left, Right };
|
||||
|
||||
enum class VertexFlags : uint32_t {
|
||||
None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8
|
||||
Empty = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8
|
||||
};
|
||||
|
||||
constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b)
|
||||
@@ -54,7 +49,7 @@ namespace Clipper2Lib {
|
||||
Point64 pt;
|
||||
Vertex* next = nullptr;
|
||||
Vertex* prev = nullptr;
|
||||
VertexFlags flags = VertexFlags::None;
|
||||
VertexFlags flags = VertexFlags::Empty;
|
||||
};
|
||||
|
||||
struct OutPt {
|
||||
@@ -62,7 +57,7 @@ namespace Clipper2Lib {
|
||||
OutPt* next = nullptr;
|
||||
OutPt* prev = nullptr;
|
||||
OutRec* outrec;
|
||||
Joiner* joiner = nullptr;
|
||||
HorzSegment* horz = nullptr;
|
||||
|
||||
OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) {
|
||||
next = this;
|
||||
@@ -84,15 +79,21 @@ namespace Clipper2Lib {
|
||||
struct OutRec {
|
||||
size_t idx = 0;
|
||||
OutRec* owner = nullptr;
|
||||
OutRecList* splits = nullptr;
|
||||
Active* front_edge = nullptr;
|
||||
Active* back_edge = nullptr;
|
||||
OutPt* pts = nullptr;
|
||||
PolyPath* polypath = nullptr;
|
||||
OutRecList* splits = nullptr;
|
||||
OutRec* recursive_split = nullptr;
|
||||
Rect64 bounds = {};
|
||||
Path64 path;
|
||||
bool is_open = false;
|
||||
~OutRec() { if (splits) delete splits; };
|
||||
|
||||
~OutRec() {
|
||||
if (splits) delete splits;
|
||||
// nb: don't delete the split pointers
|
||||
// as these are owned by ClipperBase's outrec_list_
|
||||
};
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
@@ -124,6 +125,7 @@ namespace Clipper2Lib {
|
||||
Vertex* vertex_top = nullptr;
|
||||
LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti)
|
||||
bool is_left_bound = false;
|
||||
JoinWith join_with = JoinWith::NoJoin;
|
||||
};
|
||||
|
||||
struct LocalMinima {
|
||||
@@ -138,11 +140,24 @@ namespace Clipper2Lib {
|
||||
Point64 pt;
|
||||
Active* edge1;
|
||||
Active* edge2;
|
||||
IntersectNode() : pt(Point64(0, 0)), edge1(NULL), edge2(NULL) {}
|
||||
IntersectNode() : pt(Point64(0,0)), edge1(NULL), edge2(NULL) {}
|
||||
IntersectNode(Active* e1, Active* e2, Point64& pt_) :
|
||||
pt(pt_), edge1(e1), edge2(e2)
|
||||
{
|
||||
}
|
||||
pt(pt_), edge1(e1), edge2(e2) {}
|
||||
};
|
||||
|
||||
struct HorzSegment {
|
||||
OutPt* left_op;
|
||||
OutPt* right_op = nullptr;
|
||||
bool left_to_right = true;
|
||||
HorzSegment() : left_op(nullptr) { }
|
||||
explicit HorzSegment(OutPt* op) : left_op(op) { }
|
||||
};
|
||||
|
||||
struct HorzJoin {
|
||||
OutPt* op1 = nullptr;
|
||||
OutPt* op2 = nullptr;
|
||||
HorzJoin() {};
|
||||
explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { }
|
||||
};
|
||||
|
||||
#ifdef USINGZ
|
||||
@@ -153,11 +168,30 @@ namespace Clipper2Lib {
|
||||
const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD;
|
||||
#endif
|
||||
|
||||
typedef std::vector<HorzSegment> HorzSegmentList;
|
||||
typedef std::unique_ptr<LocalMinima> LocalMinima_ptr;
|
||||
typedef std::vector<LocalMinima_ptr> LocalMinimaList;
|
||||
typedef std::vector<IntersectNode> IntersectNodeList;
|
||||
|
||||
// ReuseableDataContainer64 ------------------------------------------------
|
||||
|
||||
class ReuseableDataContainer64 {
|
||||
private:
|
||||
friend class ClipperBase;
|
||||
LocalMinimaList minima_list_;
|
||||
std::vector<Vertex*> vertex_lists_;
|
||||
void AddLocMin(Vertex& vert, PathType polytype, bool is_open);
|
||||
public:
|
||||
virtual ~ReuseableDataContainer64();
|
||||
void Clear();
|
||||
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
|
||||
};
|
||||
|
||||
// ClipperBase -------------------------------------------------------------
|
||||
|
||||
class ClipperBase {
|
||||
private:
|
||||
ClipType cliptype_ = ClipType::None;
|
||||
ClipType cliptype_ = ClipType::NoClip;
|
||||
FillRule fillrule_ = FillRule::EvenOdd;
|
||||
FillRule fillpos = FillRule::Positive;
|
||||
int64_t bot_y_ = 0;
|
||||
@@ -165,21 +199,21 @@ namespace Clipper2Lib {
|
||||
bool using_polytree_ = false;
|
||||
Active* actives_ = nullptr;
|
||||
Active *sel_ = nullptr;
|
||||
Joiner *horz_joiners_ = nullptr;
|
||||
std::vector<LocalMinima*> minima_list_; //pointers in case of memory reallocs
|
||||
std::vector<LocalMinima*>::iterator current_locmin_iter_;
|
||||
LocalMinimaList minima_list_; //pointers in case of memory reallocs
|
||||
LocalMinimaList::iterator current_locmin_iter_;
|
||||
std::vector<Vertex*> vertex_lists_;
|
||||
std::priority_queue<int64_t> scanline_list_;
|
||||
std::vector<IntersectNode> intersect_nodes_;
|
||||
std::vector<Joiner*> joiner_list_; //pointers in case of memory reallocs
|
||||
IntersectNodeList intersect_nodes_;
|
||||
HorzSegmentList horz_seg_list_;
|
||||
std::vector<HorzJoin> horz_join_list_;
|
||||
void Reset();
|
||||
void InsertScanline(int64_t y);
|
||||
bool PopScanline(int64_t &y);
|
||||
bool PopLocalMinima(int64_t y, LocalMinima *&local_minima);
|
||||
inline void InsertScanline(int64_t y);
|
||||
inline bool PopScanline(int64_t &y);
|
||||
inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima);
|
||||
void DisposeAllOutRecs();
|
||||
void DisposeVerticesAndLocalMinima();
|
||||
void DeleteEdges(Active*& e);
|
||||
void AddLocMin(Vertex &vert, PathType polytype, bool is_open);
|
||||
inline void AddLocMin(Vertex &vert, PathType polytype, bool is_open);
|
||||
bool IsContributingClosed(const Active &e) const;
|
||||
inline bool IsContributingOpen(const Active &e) const;
|
||||
void SetWindCountForClosedPathEdge(Active &edge);
|
||||
@@ -190,7 +224,7 @@ namespace Clipper2Lib {
|
||||
inline bool PopHorz(Active *&e);
|
||||
inline OutPt* StartOpenPath(Active &e, const Point64& pt);
|
||||
inline void UpdateEdgeIntoAEL(Active *e);
|
||||
OutPt* IntersectEdges(Active &e1, Active &e2, const Point64& pt);
|
||||
void IntersectEdges(Active &e1, Active &e2, const Point64& pt);
|
||||
inline void DeleteFromAEL(Active &e);
|
||||
inline void AdjustCurrXAndCopyToSEL(const int64_t top_y);
|
||||
void DoIntersections(const int64_t top_y);
|
||||
@@ -198,38 +232,41 @@ namespace Clipper2Lib {
|
||||
bool BuildIntersectList(const int64_t top_y);
|
||||
void ProcessIntersectList();
|
||||
void SwapPositionsInAEL(Active& edge1, Active& edge2);
|
||||
OutRec* NewOutRec();
|
||||
OutPt* AddOutPt(const Active &e, const Point64& pt);
|
||||
OutPt* AddLocalMinPoly(Active &e1, Active &e2,
|
||||
const Point64& pt, bool is_new = false);
|
||||
OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt);
|
||||
void DoHorizontal(Active &horz);
|
||||
bool ResetHorzDirection(const Active &horz, const Active *max_pair,
|
||||
bool ResetHorzDirection(const Active &horz, const Vertex* max_vertex,
|
||||
int64_t &horz_left, int64_t &horz_right);
|
||||
void DoTopOfScanbeam(const int64_t top_y);
|
||||
Active *DoMaxima(Active &e);
|
||||
void JoinOutrecPaths(Active &e1, Active &e2);
|
||||
void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec);
|
||||
bool ValidateClosedPathEx(OutPt*& outrec);
|
||||
void CleanCollinear(OutRec* outrec);
|
||||
void FixSelfIntersects(OutRec* outrec);
|
||||
void DoSplitOp(OutRec* outRec, OutPt* splitOp);
|
||||
Joiner* GetHorzTrialParent(const OutPt* op);
|
||||
bool OutPtInTrialHorzList(OutPt* op);
|
||||
void SafeDisposeOutPts(OutPt*& op);
|
||||
void SafeDeleteOutPtJoiners(OutPt* op);
|
||||
void AddTrialHorzJoin(OutPt* op);
|
||||
void DeleteTrialHorzJoin(OutPt* op);
|
||||
void ConvertHorzTrialsToJoins();
|
||||
void AddJoin(OutPt* op1, OutPt* op2);
|
||||
void DeleteJoin(Joiner* joiner);
|
||||
void ProcessJoinerList();
|
||||
OutRec* ProcessJoin(Joiner* joiner);
|
||||
|
||||
inline void AddTrialHorzJoin(OutPt* op);
|
||||
void ConvertHorzSegsToJoins();
|
||||
void ProcessHorzJoins();
|
||||
|
||||
void Split(Active& e, const Point64& pt);
|
||||
inline void CheckJoinLeft(Active& e,
|
||||
const Point64& pt, bool check_curr_x = false);
|
||||
inline void CheckJoinRight(Active& e,
|
||||
const Point64& pt, bool check_curr_x = false);
|
||||
protected:
|
||||
bool preserve_collinear_ = true;
|
||||
bool reverse_solution_ = false;
|
||||
int error_code_ = 0;
|
||||
bool has_open_paths_ = false;
|
||||
bool succeeded_ = true;
|
||||
std::vector<OutRec*> outrec_list_; //pointers in case list memory reallocated
|
||||
OutRecList outrec_list_; //pointers in case list memory reallocated
|
||||
bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
|
||||
bool DeepCheckOwner(OutRec* outrec, OutRec* owner);
|
||||
void CleanCollinear(OutRec* outrec);
|
||||
bool CheckBounds(OutRec* outrec);
|
||||
bool CheckSplitOwner(OutRec* outrec, OutRecList* splits);
|
||||
void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath);
|
||||
#ifdef USINGZ
|
||||
ZCallback64 zCallback_ = nullptr;
|
||||
void SetZ(const Active& e1, const Active& e2, Point64& pt);
|
||||
@@ -239,9 +276,16 @@ namespace Clipper2Lib {
|
||||
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
|
||||
public:
|
||||
virtual ~ClipperBase();
|
||||
bool PreserveCollinear = true;
|
||||
bool ReverseSolution = false;
|
||||
int ErrorCode() const { return error_code_; };
|
||||
void PreserveCollinear(bool val) { preserve_collinear_ = val; };
|
||||
bool PreserveCollinear() const { return preserve_collinear_;};
|
||||
void ReverseSolution(bool val) { reverse_solution_ = val; };
|
||||
bool ReverseSolution() const { return reverse_solution_; };
|
||||
void Clear();
|
||||
void AddReuseableData(const ReuseableDataContainer64& reuseable_data);
|
||||
#ifdef USINGZ
|
||||
int64_t DefaultZ = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
// PolyPath / PolyTree --------------------------------------------------------
|
||||
@@ -256,7 +300,7 @@ namespace Clipper2Lib {
|
||||
PolyPath* parent_;
|
||||
public:
|
||||
PolyPath(PolyPath* parent = nullptr): parent_(parent){}
|
||||
virtual ~PolyPath() { Clear(); };
|
||||
virtual ~PolyPath() {};
|
||||
//https://en.cppreference.com/w/cpp/language/rule_of_three
|
||||
PolyPath(const PolyPath&) = delete;
|
||||
PolyPath& operator=(const PolyPath&) = delete;
|
||||
@@ -271,45 +315,54 @@ namespace Clipper2Lib {
|
||||
|
||||
virtual PolyPath* AddChild(const Path64& path) = 0;
|
||||
|
||||
virtual void Clear() {};
|
||||
virtual void Clear() = 0;
|
||||
virtual size_t Count() const { return 0; }
|
||||
|
||||
const PolyPath* Parent() const { return parent_; }
|
||||
|
||||
bool IsHole() const
|
||||
{
|
||||
const PolyPath* pp = parent_;
|
||||
bool is_hole = pp;
|
||||
while (pp) {
|
||||
is_hole = !is_hole;
|
||||
pp = pp->parent_;
|
||||
}
|
||||
return is_hole;
|
||||
unsigned lvl = Level();
|
||||
//Even levels except level 0
|
||||
return lvl && !(lvl & 1);
|
||||
}
|
||||
};
|
||||
|
||||
typedef typename std::vector<std::unique_ptr<PolyPath64>> PolyPath64List;
|
||||
typedef typename std::vector<std::unique_ptr<PolyPathD>> PolyPathDList;
|
||||
|
||||
class PolyPath64 : public PolyPath {
|
||||
private:
|
||||
std::vector<PolyPath64*> childs_;
|
||||
PolyPath64List childs_;
|
||||
Path64 polygon_;
|
||||
typedef typename std::vector<PolyPath64*>::const_iterator pp64_itor;
|
||||
public:
|
||||
PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
|
||||
PolyPath64* operator [] (size_t index) { return static_cast<PolyPath64*>(childs_[index]); }
|
||||
pp64_itor begin() const { return childs_.cbegin(); }
|
||||
pp64_itor end() const { return childs_.cend(); }
|
||||
explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
|
||||
explicit PolyPath64(PolyPath64* parent, const Path64& path) : PolyPath(parent) { polygon_ = path; }
|
||||
|
||||
~PolyPath64() {
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
PolyPath64* operator [] (size_t index) const
|
||||
{
|
||||
return childs_[index].get(); //std::unique_ptr
|
||||
}
|
||||
|
||||
PolyPath64* Child(size_t index) const
|
||||
{
|
||||
return childs_[index].get();
|
||||
}
|
||||
|
||||
PolyPath64List::const_iterator begin() const { return childs_.cbegin(); }
|
||||
PolyPath64List::const_iterator end() const { return childs_.cend(); }
|
||||
|
||||
PolyPath64* AddChild(const Path64& path) override
|
||||
{
|
||||
PolyPath64* result = new PolyPath64(this);
|
||||
childs_.push_back(result);
|
||||
result->polygon_ = path;
|
||||
return result;
|
||||
return childs_.emplace_back(std::make_unique<PolyPath64>(this, path)).get();
|
||||
}
|
||||
|
||||
void Clear() override
|
||||
{
|
||||
for (const PolyPath64* child : childs_) delete child;
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
@@ -322,74 +375,69 @@ namespace Clipper2Lib {
|
||||
|
||||
double Area() const
|
||||
{
|
||||
double result = Clipper2Lib::Area<int64_t>(polygon_);
|
||||
for (const PolyPath64* child : childs_)
|
||||
result += child->Area();
|
||||
return result;
|
||||
}
|
||||
|
||||
friend std::ostream& operator << (std::ostream& outstream, const PolyPath64& polypath)
|
||||
{
|
||||
const size_t level_indent = 4;
|
||||
const size_t coords_per_line = 4;
|
||||
const size_t last_on_line = coords_per_line - 1;
|
||||
unsigned level = polypath.Level();
|
||||
if (level > 0)
|
||||
{
|
||||
std::string level_padding;
|
||||
level_padding.insert(0, (level - 1) * level_indent, ' ');
|
||||
std::string caption = polypath.IsHole() ? "Hole " : "Outer Polygon ";
|
||||
std::string childs = polypath.Count() == 1 ? " child" : " children";
|
||||
outstream << level_padding.c_str() << caption << "with " << polypath.Count() << childs << std::endl;
|
||||
outstream << level_padding;
|
||||
size_t i = 0, highI = polypath.Polygon().size() - 1;
|
||||
for (; i < highI; ++i)
|
||||
{
|
||||
outstream << polypath.Polygon()[i] << ' ';
|
||||
if ((i % coords_per_line) == last_on_line)
|
||||
outstream << std::endl << level_padding;
|
||||
}
|
||||
if (highI > 0) outstream << polypath.Polygon()[i];
|
||||
outstream << std::endl;
|
||||
}
|
||||
for (auto child : polypath)
|
||||
outstream << *child;
|
||||
return outstream;
|
||||
return std::accumulate(childs_.cbegin(), childs_.cend(),
|
||||
Clipper2Lib::Area<int64_t>(polygon_),
|
||||
[](double a, const auto& child) {return a + child->Area(); });
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class PolyPathD : public PolyPath {
|
||||
private:
|
||||
std::vector<PolyPathD*> childs_;
|
||||
double inv_scale_;
|
||||
PolyPathDList childs_;
|
||||
double scale_;
|
||||
PathD polygon_;
|
||||
typedef typename std::vector<PolyPathD*>::const_iterator ppD_itor;
|
||||
public:
|
||||
PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent)
|
||||
explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent)
|
||||
{
|
||||
inv_scale_ = parent ? parent->inv_scale_ : 1.0;
|
||||
scale_ = parent ? parent->scale_ : 1.0;
|
||||
}
|
||||
PolyPathD* operator [] (size_t index)
|
||||
{
|
||||
return static_cast<PolyPathD*>(childs_[index]);
|
||||
}
|
||||
ppD_itor begin() const { return childs_.cbegin(); }
|
||||
ppD_itor end() const { return childs_.cend(); }
|
||||
|
||||
void SetInvScale(double value) { inv_scale_ = value; }
|
||||
double InvScale() { return inv_scale_; }
|
||||
explicit PolyPathD(PolyPathD* parent, const Path64& path) : PolyPath(parent)
|
||||
{
|
||||
scale_ = parent ? parent->scale_ : 1.0;
|
||||
int error_code = 0;
|
||||
polygon_ = ScalePath<double, int64_t>(path, scale_, error_code);
|
||||
}
|
||||
|
||||
explicit PolyPathD(PolyPathD* parent, const PathD& path) : PolyPath(parent)
|
||||
{
|
||||
scale_ = parent ? parent->scale_ : 1.0;
|
||||
polygon_ = path;
|
||||
}
|
||||
|
||||
~PolyPathD() {
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
PolyPathD* operator [] (size_t index) const
|
||||
{
|
||||
return childs_[index].get();
|
||||
}
|
||||
|
||||
PolyPathD* Child(size_t index) const
|
||||
{
|
||||
return childs_[index].get();
|
||||
}
|
||||
|
||||
PolyPathDList::const_iterator begin() const { return childs_.cbegin(); }
|
||||
PolyPathDList::const_iterator end() const { return childs_.cend(); }
|
||||
|
||||
void SetScale(double value) { scale_ = value; }
|
||||
double Scale() const { return scale_; }
|
||||
|
||||
PolyPathD* AddChild(const Path64& path) override
|
||||
{
|
||||
PolyPathD* result = new PolyPathD(this);
|
||||
childs_.push_back(result);
|
||||
result->polygon_ = ScalePath<double, int64_t>(path, inv_scale_);
|
||||
return result;
|
||||
return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
|
||||
}
|
||||
|
||||
PolyPathD* AddChild(const PathD& path)
|
||||
{
|
||||
return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
|
||||
}
|
||||
|
||||
void Clear() override
|
||||
{
|
||||
for (const PolyPathD* child : childs_) delete child;
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
@@ -402,10 +450,9 @@ namespace Clipper2Lib {
|
||||
|
||||
double Area() const
|
||||
{
|
||||
double result = Clipper2Lib::Area<double>(polygon_);
|
||||
for (const PolyPathD* child : childs_)
|
||||
result += child->Area();
|
||||
return result;
|
||||
return std::accumulate(childs_.begin(), childs_.end(),
|
||||
Clipper2Lib::Area<double>(polygon_),
|
||||
[](double a, const auto& child) {return a + child->Area(); });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -474,14 +521,14 @@ namespace Clipper2Lib {
|
||||
private:
|
||||
double scale_ = 1.0, invScale_ = 1.0;
|
||||
#ifdef USINGZ
|
||||
ZCallbackD zCallback_ = nullptr;
|
||||
ZCallbackD zCallbackD_ = nullptr;
|
||||
#endif
|
||||
void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen);
|
||||
void BuildTreeD(PolyPathD& polytree, PathsD& open_paths);
|
||||
public:
|
||||
explicit ClipperD(int precision = 2) : ClipperBase()
|
||||
{
|
||||
CheckPrecision(precision);
|
||||
CheckPrecisionRange(precision, error_code_);
|
||||
// to optimize scaling / descaling precision
|
||||
// set the scale to a power of double's radix (2) (#25)
|
||||
scale_ = std::pow(std::numeric_limits<double>::radix,
|
||||
@@ -490,7 +537,7 @@ namespace Clipper2Lib {
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
void SetZCallback(ZCallbackD cb) { zCallback_ = cb; };
|
||||
void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; };
|
||||
|
||||
void ZCB(const Point64& e1bot, const Point64& e1top,
|
||||
const Point64& e2bot, const Point64& e2top, Point64& pt)
|
||||
@@ -504,13 +551,13 @@ namespace Clipper2Lib {
|
||||
PointD e1t = PointD(e1top) * invScale_;
|
||||
PointD e2b = PointD(e2bot) * invScale_;
|
||||
PointD e2t = PointD(e2top) * invScale_;
|
||||
zCallback_(e1b,e1t, e2b, e2t, tmp);
|
||||
zCallbackD_(e1b,e1t, e2b, e2t, tmp);
|
||||
pt.z = tmp.z; // only update 'z'
|
||||
};
|
||||
|
||||
void CheckCallback()
|
||||
{
|
||||
if(zCallback_)
|
||||
if(zCallbackD_)
|
||||
// if the user defined float point callback has been assigned
|
||||
// then assign the proxy callback function
|
||||
ClipperBase::zCallback_ =
|
||||
@@ -525,17 +572,17 @@ namespace Clipper2Lib {
|
||||
|
||||
void AddSubject(const PathsD& subjects)
|
||||
{
|
||||
AddPaths(ScalePaths<int64_t, double>(subjects, scale_), PathType::Subject, false);
|
||||
AddPaths(ScalePaths<int64_t, double>(subjects, scale_, error_code_), PathType::Subject, false);
|
||||
}
|
||||
|
||||
void AddOpenSubject(const PathsD& open_subjects)
|
||||
{
|
||||
AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_), PathType::Subject, true);
|
||||
AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_, error_code_), PathType::Subject, true);
|
||||
}
|
||||
|
||||
void AddClip(const PathsD& clips)
|
||||
{
|
||||
AddPaths(ScalePaths<int64_t, double>(clips, scale_), PathType::Clip, false);
|
||||
AddPaths(ScalePaths<int64_t, double>(clips, scale_, error_code_), PathType::Clip, false);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths)
|
||||
@@ -573,7 +620,7 @@ namespace Clipper2Lib {
|
||||
if (ExecuteInternal(clip_type, fill_rule, true))
|
||||
{
|
||||
polytree.Clear();
|
||||
polytree.SetInvScale(invScale_);
|
||||
polytree.SetScale(invScale_);
|
||||
open_paths.clear();
|
||||
BuildTreeD(polytree, open_paths);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,16 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 15 October 2022 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2022 *
|
||||
* Date : 1 November 2023 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2023 *
|
||||
* Purpose : Minkowski Sum and Difference *
|
||||
* License : http://www.boost.org/LICENSE_1_0.txt *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_MINKOWSKI_H
|
||||
#define CLIPPER_MINKOWSKI_H
|
||||
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "clipper.core.h"
|
||||
#include "clipper2/clipper.core.h"
|
||||
|
||||
namespace Clipper2Lib
|
||||
{
|
||||
@@ -35,7 +32,7 @@ namespace Clipper2Lib
|
||||
Path64 path2(pattern.size());
|
||||
std::transform(pattern.cbegin(), pattern.cend(),
|
||||
path2.begin(), [p](const Point64& pt2) {return p + pt2; });
|
||||
tmp.push_back(path2);
|
||||
tmp.emplace_back(std::move(path2));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -45,7 +42,7 @@ namespace Clipper2Lib
|
||||
Path64 path2(pattern.size());
|
||||
std::transform(pattern.cbegin(), pattern.cend(),
|
||||
path2.begin(), [p](const Point64& pt2) {return p - pt2; });
|
||||
tmp.push_back(path2);
|
||||
tmp.emplace_back(std::move(path2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,14 +56,14 @@ namespace Clipper2Lib
|
||||
Path64 quad;
|
||||
quad.reserve(4);
|
||||
{
|
||||
quad.push_back(tmp[g][h]);
|
||||
quad.push_back(tmp[i][h]);
|
||||
quad.push_back(tmp[i][j]);
|
||||
quad.push_back(tmp[g][j]);
|
||||
quad.emplace_back(tmp[g][h]);
|
||||
quad.emplace_back(tmp[i][h]);
|
||||
quad.emplace_back(tmp[i][j]);
|
||||
quad.emplace_back(tmp[g][j]);
|
||||
};
|
||||
if (!IsPositive(quad))
|
||||
std::reverse(quad.begin(), quad.end());
|
||||
result.push_back(quad);
|
||||
result.emplace_back(std::move(quad));
|
||||
h = j;
|
||||
}
|
||||
g = i;
|
||||
@@ -92,11 +89,12 @@ namespace Clipper2Lib
|
||||
|
||||
inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
|
||||
{
|
||||
int error_code = 0;
|
||||
double scale = pow(10, decimalPlaces);
|
||||
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale);
|
||||
Path64 path64 = ScalePath<int64_t, double>(path, scale);
|
||||
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
|
||||
Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
|
||||
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero);
|
||||
return ScalePaths<double, int64_t>(tmp, 1 / scale);
|
||||
return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
|
||||
}
|
||||
|
||||
inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed)
|
||||
@@ -106,11 +104,12 @@ namespace Clipper2Lib
|
||||
|
||||
inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
|
||||
{
|
||||
int error_code = 0;
|
||||
double scale = pow(10, decimalPlaces);
|
||||
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale);
|
||||
Path64 path64 = ScalePath<int64_t, double>(path, scale);
|
||||
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
|
||||
Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
|
||||
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero);
|
||||
return ScalePaths<double, int64_t>(tmp, 1 / scale);
|
||||
return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
|
||||
}
|
||||
|
||||
} // Clipper2Lib namespace
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 15 October 2022 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2022 *
|
||||
* Date : 22 January 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Path Offset (Inflate/Shrink) *
|
||||
* License : http://www.boost.org/LICENSE_1_0.txt *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_OFFSET_H_
|
||||
#define CLIPPER_OFFSET_H_
|
||||
|
||||
#include "clipper.core.h"
|
||||
#include "clipper.engine.h"
|
||||
#include <optional>
|
||||
|
||||
namespace Clipper2Lib {
|
||||
|
||||
enum class JoinType { Square, Round, Miter };
|
||||
enum class JoinType { Square, Bevel, Round, Miter };
|
||||
//Square : Joins are 'squared' at exactly the offset distance (more complex code)
|
||||
//Bevel : Similar to Square, but the offset distance varies with angle (simple code & faster)
|
||||
|
||||
enum class EndType {Polygon, Joined, Butt, Square, Round};
|
||||
//Butt : offsets both sides of a path, with square blunt ends
|
||||
@@ -23,47 +27,62 @@ enum class EndType {Polygon, Joined, Butt, Square, Round};
|
||||
//Joined : offsets both sides of a path, with joined ends
|
||||
//Polygon: offsets only one side of a closed path
|
||||
|
||||
typedef std::function<double(const Path64& path, const PathD& path_normals, size_t curr_idx, size_t prev_idx)> DeltaCallback64;
|
||||
|
||||
class ClipperOffset {
|
||||
private:
|
||||
|
||||
class Group {
|
||||
public:
|
||||
Paths64 paths_in_;
|
||||
Paths64 paths_out_;
|
||||
Path64 path_;
|
||||
bool is_reversed_ = false;
|
||||
JoinType join_type_;
|
||||
EndType end_type_;
|
||||
Group(const Paths64& paths, JoinType join_type, EndType end_type) :
|
||||
paths_in_(paths), join_type_(join_type), end_type_(end_type) {}
|
||||
Paths64 paths_in;
|
||||
std::optional<size_t> lowest_path_idx{};
|
||||
bool is_reversed = false;
|
||||
JoinType join_type;
|
||||
EndType end_type;
|
||||
Group(const Paths64& _paths, JoinType _join_type, EndType _end_type);
|
||||
};
|
||||
|
||||
int error_code_ = 0;
|
||||
double delta_ = 0.0;
|
||||
double group_delta_ = 0.0;
|
||||
double abs_group_delta_ = 0.0;
|
||||
double temp_lim_ = 0.0;
|
||||
double steps_per_rad_ = 0.0;
|
||||
double step_sin_ = 0.0;
|
||||
double step_cos_ = 0.0;
|
||||
PathD norms;
|
||||
Paths64 solution;
|
||||
Path64 path_out;
|
||||
Paths64* solution = nullptr;
|
||||
PolyTree64* solution_tree = nullptr;
|
||||
std::vector<Group> groups_;
|
||||
JoinType join_type_ = JoinType::Square;
|
||||
JoinType join_type_ = JoinType::Bevel;
|
||||
EndType end_type_ = EndType::Polygon;
|
||||
|
||||
double miter_limit_ = 0.0;
|
||||
double arc_tolerance_ = 0.0;
|
||||
bool merge_groups_ = true;
|
||||
bool preserve_collinear_ = false;
|
||||
bool reverse_solution_ = false;
|
||||
|
||||
void DoSquare(Group& group, const Path64& path, size_t j, size_t k);
|
||||
void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a);
|
||||
void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle);
|
||||
#ifdef USINGZ
|
||||
ZCallback64 zCallback64_ = nullptr;
|
||||
void ZCB(const Point64& bot1, const Point64& top1,
|
||||
const Point64& bot2, const Point64& top2, Point64& ip);
|
||||
#endif
|
||||
DeltaCallback64 deltaCallback64_ = nullptr;
|
||||
size_t CalcSolutionCapacity();
|
||||
bool CheckReverseOrientation();
|
||||
void DoBevel(const Path64& path, size_t j, size_t k);
|
||||
void DoSquare(const Path64& path, size_t j, size_t k);
|
||||
void DoMiter(const Path64& path, size_t j, size_t k, double cos_a);
|
||||
void DoRound(const Path64& path, size_t j, size_t k, double angle);
|
||||
void BuildNormals(const Path64& path);
|
||||
void OffsetPolygon(Group& group, Path64& path);
|
||||
void OffsetOpenJoined(Group& group, Path64& path);
|
||||
void OffsetOpenPath(Group& group, Path64& path, EndType endType);
|
||||
void OffsetPoint(Group& group, Path64& path, size_t j, size_t& k);
|
||||
void DoGroupOffset(Group &group, double delta);
|
||||
void OffsetPolygon(Group& group, const Path64& path);
|
||||
void OffsetOpenJoined(Group& group, const Path64& path);
|
||||
void OffsetOpenPath(Group& group, const Path64& path);
|
||||
void OffsetPoint(Group& group, const Path64& path, size_t j, size_t k);
|
||||
void DoGroupOffset(Group &group);
|
||||
void ExecuteInternal(double delta);
|
||||
public:
|
||||
ClipperOffset(double miter_limit = 2.0,
|
||||
explicit ClipperOffset(double miter_limit = 2.0,
|
||||
double arc_tolerance = 0.0,
|
||||
bool preserve_collinear = false,
|
||||
bool reverse_solution = false) :
|
||||
@@ -73,13 +92,14 @@ public:
|
||||
|
||||
~ClipperOffset() { Clear(); };
|
||||
|
||||
int ErrorCode() const { return error_code_; };
|
||||
void AddPath(const Path64& path, JoinType jt_, EndType et_);
|
||||
void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
|
||||
void AddPath(const PathD &p, JoinType jt_, EndType et_);
|
||||
void AddPaths(const PathsD &p, JoinType jt_, EndType et_);
|
||||
void Clear() { groups_.clear(); norms.clear(); };
|
||||
|
||||
Paths64 Execute(double delta);
|
||||
void Execute(double delta, Paths64& sols_64);
|
||||
void Execute(double delta, PolyTree64& polytree);
|
||||
void Execute(DeltaCallback64 delta_cb, Paths64& paths);
|
||||
|
||||
double MiterLimit() const { return miter_limit_; }
|
||||
void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; }
|
||||
@@ -88,19 +108,17 @@ public:
|
||||
double ArcTolerance() const { return arc_tolerance_; }
|
||||
void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; }
|
||||
|
||||
//MergeGroups: A path group is one or more paths added via the AddPath or
|
||||
//AddPaths methods. By default these path groups will be offset
|
||||
//independently of other groups and this may cause overlaps (intersections).
|
||||
//However, when MergeGroups is enabled, any overlapping offsets will be
|
||||
//merged (via a clipping union operation) to remove overlaps.
|
||||
bool MergeGroups() const { return merge_groups_; }
|
||||
void MergeGroups(bool merge_groups) { merge_groups_ = merge_groups; }
|
||||
|
||||
bool PreserveCollinear() const { return preserve_collinear_; }
|
||||
void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;}
|
||||
|
||||
bool ReverseSolution() const { return reverse_solution_; }
|
||||
void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;}
|
||||
|
||||
#ifdef USINGZ
|
||||
void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; }
|
||||
#endif
|
||||
void SetDeltaCallback(DeltaCallback64 cb) { deltaCallback64_ = cb; }
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,49 +1,79 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 26 October 2022 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2022 *
|
||||
* Date : 5 July 2024 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Purpose : FAST rectangular clipping *
|
||||
* License : http://www.boost.org/LICENSE_1_0.txt *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_RECTCLIP_H
|
||||
#define CLIPPER_RECTCLIP_H
|
||||
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include "clipper.h"
|
||||
#include "clipper.core.h"
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include <queue>
|
||||
|
||||
namespace Clipper2Lib
|
||||
{
|
||||
|
||||
// Location: the order is important here, see StartLocsIsClockwise()
|
||||
enum class Location { Left, Top, Right, Bottom, Inside };
|
||||
|
||||
class RectClip {
|
||||
class OutPt2;
|
||||
typedef std::vector<OutPt2*> OutPt2List;
|
||||
|
||||
class OutPt2 {
|
||||
public:
|
||||
Point64 pt;
|
||||
size_t owner_idx = 0;
|
||||
OutPt2List* edge = nullptr;
|
||||
OutPt2* next = nullptr;
|
||||
OutPt2* prev = nullptr;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// RectClip64
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class RectClip64 {
|
||||
private:
|
||||
void ExecuteInternal(const Path64& path);
|
||||
Path64 GetPath(OutPt2*& op);
|
||||
protected:
|
||||
const Rect64 rect_;
|
||||
const Point64 mp_;
|
||||
const Path64 rectPath_;
|
||||
Path64 result_;
|
||||
const Path64 rect_as_path_;
|
||||
const Point64 rect_mp_;
|
||||
Rect64 path_bounds_;
|
||||
std::deque<OutPt2> op_container_;
|
||||
OutPt2List results_; // each path can be broken into multiples
|
||||
OutPt2List edges_[8]; // clockwise and counter-clockwise
|
||||
std::vector<Location> start_locs_;
|
||||
|
||||
void CheckEdges();
|
||||
void TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw);
|
||||
void GetNextLocation(const Path64& path,
|
||||
Location& loc, int& i, int highI);
|
||||
Location& loc, size_t& i, size_t highI);
|
||||
OutPt2* Add(Point64 pt, bool start_new = false);
|
||||
void AddCorner(Location prev, Location curr);
|
||||
void AddCorner(Location& loc, bool isClockwise);
|
||||
public:
|
||||
RectClip(const Rect64& rect) :
|
||||
explicit RectClip64(const Rect64& rect) :
|
||||
rect_(rect),
|
||||
mp_(rect.MidPoint()),
|
||||
rectPath_(rect.AsPath()) {}
|
||||
Path64 Execute(const Path64& path);
|
||||
rect_as_path_(rect.AsPath()),
|
||||
rect_mp_(rect.MidPoint()) {}
|
||||
Paths64 Execute(const Paths64& paths);
|
||||
};
|
||||
|
||||
class RectClipLines : public RectClip {
|
||||
//------------------------------------------------------------------------------
|
||||
// RectClipLines64
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class RectClipLines64 : public RectClip64 {
|
||||
private:
|
||||
void ExecuteInternal(const Path64& path);
|
||||
Path64 GetPath(OutPt2*& op);
|
||||
public:
|
||||
RectClipLines(const Rect64& rect) : RectClip(rect) {};
|
||||
Paths64 Execute(const Path64& path);
|
||||
explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {};
|
||||
Paths64 Execute(const Paths64& paths);
|
||||
};
|
||||
|
||||
} // Clipper2Lib namespace
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef CLIPPER_VERSION_H
|
||||
#define CLIPPER_VERSION_H
|
||||
|
||||
constexpr auto CLIPPER2_VERSION = "1.5.2";
|
||||
|
||||
#endif // CLIPPER_VERSION_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,48 +1,72 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 15 October 2022 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2022 *
|
||||
* Date : 22 January 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Path Offset (Inflate/Shrink) *
|
||||
* License : http://www.boost.org/LICENSE_1_0.txt *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#include <cmath>
|
||||
#include "clipper2/clipper.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
|
||||
namespace Clipper2Lib {
|
||||
|
||||
const double default_arc_tolerance = 0.25;
|
||||
const double floating_point_tolerance = 1e-12;
|
||||
|
||||
// Clipper2 approximates arcs by using series of relatively short straight
|
||||
//line segments. And logically, shorter line segments will produce better arc
|
||||
// approximations. But very short segments can degrade performance, usually
|
||||
// with little or no discernable improvement in curve quality. Very short
|
||||
// segments can even detract from curve quality, due to the effects of integer
|
||||
// rounding. Since there isn't an optimal number of line segments for any given
|
||||
// arc radius (that perfectly balances curve approximation with performance),
|
||||
// arc tolerance is user defined. Nevertheless, when the user doesn't define
|
||||
// an arc tolerance (ie leaves alone the 0 default value), the calculated
|
||||
// default arc tolerance (offset_radius / 500) generally produces good (smooth)
|
||||
// arc approximations without producing excessively small segment lengths.
|
||||
// See also: https://www.angusj.com/clipper2/Docs/Trigonometry.htm
|
||||
const double arc_const = 0.002; // <-- 1/500
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Miscellaneous methods
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Paths64::size_type GetLowestPolygonIdx(const Paths64& paths)
|
||||
std::optional<size_t> GetLowestClosedPathIdx(const Paths64& paths)
|
||||
{
|
||||
Paths64::size_type result = 0;
|
||||
Point64 lp = Point64(static_cast<int64_t>(0),
|
||||
std::numeric_limits<int64_t>::min());
|
||||
|
||||
for (Paths64::size_type i = 0 ; i < paths.size(); ++i)
|
||||
for (const Point64& p : paths[i])
|
||||
std::optional<size_t> result;
|
||||
Point64 botPt = Point64(INT64_MAX, INT64_MIN);
|
||||
for (size_t i = 0; i < paths.size(); ++i)
|
||||
{
|
||||
if (p.y < lp.y || (p.y == lp.y && p.x >= lp.x)) continue;
|
||||
for (const Point64& pt : paths[i])
|
||||
{
|
||||
if ((pt.y < botPt.y) ||
|
||||
((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
|
||||
result = i;
|
||||
lp = p;
|
||||
botPt.x = pt.x;
|
||||
botPt.y = pt.y;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
|
||||
inline double Hypot(double x, double y)
|
||||
{
|
||||
// given that this is an internal function, and given the x and y parameters
|
||||
// will always be coordinate values (or the difference between coordinate values),
|
||||
// x and y should always be within INT64_MIN to INT64_MAX. Consequently,
|
||||
// there should be no risk that the following computation will overflow
|
||||
// see https://stackoverflow.com/a/32436148/359538
|
||||
return std::sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
static PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
|
||||
{
|
||||
double dx, dy, inverse_hypot;
|
||||
if (pt1 == pt2) return PointD(0.0, 0.0);
|
||||
dx = static_cast<double>(pt2.x - pt1.x);
|
||||
dy = static_cast<double>(pt2.y - pt1.y);
|
||||
inverse_hypot = 1.0 / hypot(dx, dy);
|
||||
double dx = static_cast<double>(pt2.x - pt1.x);
|
||||
double dy = static_cast<double>(pt2.y - pt1.y);
|
||||
double inverse_hypot = 1.0 / Hypot(dx, dy);
|
||||
dx *= inverse_hypot;
|
||||
dy *= inverse_hypot;
|
||||
return PointD(dy, -dx);
|
||||
@@ -53,15 +77,8 @@ inline bool AlmostZero(double value, double epsilon = 0.001)
|
||||
return std::fabs(value) < epsilon;
|
||||
}
|
||||
|
||||
inline double Hypot(double x, double y)
|
||||
{
|
||||
//see https://stackoverflow.com/a/32436148/359538
|
||||
return std::sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
inline PointD NormalizeVector(const PointD& vec)
|
||||
{
|
||||
|
||||
double h = Hypot(vec.x, vec.y);
|
||||
if (AlmostZero(h)) return PointD(0,0);
|
||||
double inverseHypot = 1 / h;
|
||||
@@ -78,14 +95,63 @@ inline bool IsClosedPath(EndType et)
|
||||
return et == EndType::Polygon || et == EndType::Joined;
|
||||
}
|
||||
|
||||
inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
|
||||
static inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
|
||||
{
|
||||
#ifdef USINGZ
|
||||
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
|
||||
#else
|
||||
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
|
||||
{
|
||||
#ifdef USINGZ
|
||||
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
|
||||
#else
|
||||
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void NegatePath(PathD& path)
|
||||
{
|
||||
for (PointD& pt : path)
|
||||
{
|
||||
pt.x = -pt.x;
|
||||
pt.y = -pt.y;
|
||||
#ifdef USINGZ
|
||||
pt.z = pt.z;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// ClipperOffset::Group methods
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type):
|
||||
paths_in(_paths), join_type(_join_type), end_type(_end_type)
|
||||
{
|
||||
bool is_joined =
|
||||
(end_type == EndType::Polygon) ||
|
||||
(end_type == EndType::Joined);
|
||||
for (Path64& p: paths_in)
|
||||
StripDuplicates(p, is_joined);
|
||||
|
||||
if (end_type == EndType::Polygon)
|
||||
{
|
||||
lowest_path_idx = GetLowestClosedPathIdx(paths_in);
|
||||
// the lowermost path must be an outer path, so if its orientation is negative,
|
||||
// then flag the whole group is 'reversed' (will negate delta etc.)
|
||||
// as this is much more efficient than reversing every path.
|
||||
is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
lowest_path_idx = std::nullopt;
|
||||
is_reversed = false;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -94,28 +160,13 @@ inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
|
||||
|
||||
void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_)
|
||||
{
|
||||
Paths64 paths;
|
||||
paths.push_back(path);
|
||||
AddPaths(paths, jt_, et_);
|
||||
groups_.emplace_back(Paths64(1, path), jt_, et_);
|
||||
}
|
||||
|
||||
void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_)
|
||||
{
|
||||
if (paths.size() == 0) return;
|
||||
groups_.push_back(Group(paths, jt_, et_));
|
||||
}
|
||||
|
||||
void ClipperOffset::AddPath(const Clipper2Lib::PathD& path, JoinType jt_, EndType et_)
|
||||
{
|
||||
PathsD paths;
|
||||
paths.push_back(path);
|
||||
AddPaths(paths, jt_, et_);
|
||||
}
|
||||
|
||||
void ClipperOffset::AddPaths(const PathsD& paths, JoinType jt_, EndType et_)
|
||||
{
|
||||
if (paths.size() == 0) return;
|
||||
groups_.push_back(Group(PathsDToPaths64(paths), jt_, et_));
|
||||
groups_.emplace_back(paths, jt_, et_);
|
||||
}
|
||||
|
||||
void ClipperOffset::BuildNormals(const Path64& path)
|
||||
@@ -123,64 +174,55 @@ void ClipperOffset::BuildNormals(const Path64& path)
|
||||
norms.clear();
|
||||
norms.reserve(path.size());
|
||||
if (path.size() == 0) return;
|
||||
Path64::const_iterator path_iter, path_last_iter = --path.cend();
|
||||
for (path_iter = path.cbegin(); path_iter != path_last_iter; ++path_iter)
|
||||
norms.push_back(GetUnitNormal(*path_iter,*(path_iter +1)));
|
||||
norms.push_back(GetUnitNormal(*path_last_iter, *(path.cbegin())));
|
||||
Path64::const_iterator path_iter, path_stop_iter = --path.cend();
|
||||
for (path_iter = path.cbegin(); path_iter != path_stop_iter; ++path_iter)
|
||||
norms.emplace_back(GetUnitNormal(*path_iter,*(path_iter +1)));
|
||||
norms.emplace_back(GetUnitNormal(*path_stop_iter, *(path.cbegin())));
|
||||
}
|
||||
|
||||
inline PointD TranslatePoint(const PointD& pt, double dx, double dy)
|
||||
void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
return PointD(pt.x + dx, pt.y + dy);
|
||||
}
|
||||
|
||||
inline PointD ReflectPoint(const PointD& pt, const PointD& pivot)
|
||||
{
|
||||
return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
|
||||
}
|
||||
|
||||
PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b,
|
||||
const PointD& pt2a, const PointD& pt2b)
|
||||
{
|
||||
if (pt1a.x == pt1b.x) //vertical
|
||||
PointD pt1, pt2;
|
||||
if (j == k)
|
||||
{
|
||||
if (pt2a.x == pt2b.x) return PointD(0, 0);
|
||||
|
||||
double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
|
||||
double b2 = pt2a.y - m2 * pt2a.x;
|
||||
return PointD(pt1a.x, m2 * pt1a.x + b2);
|
||||
}
|
||||
else if (pt2a.x == pt2b.x) //vertical
|
||||
{
|
||||
double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
|
||||
double b1 = pt1a.y - m1 * pt1a.x;
|
||||
return PointD(pt2a.x, m1 * pt2a.x + b1);
|
||||
double abs_delta = std::abs(group_delta_);
|
||||
#ifdef USINGZ
|
||||
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y, path[j].z);
|
||||
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y, path[j].z);
|
||||
#else
|
||||
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y);
|
||||
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
|
||||
double b1 = pt1a.y - m1 * pt1a.x;
|
||||
double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
|
||||
double b2 = pt2a.y - m2 * pt2a.x;
|
||||
if (m1 == m2) return PointD(0, 0);
|
||||
double x = (b2 - b1) / (m1 - m2);
|
||||
return PointD(x, m1 * x + b1);
|
||||
#ifdef USINGZ
|
||||
pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y, path[j].z);
|
||||
pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y, path[j].z);
|
||||
#else
|
||||
pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y);
|
||||
pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y);
|
||||
#endif
|
||||
}
|
||||
path_out.emplace_back(pt1);
|
||||
path_out.emplace_back(pt2);
|
||||
}
|
||||
|
||||
void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k)
|
||||
void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
PointD vec;
|
||||
if (j == k)
|
||||
vec = PointD(norms[0].y, -norms[0].x);
|
||||
vec = PointD(norms[j].y, -norms[j].x);
|
||||
else
|
||||
vec = GetAvgUnitVector(
|
||||
PointD(-norms[k].y, norms[k].x),
|
||||
PointD(norms[j].y, -norms[j].x));
|
||||
|
||||
double abs_delta = std::abs(group_delta_);
|
||||
|
||||
// now offset the original vertex delta units along unit vector
|
||||
PointD ptQ = PointD(path[j]);
|
||||
ptQ = TranslatePoint(ptQ, abs_group_delta_ * vec.x, abs_group_delta_ * vec.y);
|
||||
ptQ = TranslatePoint(ptQ, abs_delta * vec.x, abs_delta * vec.y);
|
||||
// get perpendicular vertices
|
||||
PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x);
|
||||
PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x);
|
||||
@@ -189,51 +231,77 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t
|
||||
if (j == k)
|
||||
{
|
||||
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
|
||||
PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
|
||||
PointD pt = ptQ;
|
||||
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
//get the second intersect point through reflecion
|
||||
group.path_.push_back(Point64(ReflectPoint(pt, ptQ)));
|
||||
group.path_.push_back(Point64(pt));
|
||||
path_out.emplace_back(ReflectPoint(pt, ptQ));
|
||||
path_out.emplace_back(pt);
|
||||
}
|
||||
else
|
||||
{
|
||||
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
|
||||
PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
|
||||
group.path_.push_back(Point64(pt));
|
||||
PointD pt = ptQ;
|
||||
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
path_out.emplace_back(pt);
|
||||
//get the second intersect point through reflecion
|
||||
group.path_.push_back(Point64(ReflectPoint(pt, ptQ)));
|
||||
path_out.emplace_back(ReflectPoint(pt, ptQ));
|
||||
}
|
||||
}
|
||||
|
||||
void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a)
|
||||
void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a)
|
||||
{
|
||||
double q = group_delta_ / (cos_a + 1);
|
||||
group.path_.push_back(Point64(
|
||||
#ifdef USINGZ
|
||||
path_out.emplace_back(
|
||||
path[j].x + (norms[k].x + norms[j].x) * q,
|
||||
path[j].y + (norms[k].y + norms[j].y) * q));
|
||||
path[j].y + (norms[k].y + norms[j].y) * q,
|
||||
path[j].z);
|
||||
#else
|
||||
path_out.emplace_back(
|
||||
path[j].x + (norms[k].x + norms[j].x) * q,
|
||||
path[j].y + (norms[k].y + norms[j].y) * q);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle)
|
||||
void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle)
|
||||
{
|
||||
//even though angle may be negative this is a convex join
|
||||
Point64 pt = path[j];
|
||||
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle)));
|
||||
double step_sin = std::sin(angle / steps);
|
||||
double step_cos = std::cos(angle / steps);
|
||||
|
||||
PointD pt2 = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
|
||||
if (j == k) pt2.Negate();
|
||||
|
||||
group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y));
|
||||
for (int i = 0; i < steps; i++)
|
||||
{
|
||||
pt2 = PointD(pt2.x * step_cos - step_sin * pt2.y,
|
||||
pt2.x * step_sin + pt2.y * step_cos);
|
||||
group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y));
|
||||
if (deltaCallback64_) {
|
||||
// when deltaCallback64_ is assigned, group_delta_ won't be constant,
|
||||
// so we'll need to do the following calculations for *every* vertex.
|
||||
double abs_delta = std::fabs(group_delta_);
|
||||
double arcTol = (arc_tolerance_ > floating_point_tolerance ?
|
||||
std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const);
|
||||
double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
|
||||
step_sin_ = std::sin(2 * PI / steps_per_360);
|
||||
step_cos_ = std::cos(2 * PI / steps_per_360);
|
||||
if (group_delta_ < 0.0) step_sin_ = -step_sin_;
|
||||
steps_per_rad_ = steps_per_360 / (2 * PI);
|
||||
}
|
||||
group.path_.push_back(GetPerpendic(path[j], norms[j], group_delta_));
|
||||
|
||||
Point64 pt = path[j];
|
||||
PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
|
||||
|
||||
if (j == k) offsetVec.Negate();
|
||||
#ifdef USINGZ
|
||||
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z);
|
||||
#else
|
||||
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
|
||||
#endif
|
||||
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
|
||||
for (int i = 1; i < steps; ++i) // ie 1 less than steps
|
||||
{
|
||||
offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
|
||||
offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
|
||||
#ifdef USINGZ
|
||||
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z);
|
||||
#else
|
||||
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
|
||||
#endif
|
||||
}
|
||||
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_));
|
||||
}
|
||||
|
||||
void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k)
|
||||
void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
// Let A = change in angle where edges join
|
||||
// A == 0: ie no change in angle (flat join)
|
||||
@@ -241,93 +309,109 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k)
|
||||
// sin(A) < 0: right turning
|
||||
// cos(A) < 0: change in angle is more than 90 degree
|
||||
|
||||
if (path[j] == path[k]) { k = j; return; }
|
||||
if (path[j] == path[k]) return;
|
||||
|
||||
double sin_a = CrossProduct(norms[j], norms[k]);
|
||||
double cos_a = DotProduct(norms[j], norms[k]);
|
||||
if (sin_a > 1.0) sin_a = 1.0;
|
||||
else if (sin_a < -1.0) sin_a = -1.0;
|
||||
|
||||
bool almostNoAngle = AlmostZero(sin_a) && cos_a > 0;
|
||||
// when there's almost no angle of deviation or it's concave
|
||||
if (almostNoAngle || (sin_a * group_delta_ < 0))
|
||||
{
|
||||
Point64 p1 = Point64(
|
||||
path[j].x + norms[k].x * group_delta_,
|
||||
path[j].y + norms[k].y * group_delta_);
|
||||
Point64 p2 = Point64(
|
||||
path[j].x + norms[j].x * group_delta_,
|
||||
path[j].y + norms[j].y * group_delta_);
|
||||
group.path_.push_back(p1);
|
||||
if (p1 != p2)
|
||||
{
|
||||
// when concave add an extra vertex to ensure neat clipping
|
||||
if (!almostNoAngle) group.path_.push_back(path[j]);
|
||||
group.path_.push_back(p2);
|
||||
if (deltaCallback64_) {
|
||||
group_delta_ = deltaCallback64_(path, norms, j, k);
|
||||
if (group.is_reversed) group_delta_ = -group_delta_;
|
||||
}
|
||||
}
|
||||
else // it's convex
|
||||
if (std::fabs(group_delta_) <= floating_point_tolerance)
|
||||
{
|
||||
if (join_type_ == JoinType::Round)
|
||||
DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
|
||||
path_out.emplace_back(path[j]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
|
||||
{
|
||||
// is concave
|
||||
// by far the simplest way to construct concave joins, especially those joining very
|
||||
// short segments, is to insert 3 points that produce negative regions. These regions
|
||||
// will be removed later by the finishing union operation. This is also the best way
|
||||
// to ensure that path reversals (ie over-shrunk paths) are removed.
|
||||
#ifdef USINGZ
|
||||
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_), path[j].z);
|
||||
path_out.emplace_back(path[j]); // (#405, #873, #916)
|
||||
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_), path[j].z);
|
||||
#else
|
||||
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_));
|
||||
path_out.emplace_back(path[j]); // (#405, #873, #916)
|
||||
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_));
|
||||
#endif
|
||||
}
|
||||
else if (cos_a > 0.999 && join_type_ != JoinType::Round)
|
||||
{
|
||||
// almost straight - less than 2.5 degree (#424, #482, #526 & #724)
|
||||
DoMiter(path, j, k, cos_a);
|
||||
}
|
||||
else if (join_type_ == JoinType::Miter)
|
||||
{
|
||||
// miter unless the angle is so acute the miter would exceeds ML
|
||||
if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
|
||||
else DoSquare(group, path, j, k);
|
||||
// miter unless the angle is sufficiently acute to exceed ML
|
||||
if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a);
|
||||
else DoSquare(path, j, k);
|
||||
}
|
||||
// don't bother squaring angles that deviate < ~20 degrees because
|
||||
// squaring will be indistinguishable from mitering and just be a lot slower
|
||||
else if (cos_a > 0.9)
|
||||
DoMiter(group, path, j, k, cos_a);
|
||||
else if (join_type_ == JoinType::Round)
|
||||
DoRound(path, j, k, std::atan2(sin_a, cos_a));
|
||||
else if ( join_type_ == JoinType::Bevel)
|
||||
DoBevel(path, j, k);
|
||||
else
|
||||
DoSquare(group, path, j, k);
|
||||
}
|
||||
k = j;
|
||||
DoSquare(path, j, k);
|
||||
}
|
||||
|
||||
void ClipperOffset::OffsetPolygon(Group& group, Path64& path)
|
||||
void ClipperOffset::OffsetPolygon(Group& group, const Path64& path)
|
||||
{
|
||||
group.path_.clear();
|
||||
for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i)
|
||||
OffsetPoint(group, path, i, j);
|
||||
group.paths_out_.push_back(group.path_);
|
||||
path_out.clear();
|
||||
for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j)
|
||||
OffsetPoint(group, path, j, k);
|
||||
solution->emplace_back(path_out);
|
||||
}
|
||||
|
||||
void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path)
|
||||
void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
|
||||
{
|
||||
OffsetPolygon(group, path);
|
||||
std::reverse(path.begin(), path.end());
|
||||
BuildNormals(path);
|
||||
OffsetPolygon(group, path);
|
||||
Path64 reverse_path(path);
|
||||
std::reverse(reverse_path.begin(), reverse_path.end());
|
||||
|
||||
//rebuild normals
|
||||
std::reverse(norms.begin(), norms.end());
|
||||
norms.emplace_back(norms[0]);
|
||||
norms.erase(norms.begin());
|
||||
NegatePath(norms);
|
||||
|
||||
OffsetPolygon(group, reverse_path);
|
||||
}
|
||||
|
||||
void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type)
|
||||
void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
|
||||
{
|
||||
group.path_.clear();
|
||||
|
||||
// do the line start cap
|
||||
switch (end_type)
|
||||
if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0);
|
||||
|
||||
if (std::fabs(group_delta_) <= floating_point_tolerance)
|
||||
path_out.emplace_back(path[0]);
|
||||
else
|
||||
{
|
||||
switch (end_type_)
|
||||
{
|
||||
case EndType::Butt:
|
||||
group.path_.push_back(Point64(
|
||||
path[0].x - norms[0].x * group_delta_,
|
||||
path[0].y - norms[0].y * group_delta_));
|
||||
group.path_.push_back(GetPerpendic(path[0], norms[0], group_delta_));
|
||||
DoBevel(path, 0, 0);
|
||||
break;
|
||||
case EndType::Round:
|
||||
DoRound(group, path, 0,0, PI);
|
||||
DoRound(path, 0, 0, PI);
|
||||
break;
|
||||
default:
|
||||
DoSquare(group, path, 0, 0);
|
||||
DoSquare(path, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t highI = path.size() - 1;
|
||||
|
||||
// offset the left side going forward
|
||||
for (Path64::size_type i = 1, k = 0; i < highI; ++i)
|
||||
OffsetPoint(group, path, i, k);
|
||||
for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j)
|
||||
OffsetPoint(group, path, j, k);
|
||||
|
||||
// reverse normals
|
||||
for (size_t i = highI; i > 0; --i)
|
||||
@@ -335,151 +419,236 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type)
|
||||
norms[0] = norms[highI];
|
||||
|
||||
// do the line end cap
|
||||
switch (end_type)
|
||||
if (deltaCallback64_)
|
||||
group_delta_ = deltaCallback64_(path, norms, highI, highI);
|
||||
|
||||
if (std::fabs(group_delta_) <= floating_point_tolerance)
|
||||
path_out.emplace_back(path[highI]);
|
||||
else
|
||||
{
|
||||
switch (end_type_)
|
||||
{
|
||||
case EndType::Butt:
|
||||
group.path_.push_back(Point64(
|
||||
path[highI].x - norms[highI].x * group_delta_,
|
||||
path[highI].y - norms[highI].y * group_delta_));
|
||||
group.path_.push_back(GetPerpendic(path[highI], norms[highI], group_delta_));
|
||||
DoBevel(path, highI, highI);
|
||||
break;
|
||||
case EndType::Round:
|
||||
DoRound(group, path, highI, highI, PI);
|
||||
DoRound(path, highI, highI, PI);
|
||||
break;
|
||||
default:
|
||||
DoSquare(group, path, highI, highI);
|
||||
DoSquare(path, highI, highI);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = highI, k = 0; i > 0; --i)
|
||||
OffsetPoint(group, path, i, k);
|
||||
group.paths_out_.push_back(group.path_);
|
||||
for (size_t j = highI -1, k = highI; j > 0; k = j, --j)
|
||||
OffsetPoint(group, path, j, k);
|
||||
solution->emplace_back(path_out);
|
||||
}
|
||||
|
||||
void ClipperOffset::DoGroupOffset(Group& group, double delta)
|
||||
void ClipperOffset::DoGroupOffset(Group& group)
|
||||
{
|
||||
if (group.end_type_ != EndType::Polygon) delta = std::abs(delta) * 0.5;
|
||||
bool isClosedPaths = IsClosedPath(group.end_type_);
|
||||
|
||||
if (isClosedPaths)
|
||||
if (group.end_type == EndType::Polygon)
|
||||
{
|
||||
//the lowermost polygon must be an outer polygon. So we can use that as the
|
||||
//designated orientation for outer polygons (needed for tidy-up clipping)
|
||||
Paths64::size_type lowestIdx = GetLowestPolygonIdx(group.paths_in_);
|
||||
// nb: don't use the default orientation here ...
|
||||
double area = Area(group.paths_in_[lowestIdx]);
|
||||
if (area == 0) return;
|
||||
group.is_reversed_ = (area < 0);
|
||||
if (group.is_reversed_) delta = -delta;
|
||||
// a straight path (2 points) can now also be 'polygon' offset
|
||||
// where the ends will be treated as (180 deg.) joins
|
||||
if (!group.lowest_path_idx.has_value()) delta_ = std::abs(delta_);
|
||||
group_delta_ = (group.is_reversed) ? -delta_ : delta_;
|
||||
}
|
||||
else
|
||||
group.is_reversed_ = false;
|
||||
group_delta_ = std::abs(delta_);// *0.5;
|
||||
|
||||
group_delta_ = delta;
|
||||
abs_group_delta_ = std::abs(group_delta_);
|
||||
join_type_ = group.join_type_;
|
||||
double abs_delta = std::fabs(group_delta_);
|
||||
join_type_ = group.join_type;
|
||||
end_type_ = group.end_type;
|
||||
|
||||
double arcTol = (arc_tolerance_ > floating_point_tolerance ? arc_tolerance_
|
||||
: std::log10(2 + abs_group_delta_) * default_arc_tolerance); // empirically derived
|
||||
|
||||
//calculate a sensible number of steps (for 360 deg for the given offset
|
||||
if (group.join_type_ == JoinType::Round || group.end_type_ == EndType::Round)
|
||||
if (group.join_type == JoinType::Round || group.end_type == EndType::Round)
|
||||
{
|
||||
steps_per_rad_ = PI / std::acos(1 - arcTol / abs_group_delta_) / (PI *2);
|
||||
// calculate the number of steps required to approximate a circle
|
||||
// (see https://www.angusj.com/clipper2/Docs/Trigonometry.htm)
|
||||
// arcTol - when arc_tolerance_ is undefined (0) then curve imprecision
|
||||
// will be relative to the size of the offset (delta). Obviously very
|
||||
//large offsets will almost always require much less precision.
|
||||
double arcTol = (arc_tolerance_ > floating_point_tolerance) ?
|
||||
std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const;
|
||||
|
||||
double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
|
||||
step_sin_ = std::sin(2 * PI / steps_per_360);
|
||||
step_cos_ = std::cos(2 * PI / steps_per_360);
|
||||
if (group_delta_ < 0.0) step_sin_ = -step_sin_;
|
||||
steps_per_rad_ = steps_per_360 / (2 * PI);
|
||||
}
|
||||
|
||||
bool is_closed_path = IsClosedPath(group.end_type_);
|
||||
Paths64::const_iterator path_iter;
|
||||
for(path_iter = group.paths_in_.cbegin(); path_iter != group.paths_in_.cend(); ++path_iter)
|
||||
//double min_area = PI * Sqr(group_delta_);
|
||||
Paths64::const_iterator path_in_it = group.paths_in.cbegin();
|
||||
for ( ; path_in_it != group.paths_in.cend(); ++path_in_it)
|
||||
{
|
||||
Path64 path = StripDuplicates(*path_iter, is_closed_path);
|
||||
Path64::size_type cnt = path.size();
|
||||
if (cnt == 0) continue;
|
||||
Path64::size_type pathLen = path_in_it->size();
|
||||
path_out.clear();
|
||||
|
||||
if (cnt == 1) // single point - only valid with open paths
|
||||
if (pathLen == 1) // single point
|
||||
{
|
||||
group.path_ = Path64();
|
||||
if (deltaCallback64_)
|
||||
{
|
||||
group_delta_ = deltaCallback64_(*path_in_it, norms, 0, 0);
|
||||
if (group.is_reversed) group_delta_ = -group_delta_;
|
||||
abs_delta = std::fabs(group_delta_);
|
||||
}
|
||||
|
||||
if (group_delta_ < 1) continue;
|
||||
const Point64& pt = (*path_in_it)[0];
|
||||
//single vertex so build a circle or square ...
|
||||
if (group.join_type_ == JoinType::Round)
|
||||
if (group.join_type == JoinType::Round)
|
||||
{
|
||||
double radius = abs_group_delta_;
|
||||
group.path_ = Ellipse(path[0], radius, radius);
|
||||
double radius = abs_delta;
|
||||
size_t steps = steps_per_rad_ > 0 ? static_cast<size_t>(std::ceil(steps_per_rad_ * 2 * PI)) : 0; //#617
|
||||
path_out = Ellipse(pt, radius, radius, steps);
|
||||
#ifdef USINGZ
|
||||
for (auto& p : path_out) p.z = pt.z;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
int d = (int)std::ceil(abs_group_delta_);
|
||||
Rect64 r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d);
|
||||
group.path_ = r.AsPath();
|
||||
}
|
||||
group.paths_out_.push_back(group.path_);
|
||||
}
|
||||
else
|
||||
{
|
||||
BuildNormals(path);
|
||||
if (group.end_type_ == EndType::Polygon) OffsetPolygon(group, path);
|
||||
else if (group.end_type_ == EndType::Joined) OffsetOpenJoined(group, path);
|
||||
else OffsetOpenPath(group, path, group.end_type_);
|
||||
}
|
||||
int d = (int)std::ceil(abs_delta);
|
||||
Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d);
|
||||
path_out = r.AsPath();
|
||||
#ifdef USINGZ
|
||||
for (auto& p : path_out) p.z = pt.z;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!merge_groups_)
|
||||
{
|
||||
//clean up self-intersections ...
|
||||
Clipper64 c;
|
||||
c.PreserveCollinear = false;
|
||||
//the solution should retain the orientation of the input
|
||||
c.ReverseSolution = reverse_solution_ != group.is_reversed_;
|
||||
c.AddSubject(group.paths_out_);
|
||||
if (group.is_reversed_)
|
||||
c.Execute(ClipType::Union, FillRule::Negative, group.paths_out_);
|
||||
else
|
||||
c.Execute(ClipType::Union, FillRule::Positive, group.paths_out_);
|
||||
}
|
||||
solution->emplace_back(path_out);
|
||||
continue;
|
||||
} // end of offsetting a single point
|
||||
|
||||
solution.reserve(solution.size() + group.paths_out_.size());
|
||||
copy(group.paths_out_.begin(), group.paths_out_.end(), back_inserter(solution));
|
||||
group.paths_out_.clear();
|
||||
if ((pathLen == 2) && (group.end_type == EndType::Joined))
|
||||
end_type_ = (group.join_type == JoinType::Round) ?
|
||||
EndType::Round :
|
||||
EndType::Square;
|
||||
|
||||
BuildNormals(*path_in_it);
|
||||
if (end_type_ == EndType::Polygon) OffsetPolygon(group, *path_in_it);
|
||||
else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, *path_in_it);
|
||||
else OffsetOpenPath(group, *path_in_it);
|
||||
}
|
||||
}
|
||||
|
||||
Paths64 ClipperOffset::Execute(double delta)
|
||||
#ifdef USINGZ
|
||||
void ClipperOffset::ZCB(const Point64& bot1, const Point64& top1,
|
||||
const Point64& bot2, const Point64& top2, Point64& ip)
|
||||
{
|
||||
solution.clear();
|
||||
if (std::abs(delta) < default_arc_tolerance)
|
||||
if (bot1.z && ((bot1.z == bot2.z) || (bot1.z == top2.z))) ip.z = bot1.z;
|
||||
else if (bot2.z && (bot2.z == top1.z)) ip.z = bot2.z;
|
||||
else if (top1.z && (top1.z == top2.z)) ip.z = top1.z;
|
||||
else if (zCallback64_) zCallback64_(bot1, top1, bot2, top2, ip);
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t ClipperOffset::CalcSolutionCapacity()
|
||||
{
|
||||
size_t result = 0;
|
||||
for (const Group& g : groups_)
|
||||
result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ClipperOffset::CheckReverseOrientation()
|
||||
{
|
||||
// nb: this assumes there's consistency in orientation between groups
|
||||
bool is_reversed_orientation = false;
|
||||
for (const Group& g : groups_)
|
||||
if (g.end_type == EndType::Polygon)
|
||||
{
|
||||
is_reversed_orientation = g.is_reversed;
|
||||
break;
|
||||
}
|
||||
return is_reversed_orientation;
|
||||
}
|
||||
|
||||
void ClipperOffset::ExecuteInternal(double delta)
|
||||
{
|
||||
error_code_ = 0;
|
||||
if (groups_.size() == 0) return;
|
||||
solution->reserve(CalcSolutionCapacity());
|
||||
|
||||
if (std::abs(delta) < 0.5) // ie: offset is insignificant
|
||||
{
|
||||
Paths64::size_type sol_size = 0;
|
||||
for (const Group& group : groups_) sol_size += group.paths_in.size();
|
||||
solution->reserve(sol_size);
|
||||
for (const Group& group : groups_)
|
||||
copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(*solution));
|
||||
}
|
||||
else
|
||||
{
|
||||
solution.reserve(solution.size() + group.paths_in_.size());
|
||||
copy(group.paths_in_.begin(), group.paths_in_.end(), back_inserter(solution));
|
||||
}
|
||||
return solution;
|
||||
}
|
||||
|
||||
temp_lim_ = (miter_limit_ <= 1) ?
|
||||
2.0 :
|
||||
2.0 / (miter_limit_ * miter_limit_);
|
||||
|
||||
std::vector<Group>::iterator groups_iter;
|
||||
for (groups_iter = groups_.begin();
|
||||
groups_iter != groups_.end(); ++groups_iter)
|
||||
delta_ = delta;
|
||||
std::vector<Group>::iterator git;
|
||||
for (git = groups_.begin(); git != groups_.end(); ++git)
|
||||
{
|
||||
DoGroupOffset(*groups_iter, delta);
|
||||
DoGroupOffset(*git);
|
||||
if (!error_code_) continue; // all OK
|
||||
solution->clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (merge_groups_ && groups_.size() > 0)
|
||||
{
|
||||
if (!solution->size()) return;
|
||||
|
||||
bool paths_reversed = CheckReverseOrientation();
|
||||
//clean up self-intersections ...
|
||||
Clipper64 c;
|
||||
c.PreserveCollinear = false;
|
||||
c.PreserveCollinear(false);
|
||||
//the solution should retain the orientation of the input
|
||||
c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed_;
|
||||
|
||||
c.AddSubject(solution);
|
||||
if (groups_[0].is_reversed_)
|
||||
c.Execute(ClipType::Union, FillRule::Negative, solution);
|
||||
c.ReverseSolution(reverse_solution_ != paths_reversed);
|
||||
#ifdef USINGZ
|
||||
auto fp = std::bind(&ClipperOffset::ZCB, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3,
|
||||
std::placeholders::_4, std::placeholders::_5);
|
||||
c.SetZCallback(fp);
|
||||
#endif
|
||||
c.AddSubject(*solution);
|
||||
if (solution_tree)
|
||||
{
|
||||
if (paths_reversed)
|
||||
c.Execute(ClipType::Union, FillRule::Negative, *solution_tree);
|
||||
else
|
||||
c.Execute(ClipType::Union, FillRule::Positive, solution);
|
||||
c.Execute(ClipType::Union, FillRule::Positive, *solution_tree);
|
||||
}
|
||||
return solution;
|
||||
else
|
||||
{
|
||||
if (paths_reversed)
|
||||
c.Execute(ClipType::Union, FillRule::Negative, *solution);
|
||||
else
|
||||
c.Execute(ClipType::Union, FillRule::Positive, *solution);
|
||||
}
|
||||
}
|
||||
|
||||
void ClipperOffset::Execute(double delta, Paths64& paths64)
|
||||
{
|
||||
paths64.clear();
|
||||
solution = &paths64;
|
||||
solution_tree = nullptr;
|
||||
ExecuteInternal(delta);
|
||||
}
|
||||
|
||||
|
||||
void ClipperOffset::Execute(double delta, PolyTree64& polytree)
|
||||
{
|
||||
polytree.Clear();
|
||||
solution_tree = &polytree;
|
||||
solution = new Paths64();
|
||||
ExecuteInternal(delta);
|
||||
delete solution;
|
||||
solution = nullptr;
|
||||
}
|
||||
|
||||
void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths)
|
||||
{
|
||||
deltaCallback64_ = delta_cb;
|
||||
Execute(1.0, paths);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -215,5 +215,7 @@ namespace ImGui
|
||||
const wchar_t ClipboardBtnIcon = 0x0848;
|
||||
const wchar_t ClipboardBtnDarkIcon = 0x0849;
|
||||
// void MyFunction(const char* name, const MyMatrix44& v);
|
||||
|
||||
const wchar_t FilamentGreen = 0x0850;
|
||||
}
|
||||
|
||||
|
||||
@@ -2134,7 +2134,7 @@ bool ImGui::QDTBeginCombo(const char *label, const char *preview_value, ImGuiCom
|
||||
bool hovered, held;
|
||||
bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
|
||||
|
||||
bool push_color_count = 0;
|
||||
unsigned int push_color_count = 0;
|
||||
if (hovered || g.ActiveId == id) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, GetColorU32(ImGuiCol_BorderActive));
|
||||
push_color_count = 1;
|
||||
@@ -4170,7 +4170,7 @@ bool ImGui::QDTInputScalar(const char *label, ImGuiDataType data_type, void *p_d
|
||||
// We are only allowed to access the state if we are already the active widget.
|
||||
ImGuiInputTextState *state = GetInputTextState(id);
|
||||
|
||||
bool push_color_count = 0;
|
||||
unsigned int push_color_count = 0;
|
||||
if (hovered || g.ActiveId == id) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, GetColorU32(ImGuiCol_BorderActive));
|
||||
push_color_count = 1;
|
||||
|
||||
@@ -1556,7 +1556,9 @@ STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codep
|
||||
return 0; // not found
|
||||
}
|
||||
// @TODO
|
||||
#ifndef __APPLE__
|
||||
STBTT_assert(0);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
9
src/imguizmo/CMakeLists.txt
Normal file
9
src/imguizmo/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
project(imguizmo)
|
||||
|
||||
add_library(imguizmo STATIC
|
||||
ImGuizmo.h
|
||||
ImGuizmo.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(imguizmo PRIVATE imgui)
|
||||
3148
src/imguizmo/ImGuizmo.cpp
Normal file
3148
src/imguizmo/ImGuizmo.cpp
Normal file
File diff suppressed because it is too large
Load Diff
295
src/imguizmo/ImGuizmo.h
Normal file
295
src/imguizmo/ImGuizmo.h
Normal file
@@ -0,0 +1,295 @@
|
||||
// https://github.com/CedricGuillemet/ImGuizmo
|
||||
// v 1.89 WIP
|
||||
//
|
||||
// The MIT License(MIT)
|
||||
//
|
||||
// Copyright(c) 2021 Cedric Guillemet
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files(the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions :
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// -------------------------------------------------------------------------------------------
|
||||
// History :
|
||||
// 2019/11/03 View gizmo
|
||||
// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode.
|
||||
// 2016/09/09 Hatched negative axis. Snapping. Documentation update.
|
||||
// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved
|
||||
// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results.
|
||||
// 2016/08/31 First version
|
||||
//
|
||||
// -------------------------------------------------------------------------------------------
|
||||
// Future (no order):
|
||||
//
|
||||
// - Multi view
|
||||
// - display rotation/translation/scale infos in local/world space and not only local
|
||||
// - finish local/world matrix application
|
||||
// - OPERATION as bitmask
|
||||
//
|
||||
// -------------------------------------------------------------------------------------------
|
||||
// Example
|
||||
#if 0
|
||||
void EditTransform(const Camera& camera, matrix_t& matrix)
|
||||
{
|
||||
static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE);
|
||||
static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD);
|
||||
if (ImGui::IsKeyPressed(90))
|
||||
mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
|
||||
if (ImGui::IsKeyPressed(69))
|
||||
mCurrentGizmoOperation = ImGuizmo::ROTATE;
|
||||
if (ImGui::IsKeyPressed(82)) // r Key
|
||||
mCurrentGizmoOperation = ImGuizmo::SCALE;
|
||||
if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE))
|
||||
mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE))
|
||||
mCurrentGizmoOperation = ImGuizmo::ROTATE;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE))
|
||||
mCurrentGizmoOperation = ImGuizmo::SCALE;
|
||||
float matrixTranslation[3], matrixRotation[3], matrixScale[3];
|
||||
ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale);
|
||||
ImGui::InputFloat3("Tr", matrixTranslation, 3);
|
||||
ImGui::InputFloat3("Rt", matrixRotation, 3);
|
||||
ImGui::InputFloat3("Sc", matrixScale, 3);
|
||||
ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16);
|
||||
|
||||
if (mCurrentGizmoOperation != ImGuizmo::SCALE)
|
||||
{
|
||||
if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL))
|
||||
mCurrentGizmoMode = ImGuizmo::LOCAL;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD))
|
||||
mCurrentGizmoMode = ImGuizmo::WORLD;
|
||||
}
|
||||
static bool useSnap(false);
|
||||
if (ImGui::IsKeyPressed(83))
|
||||
useSnap = !useSnap;
|
||||
ImGui::Checkbox("", &useSnap);
|
||||
ImGui::SameLine();
|
||||
vec_t snap;
|
||||
switch (mCurrentGizmoOperation)
|
||||
{
|
||||
case ImGuizmo::TRANSLATE:
|
||||
snap = config.mSnapTranslation;
|
||||
ImGui::InputFloat3("Snap", &snap.x);
|
||||
break;
|
||||
case ImGuizmo::ROTATE:
|
||||
snap = config.mSnapRotation;
|
||||
ImGui::InputFloat("Angle Snap", &snap.x);
|
||||
break;
|
||||
case ImGuizmo::SCALE:
|
||||
snap = config.mSnapScale;
|
||||
ImGui::InputFloat("Scale Snap", &snap.x);
|
||||
break;
|
||||
}
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y);
|
||||
ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL);
|
||||
}
|
||||
#endif
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_IMGUI_API
|
||||
#include "imconfig.h"
|
||||
#endif
|
||||
#ifndef IMGUI_API
|
||||
#define IMGUI_API
|
||||
#endif
|
||||
|
||||
#ifndef IMGUIZMO_NAMESPACE
|
||||
#define IMGUIZMO_NAMESPACE ImGuizmo
|
||||
#endif
|
||||
|
||||
namespace IMGUIZMO_NAMESPACE
|
||||
{
|
||||
// call inside your own window and before Manipulate() in order to draw gizmo to that window.
|
||||
// Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList()).
|
||||
IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr);
|
||||
|
||||
// call BeginFrame right after ImGui_XXXX_NewFrame();
|
||||
IMGUI_API void BeginFrame();
|
||||
|
||||
// this is necessary because when imguizmo is compiled into a dll, and imgui into another
|
||||
// globals are not shared between them.
|
||||
// More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam
|
||||
// expose method to set imgui context
|
||||
IMGUI_API void SetImGuiContext(ImGuiContext* ctx);
|
||||
|
||||
// return true if mouse cursor is over any gizmo control (axis, plan or screen component)
|
||||
IMGUI_API bool IsOver();
|
||||
|
||||
// return true if mouse IsOver or if the gizmo is in moving state
|
||||
IMGUI_API bool IsUsing();
|
||||
|
||||
// return true if any gizmo is in moving state
|
||||
IMGUI_API bool IsUsingAny();
|
||||
|
||||
// enable/disable the gizmo. Stay in the state until next call to Enable.
|
||||
// gizmo is rendered with gray half transparent color when disabled
|
||||
IMGUI_API void Enable(bool enable);
|
||||
|
||||
// helper functions for manualy editing translation/rotation/scale with an input float
|
||||
// translation, rotation and scale float points to 3 floats each
|
||||
// Angles are in degrees (more suitable for human editing)
|
||||
// example:
|
||||
// float matrixTranslation[3], matrixRotation[3], matrixScale[3];
|
||||
// ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale);
|
||||
// ImGui::InputFloat3("Tr", matrixTranslation, 3);
|
||||
// ImGui::InputFloat3("Rt", matrixRotation, 3);
|
||||
// ImGui::InputFloat3("Sc", matrixScale, 3);
|
||||
// ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16);
|
||||
//
|
||||
// These functions have some numerical stability issues for now. Use with caution.
|
||||
IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale);
|
||||
IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix);
|
||||
|
||||
IMGUI_API void SetRect(float x, float y, float width, float height);
|
||||
// default is false
|
||||
IMGUI_API void SetOrthographic(bool isOrthographic);
|
||||
|
||||
// Render a cube with face color corresponding to face normal. Usefull for debug/tests
|
||||
IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount);
|
||||
IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize);
|
||||
|
||||
// call it when you want a gizmo
|
||||
// Needs view and projection matrices.
|
||||
// matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional
|
||||
// translation is applied in world space
|
||||
enum OPERATION
|
||||
{
|
||||
TRANSLATE_X = (1u << 0),
|
||||
TRANSLATE_Y = (1u << 1),
|
||||
TRANSLATE_Z = (1u << 2),
|
||||
ROTATE_X = (1u << 3),
|
||||
ROTATE_Y = (1u << 4),
|
||||
ROTATE_Z = (1u << 5),
|
||||
ROTATE_SCREEN = (1u << 6),
|
||||
SCALE_X = (1u << 7),
|
||||
SCALE_Y = (1u << 8),
|
||||
SCALE_Z = (1u << 9),
|
||||
BOUNDS = (1u << 10),
|
||||
SCALE_XU = (1u << 11),
|
||||
SCALE_YU = (1u << 12),
|
||||
SCALE_ZU = (1u << 13),
|
||||
|
||||
TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z,
|
||||
ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN,
|
||||
SCALE = SCALE_X | SCALE_Y | SCALE_Z,
|
||||
SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, // universal
|
||||
UNIVERSAL = TRANSLATE | ROTATE | SCALEU
|
||||
};
|
||||
|
||||
inline OPERATION operator|(OPERATION lhs, OPERATION rhs)
|
||||
{
|
||||
return static_cast<OPERATION>(static_cast<int>(lhs) | static_cast<int>(rhs));
|
||||
}
|
||||
|
||||
enum MODE
|
||||
{
|
||||
LOCAL,
|
||||
WORLD
|
||||
};
|
||||
|
||||
IMGUI_API bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix = NULL, const float* snap = NULL, const float* localBounds = NULL, const float* boundsSnap = NULL);
|
||||
//
|
||||
// Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en
|
||||
// It seems to be a defensive patent in the US. I don't think it will bring troubles using it as
|
||||
// other software are using the same mechanics. But just in case, you are now warned!
|
||||
//
|
||||
IMGUI_API bool ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor);
|
||||
|
||||
// use this version if you did not call Manipulate before and you are just using ViewManipulate
|
||||
IMGUI_API bool ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor);
|
||||
|
||||
IMGUI_API void SetID(int id);
|
||||
|
||||
// return true if the cursor is over the operation's gizmo
|
||||
IMGUI_API bool IsOver(OPERATION op);
|
||||
IMGUI_API void SetGizmoSizeClipSpace(float value);
|
||||
|
||||
// Allow axis to flip
|
||||
// When true (default), the guizmo axis flip for better visibility
|
||||
// When false, they always stay along the positive world/local axis
|
||||
IMGUI_API void AllowAxisFlip(bool value);
|
||||
|
||||
// Configure the limit where axis are hidden
|
||||
IMGUI_API void SetAxisLimit(float value);
|
||||
// Configure the limit where planes are hiden
|
||||
IMGUI_API void SetPlaneLimit(float value);
|
||||
|
||||
enum COLOR
|
||||
{
|
||||
DIRECTION_X, // directionColor[0]
|
||||
DIRECTION_Y, // directionColor[1]
|
||||
DIRECTION_Z, // directionColor[2]
|
||||
PLANE_X, // planeColor[0]
|
||||
PLANE_Y, // planeColor[1]
|
||||
PLANE_Z, // planeColor[2]
|
||||
SELECTION, // selectionColor
|
||||
INACTIVE, // inactiveColor
|
||||
TRANSLATION_LINE, // translationLineColor
|
||||
SCALE_LINE,
|
||||
ROTATION_USING_BORDER,
|
||||
ROTATION_USING_FILL,
|
||||
HATCHED_AXIS_LINES,
|
||||
TEXT,
|
||||
TEXT_SHADOW,
|
||||
FACE,
|
||||
COUNT
|
||||
};
|
||||
|
||||
enum Axis
|
||||
{
|
||||
Axis_X,
|
||||
Axis_Y,
|
||||
Axis_Z,
|
||||
Axis_COUNT,
|
||||
};
|
||||
|
||||
enum FACES
|
||||
{
|
||||
FACE_BACK,
|
||||
FACE_TOP,
|
||||
FACE_RIGHT,
|
||||
FACE_FRONT,
|
||||
FACE_BOTTOM,
|
||||
FACE_LEFT,
|
||||
FACES_COUNT
|
||||
};
|
||||
|
||||
struct Style
|
||||
{
|
||||
IMGUI_API Style();
|
||||
|
||||
float TranslationLineThickness; // Thickness of lines for translation gizmo
|
||||
float TranslationLineArrowSize; // Size of arrow at the end of lines for translation gizmo
|
||||
float RotationLineThickness; // Thickness of lines for rotation gizmo
|
||||
float RotationOuterLineThickness; // Thickness of line surrounding the rotation gizmo
|
||||
float ScaleLineThickness; // Thickness of lines for scale gizmo
|
||||
float ScaleLineCircleSize; // Size of circle at the end of lines for scale gizmo
|
||||
float HatchedAxisLineThickness; // Thickness of hatched axis lines
|
||||
float CenterCircleSize; // Size of circle at the center of the translate/scale gizmo
|
||||
|
||||
ImVec4 Colors[COLOR::COUNT];
|
||||
|
||||
char AxisLabels[Axis::Axis_COUNT][32];
|
||||
char FaceLabels[FACES::FACES_COUNT][32];
|
||||
};
|
||||
|
||||
IMGUI_API Style& GetStyle();
|
||||
}
|
||||
21
src/imguizmo/LICENSE
Normal file
21
src/imguizmo/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Cedric Guillemet
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
192
src/imguizmo/README.md
Normal file
192
src/imguizmo/README.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# ImGuizmo
|
||||
|
||||
Latest stable tagged version is 1.83. Current master version is 1.84 WIP.
|
||||
|
||||
What started with the gizmo is now a collection of dear imgui widgets and more advanced controls.
|
||||
|
||||
## Guizmos
|
||||
|
||||
### ImViewGizmo
|
||||
|
||||
Manipulate view orientation with 1 single line of code
|
||||
|
||||

|
||||
|
||||
### ImGuizmo
|
||||
|
||||
ImGizmo is a small (.h and .cpp) library built ontop of Dear ImGui that allow you to manipulate(Rotate & translate at the moment) 4x4 float matrices. No other dependancies. Coded with Immediate Mode (IM) philosophy in mind.
|
||||
|
||||
Built against DearImgui 1.53WIP
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
There is now a sample for Win32/OpenGL ! With a binary in bin directory.
|
||||

|
||||
|
||||
### ImSequencer
|
||||
|
||||
A WIP little sequencer used to edit frame start/end for different events in a timeline.
|
||||

|
||||
Check the sample for the documentation. More to come...
|
||||
|
||||
### Graph Editor
|
||||
|
||||
Nodes + connections. Custom draw inside nodes is possible with the delegate system in place.
|
||||

|
||||
|
||||
### API doc
|
||||
|
||||
Call BeginFrame right after ImGui_XXXX_NewFrame();
|
||||
|
||||
```C++
|
||||
void BeginFrame();
|
||||
```
|
||||
|
||||
return true if mouse cursor is over any gizmo control (axis, plan or screen component)
|
||||
|
||||
```C++
|
||||
bool IsOver();**
|
||||
```
|
||||
|
||||
return true if mouse IsOver or if the gizmo is in moving state
|
||||
|
||||
```C++
|
||||
bool IsUsing();**
|
||||
```
|
||||
|
||||
enable/disable the gizmo. Stay in the state until next call to Enable. gizmo is rendered with gray half transparent color when disabled
|
||||
|
||||
```C++
|
||||
void Enable(bool enable);**
|
||||
```
|
||||
|
||||
helper functions for manualy editing translation/rotation/scale with an input float
|
||||
translation, rotation and scale float points to 3 floats each
|
||||
Angles are in degrees (more suitable for human editing)
|
||||
example:
|
||||
|
||||
```C++
|
||||
float matrixTranslation[3], matrixRotation[3], matrixScale[3];
|
||||
ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale);
|
||||
ImGui::InputFloat3("Tr", matrixTranslation, 3);
|
||||
ImGui::InputFloat3("Rt", matrixRotation, 3);
|
||||
ImGui::InputFloat3("Sc", matrixScale, 3);
|
||||
ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16);
|
||||
```
|
||||
|
||||
These functions have some numerical stability issues for now. Use with caution.
|
||||
|
||||
```C++
|
||||
void DecomposeMatrixToComponents(const float *matrix, float *translation, float *rotation, float *scale);
|
||||
void RecomposeMatrixFromComponents(const float *translation, const float *rotation, const float *scale, float *matrix);**
|
||||
```
|
||||
|
||||
Render a cube with face color corresponding to face normal. Usefull for debug/test
|
||||
|
||||
```C++
|
||||
void DrawCube(const float *view, const float *projection, float *matrix);**
|
||||
```
|
||||
|
||||
Call it when you want a gizmo
|
||||
Needs view and projection matrices.
|
||||
Matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional. snap points to a float[3] for translation and to a single float for scale or rotation. Snap angle is in Euler Degrees.
|
||||
|
||||
```C++
|
||||
enum OPERATION
|
||||
{
|
||||
TRANSLATE,
|
||||
ROTATE,
|
||||
SCALE
|
||||
};
|
||||
|
||||
enum MODE
|
||||
{
|
||||
LOCAL,
|
||||
WORLD
|
||||
};
|
||||
|
||||
void Manipulate(const float *view, const float *projection, OPERATION operation, MODE mode, float *matrix, float *deltaMatrix = 0, float *snap = 0);**
|
||||
```
|
||||
|
||||
### ImGui Example
|
||||
|
||||
Code for :
|
||||
|
||||

|
||||
|
||||
```C++
|
||||
void EditTransform(const Camera& camera, matrix_t& matrix)
|
||||
{
|
||||
static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE);
|
||||
static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD);
|
||||
if (ImGui::IsKeyPressed(90))
|
||||
mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
|
||||
if (ImGui::IsKeyPressed(69))
|
||||
mCurrentGizmoOperation = ImGuizmo::ROTATE;
|
||||
if (ImGui::IsKeyPressed(82)) // r Key
|
||||
mCurrentGizmoOperation = ImGuizmo::SCALE;
|
||||
if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE))
|
||||
mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE))
|
||||
mCurrentGizmoOperation = ImGuizmo::ROTATE;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE))
|
||||
mCurrentGizmoOperation = ImGuizmo::SCALE;
|
||||
float matrixTranslation[3], matrixRotation[3], matrixScale[3];
|
||||
ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale);
|
||||
ImGui::InputFloat3("Tr", matrixTranslation, 3);
|
||||
ImGui::InputFloat3("Rt", matrixRotation, 3);
|
||||
ImGui::InputFloat3("Sc", matrixScale, 3);
|
||||
ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16);
|
||||
|
||||
if (mCurrentGizmoOperation != ImGuizmo::SCALE)
|
||||
{
|
||||
if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL))
|
||||
mCurrentGizmoMode = ImGuizmo::LOCAL;
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD))
|
||||
mCurrentGizmoMode = ImGuizmo::WORLD;
|
||||
}
|
||||
static bool useSnap(false);
|
||||
if (ImGui::IsKeyPressed(83))
|
||||
useSnap = !useSnap;
|
||||
ImGui::Checkbox("", &useSnap);
|
||||
ImGui::SameLine();
|
||||
vec_t snap;
|
||||
switch (mCurrentGizmoOperation)
|
||||
{
|
||||
case ImGuizmo::TRANSLATE:
|
||||
snap = config.mSnapTranslation;
|
||||
ImGui::InputFloat3("Snap", &snap.x);
|
||||
break;
|
||||
case ImGuizmo::ROTATE:
|
||||
snap = config.mSnapRotation;
|
||||
ImGui::InputFloat("Angle Snap", &snap.x);
|
||||
break;
|
||||
case ImGuizmo::SCALE:
|
||||
snap = config.mSnapScale;
|
||||
ImGui::InputFloat("Scale Snap", &snap.x);
|
||||
break;
|
||||
}
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y);
|
||||
ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL);
|
||||
}
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
ImGuizmo can be installed via [vcpkg](https://github.com/microsoft/vcpkg) and used cmake
|
||||
|
||||
```bash
|
||||
vcpkg install imguizmo
|
||||
```
|
||||
|
||||
See the [vcpkg example](/vcpkg-example) for more details
|
||||
|
||||
## License
|
||||
|
||||
ImGuizmo is licensed under the MIT License, see [LICENSE](/LICENSE) for more information.
|
||||
@@ -656,7 +656,7 @@ inline std::string toString(const S& /*sh*/)
|
||||
}
|
||||
|
||||
template<Formats, class S>
|
||||
inline std::string serialize(const S& /*sh*/, double /*scale*/=1, std::string fill = "none", std::string stroke = "black", float stroke_width = 1)
|
||||
inline std::string serialize(const S & /*sh*/, const std::string &name = "", double /*scale*/ = 1, std::string fill = "none", std::string stroke = "black", float stroke_width = 1)
|
||||
{
|
||||
static_assert(always_false<S>::value,
|
||||
"shapelike::serialize() unimplemented!");
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <libnest2d/geometry_traits.hpp>
|
||||
#define LARGE_COST_TO_REJECT 1e7
|
||||
#define COST_OF_NEW_PLATE 0.1
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
@@ -75,8 +76,8 @@ class _Item {
|
||||
|
||||
public:
|
||||
int itemid_{ 0 };
|
||||
std::vector<int> extrude_ids;
|
||||
int filament_temp_type = -1; // -1 means unset. otherwise should be {0,1,2}
|
||||
std::map<int, std::string> extrude_id_filament_types; // extrude id to filament type
|
||||
int filament_temp_type = -1; // -1 means unset. otherwise should be one of FilamentTempType ie {0,1,2}
|
||||
double height{ 0 };
|
||||
double print_temp{ 0 };
|
||||
double bed_temp{ 0 };
|
||||
@@ -85,7 +86,9 @@ public:
|
||||
//QDS: virtual object to mark unprintable region on heatbed
|
||||
bool is_virt_object{ false };
|
||||
bool is_wipe_tower{ false };
|
||||
bool has_tried_with_excluded{ false };
|
||||
bool is_extrusion_cali_object{ false };
|
||||
bool has_tried_without_extrusion_cali_obj{ false };
|
||||
std::vector<double> allowed_rotations{0.};
|
||||
|
||||
/// The type of the shape which was handed over as the template argument.
|
||||
using ShapeType = RawShape;
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
#include "placer_boilerplate.hpp"
|
||||
|
||||
// temporary
|
||||
//#include "../tools/svgtools.hpp"
|
||||
//#include <iomanip> // setprecision
|
||||
#include "../tools/svgtools.hpp"
|
||||
#include <iomanip> // setprecision
|
||||
|
||||
#include <libnest2d/parallel.hpp>
|
||||
|
||||
@@ -130,6 +130,9 @@ struct NfpPConfig {
|
||||
|
||||
//QDS: sort function for selector
|
||||
std::function<bool(_Item<RawShape>& i1, _Item<RawShape>& i2)> sortfunc;
|
||||
|
||||
std::function<void(const std::string &)> progressFunc = {};
|
||||
|
||||
//QDS: excluded region for V4 bed
|
||||
std::vector<_Item<RawShape> > m_excluded_regions;
|
||||
_ItemGroup<RawShape> m_excluded_items;
|
||||
@@ -557,10 +560,12 @@ public:
|
||||
template<class Range = ConstItemRange<typename Base::DefaultIter>>
|
||||
PackResult trypack(Item& item,
|
||||
const Range& remaining = Range()) {
|
||||
auto result = _trypack(item, remaining);
|
||||
if (item.is_wipe_tower) {
|
||||
PackResult result1 = _trypack_with_original_pos(item);
|
||||
if (result1.score() >= 0 && result1.score() < LARGE_COST_TO_REJECT) return result1;
|
||||
}
|
||||
|
||||
// Experimental
|
||||
// if(!result) repack(item, result);
|
||||
auto result = _trypack(item, remaining);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -685,11 +690,20 @@ private:
|
||||
};
|
||||
|
||||
using Edges = EdgeCache<RawShape>;
|
||||
// item won't overlap with virtual objects if it's inside or touches NFP
|
||||
// @return 1 if current item overlaps with virtual objects, 0 otherwise
|
||||
bool overlapWithVirtObject(const Item& item, const Box& binbb){
|
||||
if (items_.empty()) return 0;
|
||||
Shapes nfps = calcnfp(item, binbb, Lvl<MaxNfpLevel::value>());
|
||||
auto v = item.referenceVertex();
|
||||
for (const RawShape &nfp : nfps) {
|
||||
if (sl::isInside(v, nfp) || sl::touches(v, nfp)) { return false; }
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
template<class Range = ConstItemRange<typename Base::DefaultIter>>
|
||||
PackResult _trypack(
|
||||
Item& item,
|
||||
const Range& remaining = Range()) {
|
||||
PackResult _trypack(Item& item, const Range& remaining = Range()) {
|
||||
|
||||
PackResult ret;
|
||||
|
||||
@@ -755,41 +769,41 @@ private:
|
||||
};
|
||||
}
|
||||
|
||||
bool first_object = std::all_of(items_.begin(), items_.end(), [&](const Item &rawShape) { return rawShape.is_virt_object && !rawShape.is_wipe_tower; });
|
||||
|
||||
// item won't overlap with virtual objects if it's inside or touches NFP
|
||||
// @return 1 if current item overlaps with virtual objects, 0 otherwise
|
||||
auto overlapWithVirtObject = [&]() -> double {
|
||||
if (items_.empty()) return 0;
|
||||
nfps = calcnfp(item, binbb, Lvl<MaxNfpLevel::value>());
|
||||
auto v = item.referenceVertex();
|
||||
for (const RawShape &nfp : nfps) {
|
||||
if (sl::isInside(v, nfp) || sl::touches(v, nfp)) { return 0; }
|
||||
for (auto &it : items_) {
|
||||
config_.progressFunc((boost::format("_trypack: plate: %4%, existing object: %1%, pos: (%2%, %3%)") % it.get().name % unscale_(it.get().translation()[0]) %
|
||||
unscale_(it.get().translation()[1]) % plateID())
|
||||
.str());
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
bool first_object = std::all_of(items_.begin(), items_.end(), [&](const Item &rawShape) { return rawShape.is_virt_object && !rawShape.is_wipe_tower; });
|
||||
|
||||
if (first_object) {
|
||||
setInitialPosition(item);
|
||||
auto best_tr = item.translation();
|
||||
auto best_rot = item.rotation();
|
||||
best_overfit = overfit(item.transformedShape(), bin_) + overlapWithVirtObject();
|
||||
best_overfit = std::numeric_limits<double>::max();
|
||||
|
||||
// try normal inflation first, then 0 inflation if not fit. See STUDIO-5566.
|
||||
// Note for by-object printing, bed is expanded by -config_.bed_shrink.x().
|
||||
Coord inflation_back = item.inflation();
|
||||
Coord inflations[2]={inflation_back, std::abs(config_.bed_shrink.x())};
|
||||
Coord inflations[2]={inflation_back, 0};
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
item.inflation(inflations[i]);
|
||||
for (auto rot : config_.rotations) {
|
||||
for (auto rot : item.allowed_rotations) {
|
||||
item.translation(initial_tr);
|
||||
item.rotation(initial_rot + rot);
|
||||
setInitialPosition(item);
|
||||
if (!overlapWithVirtObject(item, binbb)) {
|
||||
double of = overfit(item.transformedShape(), bin_);
|
||||
if (of + overlapWithVirtObject() < best_overfit) {
|
||||
if (of < best_overfit) {
|
||||
best_overfit = of;
|
||||
best_tr = item.translation();
|
||||
best_rot = item.rotation();
|
||||
if (best_overfit <= 0) {
|
||||
config_.progressFunc("First object " + item.name + " can fit with rot=" + std::to_string(rot));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
can_pack = best_overfit <= 0;
|
||||
@@ -806,7 +820,7 @@ private:
|
||||
|
||||
Pile merged_pile = merged_pile_;
|
||||
|
||||
for(auto rot : config_.rotations) {
|
||||
for(auto rot : item.allowed_rotations) {
|
||||
|
||||
item.translation(initial_tr);
|
||||
item.rotation(initial_rot + rot);
|
||||
@@ -991,6 +1005,7 @@ private:
|
||||
final_rot = initial_rot + rot;
|
||||
can_pack = true;
|
||||
global_score = best_score;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,41 +1013,7 @@ private:
|
||||
item.rotation(final_rot);
|
||||
}
|
||||
|
||||
#ifdef SVGTOOLS_HPP
|
||||
if (config_.save_svg) {
|
||||
svg::SVGWriter<RawShape> svgwriter;
|
||||
Box binbb2(binbb.width() * 2, binbb.height() * 2, binbb.center()); // expand bbox to allow object be drawed outside
|
||||
svgwriter.setSize(binbb2);
|
||||
svgwriter.conf_.x0 = binbb.width();
|
||||
svgwriter.conf_.y0 = -binbb.height() / 2; // origin is top left corner
|
||||
svgwriter.add_comment("bed");
|
||||
svgwriter.writeShape(box2RawShape(binbb), "none", "black");
|
||||
svgwriter.add_comment("nfps");
|
||||
for (int i = 0; i < nfps.size(); i++) svgwriter.writeShape(nfps[i], "none", "blue");
|
||||
for (int i = 0; i < items_.size(); i++) {
|
||||
svgwriter.add_comment(items_[i].get().name);
|
||||
svgwriter.writeItem(items_[i], "none", "black");
|
||||
}
|
||||
svgwriter.add_comment("merged_pile_");
|
||||
for (int i = 0; i < merged_pile_.size(); i++) svgwriter.writeShape(merged_pile_[i], "none", "yellow");
|
||||
svgwriter.add_comment("current item");
|
||||
svgwriter.writeItem(item, "red", "red", 2);
|
||||
|
||||
std::stringstream ss;
|
||||
ss.setf(std::ios::fixed | std::ios::showpoint);
|
||||
ss.precision(1);
|
||||
ss << "t=" << round(item.translation().x() / 1e6) << ","
|
||||
<< round(item.translation().y() / 1e6)
|
||||
//<< "-rot=" << round(item.rotation().toDegrees())
|
||||
<< "-sco=" << round(global_score);
|
||||
svgwriter.draw_text(20, 20, ss.str(), "blue", 20);
|
||||
ss.str("");
|
||||
ss << "items.size=" << items_.size() << "-merged_pile.size=" << merged_pile_.size();
|
||||
svgwriter.draw_text(20, 40, ss.str(), "blue", 20);
|
||||
svgwriter.save(boost::filesystem::path("C:/Users/arthur.tang/AppData/Roaming/QIDIStudioInternal/SVG") /
|
||||
("nfpplacer_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + ".svg"));
|
||||
}
|
||||
#endif
|
||||
if (config_.save_svg) saveSVG(binbb, nfps, item, global_score, can_pack);
|
||||
|
||||
if(can_pack) {
|
||||
ret = PackResult(item);
|
||||
@@ -1045,6 +1026,87 @@ private:
|
||||
return ret;
|
||||
}
|
||||
|
||||
PackResult _trypack_with_original_pos(Item &item)
|
||||
{
|
||||
PackResult ret;
|
||||
|
||||
bool can_pack = false;
|
||||
double best_overfit = std::numeric_limits<double>::max();
|
||||
double global_score = std::numeric_limits<double>::max();
|
||||
|
||||
auto initial_tr = item.translation();
|
||||
auto initial_rot = item.rotation();
|
||||
Vertex final_tr = initial_tr;
|
||||
Radians final_rot = initial_rot;
|
||||
Shapes nfps;
|
||||
|
||||
auto binbb = sl::boundingBox(bin_);
|
||||
|
||||
for (auto &it : items_) {
|
||||
config_.progressFunc((boost::format("_trypack_with_original_pos: plate: %4%, existing object: %1%, pos: (%2%, %3%)") % it.get().name % unscale_(it.get().translation()[0]) %
|
||||
unscale_(it.get().translation()[1]) % plateID())
|
||||
.str());
|
||||
}
|
||||
|
||||
{
|
||||
for (auto rot : item.allowed_rotations) {
|
||||
item.translation(initial_tr);
|
||||
item.rotation(initial_rot + rot);
|
||||
|
||||
if (!overlapWithVirtObject(item, binbb)) {
|
||||
can_pack = true;
|
||||
final_tr = initial_tr;
|
||||
final_rot = initial_rot + rot;
|
||||
global_score = 0.3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
item.translation(final_tr);
|
||||
item.rotation(final_rot);
|
||||
}
|
||||
|
||||
if (config_.save_svg) saveSVG(binbb, nfps, item, global_score, can_pack);
|
||||
|
||||
if (can_pack) {
|
||||
ret = PackResult(item);
|
||||
ret.score_ = global_score;
|
||||
// merged_pile_ = nfp::merge(merged_pile_, item.transformedShape());
|
||||
config_.progressFunc((boost::format("_trypack_with_original_pos: item %1% can pack") % item.name).str());
|
||||
} else {
|
||||
ret = PackResult(best_overfit);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void saveSVG(Box &binbb, Shapes &nfps, Item &item, double global_score, bool can_pack)
|
||||
{
|
||||
svg::SVGWriter<RawShape> svgwriter;
|
||||
Box binbb2(binbb.width() * 2, binbb.height() * 2, binbb.center()); // expand bbox to allow object be drawed outside
|
||||
svgwriter.setSize(binbb2);
|
||||
svgwriter.conf_.x0 = binbb.width();
|
||||
svgwriter.conf_.y0 = -binbb.height() / 2; // origin is top left corner
|
||||
svgwriter.writeShape(box2RawShape(binbb), "bed", "none", "black");
|
||||
for (int i = 0; i < nfps.size(); i++) svgwriter.writeShape(nfps[i], "nfp_" + std::to_string(i), "none", "blue", 0.2);
|
||||
for (int i = 0; i < items_.size(); i++) { svgwriter.writeItem(items_[i], items_[i].get().name, "none", "black", 0.2); }
|
||||
for (int i = 0; i < merged_pile_.size(); i++) svgwriter.writeShape(merged_pile_[i], "merged_pile_" + std::to_string(i), "none", "yellow", 0.2);
|
||||
svgwriter.writeItem(item, item.name, "red", "red", 0.3);
|
||||
|
||||
std::stringstream ss;
|
||||
ss.setf(std::ios::fixed | std::ios::showpoint);
|
||||
ss.precision(1);
|
||||
ss << "t=" << round(item.translation().x() / 1e6) << ","
|
||||
<< round(item.translation().y() / 1e6)
|
||||
//<< "-rot=" << round(item.rotation().toDegrees())
|
||||
<< "-sco=" << round(global_score);
|
||||
svgwriter.draw_text(20, 20, ss.str(), "blue", 10);
|
||||
ss.str("");
|
||||
ss << "items.size=" << items_.size() << "-merged_pile.size=" << merged_pile_.size();
|
||||
svgwriter.draw_text(20, 40, ss.str(), "blue", 10);
|
||||
svgwriter.save(boost::filesystem::path("SVG") / ("plate_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + "_canPack=" + std::to_string(can_pack) + ".svg"));
|
||||
}
|
||||
|
||||
RawShape box2RawShape(Box& bbin)
|
||||
{
|
||||
RawShape binrsh;
|
||||
@@ -1158,16 +1220,7 @@ private:
|
||||
auto d = cb - ci;
|
||||
|
||||
// QDS make sure the item won't clash with excluded regions
|
||||
// do we have wipe tower after arranging?
|
||||
size_t n_objs = 0;
|
||||
std::set<int> extruders;
|
||||
for (const Item& item : items_) {
|
||||
if (!item.is_virt_object) {
|
||||
extruders.insert(item.extrude_ids.begin(), item.extrude_ids.end());
|
||||
n_objs++;
|
||||
}
|
||||
}
|
||||
bool need_wipe_tower = extruders.size() > 1;
|
||||
|
||||
std::vector<RawShape> objs,excludes;
|
||||
for (Item &item : items_) {
|
||||
|
||||
@@ -88,7 +88,23 @@ public:
|
||||
for (auto it = store_.begin(); it != store_.end(); ++it) {
|
||||
std::stringstream ss;
|
||||
ss << "initial order: " << it->get().name << ", p=" << it->get().priority() << ", bed_temp=" << it->get().bed_temp << ", height=" << it->get().height
|
||||
<< ", area=" << it->get().area();
|
||||
<< ", area=" << it->get().area() << ", allowed_rotations=";
|
||||
for(auto r: it->get().allowed_rotations) ss << r << ", ";
|
||||
if (this->unfitindicator_)
|
||||
this->unfitindicator_(ss.str());
|
||||
}
|
||||
// debug: write down fixed items
|
||||
for (size_t i = 0; i < fixed_bins.size(); i++) {
|
||||
if (fixed_bins[i].empty())
|
||||
continue;
|
||||
std::stringstream ss;
|
||||
ss << "fixed bin " << i << ": ";
|
||||
for (auto it = fixed_bins[i].begin(); it != fixed_bins[i].end(); ++it) {
|
||||
ss << it->get().name << ", p=" << it->get().priority() << ", bed_temp=" << it->get().bed_temp << ", height=" << it->get().height
|
||||
<< ", area=" << it->get().area() << ", allowed_rotations=";
|
||||
for(auto r: it->get().allowed_rotations) ss << r << ", ";
|
||||
ss << "; ";
|
||||
}
|
||||
if (this->unfitindicator_)
|
||||
this->unfitindicator_(ss.str());
|
||||
}
|
||||
@@ -102,7 +118,7 @@ public:
|
||||
|
||||
auto& cancelled = this->stopcond_;
|
||||
|
||||
this->template remove_unpackable_items<Placer>(store_, bin, pconfig);
|
||||
//this->template remove_unpackable_items<Placer>(store_, bin, pconfig);
|
||||
|
||||
for (auto it = store_.begin(); it != store_.end() && !cancelled(); ++it) {
|
||||
// skip unpackable item
|
||||
@@ -115,13 +131,21 @@ public:
|
||||
double score_all_plates = 0, score_all_plates_best = std::numeric_limits<double>::max();
|
||||
typename Placer::PackResult result, result_best, result_firstfit;
|
||||
int j = 0;
|
||||
while(!was_packed && !cancelled()) {
|
||||
for(; j < placers.size() && !was_packed && !cancelled(); j++) {
|
||||
while (!was_packed && !cancelled() && placers.size() <= MAX_NUM_PLATES) {
|
||||
for(; j < placers.size() && !was_packed && !cancelled() && j<MAX_NUM_PLATES; j++) {
|
||||
if (it->get().is_wipe_tower && it->get().binId() != placers[j].plateID()) {
|
||||
if (this->unfitindicator_)
|
||||
this->unfitindicator_(it->get().name + " cant be placed in plate_id=" + std::to_string(j) + "/" + std::to_string(placers.size()) + ", continue to next plate");
|
||||
continue;
|
||||
}
|
||||
result = placers[j].pack(*it, rem(it, store_));
|
||||
score = result.score();
|
||||
score_all_plates = score;
|
||||
score_all_plates = score + COST_OF_NEW_PLATE * j; // add a larger cost to larger plate id to encourace to use less plates
|
||||
for (int i = 0; i < placers.size(); i++) { score_all_plates += placers[i].score();}
|
||||
if (this->unfitindicator_) this->unfitindicator_(it->get().name + " bed_id="+std::to_string(j) + ",score=" + std::to_string(score)+", score_all_plates="+std::to_string(score_all_plates));
|
||||
if (this->unfitindicator_)
|
||||
this->unfitindicator_((boost::format("item %1% bed_id=%2%, score=%3%, score_all_plates=%4%, pos=(%5%, %6%)") % it->get().name % j % score %
|
||||
score_all_plates % unscale_(it->get().translation()[0]) % unscale_(it->get().translation()[1]))
|
||||
.str());
|
||||
|
||||
if(score >= 0 && score < LARGE_COST_TO_REJECT) {
|
||||
if (bed_id_firstfit == -1) {
|
||||
@@ -161,42 +185,42 @@ public:
|
||||
makeProgress(placers[j], j);
|
||||
}
|
||||
|
||||
if (was_packed && it->get().has_tried_with_excluded) {
|
||||
placers[j].clearItems([](const Item &itm) { return itm.isFixed() && !itm.is_wipe_tower; });
|
||||
if (fixed_bins.size() >= placers.size())
|
||||
placers[j].preload(fixed_bins[placers.size() - 1]);
|
||||
}
|
||||
bool placer_not_packed = !was_packed && !placers.empty() && j == placers.size() && placers[j - 1].getPackedSize() == 0; // large item is not placed into the bin
|
||||
// if the object can't be packed, try to pack it without extrusion calibration object
|
||||
bool placer_not_packed = !was_packed && j > 0 && j == placers.size() && placers[j - 1].getPackedSize() == 0; // large item is not placed into the bin
|
||||
if (placer_not_packed) {
|
||||
if (it->get().has_tried_with_excluded == false) {
|
||||
it->get().has_tried_with_excluded = true;
|
||||
placers[j - 1].clearItems([](const Item &itm) { return itm.isFixed()&&!itm.is_wipe_tower; });
|
||||
placers[j - 1].preload(pconfig.m_excluded_items);
|
||||
if (it->get().has_tried_without_extrusion_cali_obj == false) {
|
||||
it->get().has_tried_without_extrusion_cali_obj = true;
|
||||
placers[j - 1].clearItems([](const Item &itm) { return itm.is_extrusion_cali_object; });
|
||||
j = j - 1;
|
||||
continue;
|
||||
} else {
|
||||
placers[j - 1].clearItems([](const Item &itm) { return itm.isFixed() && !itm.is_wipe_tower; });
|
||||
placers[j - 1].preload(fixed_bins[placers.size() - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
if(!was_packed){
|
||||
if (this->unfitindicator_ && !placers.empty())
|
||||
this->unfitindicator_(it->get().name + " not fit! height=" +std::to_string(it->get().height)
|
||||
+ " ,plate_id=" + std::to_string(j-1)
|
||||
+ ", score=" + std::to_string(score)
|
||||
+ ", best_bed_id=" + std::to_string(best_bed_id)
|
||||
+ ", score_all_plates=" + std::to_string(score_all_plates)
|
||||
+", overfit=" + std::to_string(result.overfit()));
|
||||
|
||||
this->unfitindicator_(it->get().name + " not fit! plate_id=" + std::to_string(placers.back().plateID()) + ", score=" + std::to_string(score) +
|
||||
", best_bed_id=" + std::to_string(best_bed_id) + ", score_all_plates=" + std::to_string(score_all_plates) +
|
||||
", item.bed_id=" + std::to_string(it->get().binId()));
|
||||
if (!placers.empty() && placers.back().getItems().empty()) {
|
||||
it->get().binId(BIN_ID_UNFIT);
|
||||
if (this->unfitindicator_) this->unfitindicator_(it->get().name + " can't fit into a new bin. Can't fit!");
|
||||
// remove the last empty placer to force next item to be fit in existing plates first
|
||||
if (placers.size() > 1) placers.pop_back();
|
||||
break;
|
||||
}
|
||||
if (placers.size() < MAX_NUM_PLATES) {
|
||||
placers.emplace_back(bin);
|
||||
placers.back().plateID(placers.size() - 1);
|
||||
placers.back().configure(pconfig);
|
||||
if (fixed_bins.size() >= placers.size())
|
||||
placers.back().preload(fixed_bins[placers.size() - 1]);
|
||||
//placers.back().preload(pconfig.m_excluded_items);
|
||||
if (fixed_bins.size() >= placers.size()) placers.back().preload(fixed_bins[placers.size() - 1]);
|
||||
// placers.back().preload(pconfig.m_excluded_items);
|
||||
packed_bins_.emplace_back();
|
||||
j = placers.size() - 1;
|
||||
} else {
|
||||
it->get().binId(BIN_ID_UNFIT);
|
||||
if (this->unfitindicator_) this->unfitindicator_(it->get().name + " can't fit into any bin. Can't fit!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,7 +443,7 @@ inline void offset(PolygonImpl& sh, bp2d::Coord distance)
|
||||
|
||||
#ifndef DISABLE_BOOST_SERIALIZE
|
||||
template<> inline std::string serialize<libnest2d::Formats::SVG>(
|
||||
const PolygonImpl& sh, double scale, std::string fill, std::string stroke, float stroke_width)
|
||||
const PolygonImpl& sh, const std::string& name, double scale, std::string fill, std::string stroke, float stroke_width)
|
||||
{
|
||||
std::stringstream ss;
|
||||
std::string style = "fill: "+fill+"; stroke: "+stroke+"; stroke-width: "+std::to_string(stroke_width)+"px; ";
|
||||
@@ -478,7 +478,14 @@ template<> inline std::string serialize<libnest2d::Formats::SVG>(
|
||||
|
||||
ss << svg_data << std::endl;
|
||||
|
||||
return ss.str();
|
||||
std::string svg_content = ss.str();
|
||||
if (!name.empty()) {
|
||||
size_t pos = svg_content.find_first_of("<g ");
|
||||
if (pos != std::string::npos) { svg_content.insert(pos + 3, "id=\"" + name + "\" ");
|
||||
}
|
||||
}
|
||||
|
||||
return svg_content;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ public:
|
||||
|
||||
inline Radians angleToX() const {
|
||||
double ret = std::atan2(getY(axis_), getX(axis_));
|
||||
// horizontal or vertical up-right rectangles are not distinguished, but we need to get the horizontal one
|
||||
if (abs(ret) < EPSILON && abs(bottom_) < abs(right_)) ret += Pi / 2;
|
||||
auto s = std::signbit(ret);
|
||||
if(s) ret += Pi_2;
|
||||
return -ret;
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
double x0, y0;
|
||||
Config():
|
||||
origo_location(BOTTOMLEFT), mm_in_coord_units(1000000),
|
||||
width(500), height(500),x0(100) {}
|
||||
width(500), height(500),x0(100),y0(0) {}
|
||||
|
||||
};
|
||||
|
||||
@@ -47,14 +47,15 @@ public:
|
||||
|
||||
void setSize(const Box &box) {
|
||||
conf_.x0 = box.width() / 5;
|
||||
conf_.x0 = box.height() / 5;
|
||||
conf_.y0 = box.height() / 5;
|
||||
conf_.height = static_cast<double>(box.height() + conf_.y0*2) /
|
||||
conf_.mm_in_coord_units;
|
||||
conf_.width = static_cast<double>(box.width() + conf_.x0*2) /
|
||||
conf_.mm_in_coord_units;
|
||||
}
|
||||
|
||||
void writeShape(RawShape tsh, std::string fill = "none", std::string stroke = "black", float stroke_width = 1) {
|
||||
void writeShape(RawShape tsh, const std::string &name = "", std::string fill = "none", std::string stroke = "black", float stroke_width = 1)
|
||||
{
|
||||
if(svg_layers_.empty()) addLayer();
|
||||
if(conf_.origo_location == BOTTOMLEFT) {
|
||||
auto d = static_cast<Coord>(
|
||||
@@ -74,13 +75,14 @@ public:
|
||||
}
|
||||
}
|
||||
currentLayer() +=
|
||||
shapelike::serialize<Formats::SVG>(tsh,
|
||||
shapelike::serialize<Formats::SVG>(tsh, name,
|
||||
1.0 / conf_.mm_in_coord_units, fill, stroke, stroke_width) +
|
||||
"\n";
|
||||
}
|
||||
|
||||
void writeItem(const Item& item, std::string fill = "none", std::string stroke = "black", float stroke_width = 1) {
|
||||
writeShape(item.transformedShape(), fill, stroke, stroke_width);
|
||||
void writeItem(const Item &item, const std::string &name = "", std::string fill = "none", std::string stroke = "black", float stroke_width = 1)
|
||||
{
|
||||
writeShape(item.transformedShape(), name, fill, stroke, stroke_width);
|
||||
}
|
||||
|
||||
void writePackGroup(const PackGroup& result) {
|
||||
@@ -97,7 +99,7 @@ public:
|
||||
auto it = from;
|
||||
PackGroup pg;
|
||||
while(it != to) {
|
||||
if(it->binId() == BIN_ID_UNSET) continue;
|
||||
if (it->binId() == BIN_ID_UNFIT) continue;
|
||||
while(pg.size() <= size_t(it->binId())) pg.emplace_back();
|
||||
pg[it->binId()].emplace_back(*it);
|
||||
++it;
|
||||
@@ -159,6 +161,8 @@ public:
|
||||
currentLayer() += "<!-- " + comment + " -->\n";
|
||||
}
|
||||
|
||||
void clear() { svg_layers_.clear(); }
|
||||
|
||||
private:
|
||||
|
||||
std::string& currentLayer() { return svg_layers_.back(); }
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
#include "mcut/mcut.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>@SLIC3R_BUILD_ID@</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -132,10 +137,5 @@
|
||||
<key>ASAN_OPTIONS</key>
|
||||
<string>detect_container_overflow=0</string>
|
||||
</dict>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
Reference in New Issue
Block a user