diff --git a/resources/images/copy_menu.svg b/resources/images/copy_menu.svg
new file mode 100644
index 000000000..23e0bfeb2
--- /dev/null
+++ b/resources/images/copy_menu.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/resources/images/copy_menu_dark.svg b/resources/images/copy_menu_dark.svg
new file mode 100644
index 000000000..eaee113a1
--- /dev/null
+++ b/resources/images/copy_menu_dark.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/resources/images/toolbar_measure.svg b/resources/images/toolbar_measure.svg
new file mode 100644
index 000000000..1606b5ee6
--- /dev/null
+++ b/resources/images/toolbar_measure.svg
@@ -0,0 +1,93 @@
+
+
+
+
diff --git a/resources/images/toolbar_measure_dark.svg b/resources/images/toolbar_measure_dark.svg
new file mode 100644
index 000000000..a273e7534
--- /dev/null
+++ b/resources/images/toolbar_measure_dark.svg
@@ -0,0 +1,93 @@
+
+
+
+
diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h
index 87c73dc74..358912148 100644
--- a/src/imgui/imconfig.h
+++ b/src/imgui/imconfig.h
@@ -212,6 +212,8 @@ namespace ImGui
const wchar_t AddFilamentDarkIcon = 0x0845;
const wchar_t DeleteFilamentIcon = 0x0846;
const wchar_t DeleteFilamentDarkIcon = 0x0847;
+ const wchar_t ClipboardBtnIcon = 0x0848;
+ const wchar_t ClipboardBtnDarkIcon = 0x0849;
// void MyFunction(const char* name, const MyMatrix44& v);
}
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 75b981e99..04e0b4c82 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -48,6 +48,8 @@ set(lisbslic3r_sources
ClipperUtils.hpp
Clipper2Utils.cpp
Clipper2Utils.hpp
+ Color.cpp
+ Color.hpp
Config.cpp
Config.hpp
CurveAnalyzer.cpp
@@ -197,6 +199,9 @@ set(lisbslic3r_sources
ModelArrange.cpp
MultiMaterialSegmentation.cpp
MultiMaterialSegmentation.hpp
+ Measure.hpp
+ Measure.cpp
+ MeasureUtils.hpp
CustomGCode.cpp
CustomGCode.hpp
Arrange.hpp
@@ -280,6 +285,7 @@ set(lisbslic3r_sources
Surface.hpp
SurfaceCollection.cpp
SurfaceCollection.hpp
+ SurfaceMesh.hpp
SVG.cpp
SVG.hpp
Technologies.hpp
diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp
new file mode 100644
index 000000000..7777b0e3e
--- /dev/null
+++ b/src/libslic3r/Color.cpp
@@ -0,0 +1,425 @@
+#include "libslic3r.h"
+#include "Color.hpp"
+
+#include
+
+static const float INV_255 = 1.0f / 255.0f;
+
+namespace Slic3r {
+
+// Conversion from RGB to HSV color space
+// The input RGB values are in the range [0, 1]
+// The output HSV values are in the ranges h = [0, 360], and s, v = [0, 1]
+static void RGBtoHSV(float r, float g, float b, float& h, float& s, float& v)
+{
+ assert(0.0f <= r && r <= 1.0f);
+ assert(0.0f <= g && g <= 1.0f);
+ assert(0.0f <= b && b <= 1.0f);
+
+ const float max_comp = std::max(std::max(r, g), b);
+ const float min_comp = std::min(std::min(r, g), b);
+ const float delta = max_comp - min_comp;
+
+ if (delta > 0.0f) {
+ if (max_comp == r)
+ h = 60.0f * (std::fmod(((g - b) / delta), 6.0f));
+ else if (max_comp == g)
+ h = 60.0f * (((b - r) / delta) + 2.0f);
+ else if (max_comp == b)
+ h = 60.0f * (((r - g) / delta) + 4.0f);
+
+ s = (max_comp > 0.0f) ? delta / max_comp : 0.0f;
+ }
+ else {
+ h = 0.0f;
+ s = 0.0f;
+ }
+ v = max_comp;
+
+ while (h < 0.0f) { h += 360.0f; }
+ while (h > 360.0f) { h -= 360.0f; }
+
+ assert(0.0f <= s && s <= 1.0f);
+ assert(0.0f <= v && v <= 1.0f);
+ assert(0.0f <= h && h <= 360.0f);
+}
+
+// Conversion from HSV to RGB color space
+// The input HSV values are in the ranges h = [0, 360], and s, v = [0, 1]
+// The output RGB values are in the range [0, 1]
+static void HSVtoRGB(float h, float s, float v, float& r, float& g, float& b)
+{
+ assert(0.0f <= s && s <= 1.0f);
+ assert(0.0f <= v && v <= 1.0f);
+ assert(0.0f <= h && h <= 360.0f);
+
+ const float chroma = v * s;
+ const float h_prime = std::fmod(h / 60.0f, 6.0f);
+ const float x = chroma * (1.0f - std::abs(std::fmod(h_prime, 2.0f) - 1.0f));
+ const float m = v - chroma;
+
+ if (0.0f <= h_prime && h_prime < 1.0f) {
+ r = chroma;
+ g = x;
+ b = 0.0f;
+ }
+ else if (1.0f <= h_prime && h_prime < 2.0f) {
+ r = x;
+ g = chroma;
+ b = 0.0f;
+ }
+ else if (2.0f <= h_prime && h_prime < 3.0f) {
+ r = 0.0f;
+ g = chroma;
+ b = x;
+ }
+ else if (3.0f <= h_prime && h_prime < 4.0f) {
+ r = 0.0f;
+ g = x;
+ b = chroma;
+ }
+ else if (4.0f <= h_prime && h_prime < 5.0f) {
+ r = x;
+ g = 0.0f;
+ b = chroma;
+ }
+ else if (5.0f <= h_prime && h_prime < 6.0f) {
+ r = chroma;
+ g = 0.0f;
+ b = x;
+ }
+ else {
+ r = 0.0f;
+ g = 0.0f;
+ b = 0.0f;
+ }
+
+ r += m;
+ g += m;
+ b += m;
+
+ assert(0.0f <= r && r <= 1.0f);
+ assert(0.0f <= g && g <= 1.0f);
+ assert(0.0f <= b && b <= 1.0f);
+}
+
+class Randomizer
+{
+ std::random_device m_rd;
+
+public:
+ float random_float(float min, float max) {
+ std::mt19937 rand_generator(m_rd());
+ std::uniform_real_distribution distrib(min, max);
+ return distrib(rand_generator);
+ }
+};
+
+ColorRGB::ColorRGB(float r, float g, float b)
+: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f) })
+{
+}
+
+ColorRGB::ColorRGB(unsigned char r, unsigned char g, unsigned char b)
+: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f) })
+{
+}
+
+bool ColorRGB::operator < (const ColorRGB& other) const
+{
+ for (size_t i = 0; i < 3; ++i) {
+ if (m_data[i] < other.m_data[i])
+ return true;
+ else if (m_data[i] > other.m_data[i])
+ return false;
+ }
+
+ return false;
+}
+
+bool ColorRGB::operator > (const ColorRGB& other) const
+{
+ for (size_t i = 0; i < 3; ++i) {
+ if (m_data[i] > other.m_data[i])
+ return true;
+ else if (m_data[i] < other.m_data[i])
+ return false;
+ }
+
+ return false;
+}
+
+ColorRGB ColorRGB::operator + (const ColorRGB& other) const
+{
+ ColorRGB ret;
+ for (size_t i = 0; i < 3; ++i) {
+ ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f);
+ }
+ return ret;
+}
+
+ColorRGB ColorRGB::operator * (float value) const
+{
+ assert(value >= 0.0f);
+ ColorRGB ret;
+ for (size_t i = 0; i < 3; ++i) {
+ ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f);
+ }
+ return ret;
+}
+
+ColorRGBA::ColorRGBA(float r, float g, float b, float a)
+: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f), std::clamp(a, 0.0f, 1.0f) })
+{
+}
+
+ColorRGBA::ColorRGBA(std::array color)
+ : m_data({std::clamp(color[0], 0.0f, 1.0f), std::clamp(color[1], 0.0f, 1.0f), std::clamp(color[2], 0.0f, 1.0f), std::clamp(color[3], 0.0f, 1.0f)})
+{
+
+}
+ColorRGBA::ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f), std::clamp(a * INV_255, 0.0f, 1.0f) })
+{
+}
+
+bool ColorRGBA::operator < (const ColorRGBA& other) const
+{
+ for (size_t i = 0; i < 3; ++i) {
+ if (m_data[i] < other.m_data[i])
+ return true;
+ else if (m_data[i] > other.m_data[i])
+ return false;
+ }
+
+ return false;
+}
+
+bool ColorRGBA::operator > (const ColorRGBA& other) const
+{
+ for (size_t i = 0; i < 3; ++i) {
+ if (m_data[i] > other.m_data[i])
+ return true;
+ else if (m_data[i] < other.m_data[i])
+ return false;
+ }
+
+ return false;
+}
+
+ColorRGBA ColorRGBA::operator + (const ColorRGBA& other) const
+{
+ ColorRGBA ret;
+ for (size_t i = 0; i < 3; ++i) {
+ ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f);
+ }
+ return ret;
+}
+
+ColorRGBA ColorRGBA::operator * (float value) const
+{
+ assert(value >= 0.0f);
+ ColorRGBA ret;
+ for (size_t i = 0; i < 3; ++i) {
+ ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f);
+ }
+ ret.m_data[3] = this->m_data[3];
+ return ret;
+}
+
+ColorRGB operator * (float value, const ColorRGB& other) { return other * value; }
+ColorRGBA operator * (float value, const ColorRGBA& other) { return other * value; }
+
+ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t)
+{
+ assert(0.0f <= t && t <= 1.0f);
+ return (1.0f - t) * a + t * b;
+}
+
+ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t)
+{
+ assert(0.0f <= t && t <= 1.0f);
+ return (1.0f - t) * a + t * b;
+}
+
+ColorRGB complementary(const ColorRGB& color)
+{
+ return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b() };
+}
+
+ColorRGBA complementary(const ColorRGBA& color)
+{
+ return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b(), color.a() };
+}
+
+ColorRGB saturate(const ColorRGB& color, float factor)
+{
+ float h, s, v;
+ RGBtoHSV(color.r(), color.g(), color.b(), h, s, v);
+ s = std::clamp(s * factor, 0.0f, 1.0f);
+ float r, g, b;
+ HSVtoRGB(h, s, v, r, g, b);
+ return { r, g, b };
+}
+
+ColorRGBA saturate(const ColorRGBA& color, float factor)
+{
+ return to_rgba(saturate(to_rgb(color), factor), color.a());
+}
+
+ColorRGB opposite(const ColorRGB& color)
+{
+ float h, s, v;
+ RGBtoHSV(color.r(), color.g(), color.b(), h, s, v);
+
+ h += 65.0f; // 65 instead 60 to avoid circle values
+ if (h > 360.0f)
+ h -= 360.0f;
+
+ Randomizer rnd;
+ s = rnd.random_float(0.65f, 1.0f);
+ v = rnd.random_float(0.65f, 1.0f);
+
+ float r, g, b;
+ HSVtoRGB(h, s, v, r, g, b);
+ return { r, g, b };
+}
+
+ColorRGB opposite(const ColorRGB& a, const ColorRGB& b)
+{
+ float ha, sa, va;
+ RGBtoHSV(a.r(), a.g(), a.b(), ha, sa, va);
+ float hb, sb, vb;
+ RGBtoHSV(b.r(), b.g(), b.b(), hb, sb, vb);
+
+ float delta_h = std::abs(ha - hb);
+ float start_h = (delta_h > 180.0f) ? std::min(ha, hb) : std::max(ha, hb);
+
+ start_h += 5.0f; // to avoid circle change of colors for 120 deg
+ if (delta_h < 180.0f)
+ delta_h = 360.0f - delta_h;
+
+ Randomizer rnd;
+ float out_h = start_h + 0.5f * delta_h;
+ if (out_h > 360.0f)
+ out_h -= 360.0f;
+ float out_s = rnd.random_float(0.65f, 1.0f);
+ float out_v = rnd.random_float(0.65f, 1.0f);
+
+ float out_r, out_g, out_b;
+ HSVtoRGB(out_h, out_s, out_v, out_r, out_g, out_b);
+ return { out_r, out_g, out_b };
+}
+
+bool can_decode_color(const std::string &color)
+{
+ return (color.size() == 7 && color.front() == '#') || (color.size() == 9 && color.front() == '#');
+}
+
+bool decode_color(const std::string& color_in, ColorRGB& color_out)
+{
+ ColorRGBA rgba;
+ if (!decode_color(color_in, rgba))
+ return false;
+
+ color_out = to_rgb(rgba);
+ return true;
+}
+
+bool decode_color(const std::string& color_in, ColorRGBA& color_out)
+{
+ auto hex_digit_to_int = [](const char c) {
+ return
+ (c >= '0' && c <= '9') ? int(c - '0') :
+ (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
+ (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
+ };
+
+ color_out = ColorRGBA::BLACK();
+ if (can_decode_color(color_in)) {
+ const char *c = color_in.data() + 1;
+ if (color_in.size() == 7) {
+ for (unsigned int i = 0; i < 3; ++i) {
+ const int digit1 = hex_digit_to_int(*c++);
+ const int digit2 = hex_digit_to_int(*c++);
+ if (digit1 != -1 && digit2 != -1)
+ color_out.set(i, float(digit1 * 16 + digit2) * INV_255);
+ }
+ } else {
+ for (unsigned int i = 0; i < 4; ++i) {
+ const int digit1 = hex_digit_to_int(*c++);
+ const int digit2 = hex_digit_to_int(*c++);
+ if (digit1 != -1 && digit2 != -1)
+ color_out.set(i, float(digit1 * 16 + digit2) * INV_255);
+ }
+ }
+ } else
+ return false;
+
+ assert(0.0f <= color_out.r() && color_out.r() <= 1.0f);
+ assert(0.0f <= color_out.g() && color_out.g() <= 1.0f);
+ assert(0.0f <= color_out.b() && color_out.b() <= 1.0f);
+ assert(0.0f <= color_out.a() && color_out.a() <= 1.0f);
+ return true;
+}
+
+bool decode_colors(const std::vector& colors_in, std::vector& colors_out)
+{
+ colors_out = std::vector(colors_in.size(), ColorRGB::BLACK());
+ for (size_t i = 0; i < colors_in.size(); ++i) {
+ if (!decode_color(colors_in[i], colors_out[i]))
+ return false;
+ }
+ return true;
+}
+
+bool decode_colors(const std::vector& colors_in, std::vector& colors_out)
+{
+ colors_out = std::vector(colors_in.size(), ColorRGBA::BLACK());
+ for (size_t i = 0; i < colors_in.size(); ++i) {
+ if (!decode_color(colors_in[i], colors_out[i]))
+ return false;
+ }
+ return true;
+}
+
+std::string encode_color(const ColorRGB& color)
+{
+ char buffer[64];
+ ::sprintf(buffer, "#%02X%02X%02X", color.r_uchar(), color.g_uchar(), color.b_uchar());
+ return std::string(buffer);
+}
+
+std::string encode_color(const ColorRGBA& color) { return encode_color(to_rgb(color)); }
+
+ColorRGB to_rgb(const ColorRGBA& other_rgba) { return { other_rgba.r(), other_rgba.g(), other_rgba.b() }; }
+ColorRGBA to_rgba(const ColorRGB& other_rgb) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), 1.0f }; }
+ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), alpha }; }
+
+ColorRGBA picking_decode(unsigned int id)
+{
+ return {
+ float((id >> 0) & 0xff) * INV_255, // red
+ float((id >> 8) & 0xff) * INV_255, // green
+ float((id >> 16) & 0xff) * INV_255, // blue
+ float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff)) * INV_255 // checksum for validating against unwanted alpha blending and multi sampling
+ };
+}
+
+unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b) { return r + (g << 8) + (b << 16); }
+
+unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue)
+{
+ // 8 bit hash for the color
+ unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff;
+ // Increase enthropy by a bit reversal
+ b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+ b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+ b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+ // Flip every second bit to increase the enthropy even more.
+ b ^= 0x55;
+ return b;
+}
+
+} // namespace Slic3r
+
diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp
new file mode 100644
index 000000000..5719d0eb9
--- /dev/null
+++ b/src/libslic3r/Color.hpp
@@ -0,0 +1,178 @@
+#ifndef slic3r_Color_hpp_
+#define slic3r_Color_hpp_
+
+#include
+#include
+
+namespace Slic3r {
+
+class ColorRGB
+{
+ std::array m_data{1.0f, 1.0f, 1.0f};
+
+public:
+ ColorRGB() = default;
+ ColorRGB(float r, float g, float b);
+ ColorRGB(unsigned char r, unsigned char g, unsigned char b);
+ ColorRGB(const ColorRGB& other) = default;
+
+ ColorRGB& operator = (const ColorRGB& other) { m_data = other.m_data; return *this; }
+
+ bool operator == (const ColorRGB& other) const { return m_data == other.m_data; }
+ bool operator != (const ColorRGB& other) const { return !operator==(other); }
+ bool operator < (const ColorRGB& other) const;
+ bool operator > (const ColorRGB& other) const;
+
+ ColorRGB operator + (const ColorRGB& other) const;
+ ColorRGB operator * (float value) const;
+
+ const float* const data() const { return m_data.data(); }
+
+ float r() const { return m_data[0]; }
+ float g() const { return m_data[1]; }
+ float b() const { return m_data[2]; }
+
+ void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); }
+ void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); }
+ void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); }
+
+ void set(unsigned int comp, float value) {
+ assert(0 <= comp && comp <= 2);
+ m_data[comp] = std::clamp(value, 0.0f, 1.0f);
+ }
+
+ unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); }
+ unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); }
+ unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); }
+
+ static const ColorRGB BLACK() { return { 0.0f, 0.0f, 0.0f }; }
+ static const ColorRGB BLUE() { return { 0.0f, 0.0f, 1.0f }; }
+ static const ColorRGB BLUEISH() { return { 0.5f, 0.5f, 1.0f }; }
+ static const ColorRGB CYAN() { return { 0.0f, 1.0f, 1.0f }; }
+ static const ColorRGB DARK_GRAY() { return { 0.25f, 0.25f, 0.25f }; }
+ static const ColorRGB DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f }; }
+ static const ColorRGB GRAY() { return { 0.5f, 0.5f, 0.5f }; }
+ static const ColorRGB GREEN() { return { 0.0f, 1.0f, 0.0f }; }
+ static const ColorRGB GREENISH() { return { 0.5f, 1.0f, 0.5f }; }
+ static const ColorRGB LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f }; }
+ static const ColorRGB MAGENTA() { return { 1.0f, 0.0f, 1.0f }; }
+ static const ColorRGB ORANGE() { return { 0.92f, 0.50f, 0.26f }; }
+ static const ColorRGB RED() { return { 1.0f, 0.0f, 0.0f }; }
+ static const ColorRGB REDISH() { return { 1.0f, 0.5f, 0.5f }; }
+ static const ColorRGB YELLOW() { return { 1.0f, 1.0f, 0.0f }; }
+ static const ColorRGB WHITE() { return { 1.0f, 1.0f, 1.0f }; }
+ static const ColorRGB ORCA() { return {0.0f, 150.f / 255.0f, 136.0f / 255}; }
+
+ static const ColorRGB X() { return { 0.75f, 0.0f, 0.0f }; }
+ static const ColorRGB Y() { return { 0.0f, 0.75f, 0.0f }; }
+ static const ColorRGB Z() { return { 0.0f, 0.0f, 0.75f }; }
+};
+
+class ColorRGBA
+{
+ std::array m_data{ 1.0f, 1.0f, 1.0f, 1.0f };
+
+public:
+ ColorRGBA() = default;
+ ColorRGBA(float r, float g, float b, float a);
+ ColorRGBA(std::array color);
+ ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+ ColorRGBA(const ColorRGBA& other) = default;
+
+ ColorRGBA& operator = (const ColorRGBA& other) { m_data = other.m_data; return *this; }
+
+ bool operator == (const ColorRGBA& other) const { return m_data == other.m_data; }
+ bool operator != (const ColorRGBA& other) const { return !operator==(other); }
+ bool operator < (const ColorRGBA& other) const;
+ bool operator > (const ColorRGBA& other) const;
+
+ ColorRGBA operator + (const ColorRGBA& other) const;
+ ColorRGBA operator * (float value) const;
+
+ const float* const data() const { return m_data.data(); }
+ const std::array &get_data() const { return m_data; }
+
+ float r() const { return m_data[0]; }
+ float g() const { return m_data[1]; }
+ float b() const { return m_data[2]; }
+ float a() const { return m_data[3]; }
+
+ void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); }
+ void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); }
+ void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); }
+ void a(float a) { m_data[3] = std::clamp(a, 0.0f, 1.0f); }
+
+ void set(unsigned int comp, float value) {
+ assert(0 <= comp && comp <= 3);
+ m_data[comp] = std::clamp(value, 0.0f, 1.0f);
+ }
+
+ unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); }
+ unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); }
+ unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); }
+ unsigned char a_uchar() const { return static_cast(m_data[3] * 255.0f); }
+
+ bool is_transparent() const { return m_data[3] < 1.0f; }
+
+ static const ColorRGBA BLACK() { return { 0.0f, 0.0f, 0.0f, 1.0f }; }
+ static const ColorRGBA BLUE() { return { 0.0f, 0.0f, 1.0f, 1.0f }; }
+ static const ColorRGBA BLUEISH() { return { 0.5f, 0.5f, 1.0f, 1.0f }; }
+ static const ColorRGBA CYAN() { return { 0.0f, 1.0f, 1.0f, 1.0f }; }
+ static const ColorRGBA DARK_GRAY() { return { 0.25f, 0.25f, 0.25f, 1.0f }; }
+ static const ColorRGBA DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f, 1.0f }; }
+ static const ColorRGBA GRAY() { return { 0.5f, 0.5f, 0.5f, 1.0f }; }
+ static const ColorRGBA GREEN() { return { 0.0f, 1.0f, 0.0f, 1.0f }; }
+ static const ColorRGBA GREENISH() { return { 0.5f, 1.0f, 0.5f, 1.0f }; }
+ static const ColorRGBA LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f, 1.0f }; }
+ static const ColorRGBA MAGENTA() { return { 1.0f, 0.0f, 1.0f, 1.0f }; }
+ static const ColorRGBA ORANGE() { return { 0.923f, 0.504f, 0.264f, 1.0f }; }
+ static const ColorRGBA RED() { return { 1.0f, 0.0f, 0.0f, 1.0f }; }
+ static const ColorRGBA REDISH() { return { 1.0f, 0.5f, 0.5f, 1.0f }; }
+ static const ColorRGBA YELLOW() { return { 1.0f, 1.0f, 0.0f, 1.0f }; }
+ static const ColorRGBA WHITE() { return { 1.0f, 1.0f, 1.0f, 1.0f }; }
+ static const ColorRGBA ORCA() { return {0.0f, 150.f / 255.0f, 136.0f / 255, 1.0f}; }
+
+ static const ColorRGBA X() { return { 0.75f, 0.0f, 0.0f, 1.0f }; }
+ static const ColorRGBA Y() { return { 0.0f, 0.75f, 0.0f, 1.0f }; }
+ static const ColorRGBA Z() { return { 0.0f, 0.0f, 0.75f, 1.0f }; }
+};
+
+ColorRGB operator * (float value, const ColorRGB& other);
+ColorRGBA operator * (float value, const ColorRGBA& other);
+
+ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t);
+ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t);
+
+ColorRGB complementary(const ColorRGB& color);
+ColorRGBA complementary(const ColorRGBA& color);
+
+ColorRGB saturate(const ColorRGB& color, float factor);
+ColorRGBA saturate(const ColorRGBA& color, float factor);
+
+ColorRGB opposite(const ColorRGB& color);
+ColorRGB opposite(const ColorRGB& a, const ColorRGB& b);
+
+bool can_decode_color(const std::string& color);
+
+bool decode_color(const std::string& color_in, ColorRGB& color_out);
+bool decode_color(const std::string& color_in, ColorRGBA& color_out);
+
+bool decode_colors(const std::vector& colors_in, std::vector& colors_out);
+bool decode_colors(const std::vector& colors_in, std::vector& colors_out);
+
+std::string encode_color(const ColorRGB& color);
+std::string encode_color(const ColorRGBA& color);
+
+ColorRGB to_rgb(const ColorRGBA& other_rgba);
+ColorRGBA to_rgba(const ColorRGB& other_rgb);
+ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha);
+
+ColorRGBA picking_decode(unsigned int id);
+unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b);
+// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components
+// were not interpolated by alpha blending or multi sampling.
+unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue);
+
+} // namespace Slic3r
+
+#endif /* slic3r_Color_hpp_ */
diff --git a/src/libslic3r/Geometry/Circle.cpp b/src/libslic3r/Geometry/Circle.cpp
index 4d7c38ccc..a780a20bc 100644
--- a/src/libslic3r/Geometry/Circle.cpp
+++ b/src/libslic3r/Geometry/Circle.cpp
@@ -108,30 +108,32 @@ Circled circle_taubin_newton(const Vec2ds& input, size_t cycles)
return out;
}
-Circled circle_ransac(const Vec2ds& input, size_t iterations)
+Circled circle_ransac(const Vec2ds &input, size_t iterations, double *min_error)
{
if (input.size() < 3)
return Circled::make_invalid();
- std::mt19937 rng;
+ std::mt19937 rng;
std::vector samples;
- Circled circle_best = Circled::make_invalid();
- double err_min = std::numeric_limits::max();
- for (size_t iter = 0; iter < iterations; ++ iter) {
+ Circled circle_best = Circled::make_invalid();
+ double err_min = std::numeric_limits::max();
+ for (size_t iter = 0; iter < iterations; ++iter) {
samples.clear();
std::sample(input.begin(), input.end(), std::back_inserter(samples), 3, rng);
Circled c;
c.center = Geometry::circle_center(samples[0], samples[1], samples[2], EPSILON);
- c.radius = std::accumulate(input.begin(), input.end(), 0., [&c](double acc, const Vec2d& pt) { return (pt - c.center).norm() + acc; });
+ c.radius = std::accumulate(input.begin(), input.end(), 0., [&c](double acc, const Vec2d &pt) { return (pt - c.center).norm() + acc; });
c.radius /= double(input.size());
double err = 0;
for (const Vec2d &pt : input)
err = std::max(err, std::abs((pt - c.center).norm() - c.radius));
if (err < err_min) {
- err_min = err;
+ err_min = err;
circle_best = c;
}
}
+ if (min_error)
+ *min_error = err_min;
return circle_best;
}
diff --git a/src/libslic3r/Geometry/Circle.hpp b/src/libslic3r/Geometry/Circle.hpp
index 39973d916..62518718d 100644
--- a/src/libslic3r/Geometry/Circle.hpp
+++ b/src/libslic3r/Geometry/Circle.hpp
@@ -102,7 +102,7 @@ inline Vec2d circle_center_taubin_newton(const Vec2ds& input, size_t cycles = 20
Circled circle_taubin_newton(const Vec2ds& input, size_t cycles = 20);
// Find circle using RANSAC randomized algorithm.
-Circled circle_ransac(const Vec2ds& input, size_t iterations = 20);
+Circled circle_ransac(const Vec2ds &input, size_t iterations = 20, double *min_error = nullptr);
// Randomized algorithm by Emo Welzl, working with squared radii for efficiency. The returned circle radius is inflated by epsilon.
template
diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp
new file mode 100644
index 000000000..8a4a5ce72
--- /dev/null
+++ b/src/libslic3r/Measure.cpp
@@ -0,0 +1,1247 @@
+///|/ Copyright (c) Prusa Research 2022 - 2023 Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
+#include "libslic3r/libslic3r.h"
+#include "Measure.hpp"
+#include "MeasureUtils.hpp"
+
+#include "libslic3r/Geometry/Circle.hpp"
+#include "libslic3r/SurfaceMesh.hpp"
+
+
+#include
+#include
+
+#define DEBUG_EXTRACT_ALL_FEATURES_AT_ONCE 0
+
+namespace Slic3r {
+namespace Measure {
+
+
+constexpr double feature_hover_limit = 0.5; // how close to a feature the mouse must be to highlight it
+
+static std::tuple get_center_and_radius(const std::vector& points, const Transform3d& trafo, const Transform3d& trafo_inv)
+{
+ Vec2ds out;
+ double z = 0.;
+ for (const Vec3d& pt : points) {
+ Vec3d pt_transformed = trafo * pt;
+ z = pt_transformed.z();
+ out.emplace_back(pt_transformed.x(), pt_transformed.y());
+ }
+
+ const int iter = points.size() < 10 ? 2 :
+ points.size() < 100 ? 4 :
+ 6;
+
+ double error = std::numeric_limits::max();
+ auto circle = Geometry::circle_ransac(out, iter, &error);
+
+ return std::make_tuple(trafo.inverse() * Vec3d(circle.center.x(), circle.center.y(), z), circle.radius, error);
+}
+
+
+
+static std::array orthonormal_basis(const Vec3d& v)
+{
+ std::array ret;
+ ret[2] = v.normalized();
+ int index;
+ ret[2].cwiseAbs().maxCoeff(&index);
+ switch (index)
+ {
+ case 0: { ret[0] = Vec3d(ret[2].y(), -ret[2].x(), 0.0).normalized(); break; }
+ case 1: { ret[0] = Vec3d(0.0, ret[2].z(), -ret[2].y()).normalized(); break; }
+ case 2: { ret[0] = Vec3d(-ret[2].z(), 0.0, ret[2].x()).normalized(); break; }
+ }
+ ret[1] = ret[2].cross(ret[0]).normalized();
+ return ret;
+}
+
+
+
+class MeasuringImpl {
+public:
+ explicit MeasuringImpl(const indexed_triangle_set& its);
+ struct PlaneData {
+ std::vector facets;
+ std::vector> borders; // FIXME: should be in fact local in update_planes()
+ std::vector surface_features;
+ Vec3d normal;
+ float area;
+ bool features_extracted = false;
+ };
+
+ std::optional get_feature(size_t face_idx, const Vec3d& point);
+ int get_num_of_planes() const;
+ const std::vector& get_plane_triangle_indices(int idx) const;
+ const std::vector& get_plane_features(unsigned int plane_id);
+ const indexed_triangle_set& get_its() const;
+
+private:
+ void update_planes();
+ void extract_features(int plane_idx);
+
+ std::vector m_planes;
+ std::vector m_face_to_plane;
+ indexed_triangle_set m_its;
+};
+
+
+
+
+
+
+MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its)
+: m_its(its)
+{
+ update_planes();
+
+ // Extracting features will be done as needed.
+ // To extract all planes at once, run the following:
+#if DEBUG_EXTRACT_ALL_FEATURES_AT_ONCE
+ for (int i=0; i face_normals = its_face_normals(m_its);
+ const std::vector face_neighbors = its_face_neighbors(m_its);
+ std::vector facet_queue(num_of_facets, 0);
+ int facet_queue_cnt = 0;
+ const stl_normal* normal_ptr = nullptr;
+ size_t seed_facet_idx = 0;
+
+ auto is_same_normal = [](const stl_normal& a, const stl_normal& b) -> bool {
+ return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001);
+ };
+
+ m_planes.clear();
+ m_planes.reserve(num_of_facets / 5); // empty plane data object is quite lightweight, let's save the initial reallocations
+
+
+ // First go through all the triangles and fill in m_planes vector. For each "plane"
+ // detected on the model, it will contain list of facets that are part of it.
+ // We will also fill in m_face_to_plane, which contains index into m_planes
+ // for each of the source facets.
+ while (1) {
+ // Find next unvisited triangle:
+ for (; seed_facet_idx < num_of_facets; ++ seed_facet_idx)
+ if (m_face_to_plane[seed_facet_idx] == size_t(-1)) {
+ facet_queue[facet_queue_cnt ++] = seed_facet_idx;
+ normal_ptr = &face_normals[seed_facet_idx];
+ m_face_to_plane[seed_facet_idx] = m_planes.size();
+ m_planes.emplace_back();
+ break;
+ }
+ if (seed_facet_idx == num_of_facets)
+ break; // Everything was visited already
+
+ while (facet_queue_cnt > 0) {
+ int facet_idx = facet_queue[-- facet_queue_cnt];
+ const stl_normal& this_normal = face_normals[facet_idx];
+ if (is_same_normal(this_normal, *normal_ptr)) {
+// const Vec3i& face = m_its.indices[facet_idx];
+
+ m_face_to_plane[facet_idx] = m_planes.size() - 1;
+ m_planes.back().facets.emplace_back(facet_idx);
+ for (int j = 0; j < 3; ++ j)
+ if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && m_face_to_plane[neighbor_idx] == size_t(-1))
+ facet_queue[facet_queue_cnt ++] = neighbor_idx;
+ }
+ }
+
+ m_planes.back().normal = normal_ptr->cast();
+ std::sort(m_planes.back().facets.begin(), m_planes.back().facets.end());
+ }
+
+ // Check that each facet is part of one of the planes.
+ assert(std::none_of(m_face_to_plane.begin(), m_face_to_plane.end(), [](size_t val) { return val == size_t(-1); }));
+
+ // Now we will walk around each of the planes and save vertices which form the border.
+ const SurfaceMesh sm(m_its);
+
+ const auto& face_to_plane = m_face_to_plane;
+ auto& planes = m_planes;
+
+ tbb::parallel_for(tbb::blocked_range(0, m_planes.size()),
+ [&planes, &face_to_plane, &face_neighbors, &sm](const tbb::blocked_range& range) {
+ for (size_t plane_id = range.begin(); plane_id != range.end(); ++plane_id) {
+
+ const auto& facets = planes[plane_id].facets;
+ planes[plane_id].borders.clear();
+ std::vector> visited(facets.size(), {false, false, false});
+
+ for (int face_id=0; face_id& last_border = planes[plane_id].borders.back();
+ last_border.reserve(4);
+ last_border.emplace_back(sm.point(sm.source(he)).cast());
+ //Vertex_index target = sm.target(he);
+ const Halfedge_index he_start = he;
+
+ Face_index fi = he.face();
+ auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi));
+ assert(face_it != facets.end());
+ assert(*face_it == int(fi));
+ visited[face_it - facets.begin()][he.side()] = true;
+
+ do {
+ const Halfedge_index he_orig = he;
+ he = sm.next_around_target(he);
+ if (he.is_invalid())
+ goto PLANE_FAILURE;
+
+ // For broken meshes, the iteration might never get back to he_orig.
+ // Remember all halfedges we saw to break out of such infinite loops.
+ boost::container::small_vector he_seen;
+
+ while ( face_to_plane[sm.face(he)] == plane_id && he != he_orig) {
+ he_seen.emplace_back(he);
+ he = sm.next_around_target(he);
+ if (he.is_invalid() || std::find(he_seen.begin(), he_seen.end(), he) != he_seen.end())
+ goto PLANE_FAILURE;
+ }
+ he = sm.opposite(he);
+ if (he.is_invalid())
+ goto PLANE_FAILURE;
+
+ Face_index fi = he.face();
+ auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi));
+ if (face_it == facets.end() || *face_it != int(fi)) // This indicates a broken mesh.
+ goto PLANE_FAILURE;
+
+ if (visited[face_it - facets.begin()][he.side()] && he != he_start) {
+ last_border.resize(1);
+ break;
+ }
+ visited[face_it - facets.begin()][he.side()] = true;
+
+ last_border.emplace_back(sm.point(sm.source(he)).cast());
+
+ // In case of broken meshes, this loop might be infinite. Break
+ // out in case it is clearly going bad.
+ if (last_border.size() > 3*facets.size()+1)
+ goto PLANE_FAILURE;
+
+ } while (he != he_start);
+
+ if (last_border.size() == 1)
+ planes[plane_id].borders.pop_back();
+ else {
+ assert(last_border.front() == last_border.back());
+ last_border.pop_back();
+ }
+ }
+ }
+ continue; // There was no failure.
+
+ PLANE_FAILURE:
+ planes[plane_id].borders.clear();
+ }});
+ m_planes.shrink_to_fit();
+}
+
+void MeasuringImpl::extract_features(int plane_idx)
+{
+ assert(! m_planes[plane_idx].features_extracted);
+
+ PlaneData& plane = m_planes[plane_idx];
+ plane.surface_features.clear();
+ const Vec3d& normal = plane.normal;
+
+ Eigen::Quaterniond q;
+ q.setFromTwoVectors(plane.normal, Vec3d::UnitZ());
+ Transform3d trafo = Transform3d::Identity();
+ trafo.rotate(q);
+ const Transform3d trafo_inv = trafo.inverse();
+
+ std::vector angles; // placed in outer scope to prevent reallocations
+ std::vector lengths;
+
+ for (const std::vector& border : plane.borders) {
+ if (border.size() <= 1)
+ continue;
+
+ bool done = false;
+
+ if (border.size() > 4) {
+ const auto& [center, radius, err] = get_center_and_radius(border, trafo, trafo_inv);
+
+ if (err < 0.05) {
+ // The whole border is one circle. Just add it into the list of features
+ // and we are done.
+
+ bool is_polygon = border.size()>4 && border.size()<=8;
+ bool lengths_match = std::all_of(border.begin()+2, border.end(), [is_polygon](const Vec3d& pt) {
+ return Slic3r::is_approx((pt - *((&pt)-1)).squaredNorm(), (*((&pt)-1) - *((&pt)-2)).squaredNorm(), is_polygon ? 0.01 : 0.01);
+ });
+
+ if (lengths_match && (is_polygon || border.size() > 8)) {
+ if (is_polygon) {
+ // This is a polygon, add the separate edges with the center.
+ for (int j=0; j int {
+ assert(std::abs(offset) < border_size);
+ int out = idx+offset;
+ if (out >= border_size)
+ out = out - border_size;
+ else if (out < 0)
+ out = border_size + out;
+
+ return out;
+ };
+
+ // First calculate angles at all the vertices.
+ angles.clear();
+ lengths.clear();
+ int first_different_angle_idx = 0;
+ for (int i=0; i M_PI)
+ angle = 2*M_PI - angle;
+
+ angles.push_back(angle);
+ lengths.push_back(v2.norm());
+ if (first_different_angle_idx == 0 && angles.size() > 1) {
+ if (! are_angles_same(angles.back(), angles[angles.size()-2]))
+ first_different_angle_idx = angles.size()-1;
+ }
+ }
+ assert(border.size() == angles.size());
+ assert(border.size() == lengths.size());
+
+ // First go around the border and pick what might be circular segments.
+ // Save pair of indices to where such potential segments start and end.
+ // Also remember the length of these segments.
+ int start_idx = -1;
+ bool circle = false;
+ bool first_iter = true;
+ std::vector circles;
+ std::vector edges;
+ std::vector> circles_idxs;
+ //std::vector circles_lengths;
+ std::vector single_circle; // could be in loop-scope, but reallocations
+ double single_circle_length = 0.;
+ int first_pt_idx = offset_to_index(first_different_angle_idx, 1);
+ int i = first_pt_idx;
+ while (i != first_pt_idx || first_iter) {
+ if (are_angles_same(angles[i], angles[offset_to_index(i,-1)])
+ && i != offset_to_index(first_pt_idx, -1) // not the last point
+ && i != start_idx ) {
+ // circle
+ if (! circle) {
+ circle = true;
+ single_circle.clear();
+ single_circle_length = 0.;
+ start_idx = offset_to_index(i, -2);
+ single_circle = { border[start_idx], border[offset_to_index(start_idx,1)] };
+ single_circle_length += lengths[offset_to_index(i, -1)];
+ }
+ single_circle.emplace_back(border[i]);
+ single_circle_length += lengths[i];
+ } else {
+ if (circle && single_circle.size() >= 5) { // Less than 5 vertices? Not a circle.
+ single_circle.emplace_back(border[i]);
+ single_circle_length += lengths[i];
+
+ bool accept_circle = true;
+ {
+ // Check that lengths of internal (!!!) edges match.
+ int j = offset_to_index(start_idx, 3);
+ while (j != i) {
+ if (! are_lengths_same(lengths[offset_to_index(j,-1)], lengths[j])) {
+ accept_circle = false;
+ break;
+ }
+ j = offset_to_index(j, 1);
+ }
+ }
+
+ if (accept_circle) {
+ const auto& [center, radius, err] = get_center_and_radius(single_circle, trafo, trafo_inv);
+
+ // Check that the fit went well. The tolerance is high, only to
+ // reject complete failures.
+ accept_circle &= err < 0.05;
+
+ // If the segment subtends less than 90 degrees, throw it away.
+ accept_circle &= single_circle_length / radius > 0.9*M_PI/2.;
+
+ if (accept_circle) {
+ // Add the circle and remember indices into borders.
+ circles_idxs.emplace_back(start_idx, i);
+ circles.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius));
+ }
+ }
+ }
+ circle = false;
+ }
+ // Take care of the wrap around.
+ first_iter = false;
+ i = offset_to_index(i, 1);
+ }
+
+ // We have the circles. Now go around again and pick edges, while jumping over circles.
+ if (circles_idxs.empty()) {
+ // Just add all edges.
+ for (int i=1; i 1 || circles_idxs.front().first != circles_idxs.front().second) {
+ // There is at least one circular segment. Start at its end and add edges until the start of the next one.
+ int i = circles_idxs.front().second;
+ int circle_idx = 1;
+ while (true) {
+ i = offset_to_index(i, 1);
+ edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[offset_to_index(i,-1)], border[i]));
+ if (circle_idx < int(circles_idxs.size()) && i == circles_idxs[circle_idx].first) {
+ i = circles_idxs[circle_idx].second;
+ ++circle_idx;
+ }
+ if (i == circles_idxs.front().first)
+ break;
+ }
+ }
+
+ // Merge adjacent edges where needed.
+ assert(std::all_of(edges.begin(), edges.end(),
+ [](const SurfaceFeature& f) { return f.get_type() == SurfaceFeatureType::Edge; }));
+ for (int i=edges.size()-1; i>=0; --i) {
+ const auto& [first_start, first_end] = edges[i==0 ? edges.size()-1 : i-1].get_edge();
+ const auto& [second_start, second_end] = edges[i].get_edge();
+
+ if (Slic3r::is_approx(first_end, second_start)
+ && Slic3r::is_approx((first_end-first_start).normalized().dot((second_end-second_start).normalized()), 1.)) {
+ // The edges have the same direction and share a point. Merge them.
+ edges[i==0 ? edges.size()-1 : i-1] = SurfaceFeature(SurfaceFeatureType::Edge, first_start, second_end);
+ edges.erase(edges.begin() + i);
+ }
+ }
+
+ // Now move the circles and edges into the feature list for the plane.
+ assert(std::all_of(circles.begin(), circles.end(), [](const SurfaceFeature& f) {
+ return f.get_type() == SurfaceFeatureType::Circle;
+ }));
+ assert(std::all_of(edges.begin(), edges.end(), [](const SurfaceFeature& f) {
+ return f.get_type() == SurfaceFeatureType::Edge;
+ }));
+ plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(circles.begin()),
+ std::make_move_iterator(circles.end()));
+ plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(edges.begin()),
+ std::make_move_iterator(edges.end()));
+ }
+ }
+
+ // The last surface feature is the plane itself.
+ Vec3d cog = Vec3d::Zero();
+ size_t counter = 0;
+ for (const std::vector& b : plane.borders) {
+ for (size_t i = 1; i < b.size(); ++i) {
+ cog += b[i];
+ ++counter;
+ }
+ }
+ cog /= double(counter);
+ plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Plane,
+ plane.normal, cog, std::optional(), plane_idx + 0.0001));
+
+ plane.borders.clear();
+ plane.borders.shrink_to_fit();
+
+ plane.features_extracted = true;
+}
+
+std::optional MeasuringImpl::get_feature(size_t face_idx, const Vec3d& point)
+{
+ if (face_idx >= m_face_to_plane.size())
+ return std::optional();
+
+ const PlaneData& plane = m_planes[m_face_to_plane[face_idx]];
+
+ if (! plane.features_extracted)
+ extract_features(m_face_to_plane[face_idx]);
+
+ size_t closest_feature_idx = size_t(-1);
+ double min_dist = std::numeric_limits::max();
+
+ MeasurementResult res;
+ SurfaceFeature point_sf(point);
+
+ assert(plane.surface_features.empty() || plane.surface_features.back().get_type() == SurfaceFeatureType::Plane);
+
+ for (size_t i=0; idist;
+ if (dist < feature_hover_limit && dist < min_dist) {
+ min_dist = std::min(dist, min_dist);
+ closest_feature_idx = i;
+ }
+ }
+ }
+
+ if (closest_feature_idx != size_t(-1)) {
+ const SurfaceFeature& f = plane.surface_features[closest_feature_idx];
+ if (f.get_type() == SurfaceFeatureType::Edge) {
+ // If this is an edge, check if we are not close to the endpoint. If so,
+ // we will include the endpoint as well. Close = 10% of the lenghth of
+ // the edge, clamped between 0.025 and 0.5 mm.
+ const auto& [sp, ep] = f.get_edge();
+ double len_sq = (ep-sp).squaredNorm();
+ double limit_sq = std::max(0.025*0.025, std::min(0.5*0.5, 0.1 * 0.1 * len_sq));
+
+ if ((point-sp).squaredNorm() < limit_sq)
+ return std::make_optional(SurfaceFeature(sp));
+ if ((point-ep).squaredNorm() < limit_sq)
+ return std::make_optional(SurfaceFeature(ep));
+ }
+ return std::make_optional(f);
+ }
+
+ // Nothing detected, return the plane as a whole.
+ assert(plane.surface_features.back().get_type() == SurfaceFeatureType::Plane);
+ return std::make_optional(plane.surface_features.back());
+}
+
+
+
+
+
+int MeasuringImpl::get_num_of_planes() const
+{
+ return (m_planes.size());
+}
+
+
+
+const std::vector& MeasuringImpl::get_plane_triangle_indices(int idx) const
+{
+ assert(idx >= 0 && idx < int(m_planes.size()));
+ return m_planes[idx].facets;
+}
+
+const std::vector& MeasuringImpl::get_plane_features(unsigned int plane_id)
+{
+ assert(plane_id < m_planes.size());
+ if (! m_planes[plane_id].features_extracted)
+ extract_features(plane_id);
+ return m_planes[plane_id].surface_features;
+}
+
+const indexed_triangle_set& MeasuringImpl::get_its() const
+{
+ return this->m_its;
+}
+
+Measuring::Measuring(const indexed_triangle_set& its)
+: priv{std::make_unique(its)}
+{}
+
+Measuring::~Measuring() {}
+
+
+
+std::optional Measuring::get_feature(size_t face_idx, const Vec3d& point) const
+{
+ return priv->get_feature(face_idx, point);
+}
+
+
+int Measuring::get_num_of_planes() const
+{
+ return priv->get_num_of_planes();
+}
+
+
+const std::vector& Measuring::get_plane_triangle_indices(int idx) const
+{
+ return priv->get_plane_triangle_indices(idx);
+}
+
+const std::vector& Measuring::get_plane_features(unsigned int plane_id) const
+{
+ return priv->get_plane_features(plane_id);
+}
+
+const indexed_triangle_set& Measuring::get_its() const
+{
+ return priv->get_its();
+}
+
+const AngleAndEdges AngleAndEdges::Dummy = { 0.0, Vec3d::Zero(), { Vec3d::Zero(), Vec3d::Zero() }, { Vec3d::Zero(), Vec3d::Zero() }, 0.0, true };
+
+static AngleAndEdges angle_edge_edge(const std::pair& e1, const std::pair& e2)
+{
+ if (are_parallel(e1, e2))
+ return AngleAndEdges::Dummy;
+
+ Vec3d e1_unit = edge_direction(e1.first, e1.second);
+ Vec3d e2_unit = edge_direction(e2.first, e2.second);
+
+ // project edges on the plane defined by them
+ Vec3d normal = e1_unit.cross(e2_unit).normalized();
+ const Eigen::Hyperplane plane(normal, e1.first);
+ Vec3d e11_proj = plane.projection(e1.first);
+ Vec3d e12_proj = plane.projection(e1.second);
+ Vec3d e21_proj = plane.projection(e2.first);
+ Vec3d e22_proj = plane.projection(e2.second);
+
+ const bool coplanar = (e2.first - e21_proj).norm() < EPSILON && (e2.second - e22_proj).norm() < EPSILON;
+
+ // rotate the plane to become the XY plane
+ auto qp = Eigen::Quaternion::FromTwoVectors(normal, Vec3d::UnitZ());
+ auto qp_inverse = qp.inverse();
+ const Vec3d e11_rot = qp * e11_proj;
+ const Vec3d e12_rot = qp * e12_proj;
+ const Vec3d e21_rot = qp * e21_proj;
+ const Vec3d e22_rot = qp * e22_proj;
+
+ // discard Z
+ const Vec2d e11_rot_2d = Vec2d(e11_rot.x(), e11_rot.y());
+ const Vec2d e12_rot_2d = Vec2d(e12_rot.x(), e12_rot.y());
+ const Vec2d e21_rot_2d = Vec2d(e21_rot.x(), e21_rot.y());
+ const Vec2d e22_rot_2d = Vec2d(e22_rot.x(), e22_rot.y());
+
+ // find intersection (arc center) of edges in XY plane
+ const Eigen::Hyperplane e1_rot_2d_line = Eigen::Hyperplane::Through(e11_rot_2d, e12_rot_2d);
+ const Eigen::Hyperplane e2_rot_2d_line = Eigen::Hyperplane::Through(e21_rot_2d, e22_rot_2d);
+ const Vec2d center_rot_2d = e1_rot_2d_line.intersection(e2_rot_2d_line);
+
+ // arc center in original coordinate
+ const Vec3d center = qp_inverse * Vec3d(center_rot_2d.x(), center_rot_2d.y(), e11_rot.z());
+
+ // ensure the edges are pointing away from the center
+ std::pair out_e1 = e1;
+ std::pair out_e2 = e2;
+ if ((center_rot_2d - e11_rot_2d).squaredNorm() > (center_rot_2d - e12_rot_2d).squaredNorm()) {
+ std::swap(e11_proj, e12_proj);
+ std::swap(out_e1.first, out_e1.second);
+ e1_unit = -e1_unit;
+ }
+ if ((center_rot_2d - e21_rot_2d).squaredNorm() > (center_rot_2d - e22_rot_2d).squaredNorm()) {
+ std::swap(e21_proj, e22_proj);
+ std::swap(out_e2.first, out_e2.second);
+ e2_unit = -e2_unit;
+ }
+
+ // arc angle
+ const double angle = std::acos(std::clamp(e1_unit.dot(e2_unit), -1.0, 1.0));
+ // arc radius
+ const Vec3d e1_proj_mid = 0.5 * (e11_proj + e12_proj);
+ const Vec3d e2_proj_mid = 0.5 * (e21_proj + e22_proj);
+ const double radius = std::min((center - e1_proj_mid).norm(), (center - e2_proj_mid).norm());
+
+ return { angle, center, out_e1, out_e2, radius, coplanar };
+}
+
+static AngleAndEdges angle_edge_plane(const std::pair& e, const std::tuple& p)
+{
+ const auto& [idx, normal, origin] = p;
+ Vec3d e1e2_unit = edge_direction(e);
+ if (are_perpendicular(e1e2_unit, normal))
+ return AngleAndEdges::Dummy;
+
+ // ensure the edge is pointing away from the intersection
+ // 1st calculate instersection between edge and plane
+ const Eigen::Hyperplane plane(normal, origin);
+ const Eigen::ParametrizedLine line = Eigen::ParametrizedLine::Through(e.first, e.second);
+ const Vec3d inters = line.intersectionPoint(plane);
+
+ // then verify edge direction and revert it, if needed
+ Vec3d e1 = e.first;
+ Vec3d e2 = e.second;
+ if ((e1 - inters).squaredNorm() > (e2 - inters).squaredNorm()) {
+ std::swap(e1, e2);
+ e1e2_unit = -e1e2_unit;
+ }
+
+ if (are_parallel(e1e2_unit, normal)) {
+ const std::array basis = orthonormal_basis(e1e2_unit);
+ const double radius = (0.5 * (e1 + e2) - inters).norm();
+ const Vec3d edge_on_plane_dir = (basis[1].dot(origin - inters) >= 0.0) ? basis[1] : -basis[1];
+ std::pair edge_on_plane = std::make_pair(inters, inters + radius * edge_on_plane_dir);
+ if (!inters.isApprox(e1)) {
+ edge_on_plane.first += radius * edge_on_plane_dir;
+ edge_on_plane.second += radius * edge_on_plane_dir;
+ }
+ return AngleAndEdges(0.5 * double(PI), inters, std::make_pair(e1, e2), edge_on_plane, radius, inters.isApprox(e1));
+ }
+
+ const Vec3d e1e2 = e2 - e1;
+ const double e1e2_len = e1e2.norm();
+
+ // calculate 2nd edge (on the plane)
+ const Vec3d temp = normal.cross(e1e2);
+ const Vec3d edge_on_plane_unit = normal.cross(temp).normalized();
+ std::pair edge_on_plane = { origin, origin + e1e2_len * edge_on_plane_unit };
+
+ // ensure the 2nd edge is pointing in the correct direction
+ const Vec3d test_edge = (edge_on_plane.second - edge_on_plane.first).cross(e1e2);
+ if (test_edge.dot(temp) < 0.0)
+ edge_on_plane = { origin, origin - e1e2_len * edge_on_plane_unit };
+
+ AngleAndEdges ret = angle_edge_edge({ e1, e2 }, edge_on_plane);
+ ret.radius = (inters - 0.5 * (e1 + e2)).norm();
+ return ret;
+}
+
+static AngleAndEdges angle_plane_plane(const std::tuple& p1, const std::tuple& p2)
+{
+ const auto& [idx1, normal1, origin1] = p1;
+ const auto& [idx2, normal2, origin2] = p2;
+
+ // are planes parallel ?
+ if (are_parallel(normal1, normal2))
+ return AngleAndEdges::Dummy;
+
+ auto intersection_plane_plane = [](const Vec3d& n1, const Vec3d& o1, const Vec3d& n2, const Vec3d& o2) {
+ Eigen::MatrixXd m(2, 3);
+ m << n1.x(), n1.y(), n1.z(), n2.x(), n2.y(), n2.z();
+ Eigen::VectorXd b(2);
+ b << o1.dot(n1), o2.dot(n2);
+ Eigen::VectorXd x = m.colPivHouseholderQr().solve(b);
+ return std::make_pair(n1.cross(n2).normalized(), Vec3d(x(0), x(1), x(2)));
+ };
+
+ // Calculate intersection line between planes
+ const auto [intersection_line_direction, intersection_line_origin] = intersection_plane_plane(normal1, origin1, normal2, origin2);
+
+ // Project planes' origin on intersection line
+ const Eigen::ParametrizedLine intersection_line = Eigen::ParametrizedLine(intersection_line_origin, intersection_line_direction);
+ const Vec3d origin1_proj = intersection_line.projection(origin1);
+ const Vec3d origin2_proj = intersection_line.projection(origin2);
+
+ // Calculate edges on planes
+ const Vec3d edge_on_plane1_unit = (origin1 - origin1_proj).normalized();
+ const Vec3d edge_on_plane2_unit = (origin2 - origin2_proj).normalized();
+ const double radius = std::max(10.0, std::max((origin1 - origin1_proj).norm(), (origin2 - origin2_proj).norm()));
+ const std::pair edge_on_plane1 = { origin1_proj + radius * edge_on_plane1_unit, origin1_proj + 2.0 * radius * edge_on_plane1_unit };
+ const std::pair edge_on_plane2 = { origin2_proj + radius * edge_on_plane2_unit, origin2_proj + 2.0 * radius * edge_on_plane2_unit };
+
+ AngleAndEdges ret = angle_edge_edge(edge_on_plane1, edge_on_plane2);
+ ret.radius = radius;
+ return ret;
+}
+
+MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& b, const Measuring* measuring)
+{
+ assert(a.get_type() != SurfaceFeatureType::Undef && b.get_type() != SurfaceFeatureType::Undef);
+
+ const bool swap = int(a.get_type()) > int(b.get_type());
+ const SurfaceFeature& f1 = swap ? b : a;
+ const SurfaceFeature& f2 = swap ? a : b;
+
+ MeasurementResult result;
+
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ if (f1.get_type() == SurfaceFeatureType::Point) {
+ if (f2.get_type() == SurfaceFeatureType::Point) {
+ Vec3d diff = (f2.get_point() - f1.get_point());
+ result.distance_strict = std::make_optional(DistAndPoints{diff.norm(), f1.get_point(), f2.get_point()});
+ result.distance_xyz = diff.cwiseAbs();
+
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Edge) {
+ const auto [s,e] = f2.get_edge();
+ const Eigen::ParametrizedLine line(s, (e-s).normalized());
+ const double dist_inf = line.distance(f1.get_point());
+ const Vec3d proj = line.projection(f1.get_point());
+ const double len_sq = (e-s).squaredNorm();
+ const double dist_start_sq = (proj-s).squaredNorm();
+ const double dist_end_sq = (proj-e).squaredNorm();
+ if (dist_start_sq < len_sq && dist_end_sq < len_sq) {
+ // projection falls on the line - the strict distance is the same as infinite
+ result.distance_strict = std::make_optional(DistAndPoints{dist_inf, f1.get_point(), proj});
+ } else { // the result is the closer of the endpoints
+ const bool s_is_closer = dist_start_sq < dist_end_sq;
+ result.distance_strict = std::make_optional(DistAndPoints{std::sqrt(std::min(dist_start_sq, dist_end_sq) + sqr(dist_inf)), f1.get_point(), s_is_closer ? s : e});
+ }
+ result.distance_infinite = std::make_optional(DistAndPoints{dist_inf, f1.get_point(), proj});
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Circle) {
+ // Find a plane containing normal, center and the point.
+ const auto [c, radius, n] = f2.get_circle();
+ const Eigen::Hyperplane circle_plane(n, c);
+ const Vec3d proj = circle_plane.projection(f1.get_point());
+ if (proj.isApprox(c)) {
+ const Vec3d p_on_circle = c + radius * get_orthogonal(n, true);
+ result.distance_strict = std::make_optional(DistAndPoints{ radius, c, p_on_circle });
+ }
+ else {
+ const Eigen::Hyperplane circle_plane(n, c);
+ const Vec3d proj = circle_plane.projection(f1.get_point());
+ const double dist = std::sqrt(std::pow((proj - c).norm() - radius, 2.) +
+ (f1.get_point() - proj).squaredNorm());
+
+ const Vec3d p_on_circle = c + radius * (proj - c).normalized();
+ result.distance_strict = std::make_optional(DistAndPoints{ dist, f1.get_point(), p_on_circle }); // TODO
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Plane) {
+ const auto [idx, normal, pt] = f2.get_plane();
+ Eigen::Hyperplane plane(normal, pt);
+ result.distance_infinite = std::make_optional(DistAndPoints{plane.absDistance(f1.get_point()), f1.get_point(), plane.projection(f1.get_point())}); // TODO
+ // TODO: result.distance_strict =
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ }
+ else if (f1.get_type() == SurfaceFeatureType::Edge) {
+ if (f2.get_type() == SurfaceFeatureType::Edge) {
+ std::vector distances;
+
+ auto add_point_edge_distance = [&distances](const Vec3d& v, const std::pair& e) {
+ const MeasurementResult res = get_measurement(SurfaceFeature(v), SurfaceFeature(SurfaceFeatureType::Edge, e.first, e.second));
+ double distance = res.distance_strict->dist;
+ Vec3d v2 = res.distance_strict->to;
+
+ const Vec3d e1e2 = e.second - e.first;
+ const Vec3d e1v2 = v2 - e.first;
+ if (e1v2.dot(e1e2) >= 0.0 && e1v2.norm() < e1e2.norm())
+ distances.emplace_back(distance, v, v2);
+ };
+
+ std::pair e1 = f1.get_edge();
+ std::pair e2 = f2.get_edge();
+
+ distances.emplace_back((e2.first - e1.first).norm(), e1.first, e2.first);
+ distances.emplace_back((e2.second - e1.first).norm(), e1.first, e2.second);
+ distances.emplace_back((e2.first - e1.second).norm(), e1.second, e2.first);
+ distances.emplace_back((e2.second - e1.second).norm(), e1.second, e2.second);
+ add_point_edge_distance(e1.first, e2);
+ add_point_edge_distance(e1.second, e2);
+ add_point_edge_distance(e2.first, e1);
+ add_point_edge_distance(e2.second, e1);
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(*it);
+
+ result.angle = angle_edge_edge(f1.get_edge(), f2.get_edge());
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Circle) {
+ const std::pair e = f1.get_edge();
+ const auto& [center, radius, normal] = f2.get_circle();
+ const Vec3d e1e2 = (e.second - e.first);
+ const Vec3d e1e2_unit = e1e2.normalized();
+
+ std::vector distances;
+ distances.emplace_back(*get_measurement(SurfaceFeature(e.first), f2).distance_strict);
+ distances.emplace_back(*get_measurement(SurfaceFeature(e.second), f2).distance_strict);
+
+ const Eigen::Hyperplane plane(e1e2_unit, center);
+ const Eigen::ParametrizedLine line = Eigen::ParametrizedLine::Through(e.first, e.second);
+ const Vec3d inter = line.intersectionPoint(plane);
+ const Vec3d e1inter = inter - e.first;
+ if (e1inter.dot(e1e2) >= 0.0 && e1inter.norm() < e1e2.norm())
+ distances.emplace_back(*get_measurement(SurfaceFeature(inter), f2).distance_strict);
+
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{it->dist, it->from, it->to});
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Plane) {
+ assert(measuring != nullptr);
+
+ const auto [from, to] = f1.get_edge();
+ const auto [idx, normal, origin] = f2.get_plane();
+
+ const Vec3d edge_unit = (to - from).normalized();
+ if (are_perpendicular(edge_unit, normal)) {
+ std::vector distances;
+ const Eigen::Hyperplane plane(normal, origin);
+ distances.push_back(DistAndPoints{ plane.absDistance(from), from, plane.projection(from) });
+ distances.push_back(DistAndPoints{ plane.absDistance(to), to, plane.projection(to) });
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to });
+ }
+ else {
+ const std::vector& plane_features = measuring->get_plane_features(idx);
+ std::vector distances;
+ for (const SurfaceFeature& sf : plane_features) {
+ if (sf.get_type() == SurfaceFeatureType::Edge) {
+ const auto m = get_measurement(sf, f1);
+ if (!m.distance_infinite.has_value()) {
+ distances.clear();
+ break;
+ }
+ else
+ distances.push_back(*m.distance_infinite);
+ }
+ }
+ if (!distances.empty()) {
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to });
+ }
+ }
+ result.angle = angle_edge_plane(f1.get_edge(), f2.get_plane());
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f1.get_type() == SurfaceFeatureType::Circle) {
+ if (f2.get_type() == SurfaceFeatureType::Circle) {
+ const auto [c0, r0, n0] = f1.get_circle();
+ const auto [c1, r1, n1] = f2.get_circle();
+
+ // The following code is an adaptation of the algorithm found in:
+ // https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/DistCircle3Circle3.h
+ // and described in:
+ // https://www.geometrictools.com/Documentation/DistanceToCircle3.pdf
+
+ struct ClosestInfo
+ {
+ double sqrDistance{ 0.0 };
+ Vec3d circle0Closest{ Vec3d::Zero() };
+ Vec3d circle1Closest{ Vec3d::Zero() };
+
+ inline bool operator < (const ClosestInfo& other) const { return sqrDistance < other.sqrDistance; }
+ };
+ std::array candidates{};
+
+ const double zero = 0.0;
+
+ const Vec3d D = c1 - c0;
+
+ if (!are_parallel(n0, n1)) {
+ // Get parameters for constructing the degree-8 polynomial phi.
+ const double one = 1.0;
+ const double two = 2.0;
+ const double r0sqr = sqr(r0);
+ const double r1sqr = sqr(r1);
+
+ // Compute U1 and V1 for the plane of circle1.
+ const std::array basis = orthonormal_basis(n1);
+ const Vec3d U1 = basis[0];
+ const Vec3d V1 = basis[1];
+
+ // Construct the polynomial phi(cos(theta)).
+ const Vec3d N0xD = n0.cross(D);
+ const Vec3d N0xU1 = n0.cross(U1);
+ const Vec3d N0xV1 = n0.cross(V1);
+ const double a0 = r1 * D.dot(U1);
+ const double a1 = r1 * D.dot(V1);
+ const double a2 = N0xD.dot(N0xD);
+ const double a3 = r1 * N0xD.dot(N0xU1);
+ const double a4 = r1 * N0xD.dot(N0xV1);
+ const double a5 = r1sqr * N0xU1.dot(N0xU1);
+ const double a6 = r1sqr * N0xU1.dot(N0xV1);
+ const double a7 = r1sqr * N0xV1.dot(N0xV1);
+ Polynomial1 p0{ a2 + a7, two * a3, a5 - a7 };
+ Polynomial1 p1{ two * a4, two * a6 };
+ Polynomial1 p2{ zero, a1 };
+ Polynomial1 p3{ -a0 };
+ Polynomial1 p4{ -a6, a4, two * a6 };
+ Polynomial1 p5{ -a3, a7 - a5 };
+ Polynomial1 tmp0{ one, zero, -one };
+ Polynomial1 tmp1 = p2 * p2 + tmp0 * p3 * p3;
+ Polynomial1 tmp2 = two * p2 * p3;
+ Polynomial1 tmp3 = p4 * p4 + tmp0 * p5 * p5;
+ Polynomial1 tmp4 = two * p4 * p5;
+ Polynomial1 p6 = p0 * tmp1 + tmp0 * p1 * tmp2 - r0sqr * tmp3;
+ Polynomial1 p7 = p0 * tmp2 + p1 * tmp1 - r0sqr * tmp4;
+
+ // Parameters for polynomial root finding. The roots[] array
+ // stores the roots. We need only the unique ones, which is
+ // the responsibility of the set uniqueRoots. The pairs[]
+ // array stores the (cosine,sine) information mentioned in the
+ // PDF. TODO: Choose the maximum number of iterations for root
+ // finding based on specific polynomial data?
+ const uint32_t maxIterations = 128;
+ int32_t degree = 0;
+ size_t numRoots = 0;
+ std::array roots{};
+ std::set uniqueRoots{};
+ size_t numPairs = 0;
+ std::array, 16> pairs{};
+ double temp = zero;
+ double sn = zero;
+
+ if (p7.GetDegree() > 0 || p7[0] != zero) {
+ // H(cs,sn) = p6(cs) + sn * p7(cs)
+ Polynomial1 phi = p6 * p6 - tmp0 * p7 * p7;
+ degree = static_cast(phi.GetDegree());
+ assert(degree > 0);
+ numRoots = RootsPolynomial::Find(degree, &phi[0], maxIterations, roots.data());
+ for (size_t i = 0; i < numRoots; ++i) {
+ uniqueRoots.insert(roots[i]);
+ }
+
+ for (auto const& cs : uniqueRoots) {
+ if (std::fabs(cs) <= one) {
+ temp = p7(cs);
+ if (temp != zero) {
+ sn = -p6(cs) / temp;
+ pairs[numPairs++] = std::make_pair(cs, sn);
+ }
+ else {
+ temp = std::max(one - sqr(cs), zero);
+ sn = std::sqrt(temp);
+ pairs[numPairs++] = std::make_pair(cs, sn);
+ if (sn != zero)
+ pairs[numPairs++] = std::make_pair(cs, -sn);
+ }
+ }
+ }
+ }
+ else {
+ // H(cs,sn) = p6(cs)
+ degree = static_cast(p6.GetDegree());
+ assert(degree > 0);
+ numRoots = RootsPolynomial::Find(degree, &p6[0], maxIterations, roots.data());
+ for (size_t i = 0; i < numRoots; ++i) {
+ uniqueRoots.insert(roots[i]);
+ }
+
+ for (auto const& cs : uniqueRoots) {
+ if (std::fabs(cs) <= one) {
+ temp = std::max(one - sqr(cs), zero);
+ sn = std::sqrt(temp);
+ pairs[numPairs++] = std::make_pair(cs, sn);
+ if (sn != zero)
+ pairs[numPairs++] = std::make_pair(cs, -sn);
+ }
+ }
+ }
+
+ for (size_t i = 0; i < numPairs; ++i) {
+ ClosestInfo& info = candidates[i];
+ Vec3d delta = D + r1 * (pairs[i].first * U1 + pairs[i].second * V1);
+ info.circle1Closest = c0 + delta;
+ const double N0dDelta = n0.dot(delta);
+ const double lenN0xDelta = n0.cross(delta).norm();
+ if (lenN0xDelta > 0.0) {
+ const double diff = lenN0xDelta - r0;
+ info.sqrDistance = sqr(N0dDelta) + sqr(diff);
+ delta -= N0dDelta * n0;
+ delta.normalize();
+ info.circle0Closest = c0 + r0 * delta;
+ }
+ else {
+ const Vec3d r0U0 = r0 * get_orthogonal(n0, true);
+ const Vec3d diff = delta - r0U0;
+ info.sqrDistance = diff.dot(diff);
+ info.circle0Closest = c0 + r0U0;
+ }
+ }
+
+ std::sort(candidates.begin(), candidates.begin() + numPairs);
+ }
+ else {
+ ClosestInfo& info = candidates[0];
+
+ const double N0dD = n0.dot(D);
+ const Vec3d normProj = N0dD * n0;
+ const Vec3d compProj = D - normProj;
+ Vec3d U = compProj;
+ const double d = U.norm();
+ U.normalize();
+
+ // The configuration is determined by the relative location of the
+ // intervals of projection of the circles on to the D-line.
+ // Circle0 projects to [-r0,r0] and circle1 projects to
+ // [d-r1,d+r1].
+ const double dmr1 = d - r1;
+ double distance;
+ if (dmr1 >= r0) {
+ // d >= r0 + r1
+ // The circles are separated (d > r0 + r1) or tangent with one
+ // outside the other (d = r0 + r1).
+ distance = dmr1 - r0;
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 - r1 * U;
+ }
+ else {
+ // d < r0 + r1
+ // The cases implicitly use the knowledge that d >= 0.
+ const double dpr1 = d + r1;
+ if (dpr1 <= r0) {
+ // Circle1 is inside circle0.
+ distance = r0 - dpr1;
+ if (d > 0.0) {
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 + r1 * U;
+ }
+ else {
+ // The circles are concentric, so U = (0,0,0).
+ // Construct a vector perpendicular to N0 to use for
+ // closest points.
+ U = get_orthogonal(n0, true);
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 + r1 * U;
+ }
+ }
+ else if (dmr1 <= -r0) {
+ // Circle0 is inside circle1.
+ distance = -r0 - dmr1;
+ if (d > 0.0) {
+ info.circle0Closest = c0 - r0 * U;
+ info.circle1Closest = c1 - r1 * U;
+ }
+ else {
+ // The circles are concentric, so U = (0,0,0).
+ // Construct a vector perpendicular to N0 to use for
+ // closest points.
+ U = get_orthogonal(n0, true);
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 + r1 * U;
+ }
+ }
+ else {
+ distance = (c1 - c0).norm();
+ info.circle0Closest = c0;
+ info.circle1Closest = c1;
+ }
+ }
+
+ info.sqrDistance = distance * distance + N0dD * N0dD;
+ }
+
+ result.distance_infinite = std::make_optional(DistAndPoints{ std::sqrt(candidates[0].sqrDistance), candidates[0].circle0Closest, candidates[0].circle1Closest }); // TODO
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Plane) {
+ assert(measuring != nullptr);
+
+ const auto [center, radius, normal1] = f1.get_circle();
+ const auto [idx2, normal2, origin2] = f2.get_plane();
+
+ const bool coplanar = are_parallel(normal1, normal2) && Eigen::Hyperplane(normal1, center).absDistance(origin2) < EPSILON;
+ if (!coplanar) {
+ const std::vector& plane_features = measuring->get_plane_features(idx2);
+ std::vector distances;
+ for (const SurfaceFeature& sf : plane_features) {
+ if (sf.get_type() == SurfaceFeatureType::Edge) {
+ const auto m = get_measurement(sf, f1);
+ if (!m.distance_infinite.has_value()) {
+ distances.clear();
+ break;
+ }
+ else
+ distances.push_back(*m.distance_infinite);
+ }
+ }
+ if (!distances.empty()) {
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to });
+ }
+ }
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f1.get_type() == SurfaceFeatureType::Plane) {
+ const auto [idx1, normal1, pt1] = f1.get_plane();
+ const auto [idx2, normal2, pt2] = f2.get_plane();
+
+ if (are_parallel(normal1, normal2)) {
+ // The planes are parallel, calculate distance.
+ const Eigen::Hyperplane plane(normal1, pt1);
+ result.distance_infinite = std::make_optional(DistAndPoints{ plane.absDistance(pt2), pt2, plane.projection(pt2) }); // TODO
+ }
+ else
+ result.angle = angle_plane_plane(f1.get_plane(), f2.get_plane());
+ }
+
+ return result;
+}
+
+void SurfaceFeature::translate(const Vec3d& displacement) {
+ switch (get_type()) {
+ case Measure::SurfaceFeatureType::Point: {
+ m_pt1 = m_pt1 + displacement;
+ break;
+ }
+ case Measure::SurfaceFeatureType::Edge: {
+ m_pt1 = m_pt1 + displacement;
+ m_pt2 = m_pt2 + displacement;
+ if (m_pt3.has_value()) { //extra_point()
+ m_pt3 = *m_pt3 + displacement;
+ }
+ break;
+ }
+ case Measure::SurfaceFeatureType::Plane: {
+ //m_pt1 is normal;
+ m_pt2 = m_pt2 + displacement;
+ break;
+ }
+ case Measure::SurfaceFeatureType::Circle: {
+ m_pt1 = m_pt1 + displacement;
+ // m_pt2 is normal;
+ break;
+ }
+ default: break;
+ }
+}
+
+}} // namespace Slic3r
+
diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp
new file mode 100644
index 000000000..823d2c757
--- /dev/null
+++ b/src/libslic3r/Measure.hpp
@@ -0,0 +1,195 @@
+///|/ Copyright (c) Prusa Research 2022 - 2023 Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
+#ifndef Slic3r_Measure_hpp_
+#define Slic3r_Measure_hpp_
+
+#include
+#include
+
+#include "Point.hpp"
+
+struct indexed_triangle_set;
+
+namespace Slic3r {
+
+class TriangleMesh;
+
+namespace Measure {
+enum class SurfaceFeatureType : int {
+ Undef = 0,
+ Point = 1 << 0,
+ Edge = 1 << 1,
+ Circle = 1 << 2,
+ Plane = 1 << 3
+};
+
+class SurfaceFeature {
+public:
+ SurfaceFeature(SurfaceFeatureType type, const Vec3d& pt1, const Vec3d& pt2, std::optional pt3 = std::nullopt, double value = 0.0)
+ : m_type(type), m_pt1(pt1), m_pt2(pt2), m_pt3(pt3), m_value(value) {}
+
+ explicit SurfaceFeature(const Vec3d& pt)
+ : m_type{SurfaceFeatureType::Point}, m_pt1{pt} {}
+
+ void translate(const Vec3d& displacement);
+ // Get type of this feature.
+ SurfaceFeatureType get_type() const { return m_type; }
+
+ // For points, return the point.
+ Vec3d get_point() const { assert(m_type == SurfaceFeatureType::Point); return m_pt1; }
+ // For edges, return start and end.
+ std::pair get_edge() const { assert(m_type == SurfaceFeatureType::Edge); return std::make_pair(m_pt1, m_pt2); }
+
+ // For circles, return center, radius and normal.
+ std::tuple get_circle() const { assert(m_type == SurfaceFeatureType::Circle); return std::make_tuple(m_pt1, m_value, m_pt2); }
+
+ // For planes, return index into vector provided by Measuring::get_plane_triangle_indices, normal and point.
+ std::tuple get_plane() const { assert(m_type == SurfaceFeatureType::Plane); return std::make_tuple(int(m_value), m_pt1, m_pt2); }
+
+ // For anything, return an extra point that should also be considered a part of this.
+ std::optional get_extra_point() const { assert(m_type != SurfaceFeatureType::Undef); return m_pt3; }
+
+ bool operator == (const SurfaceFeature& other) const {
+ if (this->m_type != other.m_type) return false;
+ switch (this->m_type)
+ {
+ case SurfaceFeatureType::Undef: { break; }
+ case SurfaceFeatureType::Point: { return (this->m_pt1.isApprox(other.m_pt1)); }
+ case SurfaceFeatureType::Edge: {
+ return (this->m_pt1.isApprox(other.m_pt1) && this->m_pt2.isApprox(other.m_pt2)) ||
+ (this->m_pt1.isApprox(other.m_pt2) && this->m_pt2.isApprox(other.m_pt1));
+ }
+ case SurfaceFeatureType::Plane:
+ case SurfaceFeatureType::Circle: {
+ return (this->m_pt1.isApprox(other.m_pt1) && this->m_pt2.isApprox(other.m_pt2) && std::abs(this->m_value - other.m_value) < EPSILON);
+ }
+ }
+
+ return false;
+ }
+
+ bool operator != (const SurfaceFeature& other) const {
+ return !operator == (other);
+ }
+
+private:
+ SurfaceFeatureType m_type{ SurfaceFeatureType::Undef };
+ Vec3d m_pt1{ Vec3d::Zero() };
+ Vec3d m_pt2{ Vec3d::Zero() };
+ std::optional m_pt3;
+ double m_value{ 0.0 };
+};
+
+
+
+class MeasuringImpl;
+
+
+class Measuring {
+public:
+ // Construct the measurement object on a given its.
+ explicit Measuring(const indexed_triangle_set& its);
+ ~Measuring();
+
+
+ // Given a face_idx where the mouse cursor points, return a feature that
+ // should be highlighted (if any).
+ std::optional get_feature(size_t face_idx, const Vec3d& point) const;
+
+ // Return total number of planes.
+ int get_num_of_planes() const;
+
+ // Returns a list of triangle indices for given plane.
+ const std::vector& get_plane_triangle_indices(int idx) const;
+
+ // Returns the surface features of the plane with the given index
+ const std::vector& get_plane_features(unsigned int plane_id) const;
+
+ // Returns the mesh used for measuring
+ const indexed_triangle_set& get_its() const;
+
+private:
+ std::unique_ptr priv;
+};
+
+
+struct DistAndPoints {
+ DistAndPoints(double dist_, Vec3d from_, Vec3d to_) : dist(dist_), from(from_), to(to_) {}
+ double dist;
+ Vec3d from;
+ Vec3d to;
+};
+
+struct AngleAndEdges {
+ AngleAndEdges(double angle_, const Vec3d& center_, const std::pair& e1_, const std::pair& e2_, double radius_, bool coplanar_)
+ : angle(angle_), center(center_), e1(e1_), e2(e2_), radius(radius_), coplanar(coplanar_) {}
+ double angle;
+ Vec3d center;
+ std::pair e1;
+ std::pair e2;
+ double radius;
+ bool coplanar;
+
+ static const AngleAndEdges Dummy;
+};
+
+struct MeasurementResult {
+ std::optional angle;
+ std::optional distance_infinite;
+ std::optional distance_strict;
+ std::optional distance_xyz;
+
+ bool has_distance_data() const {
+ return distance_infinite.has_value() || distance_strict.has_value();
+ }
+
+ bool has_any_data() const {
+ return angle.has_value() || distance_infinite.has_value() || distance_strict.has_value() || distance_xyz.has_value();
+ }
+};
+
+// Returns distance/angle between two SurfaceFeatures.
+MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& b, const Measuring* measuring = nullptr);
+
+inline Vec3d edge_direction(const Vec3d& from, const Vec3d& to) { return (to - from).normalized(); }
+inline Vec3d edge_direction(const std::pair& e) { return edge_direction(e.first, e.second); }
+inline Vec3d edge_direction(const SurfaceFeature& edge) {
+ assert(edge.get_type() == SurfaceFeatureType::Edge);
+ return edge_direction(edge.get_edge());
+}
+
+inline Vec3d plane_normal(const SurfaceFeature& plane) {
+ assert(plane.get_type() == SurfaceFeatureType::Plane);
+ return std::get<1>(plane.get_plane());
+}
+
+inline bool are_parallel(const Vec3d& v1, const Vec3d& v2) { return std::abs(std::abs(v1.dot(v2)) - 1.0) < EPSILON; }
+inline bool are_perpendicular(const Vec3d& v1, const Vec3d& v2) { return std::abs(v1.dot(v2)) < EPSILON; }
+
+inline bool are_parallel(const std::pair& e1, const std::pair& e2) {
+ return are_parallel(e1.second - e1.first, e2.second - e2.first);
+}
+inline bool are_parallel(const SurfaceFeature& f1, const SurfaceFeature& f2) {
+ if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Edge)
+ return are_parallel(edge_direction(f1), edge_direction(f2));
+ else if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Plane)
+ return are_perpendicular(edge_direction(f1), plane_normal(f2));
+ else
+ return false;
+}
+
+inline bool are_perpendicular(const SurfaceFeature& f1, const SurfaceFeature& f2) {
+ if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Edge)
+ return are_perpendicular(edge_direction(f1), edge_direction(f2));
+ else if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Plane)
+ return are_parallel(edge_direction(f1), plane_normal(f2));
+ else
+ return false;
+}
+
+} // namespace Measure
+} // namespace Slic3r
+
+#endif // Slic3r_Measure_hpp_
diff --git a/src/libslic3r/MeasureUtils.hpp b/src/libslic3r/MeasureUtils.hpp
new file mode 100644
index 000000000..177d60789
--- /dev/null
+++ b/src/libslic3r/MeasureUtils.hpp
@@ -0,0 +1,390 @@
+///|/ Copyright (c) Prusa Research 2022 Enrico Turri @enricoturri1966
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
+#ifndef Slic3r_MeasureUtils_hpp_
+#define Slic3r_MeasureUtils_hpp_
+
+#include
+
+namespace Slic3r {
+namespace Measure {
+
+// Utility class used to calculate distance circle-circle
+// Adaptation of code found in:
+// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/Polynomial1.h
+
+class Polynomial1
+{
+public:
+ Polynomial1(std::initializer_list values)
+ {
+ // C++ 11 will call the default constructor for
+ // Polynomial1 p{}, so it is guaranteed that
+ // values.size() > 0.
+ m_coefficient.resize(values.size());
+ std::copy(values.begin(), values.end(), m_coefficient.begin());
+ EliminateLeadingZeros();
+ }
+
+ // Construction and destruction. The first constructor creates a
+ // polynomial of the specified degree but sets all coefficients to
+ // zero (to ensure initialization). You are responsible for setting
+ // the coefficients, presumably with the degree-term set to a nonzero
+ // number. In the second constructor, the degree is the number of
+ // initializers plus 1, but then adjusted so that coefficient[degree]
+ // is not zero (unless all initializer values are zero).
+ explicit Polynomial1(uint32_t degree)
+ : m_coefficient(static_cast(degree) + 1, 0.0)
+ {}
+
+ // Eliminate any leading zeros in the polynomial, except in the case
+ // the degree is 0 and the coefficient is 0. The elimination is
+ // necessary when arithmetic operations cause a decrease in the degree
+ // of the result. For example, (1 + x + x^2) + (1 + 2*x - x^2) =
+ // (2 + 3*x). The inputs both have degree 2, so the result is created
+ // with degree 2. After the addition we find that the degree is in
+ // fact 1 and resize the array of coefficients. This function is
+ // called internally by the arithmetic operators, but it is exposed in
+ // the public interface in case you need it for your own purposes.
+ void EliminateLeadingZeros()
+ {
+ const size_t size = m_coefficient.size();
+ if (size > 1) {
+ const double zero = 0.0;
+ int32_t leading;
+ for (leading = static_cast(size) - 1; leading > 0; --leading) {
+ if (m_coefficient[leading] != zero)
+ break;
+ }
+
+ m_coefficient.resize(++leading);
+ }
+ }
+
+ // Set all coefficients to the specified value.
+ void SetCoefficients(double value)
+ {
+ std::fill(m_coefficient.begin(), m_coefficient.end(), value);
+ }
+
+ inline uint32_t GetDegree() const
+ {
+ // By design, m_coefficient.size() > 0.
+ return static_cast(m_coefficient.size() - 1);
+ }
+
+ inline const double& operator[](uint32_t i) const { return m_coefficient[i]; }
+ inline double& operator[](uint32_t i) { return m_coefficient[i]; }
+
+ // Evaluate the polynomial. If the polynomial is invalid, the
+ // function returns zero.
+ double operator()(double t) const
+ {
+ int32_t i = static_cast(m_coefficient.size());
+ double result = m_coefficient[--i];
+ for (--i; i >= 0; --i) {
+ result *= t;
+ result += m_coefficient[i];
+ }
+ return result;
+ }
+
+protected:
+ // The class is designed so that m_coefficient.size() >= 1.
+ std::vector m_coefficient;
+};
+
+inline Polynomial1 operator * (const Polynomial1& p0, const Polynomial1& p1)
+{
+ const uint32_t p0Degree = p0.GetDegree();
+ const uint32_t p1Degree = p1.GetDegree();
+ Polynomial1 result(p0Degree + p1Degree);
+ result.SetCoefficients(0.0);
+ for (uint32_t i0 = 0; i0 <= p0Degree; ++i0) {
+ for (uint32_t i1 = 0; i1 <= p1Degree; ++i1) {
+ result[i0 + i1] += p0[i0] * p1[i1];
+ }
+ }
+ return result;
+}
+
+inline Polynomial1 operator + (const Polynomial1& p0, const Polynomial1& p1)
+{
+ const uint32_t p0Degree = p0.GetDegree();
+ const uint32_t p1Degree = p1.GetDegree();
+ uint32_t i;
+ if (p0Degree >= p1Degree) {
+ Polynomial1 result(p0Degree);
+ for (i = 0; i <= p1Degree; ++i) {
+ result[i] = p0[i] + p1[i];
+ }
+ for (/**/; i <= p0Degree; ++i) {
+ result[i] = p0[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+ else {
+ Polynomial1 result(p1Degree);
+ for (i = 0; i <= p0Degree; ++i) {
+ result[i] = p0[i] + p1[i];
+ }
+ for (/**/; i <= p1Degree; ++i) {
+ result[i] = p1[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+}
+
+inline Polynomial1 operator - (const Polynomial1& p0, const Polynomial1& p1)
+{
+ const uint32_t p0Degree = p0.GetDegree();
+ const uint32_t p1Degree = p1.GetDegree();
+ uint32_t i;
+ if (p0Degree >= p1Degree) {
+ Polynomial1 result(p0Degree);
+ for (i = 0; i <= p1Degree; ++i) {
+ result[i] = p0[i] - p1[i];
+ }
+ for (/**/; i <= p0Degree; ++i) {
+ result[i] = p0[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+ else {
+ Polynomial1 result(p1Degree);
+ for (i = 0; i <= p0Degree; ++i) {
+ result[i] = p0[i] - p1[i];
+ }
+ for (/**/; i <= p1Degree; ++i) {
+ result[i] = -p1[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+}
+
+inline Polynomial1 operator * (double scalar, const Polynomial1& p)
+{
+ const uint32_t degree = p.GetDegree();
+ Polynomial1 result(degree);
+ for (uint32_t i = 0; i <= degree; ++i) {
+ result[i] = scalar * p[i];
+ }
+ return result;
+}
+
+// Utility class used to calculate distance circle-circle
+// Adaptation of code found in:
+// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/RootsPolynomial.h
+
+class RootsPolynomial
+{
+public:
+ // General equations: sum_{i=0}^{d} c(i)*t^i = 0. The input array 'c'
+ // must have at least d+1 elements and the output array 'root' must
+ // have at least d elements.
+
+ // Find the roots on (-infinity,+infinity).
+ static int32_t Find(int32_t degree, const double* c, uint32_t maxIterations, double* roots)
+ {
+ if (degree >= 0 && c != nullptr) {
+ const double zero = 0.0;
+ while (degree >= 0 && c[degree] == zero) {
+ --degree;
+ }
+
+ if (degree > 0) {
+ // Compute the Cauchy bound.
+ const double one = 1.0;
+ const double invLeading = one / c[degree];
+ double maxValue = zero;
+ for (int32_t i = 0; i < degree; ++i) {
+ const double value = std::fabs(c[i] * invLeading);
+ if (value > maxValue)
+ maxValue = value;
+ }
+ const double bound = one + maxValue;
+
+ return FindRecursive(degree, c, -bound, bound, maxIterations, roots);
+ }
+ else if (degree == 0)
+ // The polynomial is a nonzero constant.
+ return 0;
+ else {
+ // The polynomial is identically zero.
+ roots[0] = zero;
+ return 1;
+ }
+ }
+ else
+ // Invalid degree or c.
+ return 0;
+ }
+
+ // If you know that p(tmin) * p(tmax) <= 0, then there must be at
+ // least one root in [tmin, tmax]. Compute it using bisection.
+ static bool Find(int32_t degree, const double* c, double tmin, double tmax, uint32_t maxIterations, double& root)
+ {
+ const double zero = 0.0;
+ double pmin = Evaluate(degree, c, tmin);
+ if (pmin == zero) {
+ root = tmin;
+ return true;
+ }
+ double pmax = Evaluate(degree, c, tmax);
+ if (pmax == zero) {
+ root = tmax;
+ return true;
+ }
+
+ if (pmin * pmax > zero)
+ // It is not known whether the interval bounds a root.
+ return false;
+
+ if (tmin >= tmax)
+ // Invalid ordering of interval endpoitns.
+ return false;
+
+ for (uint32_t i = 1; i <= maxIterations; ++i) {
+ root = 0.5 * (tmin + tmax);
+
+ // This test is designed for 'float' or 'double' when tmin
+ // and tmax are consecutive floating-point numbers.
+ if (root == tmin || root == tmax)
+ break;
+
+ const double p = Evaluate(degree, c, root);
+ const double product = p * pmin;
+ if (product < zero) {
+ tmax = root;
+ pmax = p;
+ }
+ else if (product > zero) {
+ tmin = root;
+ pmin = p;
+ }
+ else
+ break;
+ }
+
+ return true;
+ }
+
+ // Support for the Find functions.
+ static int32_t FindRecursive(int32_t degree, double const* c, double tmin, double tmax, uint32_t maxIterations, double* roots)
+ {
+ // The base of the recursion.
+ const double zero = 0.0;
+ double root = zero;
+ if (degree == 1) {
+ int32_t numRoots;
+ if (c[1] != zero) {
+ root = -c[0] / c[1];
+ numRoots = 1;
+ }
+ else if (c[0] == zero) {
+ root = zero;
+ numRoots = 1;
+ }
+ else
+ numRoots = 0;
+
+ if (numRoots > 0 && tmin <= root && root <= tmax) {
+ roots[0] = root;
+ return 1;
+ }
+ return 0;
+ }
+
+ // Find the roots of the derivative polynomial scaled by 1/degree.
+ // The scaling avoids the factorial growth in the coefficients;
+ // for example, without the scaling, the high-order term x^d
+ // becomes (d!)*x through multiple differentiations. With the
+ // scaling we instead get x. This leads to better numerical
+ // behavior of the root finder.
+ const int32_t derivDegree = degree - 1;
+ std::vector derivCoeff(static_cast(derivDegree) + 1);
+ std::vector derivRoots(derivDegree);
+ for (int32_t i = 0, ip1 = 1; i <= derivDegree; ++i, ++ip1) {
+ derivCoeff[i] = c[ip1] * (double)(ip1) / (double)degree;
+ }
+ const int32_t numDerivRoots = FindRecursive(degree - 1, &derivCoeff[0], tmin, tmax, maxIterations, &derivRoots[0]);
+
+ int32_t numRoots = 0;
+ if (numDerivRoots > 0) {
+ // Find root on [tmin,derivRoots[0]].
+ if (Find(degree, c, tmin, derivRoots[0], maxIterations, root))
+ roots[numRoots++] = root;
+
+ // Find root on [derivRoots[i],derivRoots[i+1]].
+ for (int32_t i = 0, ip1 = 1; i <= numDerivRoots - 2; ++i, ++ip1) {
+ if (Find(degree, c, derivRoots[i], derivRoots[ip1], maxIterations, root))
+ roots[numRoots++] = root;
+ }
+
+ // Find root on [derivRoots[numDerivRoots-1],tmax].
+ if (Find(degree, c, derivRoots[static_cast(numDerivRoots) - 1], tmax, maxIterations, root))
+ roots[numRoots++] = root;
+ }
+ else {
+ // The polynomial is monotone on [tmin,tmax], so has at most one root.
+ if (Find(degree, c, tmin, tmax, maxIterations, root))
+ roots[numRoots++] = root;
+ }
+ return numRoots;
+ }
+
+ static double Evaluate(int32_t degree, const double* c, double t)
+ {
+ int32_t i = degree;
+ double result = c[i];
+ while (--i >= 0) {
+ result = t * result + c[i];
+ }
+ return result;
+ }
+};
+
+// Adaptation of code found in:
+// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/Vector.h
+
+// Construct a single vector orthogonal to the nonzero input vector. If
+// the maximum absolute component occurs at index i, then the orthogonal
+// vector U has u[i] = v[i+1], u[i+1] = -v[i], and all other components
+// zero. The index addition i+1 is computed modulo N.
+inline Vec3d get_orthogonal(const Vec3d& v, bool unitLength)
+{
+ double cmax = std::fabs(v[0]);
+ int32_t imax = 0;
+ for (int32_t i = 1; i < 3; ++i) {
+ double c = std::fabs(v[i]);
+ if (c > cmax) {
+ cmax = c;
+ imax = i;
+ }
+ }
+
+ Vec3d result = Vec3d::Zero();
+ int32_t inext = imax + 1;
+ if (inext == 3)
+ inext = 0;
+
+ result[imax] = v[inext];
+ result[inext] = -v[imax];
+ if (unitLength) {
+ const double sqrDistance = result[imax] * result[imax] + result[inext] * result[inext];
+ const double invLength = 1.0 / std::sqrt(sqrDistance);
+ result[imax] *= invLength;
+ result[inext] *= invLength;
+ }
+ return result;
+}
+
+} // namespace Slic3r
+} // namespace Measure
+
+#endif // Slic3r_MeasureUtils_hpp_
diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp
index 82040fea6..4a3e5deb9 100644
--- a/src/libslic3r/Point.hpp
+++ b/src/libslic3r/Point.hpp
@@ -45,6 +45,7 @@ using Vec3f = Eigen::Matrix;
using Vec2d = Eigen::Matrix;
using Vec3d = Eigen::Matrix;
// BBS
+using Vec4f = Eigen::Matrix;
using Vec4d = Eigen::Matrix;
using Points = std::vector;
@@ -70,7 +71,6 @@ using Transform2d = Eigen::Transform;
using Transform3d = Eigen::Transform;
-using ColorRGBA = std::array;
// I don't know why Eigen::Transform::Identity() return a const object...
template Transform identity() { return Transform::Identity(); }
inline const auto &identity3f = identity<3, float>;
diff --git a/src/libslic3r/SurfaceMesh.hpp b/src/libslic3r/SurfaceMesh.hpp
new file mode 100644
index 000000000..c2a46d631
--- /dev/null
+++ b/src/libslic3r/SurfaceMesh.hpp
@@ -0,0 +1,167 @@
+///|/ Copyright (c) Prusa Research 2022 Lukáš Matěna @lukasmatena
+///|/
+///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
+///|/
+#ifndef slic3r_SurfaceMesh_hpp_
+#define slic3r_SurfaceMesh_hpp_
+
+#include
+#include
+
+#include "boost/container/small_vector.hpp"
+
+namespace Slic3r {
+
+class TriangleMesh;
+
+
+
+enum Face_index : int;
+
+class Halfedge_index {
+ friend class SurfaceMesh;
+
+public:
+ Halfedge_index() : m_face(Face_index(-1)), m_side(0) {}
+ Face_index face() const { return m_face; }
+ unsigned char side() const { return m_side; }
+ bool is_invalid() const { return int(m_face) < 0; }
+ bool operator!=(const Halfedge_index& rhs) const { return ! ((*this) == rhs); }
+ bool operator==(const Halfedge_index& rhs) const { return m_face == rhs.m_face && m_side == rhs.m_side; }
+
+private:
+ Halfedge_index(int face_idx, unsigned char side_idx) : m_face(Face_index(face_idx)), m_side(side_idx) {}
+
+ Face_index m_face;
+ unsigned char m_side;
+};
+
+
+
+class Vertex_index {
+ friend class SurfaceMesh;
+
+public:
+ Vertex_index() : m_face(Face_index(-1)), m_vertex_idx(0) {}
+ bool is_invalid() const { return int(m_face) < 0; }
+ bool operator==(const Vertex_index& rhs) const = delete; // Use SurfaceMesh::is_same_vertex.
+
+private:
+ Vertex_index(int face_idx, unsigned char vertex_idx) : m_face(Face_index(face_idx)), m_vertex_idx(vertex_idx) {}
+
+ Face_index m_face;
+ unsigned char m_vertex_idx;
+};
+
+
+
+class SurfaceMesh {
+public:
+ explicit SurfaceMesh(const indexed_triangle_set& its)
+ : m_its(its),
+ m_face_neighbors(its_face_neighbors_par(its))
+ {}
+ SurfaceMesh(const SurfaceMesh&) = delete;
+ SurfaceMesh& operator=(const SurfaceMesh&) = delete;
+
+ Vertex_index source(Halfedge_index h) const { assert(! h.is_invalid()); return Vertex_index(h.m_face, h.m_side); }
+ Vertex_index target(Halfedge_index h) const { assert(! h.is_invalid()); return Vertex_index(h.m_face, h.m_side == 2 ? 0 : h.m_side + 1); }
+ Face_index face(Halfedge_index h) const { assert(! h.is_invalid()); return h.m_face; }
+
+ Halfedge_index next(Halfedge_index h) const { assert(! h.is_invalid()); h.m_side = (h.m_side + 1) % 3; return h; }
+ Halfedge_index prev(Halfedge_index h) const { assert(! h.is_invalid()); h.m_side = (h.m_side == 0 ? 2 : h.m_side - 1); return h; }
+ Halfedge_index halfedge(Vertex_index v) const { return Halfedge_index(v.m_face, (v.m_vertex_idx == 0 ? 2 : v.m_vertex_idx - 1)); }
+ Halfedge_index halfedge(Face_index f) const { return Halfedge_index(f, 0); }
+ Halfedge_index opposite(Halfedge_index h) const {
+ if (h.is_invalid())
+ return h;
+
+ int face_idx = m_face_neighbors[h.m_face][h.m_side];
+ Halfedge_index h_candidate = halfedge(Face_index(face_idx));
+
+ if (h_candidate.is_invalid())
+ return Halfedge_index(); // invalid
+
+ for (int i=0; i<3; ++i) {
+ if (is_same_vertex(source(h_candidate), target(h))) {
+ // Meshes in PrusaSlicer should be fixed enough for the following not to happen.
+ assert(is_same_vertex(target(h_candidate), source(h)));
+ return h_candidate;
+ }
+ h_candidate = next(h_candidate);
+ }
+ return Halfedge_index(); // invalid
+ }
+
+ Halfedge_index next_around_target(Halfedge_index h) const { return opposite(next(h)); }
+ Halfedge_index prev_around_target(Halfedge_index h) const { Halfedge_index op = opposite(h); return (op.is_invalid() ? Halfedge_index() : prev(op)); }
+ Halfedge_index next_around_source(Halfedge_index h) const { Halfedge_index op = opposite(h); return (op.is_invalid() ? Halfedge_index() : next(op)); }
+ Halfedge_index prev_around_source(Halfedge_index h) const { return opposite(prev(h)); }
+ Halfedge_index halfedge(Vertex_index source, Vertex_index target) const
+ {
+ Halfedge_index hi(source.m_face, source.m_vertex_idx);
+ assert(! hi.is_invalid());
+
+ const Vertex_index orig_target = this->target(hi);
+ Vertex_index current_target = orig_target;
+
+ while (! is_same_vertex(current_target, target)) {
+ hi = next_around_source(hi);
+ if (hi.is_invalid())
+ break;
+ current_target = this->target(hi);
+ if (is_same_vertex(current_target, orig_target))
+ return Halfedge_index(); // invalid
+ }
+
+ return hi;
+ }
+
+ const stl_vertex& point(Vertex_index v) const { return m_its.vertices[m_its.indices[v.m_face][v.m_vertex_idx]]; }
+
+ size_t degree(Vertex_index v) const
+ {
+ // In case the mesh is broken badly, the loop might end up to be infinite,
+ // never getting back to the first halfedge. Remember list of all half-edges
+ // and trip if any is encountered for the second time.
+ Halfedge_index h_first = halfedge(v);
+ boost::container::small_vector he_visited;
+ Halfedge_index h = next_around_target(h_first);
+ size_t degree = 2;
+ while (! h.is_invalid() && h != h_first) {
+ he_visited.emplace_back(h);
+ h = next_around_target(h);
+ if (std::find(he_visited.begin(), he_visited.end(), h) == he_visited.end())
+ return 0;
+ ++degree;
+ }
+ return h.is_invalid() ? 0 : degree - 1;
+ }
+
+ size_t degree(Face_index f) const {
+ size_t total = 0;
+ for (unsigned char i=0; i<3; ++i) {
+ size_t d = degree(Vertex_index(f, i));
+ if (d == 0)
+ return 0;
+ total += d;
+ }
+ assert(total - 6 >= 0);
+ return total - 6; // we counted 3 halfedges from f, and one more for each neighbor
+ }
+
+ bool is_border(Halfedge_index h) const { return m_face_neighbors[h.m_face][h.m_side] == -1; }
+
+ bool is_same_vertex(const Vertex_index& a, const Vertex_index& b) const { return m_its.indices[a.m_face][a.m_vertex_idx] == m_its.indices[b.m_face][b.m_vertex_idx]; }
+ Vec3i get_face_neighbors(Face_index face_id) const { assert(int(face_id) < int(m_face_neighbors.size())); return m_face_neighbors[face_id]; }
+
+
+
+private:
+ const std::vector m_face_neighbors;
+ const indexed_triangle_set& m_its;
+};
+
+} //namespace Slic3r
+
+#endif // slic3r_SurfaceMesh_hpp_
diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp
index d47c56fd9..5112b1558 100644
--- a/src/slic3r/GUI/GLModel.hpp
+++ b/src/slic3r/GUI/GLModel.hpp
@@ -2,6 +2,7 @@
#define slic3r_GLModel_hpp_
#include "libslic3r/Point.hpp"
+#include "libslic3r/Color.hpp"
#include "libslic3r/BoundingBox.hpp"
#include
#include
diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp
index 10e2a6f94..305848803 100644
--- a/src/slic3r/GUI/GUI_Utils.hpp
+++ b/src/slic3r/GUI/GUI_Utils.hpp
@@ -488,6 +488,15 @@ public:
~TaskTimer();
};
+class KeyAutoRepeatFilter
+{
+ size_t m_count{0};
+
+public:
+ void increase_count() { ++m_count; }
+ void reset_count() { m_count = 0; }
+ bool is_first() const { return m_count == 0; }
+};
/* Image Generator */
#define _3MF_COVER_SIZE wxSize(240, 240)
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp
index 52e3da422..1c3cf707d 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp
@@ -32,29 +32,12 @@ const int c_connectors_start_id = c_connectors_group_id - c_cube_z_move_id;
const float UndefFloat = -999.f;
// connector colors
-static const ColorRGBA BLACK() { return {0.0f, 0.0f, 0.0f, 1.0f}; }
-static const ColorRGBA BLUE() { return {0.0f, 0.0f, 1.0f, 1.0f}; }
-static const ColorRGBA BLUEISH() { return {0.5f, 0.5f, 1.0f, 1.0f}; }
-static const ColorRGBA CYAN() { return {0.0f, 1.0f, 1.0f, 1.0f}; }
-static const ColorRGBA DARK_GRAY() { return {0.25f, 0.25f, 0.25f, 1.0f}; }
-static const ColorRGBA DARK_YELLOW() { return {0.5f, 0.5f, 0.0f, 1.0f}; }
-static const ColorRGBA GRAY() { return {0.5f, 0.5f, 0.5f, 1.0f}; }
-static const ColorRGBA GREEN() { return {0.0f, 1.0f, 0.0f, 1.0f}; }
-static const ColorRGBA GREENISH() { return {0.5f, 1.0f, 0.5f, 1.0f}; }
-static const ColorRGBA LIGHT_GRAY() { return {0.75f, 0.75f, 0.75f, 1.0f}; }
-static const ColorRGBA MAGENTA() { return {1.0f, 0.0f, 1.0f, 1.0f}; }
-static const ColorRGBA ORANGE() { return {0.923f, 0.504f, 0.264f, 1.0f}; }
-static const ColorRGBA RED() { return {1.0f, 0.0f, 0.0f, 1.0f}; }
-static const ColorRGBA REDISH() { return {1.0f, 0.5f, 0.5f, 1.0f}; }
-static const ColorRGBA YELLOW() { return {1.0f, 1.0f, 0.0f, 1.0f}; }
-static const ColorRGBA WHITE() { return {1.0f, 1.0f, 1.0f, 1.0f}; }
-
-static const ColorRGBA PLAG_COLOR = YELLOW();
-static const ColorRGBA DOWEL_COLOR = DARK_YELLOW();
-static const ColorRGBA HOVERED_PLAG_COLOR = CYAN();
+static const ColorRGBA PLAG_COLOR = ColorRGBA::YELLOW();
+static const ColorRGBA DOWEL_COLOR = ColorRGBA::DARK_YELLOW();
+static const ColorRGBA HOVERED_PLAG_COLOR = ColorRGBA::CYAN();
static const ColorRGBA HOVERED_DOWEL_COLOR = {0.0f, 0.5f, 0.5f, 1.0f};
-static const ColorRGBA SELECTED_PLAG_COLOR = GRAY();
-static const ColorRGBA SELECTED_DOWEL_COLOR = GRAY(); // DARK_GRAY();
+static const ColorRGBA SELECTED_PLAG_COLOR = ColorRGBA::GRAY();
+static const ColorRGBA SELECTED_DOWEL_COLOR = ColorRGBA::GRAY(); // DARK_GRAY();
static const ColorRGBA CONNECTOR_DEF_COLOR = {1.0f, 1.0f, 1.0f, 0.5f};
static const ColorRGBA CONNECTOR_ERR_COLOR = {1.0f, 0.3f, 0.3f, 0.5f};
static const ColorRGBA HOVERED_ERR_COLOR = {1.0f, 0.3f, 0.3f, 1.0f};
@@ -62,8 +45,8 @@ static const ColorRGBA HOVERED_ERR_COLOR = {1.0f, 0.3f, 0.3f, 1.0f};
static const ColorRGBA CUT_PLANE_DEF_COLOR = {0.9f, 0.9f, 0.9f, 0.5f};
static const ColorRGBA CUT_PLANE_ERR_COLOR = {1.0f, 0.8f, 0.8f, 0.5f};
-static const ColorRGBA UPPER_PART_COLOR = CYAN();
-static const ColorRGBA LOWER_PART_COLOR = MAGENTA();
+static const ColorRGBA UPPER_PART_COLOR = ColorRGBA::CYAN();
+static const ColorRGBA LOWER_PART_COLOR = ColorRGBA::MAGENTA();
static const ColorRGBA MODIFIER_COLOR = {0.75f, 0.75f, 0.75f, 0.5f};
static Vec3d rotate_vec3d_around_vec3d_with_rotate_matrix(
@@ -351,27 +334,6 @@ bool GLGizmoAdvancedCut::unproject_on_cut_plane(const Vec2d &mouse_pos, Vec3d &p
return true;
}
-void GLGizmoAdvancedCut::render_glmodel(GLModel &model, const std::array &color, Transform3d view_model_matrix, bool for_picking)
-{
- glPushMatrix();
- GLShaderProgram *shader = nullptr;
- if (for_picking)
- shader = wxGetApp().get_shader("cali");
- else
- shader = wxGetApp().get_shader("gouraud_light");
- if (shader) {
- shader->start_using();
-
- glsafe(::glMultMatrixd(view_model_matrix.data()));
-
- model.set_color(-1, color);
- model.render();
-
- shader->stop_using();
- }
- glPopMatrix();
-}
-
void GLGizmoAdvancedCut::reset_cut_plane()
{
m_transformed_bounding_box = transformed_bounding_box(m_bb_center);
@@ -1143,9 +1105,9 @@ void GLGizmoAdvancedCut::render_cut_plane_and_grabbers()
bool is_valid = can_perform_cut() && has_valid_groove();
ColorRGBA cp_clr = is_valid ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR;
if (m_cut_mode == CutMode::cutTongueAndGroove) {
- cp_clr[3] = cp_clr[3] - 0.1f; // cp_clr.a(cp_clr.a() - 0.1f);
+ cp_clr.a(cp_clr.a() - 0.1f);
}
- render_glmodel(m_plane, cp_clr, Geometry::translation_transform(m_plane_center) * m_rotate_matrix);
+ render_glmodel(m_plane, cp_clr.get_data(), Geometry::translation_transform(m_plane_center) * m_rotate_matrix);
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_CULL_FACE));
@@ -1277,7 +1239,7 @@ void GLGizmoAdvancedCut::render_connectors()
const Transform3d view_model_matrix = translate_tf * m_rotate_matrix * scale_tf;
- render_glmodel(m_shapes[connector.attribs], render_color, view_model_matrix);
+ render_glmodel(m_shapes[connector.attribs], render_color.get_data(), view_model_matrix);
}
}
@@ -1878,13 +1840,12 @@ bool GLGizmoAdvancedCut::render_cut_mode_combo(double label_width, float item_wi
void GLGizmoAdvancedCut::render_color_marker(float size, const ColorRGBA &color)
{
- auto to_ImU32 = [](const ColorRGBA &color) -> ImU32 { return ImGui::GetColorU32({color[0], color[1], color[2], color[3]}); };
ImGui::SameLine();
const float radius = 0.5f * size;
ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos;
pos.x += 3 * m_imgui->scaled(1 / 15.0f);
pos.y += 1.25f * radius;
- ImGui::GetCurrentWindow()->DrawList->AddNgonFilled(pos, radius, to_ImU32(color), 6);
+ ImGui::GetCurrentWindow()->DrawList->AddNgonFilled(pos, radius, ImGuiWrapper::to_ImU32(color), 6);
}
void GLGizmoAdvancedCut::render_cut_plane_input_window(float x, float y, float bottom_limit)
@@ -2699,7 +2660,7 @@ void PartSelection::part_render(const Vec3d *normal)
for (size_t id = 0; id < m_cut_parts.size(); ++id) { // m_parts.size() test
if (normal && ((is_looking_forward && m_cut_parts[id].is_up_part) || (!is_looking_forward && !m_cut_parts[id].is_up_part)))
continue;
- GLGizmoAdvancedCut::render_glmodel(m_cut_parts[id].glmodel, m_cut_parts[id].is_up_part ? UPPER_PART_COLOR : LOWER_PART_COLOR, m_cut_parts[id].trans);
+ GLGizmoBase::render_glmodel(m_cut_parts[id].glmodel, m_cut_parts[id].is_up_part ? UPPER_PART_COLOR.get_data() : LOWER_PART_COLOR.get_data(), m_cut_parts[id].trans);
}
}
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp
index bc3648a38..5c2cb5010 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp
@@ -218,7 +218,7 @@ public:
bool unproject_on_cut_plane(const Vec2d &mouse_pos, Vec3d &pos, Vec3d &pos_world, bool respect_contours = true);
virtual bool apply_clipping_plane() { return m_connectors_editing; }
- static void render_glmodel(GLModel &model, const std::array &color, Transform3d view_model_matrix, bool for_picking = false);
+
protected:
virtual bool on_init();
virtual void on_load(cereal::BinaryInputArchive &ar) override;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
index f0b7dfe73..fb47b6ee6 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
@@ -424,7 +424,26 @@ void GLGizmoBase::render_input_window(float x, float y, float bottom_limit)
}
}
+void GLGizmoBase::render_glmodel(GLModel &model, const std::array &color, Transform3d view_model_matrix, bool for_picking, float emission_factor)
+{
+ glPushMatrix();
+ GLShaderProgram *shader = nullptr;
+ if (for_picking)
+ shader = wxGetApp().get_shader("cali");
+ else
+ shader = wxGetApp().get_shader("gouraud_light");
+ if (shader) {
+ shader->start_using();
+ shader->set_uniform("emission_factor", emission_factor);
+ glsafe(::glMultMatrixd(view_model_matrix.data()));
+ model.set_color(-1, color);
+ model.render();
+
+ shader->stop_using();
+ }
+ glPopMatrix();
+}
std::string GLGizmoBase::get_name(bool include_shortcut) const
{
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
index 786580059..442eedb44 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
@@ -2,6 +2,7 @@
#define slic3r_GLGizmoBase_hpp_
#include "libslic3r/Point.hpp"
+#include "libslic3r/Color.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GLModel.hpp"
@@ -215,7 +216,7 @@ public:
///
virtual void data_changed(bool is_serializing){};
int get_count() { return ++count; }
-
+ static void render_glmodel(GLModel &model, const std::array &color, Transform3d view_model_matrix, bool for_picking = false, float emission_factor = 0.0f);
protected:
float last_input_window_width = 0;
virtual bool on_init() = 0;
diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp
index f05c335b2..2e892d654 100644
--- a/src/slic3r/GUI/ImGuiWrapper.cpp
+++ b/src/slic3r/GUI/ImGuiWrapper.cpp
@@ -80,6 +80,8 @@ static const std::map font_icons = {
{ImGui::AddFilamentDarkIcon , "add_filament_dark"},
{ImGui::DeleteFilamentIcon , "delete_filament"},
{ImGui::DeleteFilamentDarkIcon , "delete_filament_dark"},
+ {ImGui::ClipboardBtnIcon , "copy_menu"},
+ {ImGui::ClipboardBtnDarkIcon , "copy_menu_dark"},
{ImGui::CircleButtonDarkIcon , "circle_paint_dark" },
{ImGui::TriangleButtonDarkIcon , "triangle_paint_dark" },
@@ -148,7 +150,6 @@ const ImVec4 ImGuiWrapper::COL_BUTTON_HOVERED = COL_ORANGE_LIGHT;
const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = ImGuiWrapper::COL_BUTTON_HOVERED;
//BBS
-
const ImVec4 ImGuiWrapper::COL_BLUE_LIGHT = ImVec4(0.122f, 0.557f, 0.918f, 1.0f);
const ImVec4 ImGuiWrapper::COL_GREEN_LIGHT = ImVec4(0.86f, 0.99f, 0.91f, 1.0f);
const ImVec4 ImGuiWrapper::COL_HOVER = { 0.933f, 0.933f, 0.933f, 1.0f };
@@ -158,6 +159,7 @@ const ImVec4 ImGuiWrapper::COL_SEPARATOR_DARK = { 0.24f, 0.24f, 0.27f, 1.0f }
const ImVec4 ImGuiWrapper::COL_TITLE_BG = { 0.745f, 0.745f, 0.745f, 1.0f };
const ImVec4 ImGuiWrapper::COL_WINDOW_BG = { 1.000f, 1.000f, 1.000f, 1.0f };
const ImVec4 ImGuiWrapper::COL_WINDOW_BG_DARK = { 45 / 255.f, 45 / 255.f, 49 / 255.f, 1.f };
+const ImVec4 ImGuiWrapper::COL_BAMBU = {0.0f, 150.f / 255.0f, 136.0f / 255, 1.0f};
int ImGuiWrapper::TOOLBAR_WINDOW_FLAGS = ImGuiWindowFlags_AlwaysAutoResize
| ImGuiWindowFlags_NoMove
@@ -800,11 +802,102 @@ bool ImGuiWrapper::radio_button(const wxString &label, bool active)
return ImGui::RadioButton(label_utf8.c_str(), active);
}
-bool ImGuiWrapper::image_button()
+ImFontAtlasCustomRect *ImGuiWrapper::GetTextureCustomRect(const wchar_t &tex_id)
{
- return false;
+ auto item = m_custom_glyph_rects_ids.find(tex_id);
+ return (item != m_custom_glyph_rects_ids.end()) ? ImGui::GetIO().Fonts->GetCustomRectByIndex(m_custom_glyph_rects_ids[tex_id]) : nullptr;
}
+ImU32 ImGuiWrapper::to_ImU32(const ColorRGBA &color)
+{
+ return ImGui::GetColorU32({color.r(), color.g(), color.b(), color.a()});
+}
+
+ImVec4 ImGuiWrapper::to_ImVec4(const ColorRGBA &color) {
+ return {color.r(), color.g(), color.b(), color.a()};
+}
+
+ColorRGBA ImGuiWrapper::from_ImU32(const ImU32 &color)
+{
+ return from_ImVec4(ImGui::ColorConvertU32ToFloat4(color));
+}
+
+ColorRGBA ImGuiWrapper::from_ImVec4(const ImVec4 &color)
+{
+ return {color.x, color.y, color.z, color.w};
+}
+
+static bool image_button_ex(ImGuiID id,
+ ImTextureID texture_id,
+ const ImVec2 & size,
+ const ImVec2 & uv0,
+ const ImVec2 & uv1,
+ const ImVec2 & padding,
+ const ImVec4 & bg_col,
+ const ImVec4 & tint_col,
+ ImGuiButtonFlags flags)
+{
+ ImGuiContext &g = *GImGui;
+ ImGuiWindow * window = ImGui::GetCurrentWindow();
+ if (window->SkipItems) return false;
+
+ const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
+ ImGui::ItemSize(bb);
+ if (!ImGui::ItemAdd(bb, id)) return false;
+
+ bool hovered, held;
+ bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags);
+
+ // Render
+ const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+ ImGui::RenderNavHighlight(bb, id);
+ ImGui::RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float) ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
+ if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, ImGui::GetColorU32(bg_col));
+ window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, ImGui::GetColorU32(tint_col));
+
+ return pressed;
+}
+
+bool ImGuiWrapper::image_button(
+ ImTextureID user_texture_id, const ImVec2 &size, const ImVec2 &uv0, const ImVec2 &uv1, int frame_padding, const ImVec4 &bg_col, const ImVec4 &tint_col, ImGuiButtonFlags flags)
+{
+ ImGuiContext &g = *GImGui;
+ ImGuiWindow * window = g.CurrentWindow;
+ if (window->SkipItems)
+ return false;
+
+ // Default to using texture ID as ID. User can still push string/integer prefixes.
+ ImGui::PushID((void *) (intptr_t) user_texture_id);
+ const ImGuiID id = window->GetID("#image");
+ ImGui::PopID();
+
+ const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float) frame_padding, (float) frame_padding) : g.Style.FramePadding;
+ return image_button_ex(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col, flags);
+}
+
+bool ImGuiWrapper::image_button(const wchar_t icon, const wxString &tooltip)
+{
+ const ImGuiIO & io = ImGui::GetIO();
+ const ImTextureID tex_id = io.Fonts->TexID;
+ assert(io.Fonts->TexWidth > 0 && io.Fonts->TexHeight > 0);
+ const float inv_tex_w = 1.0f / float(io.Fonts->TexWidth);
+ const float inv_tex_h = 1.0f / float(io.Fonts->TexHeight);
+ const ImFontAtlasCustomRect *const rect = GetTextureCustomRect(icon);
+ const ImVec2 size = {float(rect->Width), float(rect->Height)};
+ const ImVec2 uv0 = ImVec2(float(rect->X) * inv_tex_w, float(rect->Y) * inv_tex_h);
+ const ImVec2 uv1 = ImVec2(float(rect->X + rect->Width) * inv_tex_w, float(rect->Y + rect->Height) * inv_tex_h);
+ ImGui::PushStyleColor(ImGuiCol_Button, {0.25f, 0.25f, 0.25f, 0.0f});
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, {0.4f, 0.4f, 0.4f, 1.0f});
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive, {0.25f, 0.25f, 0.25f, 1.0f});
+ const bool res = image_button(tex_id, size, uv0, uv1);
+ ImGui::PopStyleColor(3);
+
+ if (!tooltip.empty() && ImGui::IsItemHovered()) this->tooltip(tooltip, ImGui::GetFontSize() * 20.0f);
+
+ return res;
+}
+
+
bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format)
{
return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str(), ImGuiInputTextFlags_CharsDecimal);
@@ -1791,6 +1884,11 @@ bool ImGuiWrapper::want_any_input() const
return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput;
}
+void ImGuiWrapper::disable_background_fadeout_animation()
+{
+ GImGui->DimBgRatio = 1.0f;
+}
+
#ifdef __APPLE__
static const ImWchar ranges_keyboard_shortcuts[] =
{
@@ -2150,11 +2248,11 @@ void ImGuiWrapper::init_font(bool compress)
int rect_id = io.Fonts->CustomRects.Size; // id of the rectangle added next
// add rectangles for the icons to the font atlas
for (auto& icon : font_icons)
- io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz);
+ m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz);
for (auto& icon : font_icons_large)
- io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2);
+ m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2);
for (auto& icon : font_icons_extra_large)
- io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4);
+ m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4);
// Build texture atlas
unsigned char* pixels;
diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp
index b8840ffe0..1d6c7917f 100644
--- a/src/slic3r/GUI/ImGuiWrapper.hpp
+++ b/src/slic3r/GUI/ImGuiWrapper.hpp
@@ -9,6 +9,7 @@
#include
#include "libslic3r/Point.hpp"
+#include "libslic3r/Color.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
namespace Slic3r {namespace Search {
@@ -59,6 +60,7 @@ class ImGuiWrapper
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
bool m_requires_extra_frame{ false };
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
+ std::map m_custom_glyph_rects_ids;
std::string m_clipboard_text;
public:
@@ -115,7 +117,21 @@ public:
bool bbl_button(const wxString &label);
bool button(const wxString& label, float width, float height);
bool radio_button(const wxString &label, bool active);
- bool image_button();
+
+ static ImU32 to_ImU32(const ColorRGBA &color);
+ static ImVec4 to_ImVec4(const ColorRGBA &color);
+ static ColorRGBA from_ImU32(const ImU32 &color);
+ static ColorRGBA from_ImVec4(const ImVec4 &color);
+ ImFontAtlasCustomRect *GetTextureCustomRect(const wchar_t &tex_id);
+ bool image_button(ImTextureID user_texture_id,
+ const ImVec2 & size,
+ const ImVec2 & uv0 = ImVec2(0.0, 0.0),
+ const ImVec2 & uv1 = ImVec2(1.0, 1.0),
+ int frame_padding = -1,
+ const ImVec4 & bg_col = ImVec4(0.0, 0.0, 0.0, 0.0),
+ const ImVec4 & tint_col = ImVec4(1.0, 1.0, 1.0, 1.0),
+ ImGuiButtonFlags flags = 0);
+ bool image_button(const wchar_t icon, const wxString &tooltip = L"");
bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f");
bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f");
bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f");
@@ -181,6 +197,7 @@ public:
void set_requires_extra_frame() { m_requires_extra_frame = true; }
void reset_requires_extra_frame() { m_requires_extra_frame = false; }
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
+ void disable_background_fadeout_animation();
static const ImVec4 COL_GREY_DARK;
static const ImVec4 COL_GREY_LIGHT;
@@ -201,7 +218,7 @@ public:
static const ImVec4 COL_WINDOW_BG_DARK;
static const ImVec4 COL_SEPARATOR;
static const ImVec4 COL_SEPARATOR_DARK;
-
+ static const ImVec4 COL_BAMBU;
//BBS
static void on_change_color_mode(bool is_dark);
static void push_toolbar_style(const float scale);
diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp
index fab310322..7268be2d1 100644
--- a/src/slic3r/GUI/MeshUtils.cpp
+++ b/src/slic3r/GUI/MeshUtils.cpp
@@ -88,7 +88,7 @@ void MeshClipper::render_cut(const ColorRGBA &color, const std::vector *
if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i)) continue;
auto isl = m_result->cut_islands[i];
ColorRGBA gray{0.5f, 0.5f, 0.5f, 1.f};
- isl->model.set_color(-1, isl->disabled ? gray : color);
+ isl->model.set_color(-1, isl->disabled ? gray.get_data() : color.get_data());
isl->model.render();
}
shader->stop_using();
@@ -114,7 +114,7 @@ void MeshClipper::render_contour(const ColorRGBA &color, const std::vectorbegin(), ignore_idxs->end(), i)) continue;
auto isl = m_result->cut_islands[i];
ColorRGBA red{1.0f, 0.f, 0.f, 1.f};
- isl->model_expanded.set_color(-1, isl->disabled ? red : color);
+ isl->model_expanded.set_color(-1, isl->disabled ? red.get_data() : color.get_data());
isl->model_expanded.render();
}
shader->stop_using();
@@ -420,6 +420,13 @@ Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const
return m_normals[facet_idx];
}
+MeshRaycaster::MeshRaycaster(const TriangleMesh &mesh)
+ : m_emesh(mesh, true) // calculate epsilon for triangle-ray intersection from an average edge length
+ , m_normals(its_face_normals(mesh.its))
+{
+ ;
+}
+
void MeshRaycaster::line_from_mouse_pos_static(const Vec2d &mouse_pos, const Transform3d &trafo, const Camera &camera, Vec3d &point, Vec3d &direction)
{
CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction);
@@ -447,7 +454,7 @@ void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3
pt2 = inv * pt2;
point = pt1;
- direction = pt2-pt1;
+ direction = (pt2-pt1).normalized();
}
@@ -555,6 +562,37 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
return out;
}
+bool MeshRaycaster::closest_hit(
+ const Vec2d &mouse_pos, const Transform3d &trafo, const Camera &camera, Vec3f &position, Vec3f &normal, const ClippingPlane *clipping_plane, size_t *facet_idx) const
+{
+ Vec3d point;
+ Vec3d direction;
+ line_from_mouse_pos(mouse_pos, trafo, camera, point, direction);
+
+ auto hits = m_emesh.query_ray_hits(point, direction.normalized());
+
+ if (hits.empty()) return false; // no intersection found
+
+ size_t hit_id = 0;
+ if (clipping_plane != nullptr) {
+ while (hit_id < hits.size() && clipping_plane->is_point_clipped(trafo * hits[hit_id].position())) {
+ ++hit_id;
+ }
+ }
+
+ if (hit_id == hits.size())
+ return false; // all points are obscured or cut by the clipping plane.
+
+ auto &hit = hits[hit_id];
+
+ position = hit.position().cast();
+ normal = hit.normal().cast();
+
+ if (facet_idx != nullptr)
+ *facet_idx = hit.face();
+
+ return true;
+}
Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
{
diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp
index 8d9b84e7c..236abd067 100644
--- a/src/slic3r/GUI/MeshUtils.hpp
+++ b/src/slic3r/GUI/MeshUtils.hpp
@@ -2,6 +2,7 @@
#define slic3r_MeshUtils_hpp_
#include "libslic3r/Point.hpp"
+#include "libslic3r/Color.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/SLA/IndexedMesh.hpp"
#include "admesh/stl.h"
@@ -150,11 +151,7 @@ class MeshRaycaster {
public:
// The class references extern TriangleMesh, which must stay alive
// during MeshRaycaster existence.
- MeshRaycaster(const TriangleMesh& mesh)
- : m_emesh(mesh, true) // calculate epsilon for triangle-ray intersection from an average edge length
- , m_normals(its_face_normals(mesh.its))
- {
- }
+ MeshRaycaster(const TriangleMesh &mesh);
static void line_from_mouse_pos_static(const Vec2d &mouse_pos, const Transform3d &trafo,
const Camera &camera, Vec3d &point, Vec3d &direction);
@@ -188,6 +185,17 @@ public:
const ClippingPlane* clipping_plane = nullptr // clipping plane (if active)
) const;
+ // Returns true if the ray, built from mouse position and camera direction, intersects the mesh.
+ // In this case, position and normal contain the position and normal, in model coordinates, of the intersection closest to the camera,
+ // depending on the position/orientation of the clipping_plane, if specified
+ bool closest_hit(const Vec2d & mouse_pos,
+ const Transform3d & trafo, // how to get the mesh into world coords
+ const Camera & camera, // current camera position
+ Vec3f & position, // where to save the positibon of the hit (mesh coords)
+ Vec3f & normal, // normal of the triangle that was hit
+ const ClippingPlane *clipping_plane = nullptr, // clipping plane (if active)
+ size_t * facet_idx = nullptr // index of the facet hit
+ ) const;
// Given a point in world coords, the method returns closest point on the mesh.
// The output is in mesh coords.
// normal* can be used to also get normal of the respective triangle.
@@ -204,7 +212,41 @@ private:
std::vector m_normals;
};
-
+class PickRaycaster
+{
+public:
+ //PickRaycaster(TriangleMesh *mesh) {
+ // mesh_raycaster = std::make_shared(*mesh);
+ //}
+ /*PickRaycaster(TriangleMesh *mesh, const Transform3d &tran) : PickRaycaster(mesh) {
+ set_transform(tran);
+ }*/
+ PickRaycaster(TriangleMesh *mesh, int _id)
+ {
+ mesh_raycaster = std::make_shared(*mesh);
+ m_id = _id;
+ }
+ PickRaycaster(TriangleMesh *mesh, int _id, const Transform3d &tran)
+ {
+ mesh_raycaster = std::make_shared(*mesh);
+ set_transform(tran);
+ m_id = _id;
+ }
+ void set_transform(const Transform3d &tran) {
+ world_tran.set_from_transform(tran);
+ }
+
+ std::shared_ptr mesh_raycaster{nullptr};
+ Geometry::Transformation world_tran;
+
+ bool is_active() const { return m_active; }
+ void set_active(bool active) { m_active = active; }
+ int get_id() { return m_id; }
+private:
+ bool m_active{true};
+ int m_id{-1};
+};
+
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp
index 1efd3b1b0..c86238e21 100644
--- a/src/slic3r/GUI/Selection.cpp
+++ b/src/slic3r/GUI/Selection.cpp
@@ -907,7 +907,14 @@ void Selection::move_to_center(const Vec3d& displacement, bool local)
this->set_bounding_boxes_dirty();
}
-void Selection::translate(const Vec3d& displacement, bool local)
+void Selection::setup_cache()
+{
+ if (!m_valid)
+ return;
+ set_caches();
+}
+
+void Selection::translate(const Vec3d &displacement, bool local)
{
if (!m_valid)
return;
diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp
index 5455ac517..874e5b5eb 100644
--- a/src/slic3r/GUI/Selection.hpp
+++ b/src/slic3r/GUI/Selection.hpp
@@ -334,6 +334,7 @@ public:
void stop_dragging() { m_dragging = false; }
bool is_dragging() const { return m_dragging; }
+ void setup_cache();
void translate(const Vec3d& displacement, bool local = false);
void move_to_center(const Vec3d& displacement, bool local = false);
void rotate(const Vec3d& rotation, TransformationType transformation_type);