update src

This commit is contained in:
QIDI TECH
2025-05-05 15:13:42 +08:00
parent 3fe258372a
commit eae8e18c3a
35 changed files with 11268 additions and 6351 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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};
wc.lpfnWndProc = OpenGLVersionCheck::supports_opengl2_wndproc;
wc.hInstance = (HINSTANCE)GetModuleHandle(nullptr);
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,113 +182,108 @@ 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]);
// 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);
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;
// 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;
else if (wcscmp(argv[i], L"--no-sw-renderer") == 0)
force_mesa = false;
if (wcscmp(argv[i], L"--sw-renderer") == 0)
force_mesa = true;
else if (wcscmp(argv[i], L"--no-sw-renderer") == 0)
force_mesa = false;
#endif /* SLIC3R_GUI */
argv_extended.emplace_back(argv[i]);
}
argv_extended.emplace_back(nullptr);
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);
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(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];
wchar_t dir[_MAX_DIR];
wchar_t fname[_MAX_FNAME];
wchar_t ext[_MAX_EXT];
_wsplitpath(path_to_exe, drive, dir, fname, ext);
_wmakepath(path_to_exe, drive, dir, nullptr, nullptr);
wchar_t path_to_exe[MAX_PATH + 1] = { 0 };
::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH);
wchar_t drive[_MAX_DRIVE];
wchar_t dir[_MAX_DIR];
wchar_t fname[_MAX_FNAME];
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/
if (load_mesa) {
opengl_version_check.unload_opengl_dll();
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) {
printf("MESA OpenGL library was not loaded\n");
} else
printf("MESA OpenGL library was loaded sucessfully\n");
}
// https://wiki.qt.io/Cross_compiling_Mesa_for_Windows
// http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/
if (load_mesa) {
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)
printf("MESA OpenGL library was not loaded\n");
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);
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,
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);
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
// there is just a single calling conversion, therefore no mangling of the function name.
"qidistu_main"
// there is just a single calling conversion, therefore no mangling of the function name.
"qidistu_main"
#else // stdcall calling convention declaration
"_qidistu_main@8"
"_qidistu_main@8"
#endif
);
if (qidistu_main == nullptr) {
printf("could not locate the function qidistu_main in QIDIStudio.dll\n");
return -1;
if (qidistu_main == nullptr) {
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());
}
// argc minus the trailing nullptr of the argv
return qidistu_main((int)argv_extended.size() - 1, argv_extended.data());
}
}

View File

@@ -28,7 +28,7 @@
#include <stddef.h>
#include <vector>
#include <Eigen/Geometry>
#include <Eigen/Geometry>
// Size of the binary STL header, free form.
#define LABEL_SIZE 80
@@ -178,7 +178,7 @@ struct FaceProperty
std::string to_string() const
{
std::string str;
// skip normal type facet to improve performance
// skip normal type facet to improve performance
if (type > eNormal && type < eMaxNumFaceTypes) {
str += std::to_string(type);
if (area != 0.f)
@@ -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;

View File

@@ -121,7 +121,7 @@ static FILE *stl_open_count_facets(stl_file *stl, const char *file, unsigned int
fclose(fp);
return nullptr;
}
// Find the number of facets.
char linebuf[100];
int num_lines = 1;
@@ -136,9 +136,9 @@ 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';
@@ -225,10 +225,10 @@ static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first, Impor
}
catch (...){
}
rewind(fp);
}
char normal_buf[3][32];
@@ -247,7 +247,7 @@ static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first, Impor
stl_facet facet;
if (stl->stats.type == binary) {
// Read a single facet from a binary .STL file. We assume little-endian architecture!
if (fread(&facet, 1, SIZEOF_STL_FACET, fp) != SIZEOF_STL_FACET)

View File

@@ -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

View File

@@ -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,26 +140,58 @@ 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
typedef std::function<void(const Point64& e1bot, const Point64& e1top,
typedef std::function<void(const Point64& e1bot, const Point64& e1top,
const Point64& e2bot, const Point64& e2top, Point64& pt)> ZCallback64;
typedef std::function<void(const PointD& e1bot, const PointD& e1top,
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,49 +315,58 @@ 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);
}
size_t Count() const override
size_t Count() const override
{
return childs_.size();
}
@@ -322,78 +375,73 @@ 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);
}
size_t Count() const override
size_t Count() const override
{
return childs_.size();
}
@@ -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(); });
}
};
@@ -445,7 +492,7 @@ namespace Clipper2Lib {
closed_paths.clear();
open_paths.clear();
if (ExecuteInternal(clip_type, fill_rule, false))
BuildPaths64(closed_paths, &open_paths);
BuildPaths64(closed_paths, &open_paths);
CleanUp();
return succeeded_;
}
@@ -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

View File

@@ -1,21 +1,18 @@
/*******************************************************************************
* 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
namespace Clipper2Lib
{
namespace detail
@@ -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

View File

@@ -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,49 +27,64 @@ 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 preserve_collinear = false,
bool reverse_solution = false) :
miter_limit_(miter_limit), arc_tolerance_(arc_tolerance),
preserve_collinear_(preserve_collinear),
@@ -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; }
};
}

View File

@@ -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
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

View File

@@ -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

View File

@@ -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])
{
if (p.y < lp.y || (p.y == lp.y && p.x >= lp.x)) continue;
result = i;
lp = p;
}
std::optional<size_t> result;
Point64 botPt = Point64(INT64_MAX, INT64_MIN);
for (size_t i = 0; i < paths.size(); ++i)
{
for (const Point64& pt : paths[i])
{
if ((pt.y < botPt.y) ||
((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
result = i;
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);
if (j == k)
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,245 +309,346 @@ 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));
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);
}
// 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
DoSquare(group, path, j, k);
path_out.emplace_back(path[j]);
return;
}
k = j;
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 sufficiently acute to exceed ML
if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a);
else DoSquare(path, j, k);
}
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(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
{
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_));
break;
case EndType::Round:
DoRound(group, path, 0,0, PI);
break;
default:
DoSquare(group, path, 0, 0);
break;
switch (end_type_)
{
case EndType::Butt:
DoBevel(path, 0, 0);
break;
case EndType::Round:
DoRound(path, 0, 0, PI);
break;
default:
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
// reverse normals
for (size_t i = highI; i > 0; --i)
norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
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
{
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_));
break;
case EndType::Round:
DoRound(group, path, highI, highI, PI);
break;
default:
DoSquare(group, path, highI, highI);
break;
switch (end_type_)
{
case EndType::Butt:
DoBevel(path, highI, highI);
break;
case EndType::Round:
DoRound(path, highI, highI, PI);
break;
default:
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();
//single vertex so build a circle or square ...
if (group.join_type_ == JoinType::Round)
if (deltaCallback64_)
{
double radius = abs_group_delta_;
group.path_ = Ellipse(path[0], radius, radius);
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)
{
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();
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
}
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_);
}
}
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)
{
for (const Group& group : groups_)
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)
{
solution.reserve(solution.size() + group.paths_in_.size());
copy(group.paths_in_.begin(), group.paths_in_.end(), back_inserter(solution));
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
{
temp_lim_ = (miter_limit_ <= 1) ?
2.0 :
2.0 / (miter_limit_ * miter_limit_);
delta_ = delta;
std::vector<Group>::iterator git;
for (git = groups_.begin(); git != groups_.end(); ++git)
{
DoGroupOffset(*git);
if (!error_code_) continue; // all OK
solution->clear();
}
return solution;
}
temp_lim_ = (miter_limit_ <= 1) ?
2.0 :
2.0 / (miter_limit_ * miter_limit_);
if (!solution->size()) return;
std::vector<Group>::iterator groups_iter;
for (groups_iter = groups_.begin();
groups_iter != groups_.end(); ++groups_iter)
bool paths_reversed = CheckReverseOrientation();
//clean up self-intersections ...
Clipper64 c;
c.PreserveCollinear(false);
//the solution should retain the orientation of the input
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)
{
DoGroupOffset(*groups_iter, delta);
}
if (merge_groups_ && groups_.size() > 0)
{
//clean up self-intersections ...
Clipper64 c;
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);
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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}

View 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

File diff suppressed because it is too large Load Diff

295
src/imguizmo/ImGuizmo.h Normal file
View 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
View 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
View 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
![Image of ImViewGizmo](http://i.imgur.com/7UVcyDd.gif)
### 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
![Image of Rotation](http://i.imgur.com/y4mcVoT.gif)
![Image of Translation](http://i.imgur.com/o8q8iHq.gif)
![Image of Bounds](http://i.imgur.com/3Ez5LBr.gif)
There is now a sample for Win32/OpenGL ! With a binary in bin directory.
![Image of Sample](https://i.imgur.com/nXlzyqD.png)
### ImSequencer
A WIP little sequencer used to edit frame start/end for different events in a timeline.
![Image of Rotation](http://i.imgur.com/BeyNwCn.png)
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.
![Image of GraphEditor](Images/nodeeditor.jpg)
### 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 :
![Image of dialog](http://i.imgur.com/GL5flN1.png)
```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.

View File

@@ -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!");

View File

@@ -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;

View File

@@ -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; });
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());
}
// 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; }
}
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);
double of = overfit(item.transformedShape(), bin_);
if (of + overlapWithVirtObject() < best_overfit) {
best_overfit = of;
best_tr = item.translation();
best_rot = item.rotation();
if (!overlapWithVirtObject(item, binbb)) {
double of = overfit(item.transformedShape(), bin_);
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_) {

View File

@@ -41,7 +41,7 @@ public:
std::vector<Placer> placers;
placers.reserve(last-first);
typename Base::PackGroup fixed_bins;
std::for_each(first, last, [this, &fixed_bins](Item& itm) {
if (itm.isFixed()) {
@@ -67,7 +67,7 @@ public:
std::for_each(first, last, [this,&svgwriter](Item &itm) { svgwriter.writeShape(itm, "none", "blue"); });
svgwriter.save(boost::filesystem::path("SVG") / "all_items.svg");
#endif
std::function<bool(Item& i1, Item& i2)> sortfunc;
if (pconfig.sortfunc)
sortfunc = pconfig.sortfunc;
@@ -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());
}
@@ -101,8 +117,8 @@ 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()));
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);
packed_bins_.emplace_back();
j = placers.size() - 1;
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);
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;
}
}
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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(); }

View File

@@ -38,6 +38,7 @@
#include "mcut/mcut.h"
#include <chrono>
#include <future>
#include <map>
#include <memory>

View File

@@ -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>